阅读 172

OpenGL入门 (七) —— 隐藏面消除详解

前言

本文主要是对OpenGL几个重要概念的阐述,目的是为了理解下面三行代码。

//开启深度测试
glEnable(GL_DEPTH_TEST);
//开启正面剔除
glEnable(GL_CULL_FACE);
//开启多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL);
复制代码

什么是隐藏面消除?

隐藏面消除:在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部分是对观察者不可见的,应该及早丢弃。

例如:在一个不透明墙壁后的物体,就不应该渲染。这种情况就叫做隐藏面消除

上图是上篇Demo里用OpenGL画的一个圆环,从正面看起来一切正常,但是的我们用方向键移动视角后,就会出现如下图所示的问题

2019-12-30 20-12-30.2019-12-30 20_12_48

如何解决隐藏面消除?

1.油画法

什么是油画法?
  • 先绘制场景中的离观察者较远的物体,再绘制较近的物体。
  • 如下图所示:先绘制红色(远)部分,再绘制黄色部分(较近),最后再绘制灰色(最近)部分,即可解决隐藏面消除的问题。
为什么现在不使用油画法?
  • 我们明显也能看到,肉眼看不到,没必要渲染的部分也先画出来了,所以这种方法在图形处理中是非常低效的,所以其实在开发中不会用这种方式。

  • 油画法在几个物体和观察者的距离一样时,产生重叠,那么就无法绘制,如下图所示

2.正背面剔除

为什么要使用正背面剔除?
  • 一个3D图形,我们从任何一个角度去观察,最多只能看到3个面。那么其他的面我们是看不到的,既然看不到那为什么要去消耗性能绘制呢?这就是正背面剔除法。

  • OpenGL 可以做到检查所有正面朝向观察者的面,并渲染它们.从⽽丢弃背面朝向的面. 这样可以节约⽚元着⾊器的性能,渲染的性能可以提升50%。

如何使用正背面剔除?
  • 分析顶点顺序,指定正背面,开启正背面剔除。

  • 正背面:按照逆时针顶点连接的三角形面为正面,顺时针顶点连接的三角形为背面。

  • 代码实现

//开启正面剔除
glEnable(GL_CULL_FACE);
//关闭正面剔除
glDisable(GL_CULL_FACE);
复制代码
开启正背面剔除后的效果

还记得上面绘制过的圆环吗?开启正背面剔除后,效果如下图,可以看到正面和背面的立体感都出来了,解决了隐藏面的问题,但是在观察圆环侧面的时候,有明显的凹槽效果?这是为什么呢?

2019-12-30 20-17-49.2019-12-30 20_18_06

3. Z-buffer⽅方法(深度缓冲区Depth-buffer)

为什么需要深度缓冲区?

上面发生的凹槽效果,就是因为在绘制侧面时,先绘制了离观察者近的这部分,再绘制了距离观察者远的那部分,新绘制的部分会覆盖掉之前绘制的部分,因为没办法确定谁近谁远,所以会造成这种绘制问题。

那什么是深度缓冲区呢?
  • 首先需要了解什么是深度,深度其实就是该像素点在3D世界中离观察点的距离,也就是XYZ坐标系中的Z值
  • 那深度缓冲区是什么呢?它就是一块用来存储像素点Z值的内存区域。Z越大,则离观察者越远。
那如何使用深度缓冲区呢?

首先需要了解什么是深度测试?

每个像素点只有一个深度值,在同一个像素点出现新的深度值时,会与之前进行比较,如果新的深度值大,那么离我们更远,应该是被遮挡。如果新的深度值小,那么离我们更近,我们应该先看到他,这个时候深度缓冲区储存小的深度值,并且颜色缓冲区也将进行对应的更新。比较深度值的这整个过程,就叫深度测试。

使用深度缓冲区,就需要开启深度测试,用如下代码

glEnable(GL_DEPTH_TEST);

关闭深度测试 glDisable(GL_DEPTH_TEST);

使用深度缓冲区,开启深度测试后的效果

还是上面那个圆环,开启了深度测试后,如下图所示,侧面的凹槽已经没有了,因为有了深度值,它不会去绘制被遮挡的部分,提高了效率,还解决了问题。

2019-12-30 20-21-22.2019-12-30 20_21_37

拓展:开启深度测试后,就一定不会出现问题了吗?

其实不然,如下图所示,三个图形在深度值的差异特别小的情况下,深度测试无法判断深度值,会导致无法预测的问题。会造成下面2个画面交替出现的问题,这个问题叫 ZFighting 闪烁问题

那么如何解决ZFighting闪烁问题呢?

  • 启用Polygon Offset(多边形偏移),在执行深度测试之前,细微的增加深度值,使得两个重叠的图形之间有细微的差异

//开启多边形偏移 glEnable(GL_POLYGON_OFFSET_FILL);

//关闭多边形偏移 glDisable(GL_POLYGON_OFFSET_FILL);

  • 指定偏移量,负值,将使得z值距离我们更近,⽽正值,将使得z值距离我们更更远。
//一般⽽言,只需要将-1.0 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求.
glPolygonOffset(-1,-1);
复制代码

那么如何预防ZFighting闪烁问题呢?

  1. 不要将两个物体靠的太近,手动避免三角形渲染的时候叠在一起
  2. 尽可能将近裁剪面设置的离观察点远一些,因为距离远一些,会使得整个裁剪范围内的精度变高一些。
  3. 使用更高位数的深度缓存区,一般是24位,有些硬件是32位,能够提升精度。

总结

  1. 上面说到了三种解决隐藏面消除的解决方案,但是油画法可以摒弃,有所了解即可。我们一般使用的是正背面剔除和深度测试配合使用,来解决问题并提高性能。

  2. 虽然在OpenGL中,正背面剔除和深度测试的代码量就几行,但是需要了解其原理,才能对整个渲染都有深入的理解。

  3. 因为OpenGL是状态机,所以在开启正背面剔除或深度测试后,在不使用的时候一定要记得关闭它。

点击查看本文完整demo

OpenGL入门 (一) —— OpenGL专业名词解析

OpenGL入门 (二) —— OpenGL Mac环境搭建

OpenGL入门 (三) —— 快速画一个正方形

OpenGL入门 (四) —— 渲染流程解析

OpenGL入门 (五) —— 图元绘制实战

OpenGL入门 (六) —— 矩阵基础变化实战

OpenGL入门 (七) —— 隐藏面消除详解

OpenGL入门 (八) —— 纹理坐标解析

关注下面的标签,发现更多相似文章
评论