视差贴图理解起来比法线贴图困难,写个笔记记录一下
一:原理
在观察某个点时视差贴图采样到的数据来自附近纹理坐标数据。这是由于人观察物体时视线被更靠近眼睛的高物体遮挡导致的。由于根据高度进行采样,比起法线贴图的真实感光影,纹理会拥有遮挡效果(也就会比法线贴图更立体)
高度图:
首先纹理拥有高度图,纹理所在位置的高度通过高度图进行采样。白色为高,黑色则底。
进行视差贴图时会使用深度图,即用1.0f减去采样的值获得反向数据。或者将高度图颜色反向。
偏移计算:
当观察A点时会被B点遮挡,所以观察A点时的纹理实际上显示的是B点纹理。黄色的线是视点方向向量v(是被标准化的向量),v(P)即由v经过缩放得到的偏移向量(如上图)。但在实现过程中,Parallax Mapping并不会精确的采到B点的高度数据。这是由于v(P)并没有指到B点,因为v(P)的长度=H(A)。所以根据P的投影,采样到的偏移高度数据实际是H(P)(一个接近B的结果)。但是当高度变化极快时采样的结果会与B差别很大,如下图:
二:实现
ParallaxMapping函数:
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
float height = texture(depthMap, texCoords).r;
vec3 p = viewDir.xy / viewDir.z * (height * height_scale);
return texCoords - p;
}
height从高度图采样得到高度数据H(A)
H(A)/viewDir.z可以得到沿视点方向v的偏移长度。viewDir.z相当于sinα。再乘viewDir.xy获得缩放后的v(P)即偏移的xy坐标。目标点A的纹理坐标减去v(P)就是实际遮挡后采样的xy坐标,也就是图中褐色点的xy坐标。最后根据从该点采样H(P)。
三:拓展
陡峭视差映射
这种办法的采样深度更加准确,且方法和之前的也有些区别。
Steep Parallax Mapping将深度分为多层,沿着视点方向在每层都采样一次。采样时会记录当前深度层深度和在该层根据纹理坐标在深度图中采样的到深度值并将两个值进行对比,直到找到采样值低于深度层深度的点。(图中从右往左采样即从浅到深)代码中沿p方向每1/10采样一次。
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
// number of depth layers
const float numLayers = 10;
// calculate the size of each layer
float layerDepth = 1.0 / numLayers;
// depth of current layer
float currentLayerDepth = 0.0;
// the amount to shift the texture coordinates per layer (from vector P)
vec2 P = viewDir.xy * height_scale;
float deltaTexCoords = P / numLayers;
[...]
}
提升效率的小技巧:由于从正面看时纹理位移小,可以减少采样层数,而观察角度大时偏移量大,可以增加采样层数。
const float minLayers = 8;
const float maxLayers = 32;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
由于采样样本有限图片锯齿和断层效果严重可以使用一些插值方法减少这个问题主流办法有Relief Parallax Mapping和Parallax Occlusion Mapping。
视差遮蔽映射(Parallax Occlusion Mapping)
修复锯齿断层的一中办法。在深度层与采样值深度对比时不选取跳变前后的点,而是在触碰前后的深度层间进行线性插值
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
// interpolation of texture coordinates
float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
浮雕视差映射(Relief Parallax Mapping)
该方法更精确,主要思想就是尽量找到前文提到的B点的精确位置。基于陡峭视差映射已经可以得到交点的前一个与后一个点坐标。浮雕视差映射通过二分法在这两个点之间不断迭代逼近真实的交点,使用该方法时手动设定一个迭代次数,一般5次效果就很不错,最终一次迭代的结果即最终N点近似坐标。Relief Parallax Mapping更精确一些,但是比Parallax Occlusion Mapping性能开销更多。因为Parallax Occlusion Mapping的效果和前者差不多但是效率更高,因此Parallax Occlusion Mapping更经常使用。