最爱午后红茶

Z buffering

日期图标
2023-05-20

通过前面的内容我们知道了如何对三角形进行模糊然后绘制在屏幕上,但是我们还有一个问题没有解决,那就是物体之间的遮挡关系还没有处理。

要正确地把三维空间中的物体绘制在二维的屏幕上,我们需要知道物体跟相机的距离(或者说是物体与物体间相对于相机的前后关系),如果物体之间有交集,离相机近的物体应该遮盖离相机远的物体。

画家算法(Painter’s Algorithm)

油画画家在作画时,通常是先画远处的场景,然后再画近处的场景,那很自然地近处的场景将会覆盖远处的场景。借鉴这个思路,画家算法(也叫优先填充)会先将场景按照深浅排序,这通常需要 nlog(n)nlog(n) 的时间复杂度,然后按顺序进行绘制。

画家算法(Painter algorithm)

图一:画家算法(https://zh.wikipedia.org/wiki/%E7%94%BB%E5%AE%B6%E7%AE%97%E6%B3%95)

画家算法在普通场景中是适用的,但在有些特殊场景中,画家算法就无法区分出物体的位置关系,比如:

相互遮挡的三角形
图二:相互遮挡的三角形

上图二中,R 部分遮挡住 P,P 部分遮挡住 Q,而 Q 又反过来部分遮挡住 R。人们为了解决这个问题,引入了深度缓存(或者深度缓冲

深度缓存(Z-Buffering)

View transformations 中我们说过,为了方便运算,相机是摆在原点位置并且朝 z-z 方向看,那样的话会使得物体距离相机越近,zz 坐标越大。在这里我们为了方便理解深度缓存,我们假设相机是朝 zz 轴正方向看,即物体距离相机越近,zz 坐标越小

深度缓存的核心思路就是记录被三角形覆盖到的每个像素的最小 zz 坐标(因为 zz 坐标越小意味着离相机越近,理所当然它会遮盖在同一个像素上比它远的物体)。因此,我们需要额外的空间去存储每个像素的深度信息:

  • frame buffer 存储颜色信息
  • depth buffer(z-buffer) 存储深度信息

我们先看看结果,我们生成的图片通常包含颜色信息和深度信息:

图片的渲染结果和它的深度信息

图三:图片的渲染结果和它的深度信息

从上图右图可以看出,越黑的区域就表示距离相机越近(越黑代表值越小,PS: 黑色 = #000000)。我们再看左图,这是符合我们的视觉效果的。

那 z-buffer 怎么来?通常来说物体都是由很多三角形组成的,我们在遍历每个三角形时,只记录每个像素所覆盖到的所有三角形中 zz 坐标最小的三角形的颜色信息及其 zz 坐标:

// Initialize depth buffer to ∞
// During rasterization:
for (each triangle T) {
  for (each sample(x, y, z) in T) {
    if (z < zbuffer[x, y]) {
      framebuffer[x, y] = rgb;
      zbuffer[x, y] = z;
    } else {
      // do nothing, this sample is occluded.
    }
  }
}

具体举例就像这样:

记录深度信息
图四:记录深度信息

从上图四我们可以看到,按照上述算法执行后的图像可以被正确地光栅化,而且这种算法跟遍历三角形的顺序是无关的,这是个很好的性质。

那么它的算法复杂度怎样呢?通常每个三角形都不会很大,其包含的像素可以被认为是常数个(100 左右),三角形的数量是 n,那么该算法的时间复杂度为:O(n),这是非常高效的方案。

* 未经同意不得转载。