
*阅读本文章最好能有OpenGL或DirectX一定的基础~
正式开始之前我们最好先看看视差贴图长啥样~

视差贴图
好的,如图所示,视差贴图一般就是只有黑白两色的这么一种贴图。
既然只有黑白两色的话,那么也可以说明这种贴图在每一个片元(像素)中的有效信息只有一种。比如,在法线贴图中我们利用某个像素的RGB三种颜色值表示某个面上的某点法线三个轴向(切线空间下)的信息,所以说法线贴图对于某个片元来说是相当于携带了三种有效信息。同样的,在视差贴图中由于RGB三个值都是同样的,所以,视差贴图对于某个片元来说就是相当于携带了一种有效信息。

法线贴图
好了,我们现在描述了视差贴图的一些最直观的特征,总结一下:
1.视差贴图对于某片元来说只携带了一种有效信息。
2.视差贴图某像素的RGB的三个颜色值相同。(R = G = B)
根据以上特点我们其实也能想得出来视差贴图只需要一个通道(如R通道)就可以具备该有的功能了。
那么,虽然我们已经能够知晓视差贴图中的信息了,但这种信息怎么正确理解和读取却是一个很麻烦的问题。
视差贴图所携带信息的含义是什么?
最直接的回答就是根据储存在纹理中的几何信息对顶点进行位移或偏移的一种贴图,同时也是利用了属于位移贴图(Displacement Mapping)技术的一种。
好的,以上摘自learnopengl.com中视差贴图那一节。具体啥意思不必深究,反正抓住“偏移”这个关键词即可。
用人话来说,视差贴图就是让纹理偏移来达到让某平面看起来能够有“立体感”的一种贴图。配合法线贴图效果更佳~
视差贴图实际上是携带了某片元在某个面上的深度信息(只讨论切线空间)。

简化关系
以上是可以大概表示为视差贴图的工作环境。大致的流程为:我们在摄像机下获取到某个片元P,然而真实情况下我们获得的并非是P点的UV值,而是P'的UV值。说白了就是P'点的UV值偏移到了P点。
那么如何用数学来精确描述呢?事实上精确描述有点儿难,这里我们尝试着先表达一下看看~

数学表达
根据这张图可以大概知道摄像机相对片元的面上的位移越大(切线空间下的x,y轴),UV的偏移值就越大。具体数学表达式可以为:
texcoordOffset = viewDir.xy * K (K为系数)
备注:viewDir是切线空间下的表示,viewDir = normalize(cameraPos_T - fragPos_T);后缀_T表示该向量的坐标空间,如:fragPos_T表示fragment position在切线空间(Tangent)下的坐标。
此时一个矛盾就出来了:p4'和p5'的偏移比例完全不一样啊。如果实际表面越低,偏移量就越小才对。根据这个思路,进一步的完善这个式子:
texcoordOffset = viewDir.xy * height * K
(height为视差贴图中采样得出的值,K为系数)
好的,通过以上式子我们可以得到一些灵感之类的。。。
现在我们再来讨论p2'和p3‘之间的一些小瑕疵:我们可以看到随着摄像机相对片元的位移增大,仅仅用K一个常数进行缩放很不灵活,很可能会出现奇奇怪怪的效果,我们再来完善一下~
texcoordOffset = viewDir.xy / viewDir.z * height * heightScale
(height为视差贴图中采样得出的值,heightScale为系数)
现在这样的表达式可以说是比较完善了~
即:texcoord = Texcoord - texcoordOffset (Texcoord为VAO中自带的UV值)
下面我们贴上片元着色器中的关键代码:
in vec3 fragPos_T; in vec3 lightDir_T; in vec3 cameraPos_T; in vec2 Texcoords; uniform sampler2D diffuseMap; uniform sampler2D normalMap; uniform sampler2D parallaxMap; uniform float heightScale; uniform float smoothness; uniform vec3 lightColor; out vec4 FragColor; vec2 ParallaxMapping(vec2 _texcoords, vec3 viewDir) { float height = texture2D(parallaxMap, _texcoords).r; return _texcoords - viewDir.xy / viewDir.z * height * heightScale; } void main() { vec3 viewDir_T = normalize(cameraPos_T - fragPos_T); vec2 texcoords = ParallaxMapping(Texcoords, viewDir_T); vec3 diffuseColor = texture2D(diffuseMap, texcoords).rgb; vec3 testdiffuseColor = texture2D(diffuseMap, Texcoords).rgb; vec3 ambient = 0.1 * diffuseColor; vec3 normal_T = normalize((texture2D(normalMap, texcoords).rgb - 0.5)); vec3 normal = vec3(0.0, 0.0, 1.0); float diffuseIntensity = max(dot(lightDir_T, normal_T), 0.0); vec3 diffuse = diffuseIntensity * diffuseColor * lightColor; vec3 halfWayDir_T = normalize(viewDir_T + lightDir_T); float specularIntensity = pow(max(dot(halfWayDir_T, normal_T), 0.0), smoothness) * 0.5; vec3 specular = specularIntensity * lightColor; FragColor = vec4(ambient + diffuse + specular, 1.0); }
以上就是视差贴图的工作流以及运作的大致原理。
效果图:

效果图
以上就是纯粹地利用视差贴图与法线贴图制造的“立体感”的幻觉,这个“模型”纯粹就只有6个顶点渲染出来的。当然如果算得的表面深度值过于陡峭的话,就会发生一些奇奇怪怪的扭曲的现象。比如我们把hightScale设置的太大的话,画面就会扭曲的没法儿看了~
参考资料:https://learnopengl-cn.github.io/05%20Advanced%20Lighting/05%20Parallax%20Mapping/