关于深度图的那些事

        在图形学中,深度图(Depth Map)通常存储的是相机坐标系(视图空间)下的深度值。其可以用来表示场景中物体的远近关系。一般而言,在引擎中深度图都需要我们手动开启,毕竟这是一个消耗性能的操作。且半透明物体一般是不做深度写入的。这也就是说深度图中并不存在半透明物体的信息。

深度图的存储格式

  1. 使用RGBA8格式的R通道来存储。

兼容性(优):RGBA8格式可以兼容所有设备。

数值精度(差):RGBA8格式中每个通道可以表示的浮点数精度为1/255,约等于 0.0039。在使用深度图进行深度深度判定时可能会因精度不够而导致出现较大误差。

存取效率(优):直接存取,无额外运算。

PS:如果我没有记错的话,Unity通用渲染管线的深度图就是用这个方式存储的。
  1. 用RGBA8格式进行编码

        虽然这个方案也是使用RGBA8,但是此方案是将深度信息编码到RGBA8的四个通道中,使用时再用与编码对等的解码方式进行解码。

兼容性(优):RGBA8格式可以兼容所有设备。

数值精度(优):其可以达到32位浮点数精度。

存取效率(一般):需要额外的编码和解码运算。

  1. R32F浮点纹理

        R32F浮点纹理是单通道纹理,且这个通道可以存储32位浮点数。

兼容性(一般):R32F浮点纹理在原生平台普及度已经非常高。但在 Web 平台,WebGL 2.0 才正式支持浮点纹理。

数值精度(优):其可以达到32位浮点数精度。

存取效率(优):直接存取,无额外运算。

线性深度

        所谓线性,就是指变化曲线的一阶导数为常量。例如:。在相机Camera空间(观察View空间),深度(z)的变化就是线性的,深度值可能是投影的近平面(near)和远平面(far)之间的任何值。一般,我们会将其转换到[0,1]区间中。所用公式如下(这里n表示近平面的距离,f表示远平面的距离): 因为深度(z)仍然是一阶量,所以这个公式仍然为线性的。其图像如下:

非线性深度

        在图形管线的流程中,有一个将视图空间(View Space)转为规格化设备空间(Normal Device Coordinate )的过程。经历这个过程后深度值(z)会被规范到一个固定区间中。不同的图形平台区间的大小不一定一样,比如D3D中其区间大小为[0,1],而OpenGL中其区间大小为[-1,1]。有关于其空间转换的过程,大家可以去看一下这篇文章:Direct3D - 透视投影矩阵与齐次裁剪空间,我这边就直接写出这个深度在规范到[0,1]区间的转换公式。 此公式中深度(z)不再是一阶,所以其不再是线性。其图像如下:

从图中,我们可以看到距离摄像机很近的地方,占据了大半的深度取值范围。这样也符合离摄像机越远的物体,信息就越少,对画面的贡献也越少,所以我们根本不需要为远处物体的数据提供更高的精度。

深度图取值

        之所以上文讲述线性深度和非线性深度就是为了讲明关于深度图的取值。深度图中存储的值其实是非线性深度的值。而我大部分用到深度图的时候都需要获取到视图空间下的深度值来进行一些操作的话,那么我就要将非线性深度值转为了线性深度值。下面以Unity引擎的URP(Universal Render Pipeline,通用渲染管线)举例(因为我更熟悉这个)。Unity提供了一个Shader函数LinearEyeDepth来帮助我们转换,其实现如下:

1
2
3
4
float LinearEyeDepth(float depth, float4 zBufferParam)
{
return 1.0 / (zBufferParam.z * depth + zBufferParam.w);
}

depth是我们从深度图中获取到的值。而一般对于zBufferParam,我们会传Unity自定义的宏——_ZBufferParams。其在官方文档的说明为:_ZBufferParams,float4类型,用于线性化Z缓冲区值。xyzw。所以上面的实现我们转换为下面的公式: 化简后得:

其实也就是非线性深度公式中知道后求值。其实Unity提供了几个计算线性深度的方法,其各有其存在的意义。

1
2
3
4
5
6
7
8
9
float Linear01DepthFromNear(float depth, float4 zBufferParam)
{
return 1.0 / (zBufferParam.x + zBufferParam.y / depth);
}

float Linear01Depth(float depth, float4 zBufferParam)
{
return 1.0 / (zBufferParam.x * depth + zBufferParam.y);
}

有兴趣的话,大家可以推导一下这些公式。

参考文章

  1. 图形学基础|深度缓冲(DepthBuffer):https://blog.csdn.net/qjh5606/article/details/118675803
  2. Cocos Shader入门基础七:一文彻底弄懂深度图 :https://zhuanlan.zhihu.com/p/479574432
  3. Direct3D - 透视投影矩阵与齐次裁剪空间:https://goudan-er.github.io/2016/d3d-projection/