OpenGL基础49:高度贴图(下)

接上文:OpenGL基础48:高度贴图(上)

四、陡峭视差映射

上文计算纹理偏移的方法是最简单的,但问题也比较大:当你斜视贴图的时候可以看到非常明显的错误

这里有另一种方法,这种方法类似于找连续函数在特定定义域内与x轴第一次相交的点:在范围内不停的取x值,直到当前得到的 f(x) 与上一次得到的 f(x) 符号不同,这样就可以确定交点在上一个采样点和这次采样点之间

就如上,先将总深度范围分布到同一个深度的多个层中,然后在每个层中沿着观察方向移动采样纹理坐标,直到找到第一个采样深度值 ≤ 测试深度值的点,以确定最终的纹理偏移

换成代码如下:

const float numLayers = 12;                       //样本数
float layerDepth = 1.0 / numLayers;               //每层样本深度
float currentLayerDepth = 0;                      //当前测试到的样本深度
vec3 newViewDir = transpose(TBN) * viewDir;
vec2 deltaTexCoords = newViewDir.xy * 0.06 / numLayers;           //单层样本对应的纹理偏移

vec2 currentTexCoords = texCoords;
float currentDepthMapValue = texture(material.texture_height1, currentTexCoords).r;
while(currentLayerDepth < currentDepthMapValue)                   //在找到纹理深度小于等于测试深度的采样点的时候退出循环
{
    currentLayerDepth += layerDepth;                              //继续测试下一个偏移
    currentTexCoords -= deltaTexCoords;                           //确定正确的纹理采样点
    currentDepthMapValue = texture(material.texture_height1, currentTexCoords).r;       //获取纹理深度
}
texCoords = currentTexCoords;

非常好理解,主要就是以下步骤:

  1. 设定采样精度,开始循环
  2. 根据观察方向得到当前的纹理偏移
  3. 获取当前采样点的纹理深度,判断是否在测试深度之上,如果是结束循环

必然设定的样本数量越高效果越好,但是性能的代价也越高

当然了,在这基础之上也可以进行优化,很明显的:斜视的角度越大,越需要更高的采样精度,因此可以根据观察方向的z向量来决定采样的层数:这正好可以通过点乘来判断,点乘的一个意义就是判断两个向量的“相似度”,如果观察向量与法线向量越“相似”,就说明斜视角度越小

const float minLayers = 8;
const float maxLayers = 36;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));        //样本数

五、遮蔽视差映射

视差遮蔽映射(Parallax Occlusion Mapping)和陡峭视差映射(Steep Parallax Mapping)的原理相同,只不过在最后计算的时候,不再简单的用触碰的第一个深度层的纹理坐标,而是在触碰之前和之后,在深度层之间进行线性插值

const float minLayers = 8;
const float maxLayers = 36;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));        //样本数
float layerDepth = 1.0 / numLayers;               //每层样本深度
float currentLayerDepth = 0;                      //当前测试到的样本深度
vec3 newViewDir = transpose(TBN) * viewDir;
vec2 deltaTexCoords = newViewDir.xy * 0.06 / numLayers;           //单层样本对应的纹理偏移

float lastDepth = 0;
vec2 currentTexCoords = texCoords;
float currentDepthMapValue = texture(material.texture_height1, currentTexCoords).r;
while(currentLayerDepth < currentDepthMapValue)                   //在找到纹理深度小于等于测试深度的采样点的时候退出循环
{
    lastDepth = currentDepthMapValue - currentLayerDepth;
    currentLayerDepth += layerDepth;                              //继续测试下一个偏移
    currentTexCoords -= deltaTexCoords;                           //确定正确的纹理采样点
    currentDepthMapValue = texture(material.texture_height1, currentTexCoords).r;       //获取纹理深度
}
texCoords = currentTexCoords;

if (lastDepth != 0)
{
    float currentDepth = currentLayerDepth - currentDepthMapValue;
    float weight = currentDepth / (currentDepth + lastDepth);
    vec2 finalTexCoords = (currentTexCoords + deltaTexCoords) * weight + currentTexCoords * (1 - weight);
    texCoords = finalTexCoords;
}

非常好理解,代码也好改,对应上面的图就是:不再取点 T_3 的纹理坐标,而是根据 T_3 和 T_2 下面浅蓝色的线长度之比,来取得对应 T_3 和 T_2 之间的一个纹理坐标

好了这下十分的完美,可以说几乎没有任何视觉上的错误:

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/108195475