Opengl——深度测试

前言

现实中物体是3D的,当一个物体遮挡住另一个物体时,后面的物体我们说看不见的。同样,在绘制3D场景时,应该达到同样的效果,这样看起来才更加真实。当然,前面的物体应该是不透明时,才会完全遮挡,如果前面物体有一定透明度,就涉及到了“混合”问题。本文我们只讨论物体不透明的情况。

既然物体间有遮挡的问题,那么该如何处理呢?也就是显示出前面的物体,消除后面的物体,准确讲是消除隐藏面——Hidden surface elimination,即3D物体的三角面的处理问题,近处的三角面片会遮住远处的三角面片。

最简单的做法是「画家算法」(Painter’s algorithm)。该算法较为简单,即按顺序画物体,先画远处的物体,再画近处的物体,这样近处的物体就会遮挡住远处的物体,符合实际,像下面这样(来自Z buffer 和 W buffer 簡介)。

这里写图片描述

但是有时会出现很难判断的情况,像下面的这样,三个三角形之间有重叠,那么该如何判断呢?

这里写图片描述

深度缓冲——Z-Buffer

Opengl中我们常用的做法是使用 Z-Buffer,深度缓冲区。深度缓冲和颜色缓冲类似,颜色缓冲区存储的是片段的颜色信息,而深度缓冲区存储的是深度信息。深度缓冲有系统窗口创建并将其深度值存储为16、24或32位的浮点数。一般使用的是24位深度缓冲区。

由于Opengl 是状态机,默认情况下深度测试是关闭的,可以通过GL_DEPTH_TEST 选项来打开:

glEnable(GL_DEPTH_TEST);

开启深度测试后,片段的深度信息z值将存储在深度缓冲区中,测试时,将当前的片段的深度值与缓冲区的 z 值进行比较,大于缓冲区的 z 值时,将丢弃该片段;否则保留该片段并更新深度缓冲区的深度值。启用深度测试时还应该使用GL_DEPTH_BUFFER_BIT清除深度缓冲区,否则将保留上一次进行深度测试时写入的深度信值。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

深度测试函数

Opengl允许我们修改深度测试的规则,所以提供了一个函数供我们调用,通过修改参数来改变比较的规则。(深度测试函数(depth function)):

glDepthFunc(GL_LESS);

该函数是默认的函数,意思是在片段深度值小于深度缓冲区使测试通过,下列列出了几个比较运算符:

运算符 说明
GL_ALWAYS 总是通过测试
GL_NEVER 总是不通过测试
GL_LESS 片段值 < 缓冲区深度通过测试
GL_EQUAL 片段值 = 缓冲区深度通过测试
GL_LEQUAL 片段值 <= 缓冲区深度通过测试
GL_GREATER 片段值 > 缓冲区深度通过测试
GL_NOTEQUAL 片段值 != 缓冲区深度通过测试
GL_GEQUAL 片段值 >= 缓冲区深度通过测试

测试实例

代码中我们将深度函数设为GL_ALWAYS:

glDepthFunc(GL_ALWAYS);

使用该函数相当于默认情况下的没有开启深度测试的效果相同。场景中先绘制两个 Cube,后绘制 Plane,则效果如下,Plane遮住了Cube的一部分。

这里写图片描述

设置为 GL_LESS 或开启深度测试,效果如下:

glDepthFunc(GL_LESS);

这里写图片描述

注意:测试时如果使用glEnable(GL_CULL_FACE),可能出现问题,如下图,原因是面剔除,本文不讨论该问题,简单说就是绘制三角形时是有顺序的,理解为逆时针绘制的面保留,否则剔除,所以有的面没有绘制出来是顶点的绘制顺序导致的,所以可以先去掉该函数。

这里写图片描述

深度值精度

这部分讨论一下深度值精度,里面会有一些数学的东西,不感兴趣的话可以大概了解或简单记下结论,当真正用到时再去细究。

深度缓冲区的z值在 0.0 和 1.0 之间,可以观看到的场景均在近平面和远平面之间,所以z值也在 far 和 near 之间,下面的公式可以将深度值转换到[0.0,1.0]:

Fdepth=znearfarnear

这样就得到线性的深度值。
这里写图片描述

实际中,远处的物体不用很好分辨出远近,而近处的物体则需要很好分辨远近,即远处和近处得深度值应该有不同的精度,所以采用了 1/z 来计算深度信息,则距离近平面较近的拥有很高的精度,距离越远精度值越低,下面是公式:

Fdepth=1z1near1far1near

下面曲线是z值和深度缓冲中的值的非线性关系,近平面在1.0处,远平面在50.0处,可以看出在达到10.0位置时就已经达到了90%的深度信息,也就是近处深度精度高,远处深度精度低。

这里写图片描述

深度可视化

深度值可以通过gl_FragCoord向量的z值获取到,将该值作为颜色输出,看看深度值的变化

void main()
{
    color = vec4(vec3(gl_FragCoord.z), 1.0f);
} 

开始运行可能都是白色画面,输出的颜色为1.0,远处的颜色精度低,看起来都是白色的,此时通过将相机移动近一点观察,当逐渐靠近物体时,颜色越来越暗,z值减小,应该知道深度的非线性特点,近处时,稍微移动一点,颜色就会明显由暗变亮或由亮变暗。
这里写图片描述

深度线性可视化

我们将深度值从范围 [0,1] 中转换为NDC范围内 [-1,1] 然后通过逆变换来得到一个线性的深度值,具体推导过程可以看看投影矩阵

#version 330 core

out vec4 color;
in vec2 TexCoords;
uniform sampler2D text;
float linearDepth(float depth)
{
    float near = 0.1f;
    float far = 100.0f;
    float z = depth *2.0f -1.0f;
    return (2.0f * near) / (far + near - z *(far - near));
}
void main()
{
    float depth = linearDepth(gl_FragCoord.z);
    color = vec4(vec3(depth),1.0f);
} 

我们距离物体越来越近,颜色也越来越暗,变化的速度和之前的非线性可以感受到区别。

这里写图片描述

深度冲突

深度冲突一般出现在深度信息难以比较出来的时候,当距离越远时越容易出现,因为距离较远时精度较低,这时候无法区分开哪些三角形在前,哪些在后,就会导致两个物体的面相互交替出现,玩过CS应该见过这种情形。

移动相机位置到Cube的里面,发现Cube的底面和Plane平面交替出现,这种现象就是深度冲突。

这里写图片描述

解决深度冲突的几种方法:
1. 让物体之间不要距离过小,因为距离过小就可能导致深度值无法比较出大小。一个小技巧就是即使要求两个物体有相同在同一位置,也可以将其中一个物体的位置稍微一动一点,移动的距离人眼无法分辨但是对于深度测试却很有效。
2. 尽可能把近平面设置得远一些,越靠近近平面,精度越高,所以使近平面远离观察者,可以在椎体内很有效的提高精度,下面的是计算深度值的公式,通过增大n(近平面),使得精度提高。然而把近平面移动的太远会导致近处的物体被裁剪掉。所以需要不断调整测试近平面的值,找到合适的近平面值。

这里写图片描述

  1. 使用更高位数的深度缓冲区,通常使用的深度缓冲区是24位的,有一些硬件使用32位的缓冲区,可以使精确度得到提高。

参考资料

1.Learn Opengl
2.Z buffer 和 W buffer 簡介
3.The Industry’s Foundation for High Performance Graphics

猜你喜欢

转载自blog.csdn.net/u011371324/article/details/77414190