概述
在渲染管线中的顶点变换中,介绍了顶点在各个坐标空间的变换。 变换到最后,是屏幕坐标空间。在OpenGL中,屏幕空间坐标的Z值即是深度缓冲中的深度值。深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。本文将介绍深度值的计算,以及从深度值反向计算出相机空间中的顶点的Z值。
深度值计算
在渲染管线中的顶点变换中,计算得到了透视投影矩阵:
M
p
e
r
s
p
=
[
2
n
r
−
l
0
l
+
r
l
−
r
0
0
2
n
t
−
b
b
+
t
b
−
t
0
0
0
f
+
n
f
−
n
2
n
f
n
−
f
0
0
1
0
]
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}
Mpersp=⎣⎢⎢⎡r−l2n0000t−b2n00l−rl+rb−tb+tf−nf+n100n−f2nf0⎦⎥⎥⎤
同时,也得到了视口变换矩阵:
M
v
i
e
w
p
o
r
t
=
[
w
2
0
0
w
2
0
h
2
0
h
2
0
0
1
2
1
2
0
0
0
1
]
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}
Mviewport=⎣⎢⎢⎡2w00002h00002102w2h211⎦⎥⎥⎤
首先,根据透视矩阵,计算NDC空间的Z值。这里,相机空间中的坐标经过透视矩阵变换后,还要进行齐次除法,才能得到NDC空间中的坐标。
(
x
c
l
i
p
y
c
l
i
p
z
c
l
i
p
w
c
l
i
p
)
=
M
p
e
r
s
p
(
x
e
y
e
y
e
y
e
z
e
y
e
w
e
y
e
)
\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}
⎝⎜⎜⎛xclipyclipzclipwclip⎠⎟⎟⎞=Mpersp⎝⎜⎜⎛xeyeyeyezeyeweye⎠⎟⎟⎞
( x n d c y n d c z n d c ) = ( x c l i p w c l i p y c l i p w c l i p z c l i p w c l i p ) \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} ⎝⎛xndcyndczndc⎠⎞=⎝⎜⎛wclipxclipwclipyclipwclipzclip⎠⎟⎞
由此,可以得出:
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \begin{aligned…
根据上述公式,可以得出:
z
e
y
e
=
2
n
f
(
f
+
n
)
−
z
n
d
c
(
f
−
n
)
(2)
z_{eye} = \frac{2nf}{(f+n)-z_{ndc}(f-n)} \tag{2}
zeye=(f+n)−zndc(f−n)2nf(2)
根据视口变换矩阵,可以得出:
z
w
i
n
=
1
2
z
n
d
c
+
1
2
(3)
z_{win} = \frac{1}{2}z_{ndc}+\frac{1}{2} \tag{3}
zwin=21zndc+21(3)
将
(
1
)
\left(1\right)
(1)带入
(
3
)
\left(3\right)
(3),可以得到:
z
w
i
n
=
1
2
(
z
n
d
c
+
1
)
=
1
2
(
f
+
n
f
−
n
+
−
2
n
f
z
e
y
e
(
f
−
n
)
+
1
)
=
f
−
n
f
z
e
y
e
f
−
n
=
1
n
−
1
z
e
y
e
1
n
−
1
f
\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}
zwin=21(zndc+1)=21(f−nf+n+zeye(f−n)−2nf+1)=f−nf−zeyenf=n1−f1n1−zeye1
即:
z
w
i
n
=
1
n
−
1
z
e
y
e
1
n
−
1
f
(4)
z_{win} = \frac{\frac{1}{n}-\frac{1}{z_{eye}}}{\frac{1}{n}-\frac{1}{f}} \tag{4}
zwin=n1−f1n1−zeye1(4)
到这一步,即可以求得屏幕空间中的深度。
在Learn OpenGL CN学习过的,可能对深度测试这一节的内容有些印象。它得到的深度值的公式是:
F
d
e
p
t
h
=
1
/
z
−
1
/
n
e
a
r
1
/
f
a
r
−
1
/
n
e
a
r
F_{depth} = \frac{1/z - 1/near}{1/far - 1/near}
Fdepth=1/far−1/near1/z−1/near
跟
(
4
)
\left(4\right)
(4)式对比,发现有些不一样,这是怎么回事呢?
这里要注意,本文定义的
n
n
n、
f
f
f和
z
e
y
e
z_{eye}
zeye是实际的坐标值,是负的。而深度测试文中,定义的
n
e
a
r
near
near、
f
a
r
far
far代表了近平面和远平面,而
z
z
z代表了近、远平面之间的值,它们都是正的。将
n
=
−
n
e
a
r
n=-near
n=−near、
f
=
−
f
a
r
f=-far
f=−far、
z
e
y
e
=
−
z
z_{eye}=-z
zeye=−z代入
(
4
)
\left(4\right)
(4)式,可得:
F
d
e
p
t
h
=
z
w
i
n
=
1
n
−
1
z
e
y
e
1
n
−
1
f
=
1
−
n
e
a
r
−
1
−
z
1
−
n
e
a
r
−
1
−
f
a
r
=
1
z
−
1
n
e
a
r
1
f
a
r
−
1
n
e
a
r
\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}
Fdepth=zwin=n1−f1n1−zeye1=−near1−−far1−near1−−z1=far1−near1z1−near1
深度值的线性可视化
经过上面的推导,我们得出了深度值的计算公式。
现在,反过来,我们知道了屏幕空间中的深度值,怎么求出相机空间中的深度值呢?
首先,根据
(
3
)
\left(3\right)
(3),可以推导出:
z
n
d
c
=
2
z
w
i
n
−
1
z_{ndc} = 2z_{win}-1
zndc=2zwin−1
对于公式2,得出的是实际坐标的
Z
Z
Z值。为了和OpenGL中的定义统一,也将
n
e
a
r
near
near、
f
a
r
far
far和
z
z
z代入公式
(
2
)
\left(2\right)
(2),可以得到:
z
e
y
e
=
2
(
−
n
e
a
r
)
(
−
f
a
r
)
(
(
−
f
a
r
)
+
(
−
n
e
a
r
)
)
−
z
n
d
c
(
(
−
f
a
r
)
−
(
−
n
e
a
r
)
)
=
2
n
e
a
r
f
a
r
−
(
f
a
r
+
n
e
a
r
)
−
z
n
d
c
(
n
e
a
r
−
f
a
r
)
(5)
\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}
zeye=((−far)+(−near))−zndc((−far)−(−near))2(−near)(−far)=−(far+near)−zndc(near−far)2nearfar(5)
在深度测试这一节中,得出的公式是:
f
l
o
a
t
l
i
n
e
a
r
D
e
p
t
h
=
(
2.0
∗
n
e
a
r
∗
f
a
r
)
/
(
f
a
r
+
n
e
a
r
−
z
∗
(
f
a
r
−
n
e
a
r
)
)
;
float \quad linearDepth = (2.0 * near * far) / (far + near - z * (far - near));
floatlinearDepth=(2.0∗near∗far)/(far+near−z∗(far−near));
对比发现,跟公式
(
5
)
\left(5\right)
(5)有些不一样。这是因为,
l
i
n
e
a
r
D
e
p
t
h
linearDepth
linearDepth求出的是顶点距离相机的距离,是正值。而
z
e
y
e
z_{eye}
zeye是顶点的实际坐标,是负值,将
z
e
y
e
z_{eye}
zeye取反,即可得到
l
i
n
e
a
r
D
e
p
t
h
linearDepth
linearDepth。
l
i
n
e
a
r
D
e
p
t
h
=
−
z
e
y
e
=
2
n
e
a
r
f
a
r
(
f
a
r
+
n
e
a
r
)
−
z
n
d
c
(
f
a
r
−
n
e
a
r
)
\begin{aligned} linearDepth &= -z_{eye} \\ &= \frac{2nearfar}{(far+near)-z_{ndc}(far-near)} \end{aligned}
linearDepth=−zeye=(far+near)−zndc(far−near)2nearfar
至此,推导完成。
参考
- [1] 深度测试
- [2] 渲染管线中的顶点变换