OpenGL 视差贴图
视差贴图是什么?
视差贴图是法线贴图的增强版和延伸。它们协同工作,但解决的是不同层次的问题
- 法线贴图:解决了表面细节的光照问题。它通过改变每个片元的法线向量来模拟凹凸不平的光照效果,但物体的轮廓、边缘和自身的遮挡关系依然是平坦的
- 视差贴图:解决了表面细节的几何深度和遮挡问题。它通过偏移纹理坐标,模拟了人眼观察时因为视角不同而产生的纹理位移(视差效应),从而创造出更强的深度幻觉,甚至能模拟自我遮挡
你可以把它们理解为一种进阶关系:
平面纹理 -> 法线贴图 -> 视差贴图 -> 陡峭视差贴图 -> 视差遮蔽贴图
1. 法线贴图 (Normal Mapping)
目的:
- 在不增加模型顶点数量的前提下,通过改变光照计算时使用的法线方向,来模拟物体表面的微观凹凸细节
- 它只影响光照,让平坦的表面看起来有明暗变化,像是凹凸不平的
工作原理:
- 纹理存储:法线贴图是一张 RGB 图像。其颜色值 (
r
,g
,b
) 对应着一个法向量的(x,
y,
z)
分量。这些法向量定义在切线空间(Tangent Space) 中 - 切线空间:这是一个相对于物体表面每个点的局部空间
○ N 轴:与物体表面法线方向对齐(垂直于表面)
○ T 轴:与物体表面的切线方向对齐(通常沿纹理的 U 方向)
○ B 轴(或叫 Bitangent):与切线(T)和法线(N)垂直(通常沿纹理的 V 方向)。B = cross(N, T)
- 光照计算:在片段着色器中,我们将光线方向和视线方向从世界空间转换到切线空间。然后,从法线贴图中采样得到切线空间下的法线向量,直接用于计算漫反射和高光光照。因为这个采样的法线方向各不相同,所以即使平面是平的,也能计算出凹凸的光照效果
局限性:
模型本身的形状没有改变。从侧面看,模型的轮廓依然是平滑的,而且凹凸之间不会有任何遮挡关系
2. 视差贴图 (Parallax Mapping)
目的:
- 在法线贴图的基础上,进一步模拟出深度感和遮挡关系。它通过制造视觉上的位移(视差)来欺骗眼睛,让观察者认为表面真的有高低起伏
工作原理(基本思想):
- 高度图(Height Map):视差贴图需要另一张纹理,称为高度图(通常是灰度图)。白色(值~1.0)代表该点“高”,黑色(值~0.0)代表该点“低”
- 纹理坐标偏移:核心思想是,根据表面高度和观察视角来偏移纹理坐标
○ 视角越陡峭,看到的纹理位移应该越小
○ 视角越平缓,看到的纹理位移应该越大
○ 点的高度越高,它应该“凸出来”更多,其纹理坐标的偏移也越大 - 算法步骤(在片段着色器中):
① 计算从表面点到观察者在切线空间中的方向向量V
对
②V
进行归一化,并将其x
和y
分量(代表切线和副切线方向)除以它的z
分量(代表法线方向)。这一步实质上是在根据视角的平缓程度来缩放偏移量
③ 从高度图中采样当前片段的高度值H
将缩放后的视角向量
④V.xy
与高度H
和一个控制整体深度的缩放因子scale
相乘:Offset = V.xy * (H * scale)
用这个计算出的
⑤Offset
去偏移原始的纹理坐标,然后用偏移后的新坐标去采样漫反射纹理和法线贴图// GLSL 片段着色器代码示例(简化概念) vec2 ParallaxMapping(vec2 texCoords, vec3 viewDirTangentspace) {float height = texture(heightMap, texCoords).r; // 采样高度vec2 p = viewDirTangentspace.xy / (viewDirTangentspace.z + 0.0001); // 视角缩放因子return texCoords - p * (height * parallaxScale); // 返回偏移后的坐标 }void main() {// 将观察方向转换到切线空间vec3 viewDirTangentspace = ...;// 计算视差偏移后的纹理坐标vec2 offsetTexCoords = ParallaxMapping(TexCoords, viewDirTangentspace);// 使用偏移后的坐标进行采样vec3 normal = texture(normalMap, offsetTexCoords).rgb;vec4 color = texture(diffuseMap, offsetTexCoords);// ... 后续光照计算 }
与法线贴图的关系:
- 依赖关系:视差贴图几乎总是和法线贴图一起使用。你首先使用视差贴图技术计算出更正确的、能反映深度的纹理坐标,然后用这个新坐标去采样法线贴图和漫反射贴图
- 效果叠加:法线贴图提供了细节的光照,而视差贴图提供了细节的几何位移和遮挡。两者结合,效果远超单独使用法线贴图
进阶形式:
- 陡峭视差贴图 (Steep Parallax Mapping):基本视差贴图在高度变化剧烈时容易失真。陡峭视差贴图将深度分层,进行步进式射线追踪,效果更好,是更常用的实践
- 视差遮蔽贴图 (Parallax Occlusion Mapping):在陡峭视差贴图的基础上,在最后一步进行线性插值,使得层次之间的过渡更加平滑,效果极其逼真,是当前的高标准
总结对比
特性 | 法线贴图 (Normal Mapping) | 视差贴图 (Parallax Mapping) |
---|---|---|
核心目标 | 模拟凹凸的光照细节 | 模拟凹凸的几何深度和遮挡 |
所需纹理 | 法线贴图 (Normal Map) | 高度图 (Height Map) + 法线贴图 |
技术核心 | 在切线空间中使用采样的法线进行光照计算 | 根据高度和视角偏移纹理坐标 |
性能开销 | 小 | 中到大(取决于算法复杂度) |
效果 | 表面光照有凹凸感,但轮廓平坦,无遮挡 | 具有强烈的深度感和简单的遮挡,轮廓依然平坦 |
关系 | 基础 | 增强和延伸,建立在法线贴图工作流之上 |
在 OpenGL 中实现的要点
- 切线空间计算:两者都极度依赖切线空间。你需要在 CPU 端为你的模型计算每个顶点的切线(Tangent) 和副切线(Bitangent) 向量,并将它们作为属性传递给着色器。然后用
T, B, N
矩阵构建从模型空间到切线空间的变换矩阵 - 统一的工作流:通常的流程是:
○ 在片段着色器中,先使用高度图和视角向量计算视差偏移后的纹理坐标
○ 使用这个偏移后的坐标去采样法线贴图,得到切线空间下的法线
○ 使用偏移后的坐标去采样漫反射/高光等颜色纹理
○ 在切线空间中进行光照计算 - 参数调节:视差效果有一个
height_scale
参数,需要根据你的纹理和模型进行微调。值太大会产生失真,太小则效果不明显
总而言之,法线贴图和视差贴图是相辅相成的技术。法线贴图提供了“形”,而视差贴图提供了“影”,两者结合才能创造出以假乱真的复杂表面细节