深度测试
为什么需要深度测试?
- 为了实现场景中物体的遮挡效果
- 如果没有深度测试,场景前边的物体不会挡住后边的物体;同一个物体前边的部分也不会挡住后边的部分,会使物体出现显示不全、闪烁等一些不好的现象
什么是深度测试?
- 当前片段的深度值与深度缓冲中的深度值比较,如果符合预设的比较结果,就认为测试通过,然后用当前片段的深度值替换深度缓冲中的深度值;如果测试不通过,就丢弃当前片段的深度值
- 当前片段的深度值指的是:从观察者角度看向物体时的距离,这个距离再通过投影变换(变换到裁剪空间)、标准化设备坐标(变换到[-1.0,1.0])、缩放平移(变换到[0.0,1.0])后,最终得到的值
怎么进行深度测试?
深度缓冲
- 深度缓冲中存储了每个片段的深度值,默认1.0
- 有宽度和高度,这个宽和高是他的分辨率。
- 一般是在窗口系统中自动创建的,他可以以16、24或32位float的形式存储深度值;在大部分系统中,深度缓冲的精度都是24位
深度测试
在光栅化中的位置
- 深度测试在片段着色器执行之后,并且在模板测试之后在屏幕空间坐标进行(屏幕空间坐标与glViewport所定义的视口大小密切相关)
- 屏幕空间坐标可以用gl_FragCoord在片段着色器中直接访问;vec3类型的gl_FragCoord的x、y分量分别代表片段在屏幕空间中的坐标x、y((0,0)点位于左下角);z代表片段的深度,就是需要与深度缓冲中的内容所比较的那个值
- 现代GPU提供提前深度测试的硬件特性。这个特性允许深度测试在片段着色器之前运行,但前提是需要知道某个片段始终是被遮挡的,并且不能在片段着色器中修改深度值
控制函数
- 启用——glEnable(GL_DEPTH_TEST),深度测试默认是禁用的
- 清空深度缓冲——glClear(GL_DEPTH_BUFFER_BIT),如果启用了深度测试,就要在每帧之前清空深度缓冲,不然使用的还是上一帧写入深度缓冲的深度值
- 禁用写入——glDepthMask(GL_FALSE),通过设置深度掩码,实现禁用深度缓冲的写入,使深度缓冲变为一个只读的缓冲;这个函数只在深度测试被启用的情况下有用
- 设置比较结果——前边说过,通过比较达到预设的比较结果,就认为是通过深度测试,这个比较结果就是用glDepthFunc(XX)设置的;XX默认的是GL_LESS,还有另外7种设置
深度值的大小
- 深度值是介于0.0与1.0之间的值
- 深度值是从观察者角度所看到的场景中物体的z值,这个值是投影矩阵作用后的,又经过标准设备坐标变换,再经过范围变换(变换到0.0与1.0之间)得到的值
- 深度值在透视投影中是非线性的,原因如下:裁剪空间中的坐标等于投影矩阵乘以观察空间中的坐标,即
(
x
c
y
c
z
c
w
c
)
=
p
r
o
j
M
a
t
∗
(
x
v
y
v
z
v
1.0
)
\left( \begin{matrix} x_c \\ y_c \\ z_c \\ w_c \end{matrix} \right) = projMat * \left( \begin{matrix} x_v \\ y_v \\ z_v \\ 1.0 \end{matrix} \right)
⎝⎜⎜⎛xcyczcwc⎠⎟⎟⎞=projMat∗⎝⎜⎜⎛xvyvzv1.0⎠⎟⎟⎞
而透视投影矩阵projMat为:
p
r
o
j
M
a
t
=
(
2
n
r
−
l
0
r
+
l
r
−
l
0
0
2
n
t
−
b
t
+
b
t
−
b
0
0
0
−
(
f
+
n
)
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
)
projMat = \left( \begin{matrix} \frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\ 0 & 0 & \frac{-(f+n)}{f-n} & \frac{-2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{matrix} \right)
projMat=⎝⎜⎜⎛r−l2n0000t−b2n00r−lr+lt−bt+bf−n−(f+n)−100f−n−2fn0⎠⎟⎟⎞
由此可知:
z
c
=
−
(
f
+
n
)
f
−
n
z
v
+
−
2
f
n
f
−
n
=
−
(
f
+
n
)
z
v
−
2
f
n
f
−
n
z_c = \frac{-(f+n)}{f-n}z_v + \frac{-2fn}{f-n} = \frac{-(f+n)z_v - 2fn}{f-n}
zc=f−n−(f+n)zv+f−n−2fn=f−n−(f+n)zv−2fn
因此zc与近远平面之间的距离成反比,然后再将其转换为标准设备坐标zndc = zc / wc,最后再将zndc * 0.5 + 0.5,得到[0.0,1.0]之间的值,就是最后的深度值(上面的l,r,t,b,f,n分别代表平截头体的左,右,上,下,远,近平面的位置)
深度图像
- 由于深度值在透视投影中是非线性的,深度值0.5不代表平截头体的中间,实际是很接近近平面的一个位置;这样非线性的深度值给近平面附近的物体提供了很高的精度,远平面出的物体有着较低的精度;片段的深度值随物体离近平面的距离迅速增加,所以大部分物体顶点的深度值都是接近1.0的,所以深度图像一般都偏白色
副作用
- 深度缓冲在增加了良好的透视效果同时,也有可能会引起不良的副作用,比如出现奇怪的花纹,这种现象叫做深度冲突
- 避免深度冲突的三个方法:
- 不要把多个物体放的太近
- 尽可能将近平面设置的远一些
- 使用更高精度的深度缓冲