深度纹理和法线的背后的原理
一、深度纹理存储的是什么信息?
Unity深度纹理中存储的信息
也就是Shader中使用的_CameraDepthTexture 和 _CameraDepthNormalsTexture采样的信息
是进行裁剪空间变换后的z分量再转化到0~1之后的结果
因为齐次裁剪空间下坐标的范围为-1 ~ 1,而纹理中存储的是0 ~ 1
因此Unity会将其利用以下公式进行转换
深度纹理值 = 0.5 * z + 0.5
也就是我们通过深度纹理直接采样得到的深度纹理是
进行裁剪空间变换后的z分量再转换到0~1之间的结果
二、法线纹理中存储的是什么信息?
Unity中的法线纹理存储的信息
也就是Shader中使用的_CameraDepthNormalsTexture采样得到的float4中的部分信息
它就是观察空间下的法线转换到0~1之间后的结果
因为观察空间下的单位向量的分量的取值范围是-11,而纹理中存储的信息范围是01
因此Unity会将其利用以下公式进行转换
法线纹理值 = (观察空间下法线 + 1) * 0.5
也就是我们通过法线纹理直接采样得到的法线纹理值是
观察空间下的法线,再转换到0~1之后的结果
三、Unity是如何得到深度和法线纹理
主要通过两种途径 - 从G-buffer几何缓冲区中获取
当使用延迟渲染路径时,深度和法线纹理可以直接访问到,因为延迟渲染路径会把深度和法线等信息存储到G-buffer几何缓冲区中 - 由一个专门的Pass渲染而来
当无法直接获取到深度和法线纹理时,(比如硬件不支持延迟渲染路径或使用的是前向渲染路径)
Unity会通过单独的Pass来进行渲染,获取深度和法线信息
具体是通过哪一种渲染方式,取决于使用的渲染路径和设备的硬件限制。
当使用单独的Pass渲染获得的深度和法线纹理时,两者是有区别的 - 对于深度纹理来说
Unity内部会使用着色器替换技术选择渲染类型 RenderType = "Opaque"(不透明物体类型)
然后会判断他们的渲染队列Queue是否小于等于2500
如果满足这些条件,就会使用物体的投射阴影的Pass(LightMode = ShadowCaster的Pass)
如果该Pass不存在,该物体就不会出现在深度纹理中
总之如果想要物体能正确的出现在深度纹理中 1. 必须出现在Shader中正确的设置RenderType标签 2. 必须有投射阴影用的Pass,即:LightMode = ShadowCaster的Pass - 对于法线纹理来说
Unity底层会使用一个单独的Pass把整个场景再渲染一次,从而得到深度和法线信息
这里为什么是深度和法线信息呢,因为当需要得到法线纹理时,Unity中是和深度一起获取的
上节课的使用知识点中有讲解( _CameraDepthNormalsTexture )
这个Pass包含在Unity内置的Shader中,我们可以在官方下载源文件解压后进行查看
注意:
直接采样出来的深度和法线信息是不会直接使用的
我们需要将他们通过内置函数进行转换
得到最终我们会使用的观察空间下的深度和法线信息
四、深度和法线纹理使用时调用函数的原理
1.SAMPLE_DEPTH_TEXTURE宏
1.)作用
它是用于从深度纹理中采样的宏,相比直接使用过tex2D进行采样,它会在内部帮我们适配各种不同的平台
因为不同平台对深度纹理采样的规则会有所不同
2.)得到的结果
它采样得到的深度值是裁剪空间下的z分量转换到0~1之间的结果,并且是非线性的
注意点:
非线性:指的是透视摄像机的裁剪空间中深度值分布不均匀 - 当深度值接近裁剪面时,深度值变化迅速,精度高 - 当深度值远离裁剪面时,深度值变化缓慢,精度低 - 直观解释:一个相机在观察一个3D场景时,近处的物体移动一点,视觉上变化很大,所以需 要更高的精度来记录这种变化。而远处的物体移动同样的距离,视觉上的变化很小,因此可以使 用较低的精度来记录
2.LinearEyeDepth和Linear01Depth
只需要把裁剪空间下的深度值转换到观察空间下,便可以得到线性的深度值 这两个函数都可以得到观察空间下的线性深度值
- LinearEyeDepth 得到的是像素到摄像机的实际距离
- Linear01Depth 得到的是实际距离被压缩到0~1之间的值
2.DecodeDepthNormal
1.)作用
得到观察空间下的对应像素的法线和线性深度值(0~1)
这个函数的内部执行的也是DecodeFloatRG和DecodeViewNormalStereo函数 也可以通过这两个函数单独分别提取深度和法线信息
函数中具体做的事情,就是利用法线的xy算出z,得到最终的法线信息; 将裁剪空间下的非线性深度值 转换为观察空间下线性的范围为0~1的深度值