[UnityShader入门精要读书笔记]32.获取深度和法线纹理

深度纹理实际就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。由于被存储在一张纹理中,深度纹理里的深度值范围是[0,1],而且通常是非线性分布的。总体来说,这些深度值来自于顶点变换后得到的归一化的设备坐标。一个模型要想最终被绘制在屏幕上,需要把它的顶点从模型空间变换到齐次裁剪坐标系下,这是通过在顶点着色器中乘以MVP变换矩阵得到的。在变换的最后一步,我们需要使用一个投影矩阵来变换顶点,[UnityShader入门精要读书笔记]32.获取深度和法线纹理_第1张图片

[UnityShader入门精要读书笔记]32.获取深度和法线纹理_第2张图片

[UnityShader入门精要读书笔记]32.获取深度和法线纹理_第3张图片

在Unity中,深度纹理可以直接来自于真正的深度缓存,也可以是有一个单独的Pass渲染而得,这取决于使用的渲染路径和硬件。通常来讲,当使用延迟渲染路径时,深度纹理理所当然可以访问到,因为延迟渲染会把这些信息渲染到G-buffer中。而当无法直接获取深度缓存时,深度和法线纹理是通过一个单独的Pass渲染而得到的。具体实现是,Unity会使用着色器替换技术选择那些渲染类型为Opaque的物体,判断它们使用的渲染队列是否小于等于2500(background,Geometry,AlphaTest)。如果满足条件,就把它渲染到深度和法线纹理中。因此,要想让物体出现在深度和法线纹理中,就必须在Shader中设置正确的RenderType标签。

        在Unity中,我们可以选择让一个摄像机生成一张深度纹理或是一张深度+法线纹理。当选择前者,即只需要一张单独的深度纹理时,Unity会直接获取深度缓存或是按之前讲到的着色器替换技术,选取需要的不透明物体,并使用它投射影时使用的Pass来得到深度纹理。如果Shader中不包含这样一个Pass,那么这个物体就不会出现再深度纹理中。 深度纹理的精度通常是24位或16位,这取决于使用的深度缓存的精度。如果选择生成一张深度+法线纹理,Unity会创建一张和屏幕分辨率相同、精度为32位(每个通道8位)的纹理,其中观察空间下的法线信息会被编码进纹理的R和G通道,而深度信息会被编码进B和A通道。法线信息的获取在延迟渲染中是可以非常容易就得到的,Unity只需要合并深度和法线缓存即可。而在前向渲染中,默认情况下是不会创建法线缓存的,因此Unity底层使用了一个单独的Pass把整个场景再次渲染一遍来完成。

        在Unity中,获取深度纹理是非常简单的,通过如下代码就可以获取深度纹理:

        camera.depthTecxtureMode = DepthTextureMode.Depth;在Shader中,通过声明_CameraDepthTexture变量访问。

        想要获取深度+法线纹理,camera.depthTextureMode = DepthTextureMode.DepthNormals;在Shader中通过_CameraDepthNormalsTexture变量来访问它。我们还可以组合这些模式,让一个摄像机同时产生一张深度和深度+法线纹理:

        camera.depthTextureMode |= DepthTextureMode.Depth;

        camera.depthTextureMode |= DepthTexureMode.DepthNormals;

在Unity5中,还可以在摄像机的Camera组件上看到当前摄像机是否需要渲染深度或深度+法线纹理。当在Shader中访问到深度纹理_CameraDepthTexture后,我们就可以使用当前像素的纹理坐标对它进行采样。绝大多数情况下,我们直接使用tex2D函数采样即可,但在某些平台(PS3和PSP2)上,我们需要一些特殊处理。Unity提供了一个统一的宏SAMPLE_DEPTH_TEXTURE,用来处理平台差异性造成的问题。只需要在Shader中使用SAMPLE_DEPTH_TEXTURE宏对深度纹理进行采样。例如:

float d = SAMPLE_DEPTH_TEXTURE(_cameraDepthTexture, i.uv);其中,i.uv是一个float2类型的变量,对应了当前像素的纹理坐标,类似的宏还有SAMPLE_DEPTH_TEXTURE_PROJ和SAMPLE_TEXTURE_LOD。SAMPLE_DEPTH_TEXTURE_PROJ宏同样接收两个参数——深度纹理和一个float3或float4类型的纹理坐标,它的内部使用了tex2Dproj这样的函数进行投影纹理采样,纹理坐标的前两个分量首先会除以最后一个分量,再进行纹理采样。如果提供了第四个分量,还会进行一次比较,通常用于阴影的实现中。SAMPLE_DEPTH_TEXTURE_PROJ的第二个参数通常是由顶点着色器输出插值而得到的屏幕坐标,例如:

float d = SAMPLE_DEPTH_TEXTURE_PROJ(_cameraDepthTexture, UNITY_PROJ_COORD(i.scrPos));其中,i.srcPos是在顶点着色器中通过ComputeScreenPos(o.pos)得到的屏幕坐标。

        当通过纹理采样得到深度值后,这些深度值往往是非线性的,这种非线性来自于透视投影使用的裁剪矩阵。然而,当我们的计算过程中通常是需要线性的深度值,也就是说,我们需要把投影后的深度值变换到线性空间下。[UnityShader入门精要读书笔记]32.获取深度和法线纹理_第4张图片

[UnityShader入门精要读书笔记]32.获取深度和法线纹理_第5张图片

    DecodeDepthNormal的第一个参数是对深度+法线纹理的采样结果,这个采样结果是对Unity对深度和法线信息编码后的结果,它的xy分量存储的是视角空间下的法线信息,而深度信息被编码进行了zw分量。通过调用DecodeDepthNormal函数对采样结果解码后,我们就可以得到解码后的深度值和法线。这个深度值是范围在[0,1]的线性深度值,而得到的法线则是视角空间下的法线方向。同样,我们也可以通过调用DecodeFloatRG和DecodeViewNormalStereo来解码深度+法线纹理中的深度和法线信息。

      使用帧调试器查看到的深度纹理是非线性空间的深度值,而深度+法线纹理都是由Unity编码后的结果。有事,显示出线性空间下的深度信息或解码后的法线方向更加有用。此时,我们可以自行在片元着色器中输出转换或解码后的深度和法线值,如下:

float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

float linearDepth = Linear01Depth(depth);

return fixed4(linearDepth, linearDepth, linearDepth, 1.0);

或是输出法线方向:

fixed3 normal = DecodeViewNormalStereo(tex2D(_CameraDepthNormalsTexture, i.uv).xy);

return fixed4(normal * 0.5 + 0.5, 1.0);

在查看深度纹理时,有可能得到全白或全黑的画面,这时候可以把摄像机的远裁剪皮门后面的距离调小,使视椎体的范围刚好覆盖场景的所在区域。这是因为,由于投影变换时需要覆盖从近裁剪平面到远裁剪平面的所有深度区域,当远裁剪平面的距离过大时,会导致离摄像机较近的距离被映射到非常小的深度值,如果场景是一个封闭的区域,那么这就会导致画面看起来几乎是全黑的。相反,如果一个张静是一个开放区域,且物体离摄像机的距离较远,就会导致画面几乎是全白的。

 

你可能感兴趣的:([UnityShader入门精要读书笔记]32.获取深度和法线纹理)