计算机图形: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).

坐标空间变换流程:

\[\begin{aligned} & Object \ Space\xrightarrow{Model}World\ Space\xrightarrow{Camera/View}Camera \ Space/View \ Space \\ & \xrightarrow{Perspective}Clip \ Space\xrightarrow{Perspective\ Divide}NDC \xrightarrow{viewport}Screen \ Space \end{aligned} \]

方向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,可以用一个变换表示:

\[\begin{aligned} v &= Pp\\ &= \begin{pmatrix} ...& 0 &...& 0 \\ 0 & ... & ... & 0 \\ 0 & 0 & d & e\\ 0 & 0 & ±1 & 0 \end{pmatrix} \begin{pmatrix}p_x\\p_y\\p_z\\1 \end{pmatrix}\\ &=\begin{pmatrix}...\\...\\dp_z+e\\±p_z\end{pmatrix} \end{aligned} \]

对于OpenGL投影变换,

\[d=\frac{n+f}{n-f},e=-\frac{2nf}{n-f},v_w=-p_z \]

∴在NDC中获取的深度为

\[z_{ndc}=\frac{dp_z+e}{-p_z}=-d-\frac{e}{p_z}\in[-1,1] \]

\(z_{ndc}\)\(p_z\)是非严格反比关系.

例如,\(n=-10,f=-110\),那么,\(d=-1.2,e=-22\)

对于\(p_z=-60\)

\[z_{ndc}=1.2-\frac{22}{60}=0.833 \]

于是,我们可以描绘出固定n、f时,\(z_{ndc}\)\(|p_z|\)变化曲线图:

img
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方案一致了

转换矩阵:

\[\begin{aligned} M_{st} &= S(1,1,0.5,1)T(0,0,1,0)\\ &= \begin{pmatrix} 1& 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0.5 & 0.5\\ 0 & 0 & 0 & 1 \end{pmatrix} \end{aligned} \]

下面是DirectX投影变换后,深度缓冲\(z_{ndc}\)\(1-z_{ndc}\)(Reverse-Z)的4种方案对比图:

img
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变换(模型、视图、投影变换)

posted @ 2025-06-20 19:13  明明1109  阅读(118)  评论(0)    收藏  举报