深度缓冲中的深度值计算及可视化

概述

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

深度值计算

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

[M_{persp} = egin{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} = egin{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空间中的坐标。

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

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

由此,可以得出:

[egin{equation} egin{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} ag{1} end{equation} ]

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

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

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

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

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

[egin{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}} ag{4} ]

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

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

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

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

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

[egin{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 ight)),可以推导出:

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

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

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

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

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

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

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

至此,推导完成。

参考

原文地址:https://www.cnblogs.com/bzyzhang/p/12667859.html