使用顶点光照的模型,当模型的面数很少的时候,光照效果会显得很奇怪,因为只有顶点上的光照是正确计算出来的,三角面上的光照都是通过硬件插值得到,所以难免会出现问题。基于像素的光照可以很好的改善这个问题。如果想要表现出模型表面凹凸不平,那就需要很高的面数制作出凹凸的模型。

然后就出现法线贴图。法线贴图可以在低面数的模型上表现出高面数模型的很多细节。法线贴图并不是把模型的面数提高了,而是使用法线贴图中的法线来计算光照,通过明暗效果作假,让观察者误以为模型有凹凸。法线贴图只能在明暗效果上作假(模拟凹凸),无法控制表面的凹凸程度。即使我们使用图像软件强制调出一个凹凸非常明显的法线贴图,通过仔细观察,会法线效果也是有问题的。

上面的是一张用了法线贴图的地面,在红色圆圈的地方是有问题的。我们使用一张平面图来分析下。

这是一张凸起砖块的截面图,绿色箭头表示视线的方向,白色线条表示砖块的横截面。按照常识来看,我们能看到砖块的最远的一点是蓝色点,因为蓝色点后面(红色线条部分)的砖块由于高度较低,被前面挡住了。但是从上面那张使用了法线贴图的地面效果图上可以看到,蓝色点后的砖块并没有被挡住,甚至能够看到黄色点的位置,这种效果显然是不正确的。而这是法线贴图无法避免的问题,因为上文已经说过了,法线贴图只能模拟明暗,也就是说最多只能将红色线条部分变暗(以此来模拟背光)。这就是视差贴图可以解决的一个问题,它可以让背面被遮挡住的部分完全不显示出来,除此之外还能在一定范围内调整砖块凹凸的程度。视差贴图也只是模拟作假,并没有真的改变模型表面,下面就开始分析视差贴图吧。

如图所示,视线 e 落点是点 a,但是因为模型并不是真的有凹凸,而是一个平面,所以真实的落点是在点 b。这样就变成了如何将点 a 纠正到点 b 的问题了。让我们再在参考图上加上一些辅助参数。

需要说明的是我们在分析视差贴图的时候使用的是切线空间,这和法线贴图是一样的,切线空间中的切线和副切线是与纹理坐标 uv 对齐的,上图中只显示了 u 方向上的情况,在 v 方向上是一样的。当前实际的落点是点 b,u 坐标是 ub,而理想的落点是在点 a,u坐标是 ua。如果能有一个 delta 量,把 ub 加上 delta 等于 ua,似乎就可以了。但是还有个问题是,因为视线的方向是一直在变化的,这就导致了 delta 量不可能是一个固定的值。所以暂且没有什么好的办法求出 delta,那么就把问题想简单点。这里不要求精确的 delta,只要近似的就可以。于是有了一张称为高度图的纹理,它存储了点 b 在切线空间的真实凹凸表面的凹陷或凸起程度。黑色(0)表示不凸起,白色(1)表示完全凸起。我们可以试着使用这个值来最大可能的近似模拟出 delta 值。

// 计算 uv 的偏移 delta
inline float2 ParallaxUvDelta(v2f i)
{
// 高度图中描述的高度数据
half h = tex2D(_ParallaxMap, i.uvMain).r;
// 切线空间中的视线方向
float3 viewDir = normalize(i.viewDir);
// 将三维的视线向量投影到二维的 uv 平面,乘以高度数据
// _ParallaxScale 是一个用户可调节的值,根据效果需要进行调节,数值太大造成视觉上的严重错误
float2 delta = viewDir.xy / viewDir.z * h * _ParallaxScale;
return delta;
} float2 uvDelta = ParallaxUvDelta(i);
i.uvMain += uvDelta;
i.uvBump += uvDelta;

以上就是如何利用高度图计算 uv 的偏移量的代码了。需要注意,因为这只是近似模拟,所以能否得到完美的效果完全取决于各个参数调整的是否合理。其实上面的代码只是一个框架,在此基础上可以试着对 hviewDir.z 这些参数进行一定的偏移,或许能够得到更好的效果。完成后的效果图如下,可以看到砖块就像真的凸起了一样,上文法线贴图红圈指出的问题也没有了。

不足之处是你无法让砖块无限制的凸起,当到了某个临界值后,效果就完全穿帮了。

由于 uv 的 delta 偏移量是一个估计值,并不是精确值,所以才会出现这样的情况。下面我们的目标就是让 delta uv 尽可能的精确。上面的方法中,我们只对高度图进行了一次采样,很难一下就找准落点,显然一次是不够的,因此需要对其进行改进,我们将使用逐步逼近的方式,来获得一个更精确的 delta uv。

从图中可以看出:最上层的高度值为1,最下层的高度值为0,对中间值划分为四等分(划分得越细,最终计算出来的精度就越高,效果也就越好,当然计算量也越大),这些值和高度图中的值是对应的。视线 e 会和等分线产生交点(红点),直接使用交点的 uv 对高度图进行采样,会得到对应的几个高度值(蓝点)。最理想的情况下计算出来的结果是正好在黄点上,观察下红点和蓝点,在黄点左边的蓝点高于红点,在黄点右边的红点高于蓝点,我们可以通过这个规律找到位于黄点两边最近的两个红点和蓝点。这样就可以确定黄点就在这两个红点的中间。最后,沿着 e 的方向,在这两个红点之间进行插值,即可获得黄点的位置了,而插值需要用到 h1 和 h2 这两个线段的长度(红蓝两点的间距)。差值的精确度和一开始划分的精细度有关。这就是原理描述了,下面解释下代码。

inline float2 ParallaxUvDelta(Input i)
{
float3 viewDir = normalize(i.viewDir); // 细分的层数
const float numLayers = 20; // 单层步进的高度
float layerHeight = 1.0 / numLayers;
// 最高的高度值
float currentLayerHeight = 1.0;
// delta 最大值
float2 P = viewDir.xy * _ParallaxScale;
// delta 单步逼近值
float2 deltaTexCoords = P / numLayers; // 开始一步步逼近,直到找到合适的红点
float2 currentTexCoords = i.uv_MainTex;
float currentDepthMapValue = tex2D(_ParallaxMap, currentTexCoords).r;
while(currentLayerHeight > currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords;
currentDepthMapValue = tex2D(_ParallaxMap, currentTexCoords).r;
currentLayerHeight -= layerHeight;
} // 计算 h1 和 h2
float2 prevTexCoords = currentTexCoords + deltaTexCoords;
float afterHeight = currentDepthMapValue - currentLayerHeight;
float beforeHeight = currentLayerHeight + layerHeight - tex2D(_ParallaxMap, prevTexCoords).r;
// 利用 h1 h2 得到权重,在两个红点间使用权重进行差值
float weight = afterHeight / (afterHeight + beforeHeight);
float2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); return finalTexCoords - i.uv_MainTex;
} float2 uvDelta = ParallaxUvDelta(i);
i.uvMain += uvDelta;
i.uvBump += uvDelta;

效果图。Parallax Mapping 从性能上来说消耗是很大的,所有的操作都是像素级别的,并且其中包含大量的纹理采样,根据需要使用。

最后,关于 Shader 中编写循环指令的代码,并且循环指令无法在编译期间展开,比如 while(condition) for(condition),使用 cg 语言编写的话,是无法通过编译的,因为被编译出的中间 ARB VP/FP 不包含循环指令。所以需要添加上 ‘#pragma glsl’,让编译器直接编译成 glsl。

goto blog

视差贴图(Parallax Mapping)的更多相关文章

  1. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

  2. 3D游戏图形技术解析(7)——视差映射贴图(Parallax Mapping)【转】

    http://www.cnblogs.com/taotaobujue/articles/2781371.html 视差映射贴图(Parallax Mapping) ● 传统纹理贴图的弊端 纹理贴图大家 ...

  3. 置换贴图 Displacement Mapping

    视差贴图和法线贴图都是使用特定的手段来达到欺骗视觉的目的,让人以为物体的表面是凹凸起伏的.而置换贴图却是真的将模型的顶点进行偏移,在原本的平面上创造出凹凸的效果.既然是对顶点进行偏移,那么就需要模型有 ...

  4. Parallax Mapping Shader 凸凹感【转】

    原文 http://www.azure.com.cn/default.asp?cat=11&page=2 Parallax Mapping 就是通过高度图中的高度,对纹理坐标进行偏移,来视觉上 ...

  5. 视差滚动(Parallax Scrolling)效果的原理与实现

    视差滚动(Parallax Scrolling)效果的原理与实现1.视差滚动效果的主要特点:    1)直观的设计,快速的响应速度,更合适运用于单页面    2)差异滚动 分层视差    页面上很多的 ...

  6. Parallax Mapping

    [Parallax Mapping] Parallax mapping belongs to the family of displacement mapping techniques that di ...

  7. 翻译:非常详细易懂的法线贴图(Normal Mapping)

    翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...

  8. 【ZZ】 DShader之位移贴图(Displacement Mapping)

    http://www.myexception.cn/other/1397638.html DShader之位移贴图(Displacement Mapping) www.MyException.Cn   ...

  9. 视差滚动(Parallax Scrolling)的一点小心得

    下面内容来源于我知乎的这个答案:http://www.zhihu.com/question/20990029/answer/21436067 假期有空,整理到博客园这边,并做了一些语境的调整. ——— ...

随机推荐

  1. 使用 OWIN 作为 ASP.NET Web API 的宿主

    使用 OWIN 作为 ASP.NET Web API 的宿主 ASP.NET Web API 是一种框架,用于轻松构建可以访问多种客户端(包括浏览器和移动 设备)的 HTTP 服务. ASP.NET ...

  2. C语言每日一题之No.9

    再做决定之前,我还是做好自己该做的.我不希望几年后会悔恨自己为什么在最该努力的时候不愿意吃苦.尊敬的女王陛下,请接题: 一.题目:有已按升序排好顺序的字符串a,编写程序将字符串s中的每个字符按升序的规 ...

  3. 怎样减少FLASH影片文件过大——绝对好用

    网站建设中怎样减少FLASH影片文件过大 一,制作前的处理  1声音(mp3):   GoldWave中打开需要处理的mp3,然后把它另存为---在最下一栏的属性中选择较低的字节数,例如,本来的mp3 ...

  4. 事件日志ID 2511:服务器服务无法重新创建 <sharename> 共享关系,因为 <address> 目录已不再存在

    服务器服务无法重新创建 QQMusicDownload 共享关系,因为 D:\QQMusic\QQMusicDownload 目录已不再存在.请运行 "net share QQMusicDo ...

  5. python 读取sqlite3 数据库

    import sqlite3 name = "tom" age = 30 con = sqlite3.connect("d:\\test.db") cur = ...

  6. [转]将Word转(保存)为带书签的PDF

    提到的方法非常管用,感谢原作者的分享. 原文地址:http://blog.163.com/rongting_chen/blog/static/16490684420114266192887/ 将wor ...

  7. ArrayList源码

      1.首先看对ArrayList的定义:   public class ArrayList<E> extends AbstractList<E> implements Lis ...

  8. 纯c++实现之滚动窗口

    别在MFC了,先分析下,上图 我们以左上角为坐标原点,用position_width和position_height来保存当前显示坐标. 根据msdn说明,滚动条默认情况下的值在0~100之间. 根据 ...

  9. c++101rule

    组织策略0,不拘于小结缩进, 行的长度,命名,注释,空格,制表,1-4,高警告级别干净利落地进行编译,使用构建系统,使用版本控制,代码审查风格5,一个实体应该只有一个紧凑的职责. (依赖性管理,继承, ...

  10. BIP_开发案例08_BI Publisher图表示例 饼状图/直方图/折线图(案例)

    2014-12-25 Created By BaoXinjian