深度冲突以及解决方法

深度冲突会导致画面重生成时闪烁,这会让测试人员一直揪着你不放.我们从头来理一理这个关系

深度测试

人是通过眼睛看东西的,你不可能看到被挡住的物体.所以,其实说起来,我们生活在3d世界,但眼睛只能看到一副2d图像.

说不清楚是进化不完全还是就是这个理.有可能4d生物的眼睛也是感知3d呢说不定

所以,同一方向上,谁离眼睛近,我们就看到谁,这个所谓的远近,就是深度信息了.

OpenGL里,或者说所有的三维成像,都有一个深度缓冲,记录了视野内每一个2d像素点的深度

每当有更近的像素过来,就更新深度并且把像素替换掉.就相当于看到了.反之就丢掉它.这就是所谓的深度测试

深度精度

现实世界里,深度的精度无限大,也就是说,同一方向上两个点,只要你不重合,你们的深度一定不一样,不会互斥.

而在虚拟世界里则不一样,我们可以把两个点叠在一起(同样深度),或者说两个点深度给得很接近.

大家知道,数字世界是离散的,它有一个最小精度.你不可能精确表示两个距离无限小的点.

所以,一旦真是世界两个点过于接近,在虚拟世界里,他们就重合了,这个最小可分辨的接近程度,也就是深度的精度

我们可以用下面的公式将所有的深度值映射到0到1之间

\[ F_{depth}=\frac{z-near}{far-near} \]

还记得坐标转换中介绍的平截头体(Frustum)吗?z可以是平截头体内部的任意值,near 和far值是我们之前提供给投影矩阵设置可视平截头体的那个 near 和 far 值。当z=near时,该公式的结果是0,当z=far时,该公式的结果是1,其间的任意值都可以通过这个公式映射到0到1之间。

然而,现实中我们不会用到这样的线性精度的函数,我们需要一个近处精度高以提高图像质量,远处精度低以降低资源使用的公式来控制精度,就是用下面的函数

\[ F_{depth}=\frac{\frac{1}{z}-\frac{1}{near}}{\frac{1}{far}-\frac{1}{near}} \]

深度冲突

如果两个片段的深度相同,那么该抛弃谁该显示谁呢?OpenGL中会将两个片段交替显示,就会产生奇怪的花纹。可见,在共面的情况下,很容易发生深度冲突。根据上面的精度公式,越远的物体,其精度就越低,也就越容易发生冲突,冲突效果也就越明显。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突,以下由三种方法

  • 一个也是最重要的技巧是永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠。通过在两个物体之间设置一个用户无法注意到的偏移值,你可以完全避免这两个物体之间的深度冲突。在箱子和地板的例子中,我们可以将箱子沿着正y轴稍微移动一点。箱子位置的这点微小改变将不太可能被注意到,但它能够完全减少深度冲突的发生。然而,这需要对每个物体都手动调整,并且需要进行彻底的测试来保证场景中没有物体会产生深度冲突
  • 第二个技巧是尽可能将近平面设置远一些。在前面我们提到了精度在靠近近平面时是非常高的,所以如果我们将近平面远离观察者,我们将会对整个平截头体有着更大的精度。然而,将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合你的场景的近平面距离
  • 另外一个很好的技巧是牺牲一些性能,使用更高精度的深度缓冲。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。所以,牺牲掉一些性能,你就能获得更高精度的深度测试,减少深度冲突