OpenGL学习(四)-- 正面&背面剔除和深度测试

3,010 阅读6分钟

我的 OpenGL 专题学习目录,希望和大家一起学习交流进步!


一、渲染中可能会出现的问题(不希望出现的几何图形)

默认情况下,我们所渲染的每个点、线或三角形都会再屏幕上进行光栅化,并按照在组合图元批次时指定的顺序排列,这在某些情况下会产生问题。

如果我们绘制一个由很多个三角形组成的实体对象,那么第一个绘制的三角形可能会被后面绘制的三角形覆盖。如下图这个像游泳圈似的模型,其中一些三角形在游泳圈的背面,另一些在正面,正常我们应该是看不到背面的(不考虑透明几何体的特殊情况)。这样的话,三角形绘制的顺序可能会一团糟,就变成了下图的样子:

存在问题的图形.png

二、解决方法

1. 油画法(painters algorithm):

对这些三角形排序,先渲染较远的三角形,再在它们上方渲染较近的三角形。但这种方法在图形处理中效率很低,必须在任何发生重叠的地方对每个像素进行两次写操作,速度会变慢。并且对独立的三角形排序的开销会过高。所以一般不推荐使用。

2. 正面&背面剔除:

对正面和背面三角形进行区分的原因之一就是为了进行剔除。背面剔除能极大提高性能,避免上图出现的问题。它很高效,在渲染的图元装配阶段就整体抛弃了一些三角形。

开启背面剔除: glEnable(GL_CULL_FACE);

关闭背面剔除: glDisable(GL_CULL_FACE);

请注意,我们并没有知名剔除的是正面还是背面。这是由另外一个函数 glCullFace 控制的。

void glCullFace(GLenum mode);

mode 参数的可用值为 GL_FRONTGL_BACKGL_FRONT_AND_BACK。这样要消除不透明物体的内部几何图形就需要两行代码:

void glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

在某些情况下,剔除实体几何体的正面也很有必要,比如要显示图形内部渲染的时候。在渲染透明对象时(下面马上就会讲到混合),我们经常会对一个对象进行两次渲染,第一次会开启透明并剔除正面,第二次则消除背面。这样就在渲染正面之前渲染了背面,这也是渲染透明物体的需要。

但是在开启背面剔除后,会发现上面的游泳圈模型还是显示的有问题,如图:

未开启深度测试.png
这是因为没有开启深度测试。

3. 深度测试:

  • 深度:

深度 就是在 openGL 坐标系中,像素点的 Z 坐标距离观察者的距离。观察者可能放在坐标系的任何位置,那么,就不能简单的说 Z 数值越大或越小,就是越靠近观察者。 如果观察者在Z轴的正方向,Z 值大的靠近观察者,如果是在Z轴的反方向,则 Z 值小的更靠近观察者。

  • 深度缓冲区(DepthBuffer):

深度缓冲区 原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素相关联。 首先,使用 glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值设置为最大值。 如果启用了深度缓冲区,在绘制每个像素之前,OpenGL 会把它的深度值和已经存储在这个像素的深度值进行比较。如果,新像素深度值 < 原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,它的颜色值和深度将被丢弃。 这个比较、丢弃的过程就叫做 深度测试,深度测试是另一种高效消除隐藏面的技术。

申请一个颜色缓冲区和一个深度缓冲区:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);

启用 深度测试,只需调用 glEnable(GL_DEPTH_TEST);

关闭 深度测试: glDisable(GL_DEPTH_TEST);

如果没有深度缓冲区,那么启动深度测试的命令将被忽略。 在绘制场景前,清除颜色缓冲区和深度缓冲区:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

开启了深度测试后,我们终于得到了一个我们想要的游泳圈模型:

完美的游泳圈.png

清除深度缓冲区的默认值是1.0,表示最大的深度值,深度值的范围在[0,1]之间。 用户通过 glDepthFunc(GLenum func) 函数指定深度测试的规则,这个函数包括一个参数,如下表:

参数 说明
GL_ALWAYS 总是通过测试
GL_NEVER 总是不通过测试
GL_LESS 当前深度值 < 存储的深度值时通过
GL_EQUAL 当前深度值 = 存储的深度值时通过
GL_LEQUAL 当前深度值 <= 存储的深度值时通过
GL_GREATER 当前深度值 > 存储的深度值时通过
GL_NOTEQUAL 当前深度值 != 存储的深度值时通过
GL_GEQUAL 当前深度值 >= 存储的深度值时通过

z-fighting(z冲突、闪烁)问题:

闪烁问题.png
当深度值精确度很低时,容易引起ZFighting现象,表现为两个物体靠的很近时确定谁在前,谁在后时出现了歧义。

  • 避免深度值相同造成的z-fighting冲突问题的几种做法:
    1. 在第二次绘制时,插入一个少量的偏移。
    1. 使用 glPolygonOffset 函数调节片段的深度值,使得深度值偏移而不产生重叠。
    1. 使用更高位数的深度缓冲区,通常使用的深度缓冲区是 24 位的,现在有一些硬件使用使用 32 位的缓冲区,使精确度得到提高。

以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:

1、《OpenGL超级宝典 第5版》

2、《OpenGL编程指南(第八版)》

3、作者 The fool 的《OpenGL学习脚印:深度测试(depth testing)》

4、作者 lysc_forever的《OpenGL中的深度、深度缓存、深度测试》

转载请备注原文出处,不得用于商业传播——凡几多