图形学基础—深度测试
深度测试简单地说就是,对于某一个像素点,始终只记录深度值最小的那个,也就是离相机最近的那个。
什么是深度
深度其实就是像素点在场景中距离相机的距离。
在图形学基础—MVP变换一文中,我们有提到,经过视图变换后,相机被放到了一个标准位置,并且相机朝着-Z方向看向场景。
那么此时像素点的深度就可以用Z值表示。由于相机朝着-Z方向看(右手系下),所以像素点的Z值越小,那么它对应的深度就越大,反之,如果像素点的Z值越大,那么它对应的深度就越小。
深度缓存
深度缓冲区(depth buffer)可以理解成是一张用于维护每个像素点最小深度值的图片,每个像素的初始值对应无限远的深度。
帧缓冲区(frame buffer)可以理解成是一张用于存储当前像素点颜色的图片,每个像素的初始值对应背景颜色。
深度缓冲区上的像素与帧缓冲区上的像素是一一对应的。也就是说帧缓冲区负责存储每个像素当前的最终颜色,而深度缓冲区用于存储每个像素当前的最小深度。
在决定绘制一个物体的表面时,首先需要将该表面对应的像素深度值与深度缓冲区中存储的深度值进行比较。
如果当前的深度大于或等于深度缓冲区中记录的深度,那么直接丢弃该像素。
而如果当前的深度值小于深度缓冲区中记录的深度值,则对该像素进行shading,并将shading的结果写入帧缓冲区,同时更新深度缓冲区中对应的最小深度。
也就是说,在整个着色的过程中,会同时生成两张图片,一张图片是我们看到的最终渲染出来的图片(由帧缓冲区生成),另一张是深度图(由深度缓冲区生成)。
Z-Fighting
深度测试存在一个潜在的问题,就是当两个像素足够接近时,由于 浮点型计算精度的问题,会造成有一趟渲染认为第一个像素在前面,而另一趟渲染认为第二个像素在前面。
在连续的渲染中,会使得深度测试的结果不可预测,从而导致显示出来的画面中两个像素会交替出现。
怎么解决Z-Fighting
Z-Fighting是因为两个图层的深度值过于接近造成的,那么我们可以在两个图层中间加入一个微小的间隔。这个操作叫“多边形偏移”,很多图形API都会提供这样的解决方案。
或者使用更高的位数去存储深度值,使得深度值的判断更加精准。
半透明物体
深度测试只适用于不透明物体的渲染,而半透明的物体则需要单独处理。
当绘制半透明物体时,同样会将当前的像素深度和深度缓冲区中记录的深度进行对比。
但不同的是,如果深度测试通过,则利用混色函数进行混色,并将混色结果写入帧缓冲区,但是不会更新深度缓冲区中的深度。
也就是说在绘制半透明物体时,深度缓冲区需变成只读状态。
而如果继续更新深度值,则会出现类似下图左侧的现象(半透明物体遮挡住了后面的物体)。


对数深度缓存
像素的深度是由视图变换矩阵和投影矩阵决定的,并且深度范围总是从0到1。在近平面上的点深度值为0,在远平面上的点深度值为1。
在图形学基础—MVP变换中,我们已经可以得到透视投影矩阵:
假设相机空间中的点为
那么可以得到相机空间中的深度值:
最后将相机空间中的深度值映射到0到1就可以得到深度值的计算公式:
那么我们假设定义了一个视锥,近平面为10,远平面为10000。
通过推导出的深度值公式可以计算出不同距离下对应的深度:
Z | 深度 |
---|---|
10 | 0.0 |
30 | 0.667334 |
50 | 0.800801 |
100 | 0.900901 |
900 | 0.989879 |
2000 | 0.995996 |
10000 | 1.0 |
不难发现,我们使用的深度值,并不是线性变化的,并且通过观察计算公式可以发现,深度值与呈正相关关系。
近处可以用的深度区间非常的大,0到100之间的距离就已经占用了0-0.9之间的深度,而100到10000之间只能使用剩下的0.9-1之间的深度。
这就导致一个问题,就是在近处的物体,他们能够使用的深度区间较大,所以深度值判断比较准确。
但是远处的物体只能使用很小一部分的深度区间,因此会导致深度测试的误差很大,从而使得远处的物体出现难看的锯齿,或者Z-Fighting。
而为了改善深度值的分布,就有人研究出了对数深度这一技术,使得深度的递减呈现出对数变化。
一些参考对数深度缓存资料:
Maximizing Depth Buffer Range and Precision (outerra.blogspot.com)
Logarithmic Depth Buffer (outerra.blogspot.com)
Logarithmic Depth Buffer Optimizations and Fixes (outerra.blogspot.com)