深度测试(Depth Testing)
-
深度缓冲区(depth-buffer) 是与颜色缓冲区类似的一种缓冲区,它按每个片元存储信息,并且与颜色缓冲区拥有一样的宽度和高度。深度缓冲区由窗体系统自动创建,并将深度值存储为16,24或32位的浮点值。大部分系统的深度缓冲区的精度为24位。
-
当启动深度测试时,OpenGL将片元的深度值与深度缓冲区内容进行测试。如果测试通过,片元将被渲染且其深度值被更新到缓冲区,如果失败则丢弃该片元。
-
深度测试是在片元着色器运行之后模板测试之前,在屏幕空间中进行。
-
GLSL内置一个
gl_FragCoord
变量,代表片元在屏幕的坐标,其中x和y分量为左下角为原点的屏幕坐标,z分量包含片元的深度值。 -
OpenGL中启动深度测试。
glEnable(GL_DEPTH_TEST);
- 清除深度缓冲区。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- OpenGL允许我们通过将深度掩码(mask)设置为
GL_FALSE
来禁用深度缓冲区写入。这只能在缓冲区启用的情况下设置才有意义。
glDepthMask(GL_FALSE);
1. 深度测试函数
- OpenGL允许我们通过
glDepthFunc
函数修改深度测试的比较操作符。
glDepthFunc(GL_LESS);
- 深度测试比较操作符选项列表。
函数 | 描述 |
---|---|
GL_ALWAYS | 深度测试总是通过 |
GL_NEVER | 深度测试从不通过 |
GL_LESS | 如果片元深度值小于存储的深度值则通过(默认) |
GL_EQUAL | 如果片元深度值等于存储的深度值则通过 |
GL_LEQUAL | 如果片元深度值小于或等于存储的深度值则通过 |
GL_GREATER | 如果片元深度值大于存储的深度值则通过 |
GL_NOTEQUAL | 如果片元深度值不等于存储的深度值则通过 |
GL_GEQUAL | 如果片元深度值大于或等于存储的深度值则通过 |
下面我们创建一个场景来展示不同深度测试的效果。场景是一个平面上有两个立方体,平面和立方体分别设置不同的纹理。
- 立方体的顶点数据,位置坐标和纹理坐标参考前面章节。平面的位置坐标和纹理坐标如下:
float planeVertices[] = {
// positions // texture
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
- 核心渲染代码如下(视矩阵和投影矩阵皆使用单位矩阵):
// cubes
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
// floor
glBindVertexArray(planeVAO);
glBindTexture(GL_TEXTURE_2D, floorTexture);
shader.setMat4("model", glm::mat4(1.0f));
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
-
GL_AWLAYS
效果。
GL_ALWAYS效果 -
GL_LESS
效果。
GL_LESS效果
2. 深度值精度
-
因为视空间物体的z-值可以是投影锥体远近平面之间的任何值,因此我们需要将其转换为范围为[0, 1]之间的值。下面是其中一种转换方式的公式:
其中的和值就是我们提供给投影矩阵用于产生可见截锥体的远近平面值。 -
实际应用中很少使用上述线性转换,因为投影属性的关系,一般使用一个非线性公式但与成比例,结果就是z值很小时精度较高,z值很大时精度很小。公式如下所示:
深度值非线性转换图
从上述公式可知,z值在1.0到2.0被映射到1.0到0.5,占用了[0, 1]一半,精度很高;但是z值在50.0和100.0之间则大概只占2%的[0, 1]范围。效果如下图所示:(图片取自书中)
3. 可视化深度缓冲区
- 如果我们要将片元的深度值显示为颜色,我们可以通过内置的
gl_FragCoord
矢量进行颜色计算。
FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
-
深度值显示效果,大家可以感受移动物体过程中深度值颜色的非线性变化。
非线性深度值颜色显示 - 对于片元深度值,我也可以通过逆转换,来显示其原来的线性深度值。首先,我们需要将深度值从[0, 1]转换为标准设备坐标范围[-1, 1]。
float ndc = depth * 2.0 - 1.0;
- 然后,我们逆转上述第二个公式,将非线性深度值转换为投影矩阵远近平面之间的值。
float linearDepth = (2.0 * near * far) / (far + near - ndc * (far - near));
- 我们将转换过程封装成GLSL函数,最终片元着色器中的计算如下:
float near = 0.1;
float far = 100.0;
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0;
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main()
{
// 因为near和far之间深度值大部分大于1,会显示为白色,因此这里除以far,映射到[0, 1]
float depth = LinearizeDepth(gl_FragCoord.z) / far;
FragColor = vec4(vec3(depth), 1.0);
}
-
线性深度值效果。
线性深度值颜色显示
4. 深度冲突(Z-fighting)
- 当两个平面或三角形靠的太近,而深度缓冲区没有足够的精度进行测试时会出现一种常见的视觉伪影——感觉两个图形一直在切换顺序从而导致一种奇怪的故障模式,这称为深度冲突(z-fighting)。
- 防止深度冲突的技巧:
- 从不将物体放置得太接近从而导致它们的三角形面片彼此重叠,如对物体位置做一定的偏移。
- 将近平面设置得尽可能的远,因为从上述非线性公式,我们知道越靠近近平面精度越大。
- 使用更高精度的深度缓冲区(降低性能)。
网友评论