深度测试以及透视除法的一些相关内容
深度测试内容并不难,但是一开始对一些概念性质的东西还是有点迷迷糊糊的,这里写篇bolg帮助理解一下。对应的Learn OpenGL的章节:深度测试 以及 坐标系统。
首先是透视投影(perspective)中的透视除法。相对的,也有平行投影。注:透视除法是当坐标变换到裁剪空间之后进行的,并且透视除法是由GPU自己做的。
现实生活(3D世界)中,我们知道有“近大远小”的概念,在图形学中为了模拟这种现象,我们画出来的各个物体或者物体的各个面,也要符合“近大远小”的现象。这个时候,透视除法就登场了。简单的说,就是将三维坐标(x, y, z)分别除以w分量,使得
\begin{pmatrix} x'
\\ y'
\\z'
\end{pmatrix}
=
\begin{pmatrix} x/w
\\ y/ w
\\ z/ w
\end{pmatrix}
离观察者越远的顶点坐标,w分量越大(设置w分量是通过透视矩阵自动完成的,关于透视投影矩阵,参照这里)。通过这个操作,就实现了近大远小的现象。
但是问题又来了,为了模拟现实生活(3D世界)光是做一个透视除法够不够呢?当然不够,透视除法只是将物体以及它的各个面设置为“近大远小”,而我们现实生活中,除了近大远小,还有“遮挡”的概念,也就是不能透视看到整个物体。
在OpenGL中,这种现象我们可以通过深度测试(depth testing)来实现。
什么是深度?简单理解为物体片段距离人(摄像机)的距离,也就是(片段的)Z坐标(当然,是做了透视除法之后,顶点Z坐标使得相应生成的片段改变之后的Z坐标;透视除法 -->顶点Z坐标 -->片段Z坐标)。
显而易见,对于不同片段想要渲染同一个像素点的行为,我们只要比较它们之间的深度值,来决定渲染哪个点就好(这里不讲深度,是因为深度值由深度(Z坐标)通过公式计算出来的)。
那么就涉及到一个比较深度值的问题,实际上OpenGL有一个叫深度缓冲(depth buffer)或者叫Z缓冲(Z - buffer)的东西,顾名思义,就是存储深度值(depth value)的地方。它是由窗口系统自动创建的,会以16, 24, 32位float的形式存储深度值。并且一开始深度缓冲内的深度值会自动初始化为1.0(深度值取值范围为[0.0, 1.0]。
除去深度缓冲内初始化的深度值,片段的深度值该怎么计算呢?
还记得我们创建透视投影矩阵时候用到的near 以及 far 平面吗?我们可以采取线性变化和非线性变换的方式来将片段Z值转化为取值范围为[0.0, 1.0]的深度值(其实Z值实际上就是物体概念上的深度值,但是由于深度缓冲中深度值取值范围为[0, 1],故我们需要将它实际上的深度值映射到[0, 1]的范围,成为新的深度值,从而与深度缓冲的内容进行比较):
- 线性变换
\[F_{depth}=\frac{z-near}{far-near}\]
一般在实际应用中我们不采用这种方式去计算深度值(取值范围当然是限定在[0.0, 1.0]啦),为什么?因为采取线性深度缓冲的话会导致精度没有区别。我们真的需要对1000单位远的深度值和只有1单位远的充满细节的物体使用相同的精度吗?线性方程并不会考虑这一点。
PS:精度,也就是同样取值范围的Z值,精度高的可以表示更多的深度值。 - 非线性变换
\[F_{depth} = \frac{ 1/z - 1/near}{1/far - 1/ near}\]
采用这个公式有更高的精度。
同样,这个变换的方程也存在于投影变换矩阵当中(说实话,由于推导过程是全英的,所以我并没有看懂XD)。
以上的操作基本都由投影变换矩阵去搞定,因此我们需要做的,便是启用深度测试(默认关闭),并且在每次渲染循环中清除深度缓冲:
glEnable(GL_DEPTH_TEST);
while ( ...)
{
...
glClear(GL_DEPTH_BUFFER_BIT);
...
}
同时我们也可以认为设置测试函数来修改深度测试中的比较运算符,比如GL_LESS, GL_ALWAYS之类的(具体自己看文档)。
以上!
参考文章:
[1] milo-yip的回答