从视差映射、浮雕映射中获取正确的深度值
POM和RCSM 在我之前的文章在Unity里实现松散圆锥步进Relaxed Cone Step Mapping就已经介绍过了视差映射和松散圆锥步进浮雕映射的计算方法了,但是之前并没有对计算深度值做相应的研究,同时也限制于篇幅的原因就没有再展开了,这篇文章相当于是之前文章的后续。为了简便,后续将这两种计算方法统称为视差映射。 在视差映射中计算深度值是一个很直接的想法,因为很有可能会有其他物体被放置在视差映射的表面,与之发生穿插,如果不做特殊处理,就会使用模型本身的深度值进行深度比较,导致别的物体不能有正确的被遮挡的效果,削弱了视差映射带来的真实感。网上我找了一圈,并没有找到和计算视差映射的深度值相关的文章,因此我想用这篇文章进行相关的介绍。 Unity的高清管线(HDRP)的Lit Shader支持计算像素深度偏移,提供了Primitive Length,Primitive Width,和Amplitude三个参数。Amplitude可以用来控制视差映射的强度值,虽然其一个单位和世界空间的一米完全不能直接等同起来,但是值越大视差看上去就越深,可以根据视觉实时调整这个参数。另外两个参数就很奇怪了,居然和模型的大小有关,同一个材质球,用在Quad上这里就要填1,用在Plane上就要填10,哪有这种道理?虚幻引擎则是提供了POM的接口,至于输入和输出完全都由用户控制,这里就不太好直接比较了。 回顾POM的计算过程 视差映射一般不会直接在世界空间步进,而是会先将世界空间的视线viewWS转换到切线空间viewTS,在切线空间步进。照常理_ParallaxIntensity是用来控制视差映射的深度的,因此会使用这个参数控制z方向步进的距离,但为了方便和高度图中记载的高度进行对比,会先对viewTS的z分量进行归一化,将_ParallaxIntensity在步进时乘到viewTS的xy分量上,之后就是循环比较深度进入下一个循环了。 但是为什么是切线空间呢?这是因为切线tangent和副切线bitangent代表了贴图UV的xy的正方向,将视线转换到切线空间,其实目的是将视线转到UV空间,或者说是贴图空间(Texture Space,因为其与切线空间的相似性,我们还是用TS来做简写)。这里就出现了最重要的一个问题,Unity中通过GetVertexNormalInputs获得到的世界空间的切线是经过归一化的,丢失了物体自身的缩放,所以我们其实应该先将世界坐标的视线viewWS转换到物体空间viewOS,然后再使用物体空间的tbn矩阵,将viewOS转换到切线空间viewTS。但又如我上面说到的,我们真实的目的是贴图空间,切线空间和贴图空间是存在差异性的。这也就是为什么Unity的HDRP要使用额外的参数Primitive Length和Primitive Width了,这两个参数的目的是通过额外的缩放,将切线空间和贴图空间对应起来。 这两个参数的意义应当是,贴图空间的xy分量每一个单位在物体空间的长度,这里我们记为uvScale。同时我们可以顺理成章地正式引入_ParallaxIntensity这个参数,它的含义应当是,贴图中颜色为0的点对应的物体空间的深度值。贴图空间转换到物体空间,只需要对xyz三个分量分别乘上uvScale.x,uvScale.y,和_ParallaxIntensity即可。_ParallaxIntensity这个参数我们可以作为材质球的一个输入进行控制,uvScale是一个跟模型相关的参数,我们可以在Geometry Shader中计算而得。 uvScale的计算 如上面所属,uvScale指代的是贴图空间的xy分量每一个单位在物体空间的长度。对于两个顶点v0和v1,贴图空间的xy分量其实就是这两个顶点uv值的差,物体空间的长度其实就是两个顶点之间的距离,为了对应到贴图空间上,我们需要计算这段距离在切线和副切线上的投影长度,后者除以前者就是我们需要的uvScale了。由于构成三角形的三个顶点可能会存在某两个顶点之间uv的某个分量的变化率为0,导致我们计算uvScale的时候除以零,我们在检测到这个情况的时候使用第三个顶点即可。 贴图空间变换 在获得了物体空间的切线、副切线和法线之后,为了构成贴图空间的三个基向量,我们需要对这个向量使用uvScale和_ParallaxIntensity进行缩放。这个缩放导致了我们按照以往的float3x3(tangentOS * uvScale.x, bitangentOS * uvScale.y, normalOS * _ParallaxIntensity)构成的矩阵不再是一个正交矩阵,它实际上是贴图空间到物体空间的变换矩阵的转置。因此将物体空间的视线viewOS转换到贴图空间viewTS时,我们要用这个矩阵的转置的逆左乘viewOS,将贴图空间的视线viewTS转换到物体空间viewOS时,我们要用这个矩阵的转置左乘viewTS。 深度的获取 这个就相对来说比较简单了,我们在贴图空间步进的时候,可以知道我们在贴图空间步进的z方向的深度值len。而由于我们的viewTS会做除以z分量的归一化,我们只需要用归一化前的-viewTS乘上len再除以z分量,就能知道我们在贴图空间中总的步进的向量,将其转换到物体空间再转换到世界空间,和当前点的世界空间的坐标相加后再转换到裁剪空间,其z分量除以w分量就是我们需要的深度值了。 具体的代码 这里只做了可行性的研究,应该有个方法能够简化计算矩阵的逆这一步操作。在计算世界空间的切线、副切线和法线的时候,可以不进行归一化,这样我们也就不需要先转换到物体空间再转换到贴图空间了。 POMShader.shader Shader "zznewclear13/POMShader" { Properties { [Toggle(OUTPUT_DEPTH)] _OutputDepth ("Output Depth", Float) = 1 _BaseColor("Base Color", Color) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} _HeightMap("Height Map", 2D) = "white" {} _NormalMap("Normal Map", 2D) = "bump" {} _NormalIntensity("Normal Intensity", Range(0, 2)) = 1 _ParallaxIntensity ("Parallax Intensity", Float) = 1 _ParallaxIteration ("Parallax Iteration", Float) = 15 } HLSLINCLUDE #include "Packages/com....