概述

渲染管线中的顶点变换中,介绍了顶点在各个坐标空间的变换。变换到最后,是屏幕坐标空间。在OpenGL中,屏幕空间坐标的Z值即是深度缓冲中的深度值。深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。本文将介绍深度值的计算,以及从深度值反向计算出相机空间中的顶点的Z值。

深度值计算

渲染管线中的顶点变换中,计算得到了透视投影矩阵:

\[M_{persp} =
\begin{bmatrix}
\frac{2n}{r-l} & 0 & \frac{l+r}{l-r} & 0 \\
0 & \frac{2n}{t-b} & \frac{b+t}{b-t} & 0 \\
0 & 0 & \frac{f+n}{f-n} & \frac{2nf}{n-f} \\
0 & 0 & 1 & 0 \\
\end{bmatrix}
\]

同时,也得到了视口变换矩阵:

\[M_{viewport} =
\begin{bmatrix}
\frac{w}{2} & 0 & 0 & \frac{w}{2} \\
0 & \frac{h}{2} & 0 & \frac{h}{2} \\
0 & 0 & \frac{1}{2} & \frac{1}{2} \\
0 & 0 & 0 & 1 \\
\end{bmatrix}
\]

首先,根据透视矩阵,计算NDC空间的Z值。这里,相机空间中的坐标经过透视矩阵变换后,还要进行齐次除法,才能得到NDC空间中的坐标。

\[\begin{pmatrix}
x_{clip} \\
y_{clip} \\
z_{clip} \\
w_{clip} \\
\end{pmatrix} =
M_{persp}
\begin{pmatrix}
x_{eye} \\
y_{eye} \\
z_{eye} \\
w_{eye} \\
\end{pmatrix}
\]

\[\begin{pmatrix}
x_{ndc} \\
y_{ndc} \\
z_{ndc} \\
\end{pmatrix} =
\begin{pmatrix}
\frac{x_{clip}}{w_{clip}} \\
\frac{y_{clip}}{w_{clip}} \\
\frac{z_{clip}}{w_{clip}} \\
\end{pmatrix}
\]

由此,可以得出:

\[\begin{equation}
\begin{aligned}
z_{ndc} &= \frac{\frac{f+n}{f-n}z_{eye}+\frac{-2nf}{f-n}}{z_{eye}} \\
&=\frac{f+n}{f-n}+\frac{-2nf}{z_{eye}(f-n)}
\end{aligned}
\tag{1}
\end{equation}
\]

根据上述公式,可以得出:

\[z_{eye} = \frac{2nf}{(f+n)-z_{ndc}(f-n)} \tag{2}
\]

根据视口变换矩阵,可以得出:

\[z_{win} = \frac{1}{2}z_{ndc}+\frac{1}{2} \tag{3}
\]

\(\left(1\right)\)带入\(\left(3\right)\),可以得到:

\[\begin{aligned}
z_{win} &= \frac{1}{2}(z_{ndc}+1) \\
&=\frac{1}{2}(\frac{f+n}{f-n}+\frac{-2nf}{z_{eye}(f-n)} + 1) \\
&=\frac{f-\frac{nf}{z_{eye}}}{f-n} \\
&= \frac{\frac{1}{n}-\frac{1}{z_{eye}}}{\frac{1}{n}-\frac{1}{f}}
\end{aligned}
\]

即:

\[z_{win} = \frac{\frac{1}{n}-\frac{1}{z_{eye}}}{\frac{1}{n}-\frac{1}{f}} \tag{4}
\]

到这一步,即可以求得屏幕空间中的深度。

Learn OpenGL CN学习过的,可能对深度测试这一节的内容有些印象。它得到的深度值的公式是:

\[F_{depth} = \frac{1/z – 1/near}{1/far – 1/near}
\]

\(\left(4\right)\)式对比,发现有些不一样,这是怎么回事呢?

这里要注意,本文定义的\(n\)\(f\)\(z_{eye}\)是实际的坐标值,是负的。而深度测试文中,定义的\(near\)\(far\)代表了近平面和远平面,而\(z\)代表了近、远平面之间的值,它们都是正的。将\(n=-near\)\(f=-far\)\(z_{eye}=-z\)代入\(\left(4\right)\)式,可得:

\[\begin{aligned}
F_{depth} &= z_{win} \\
&= \frac{\frac{1}{n}-\frac{1}{z_{eye}}}{\frac{1}{n}-\frac{1}{f}} \\
&= \frac{\frac{1}{-near}-\frac{1}{-z}}{\frac{1}{-near}-\frac{1}{-far}} \\
&= \frac{\frac{1}{z}-\frac{1}{near}}{\frac{1}{far}-\frac{1}{near}}
\end{aligned}
\]

深度值的线性可视化

经过上面的推导,我们得出了深度值的计算公式。

现在,反过来,我们知道了屏幕空间中的深度值,怎么求出相机空间中的深度值呢?

首先,根据\(\left(3\right)\),可以推导出:

\[z_{ndc} = 2z_{win}-1
\]

对于公式2,得出的是实际坐标的\(Z\)值。为了和OpenGL中的定义统一,也将\(near\)\(far\)\(z\)代入公式\(\left(2\right)\),可以得到:

\[\begin{equation}
\begin{aligned}
z_{eye} &= \frac{2(-near)(-far)}{((-far)+(-near))-z_{ndc}((-far)-(-near))} \\
&= \frac{2nearfar}{-(far+near)-z_{ndc}(near-far)} \\
\end{aligned}
\tag{5}
\end{equation}
\]

深度测试这一节中,得出的公式是:

\[float \quad linearDepth = (2.0 * near * far) / (far + near – z * (far – near));
\]

对比发现,跟公式\(\left(5\right)\)有些不一样。这是因为,\(linearDepth\)求出的是顶点距离相机的距离,是正值。而\(z_{eye}\)是顶点的实际坐标,是负值,将\(z_{eye}\)取反,即可得到\(linearDepth\)

\[\begin{aligned}
linearDepth &= -z_{eye} \\
&= \frac{2nearfar}{(far+near)-z_{ndc}(far-near)}
\end{aligned}
\]

至此,推导完成。

参考

版权声明:本文为bzyzhang原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/bzyzhang/p/12667859.html