计算机图形:Z-Fighting与Reverse-Z
Z-Buffer
Z缓冲(Z-Buffer),又名深度缓冲(Depth buffer),是3D图形中用于处理遮挡关系的一种技术. 主要用于判定哪个物体/fragment在前面(距离相机较近),哪个在后面(距离相机较远),从而正确显示可见表面.
通常,Z-Buffer被定义为一个二维数组,每个元素对应屏幕上一个像素点,记录了距离相机最近点的深度(z值).
在渲染一个fragment(像素)时,
- 如果|z值| < |当前Z-Buffer中对应像素值(深度)|,说明它更靠近相机,即在前面,则需要:
1)更新该fragment对应像素颜色值;
2)更新Z-Buffer中该像素z值.
- 否则:忽略该fragment.
假设我们Z-Buffer每个元素用8bit存放深度,那么深度缓冲最多有\(2^8=256\)个级别.
Z-Fighting
在渲染深度相同,或者相近的物体时,纹理会闪烁,不停地切换,这种问题称为Z-Fighting(Z冲突).
解决方法通常有,
1)手动调整模型,将重叠的模型面分开;
2)设置多边形偏移(polygon offset),如OpenGL函数glPolygonOffset;
3)使用更高精度的缓冲,如logarithmicDepthBuffer缓冲;
3)使用Reverse-Z.
Reverse-Z
什么是Reverse-Z?
Reverse-Z(反向 Z 缓冲) 是一种用于图形渲染技术,提高远处物体深度精度,减少“Z-fighting(深度闪烁)”现象,对大场景特别有效.
因为透视投影并非线性变换,会导致靠近near的地方z值密度高,靠近far的地方z值密度低.
传统透视投影中,点v会经历M(模型)、V(视图)、P(透视投影)变换后,变换到Clip Space空间,再经过透视除法,变换到NDC. 变换后,z值映射到NDC空间范围:[-1,1](OpenGL)或[0,1](DirectX).
坐标空间变换流程:
方向Z缓冲核心思想:
- 近平面n映射到1.0
- 远平面f映射到0.0
数学实现有2种:
1)\(z_{reversed}=1.0-z_{ndc}\)
2)\(z_{reversed}=\frac{f}{n}\cdot (1-\frac{n}{z})\)
Reverse-Z为什么可以解决Z-Fighting问题?
我们在投影变换时,将点v由camera space变换到clip space,再变换到NDC,可以用一个变换表示:
对于OpenGL投影变换,
∴在NDC中获取的深度为
∴\(z_{ndc}\)与\(p_z\)是非严格反比关系.
例如,\(n=-10,f=-110\),那么,\(d=-1.2,e=-22\)
对于\(p_z=-60\),
于是,我们可以描绘出固定n、f时,\(z_{ndc}\)随\(|p_z|\)变化曲线图:

from P100 Real-Time Rendering 4th edition
注意:图中\(near=-n>0,far=-f>0,far-near=100\)
从这幅图,可以看出,\(z_{ndc}\)的变化是不均匀,越靠近近平面,精度越高;越靠近远平面,精度越低.
∵OpenGL对应NDC范围\(z_{ndc}\in [-1,1]\)
而Reverse-Z需要将远平面映射到0,近平面映射到1
∴无法直接使用Reverse-Z,不过可以转换一下,将\(z_{ndc}\)由\([-1,1]\)变换到\([0,1]\),这样就与DirectX方案一致了
转换矩阵:
下面是DirectX投影变换后,深度缓冲\(z_{ndc}\),\(1-z_{ndc}\)(Reverse-Z)的4种方案对比图:

from P101 Real-Time Rendering 4th edition
左上:标准整型深度缓冲,采用了4bit精度,所以y轴呈现16级离散刻度
右上:远平面far设置为∞,双轴微量偏移表明精度损失可控
左下:float深度缓冲(3bit指数+3bit尾数),y轴非线性分布特性导致x轴精度更加糟糕
右下:float Reverse-Z,最终呈现更优的线性分布特性
参考
[1] Z-Fighting问题解决方案实例(一)
[2] https://zhuanlan.zhihu.com/p/75517534
[3] Real-Time Rendering 4th edition - Tomas
[4] 计算机图形:mvp变换(模型、视图、投影变换)

浙公网安备 33010602011771号