【项目地址:点击这里这里这里】
本节对应官网学习内容:视差贴图
结合英文原站,中文站,以及个人实践进行描述(这一节英文原站描述有一些不合理之处,进行了更正)。
1 引入
视差贴图(Parallax Mapping)是一种类似于法线贴图的技术,但是基于不同的原理。
就像法线贴图一样,它是一种可以显着提高纹理表面的细节并赋予其深度感的技术。
虽然也是一种错觉,但视差贴图在传达深度感方面要好得多,并且与法线贴图一起可以提供令人难以置信的逼真的效果。
尽管视差贴图不一定是与高级光照直接相关的技术,但仍将在此进行讨论,因为该技术是法线贴图的逻辑跟进。
请注意,强烈建议在学习视差贴图之前先了解法线贴图,尤其是切线空间。
视差贴图属于位移贴图(Displacement Mapping)技术的一种,它对根据储存在纹理中的几何信息对顶点进行位移或偏移。
一种实现方法是,比如要渲染一个平面,该平面具有大约1000个顶点,我们根据纹理中的某个值来替换这些顶点中的每个顶点,该值可以告诉我们该特定区域的平面高度。
这种包含每个纹理像素高度值的纹理称为高度图。从简单砖块表面的几何特性派生的示例高度图看起来像这样:
整个平面上的每个顶点都根据从高度贴图采样出来的高度值进行位移,根据材质的几何属性,平坦的平面变换成凹凸不平的表面。
例如一个平坦的平面利用上面的高度贴图进行置换能得到以下结果:
置换顶点有一个问题——平面必须由很多顶点组成才能获得具有真实感的效果,否则看起来效果并不会很好。
一个平坦的表面上有1000个顶点的计算量太大了。
我们能否不用这么多的顶点就能取得相似的效果呢?
事实上,上面的表面就是用6个顶点渲染出来的(两个三角形)。
上面的那个表面使用视差贴图技术渲染,位移贴图技术不需要额外的顶点数据来表达深度,它像法线贴图一样采用一种聪明的手段欺骗用户的眼睛。
视差贴图背后的思想是,修改纹理坐标使一个fragment的表面看起来比实际的更高或者更低,所有这些都是根据观察方向和高度贴图进行的。
为了理解它如何工作,看看下面砖块表面的图片:
这里粗糙的红线代表高度贴图中的数值的立体表达,向量 V ⃗ \vec{V} V代表观察方向。
如果平面进行实际位移,观察者会在点B看到表面。
然而我们的平面没有实际上进行位移,观察方向将在点A与平面接触。
视差贴图的目的是,在A位置上的fragment不再使用点A的纹理坐标而是使用点B的。
随后我们用点B的纹理坐标采样,观察者就像看到了点B一样。
这个技巧就是描述如何从点A得到点B的纹理坐标。
视差贴图尝试通过对从fragment到观察者的方向向量 V ⃗ \vec{V} V的进行缩放的方式解决这个问题。
1.1 偏移方法一
缩放的的程度是,缩放后它到表面的距离等于H(a)。
注意,这里英文官网的方法是 长度 缩放为H(a)(也就是第二种偏移方法)
但是它给出的代码,其实是利用Z的高度
在刚看到代码部分,没看下去的时候我很困惑为何前后描述不一致,在此提醒
这个用Z的缩放方法可以看这个paper的4.1节
Parallax Mapping with Offset Limiting: A Per-Pixel Approximation of Uneven Surfaces
所以我们将 V ⃗ \vec{V} V的高度,缩放为高度贴图在点A处H(A)采样得来的值。下图展示了经缩放得到的向量 P ⃗ \vec{P} P:
我们随后选出 P ⃗ \vec{P} P以及这个向量在平面投影的坐标作为纹理坐标的偏移量(texture coordinate offset)
因为向量 P ⃗ \vec{P} P是使用从高度贴图得到的高度值计算出来的,所以一个fragment的高度越高位移的量越大。
但是使用Z进行偏移的方法很少是正确的,任何出现视差的表面都会有不同的高度。
这种方法在陡峭看下来的视角下,纹理坐标偏移往往很小。小偏移量意味着Tn处的高度可能非常接近to处的高度。因此,偏移量将几乎是正确的。
但是随着视角变浅,偏移值接近有限。
当偏移值显著增大时,Tn索引与之相似的高度的概率逐渐衰减为随机概率。
这个问题可以将具有复杂高度图案的曲面简化为一团闪闪发光的像素,这些像素看起来与原始纹理贴图完全不同。
1.2 偏移方法二
解决这个问题的一个简单方法是限制偏移量,使其不会超过H(a)。
可以看这个paper的4.3节(这也是英文网站它给的方法,但是代码上却没这样做)
Parallax Mapping with Offset Limiting: A Per-Pixel Approximation of Uneven Surfaces
因为视差映射是一种近似,任何极限值都可以产生,但这个方法很好,它通过两条指令在程序中导出代码。
T n = T o + ( H ( a ) ∙ V x , y ) T_n = T_o + (H(a) ∙ V{x, y} ) Tn=To+(H(a)∙Vx,y)
这个技巧在大多数时候都没问题,但“点B”是粗略估算得到的。当表面的高度变化很快的时候,看起来就不会真实,因为向量 P ⃗ \vec{P} P最终不会和B接近。
视差贴图的另一个问题是,当表面被任意旋转以后很难指出从 P ⃗ \vec{P} P获取哪一个坐标。
我们在视差贴图中使用了另一个坐标空间,这个空间 P ⃗ \vec{P} P向量的x和y元素总是与纹理表面对齐。如果看了法线贴图教程,就知道我们实现它的方法,我们还是在切线空间中实现视差贴图。
将fragment到观察者的向量 P ⃗ \vec{P} P,转换到切线空间中,经变换的 P ⃗ \vec{P} P向量的x和y元素将于表面的切线和副切线向量对齐。
由于切线和副切线向量与表面纹理坐标的方向相同,我们可以用 P ⃗ \vec{P} P的x和y元素作为纹理坐标的偏移量,这样就不用考虑表面的方向了。
理论都有了,下面我们来动手实现视差贴图。
2 视差贴图
我们将使用一个简单的2D平面,在把它发送给GPU之前我们先计算它的切线和副切线向量;和法线贴图教程做的差不多。(我们直接继续在法线贴图的例子上做吧)
我们将向平面贴diffuse纹理、法线贴图以及一个位移贴图
这个例子中我们将视差贴图和法线贴图连用。
因为视差贴图生成表面位移了的幻觉,当光照不匹配时这种幻觉就被破坏了。
法线贴图通常根据高度贴图生成,法线贴图和高度贴图一起用能保证光照能和位移匹配。
可能已经注意到,上面链接上的那个位移贴图和教程一开始的那个高度贴图相比是颜色是相反的。
这是因为使用反色高度贴图(也叫深度贴图)去模拟深度比模拟高度更容易。
下图我们讲讲这个轻微的改变:
我们再次获得A和B,但是这次我们用向量 V ⃗ \vec{V} V减去点A的纹理坐标得到 P ⃗ \vec{P} P。
我们通过在着色器中用1.0减去采样得到的高度贴图中的值来取得深度值,而不再是高度值,或者简单地在图片编辑软件中把这个纹理进行反色操作,就像我们对连接中的那个深度贴图所做的一样。
位移贴图是在片段着色器中实现的,因为三角形表面的所有位移效果都不同。
在片段着色器中我们将需要计算fragment到观察者到方向向量 V ⃗ \vec{V} V所以我们需要观察者位置和在切线空间中的fragment位置。
法线贴图教程中我们已经有了一个顶点着色器,我们用那个就好了。(可以看上一篇笔记)
在片段着色器中,我们实现视差贴图的逻辑。片段着色器看起来会是这样的:
......
uniform sampler2D depthMap;
uniform float height_scale;
vec2