深度测试是用来解决三维渲染中物体遮挡的显示问题。一般来说,近的物体会遮挡住远的物体。而默认情况下,后绘制的像素又会把之前绘制的像素覆盖掉。当然,我们可以自己处理好物体的绘制顺序,但是深度测试是一种更加简洁的处理手段。
深度测试的实现也不复杂。我们使用一个额外的缓冲区,一般称作深度缓冲区或 z 缓冲区,来存储屏幕上每个像素的深度值。当新的像素需要绘制的时候,我们会比较这个像素的深度值与缓冲区中记录的之前的深度值。以前面挡住后面为例,如果新像素的深度值小于缓冲区里的值,新像素就会绘制;反之,这个新像素就不会被绘制。
1. OpenGL 接口
我们以 OpenGL 的接口作为参照,来了解深度测试功能。
OpenGL 中使用 glEnable 传递 GL_DEPTH_TEST 开启深度测试。
glDepthFunc 用于指定深度比较的条件。比如 GL_LESS 表示新的深度值小于存储的深度值才通过测试;GL_GREATER 表示新的深度值大于存储的深度值才通过测试。
- void glDepthFunc(GLenum func);
2. 代码实现
我们用代码实现,对深度测试的细节进行具体说明。
如代码清单 1 所示,我们先仿照 OpenGL,实现自己的 glEnable 和 glDepthFunc 接口。
如代码清单 2 所示,我们新增一个数组,用于充当深度缓冲区。并将缓冲区的大小设置成绘制屏幕的大小。
代码清单 3 是深度测试的具体实现。首先我们判断是否开启了深度测试,没开启的话,意味深度测试都会通过。接着我们定义各个深度比较枚举量实际对应的比较函数。然后我们用比较函数进行测试,如果通过就更新深度缓冲区中的值,并返回通过;否则就返回不通过。
如代码清单 4 所示,我们在光栅化阶段添加深度测试功能,如果没通过深度测试,就不绘制这个像素。注意,因为需要用到深度值,所以我们这边新增插值点的 z 坐标插值。
z 坐标插值不进行透视校正。一个原因是,不校正也“够用”了,不影响判断结果,减少计算量。
另一个原因是,OpenGL 中的深度值范围是 0 到 1。我们可以在 NDC 转屏幕坐标的时候进行转换。如果再校正,范围就不对了。
我们之前的屏幕变换矩阵,没有改变 z 值。所以,如代码清单 5 所示,我们把 z 值从 [-1,1] 映射到 [0,1]。
3. 测试
最后我们编写测试用例。如代码清单 6 所示,我们指定两个三角形,一个是渐变色的,在前面;另一个是纯色的,在后面。同时我们开启了深度测试,并指定 TDepthFunc::Less,即深度值小的在前面,会遮挡住后面深度值大的。
为了说明问题,如代码清单 7 所示,特意调用两次绘制函数。先画前面的,后画后面的。
图 1 是测试结果,可以看到虽然后面的纯色三角形是后画的,但是开启了深度测试,还是能正确绘制遮挡关系。
本节完整代码见 tag/depth_test。