OpenGL-正背面剔除及深度测试

382 阅读4分钟

今天,我们用默认光源着色器通过光源、阴影体现立体效果绘制了一个甜甜圈。如图:

然而当我们移动旋转时,出现了意外:

由很多三角形组成的甜甜圈在不动时,我们是看不到背面的(它不是透明几何体)。当我们移动时,三角形的绘制就出了问题。

在默认情况下,我们所渲染的每个点、线或三角形都会在屏幕上进行光栅化,并且会按照在组合图元批次时指定的顺序进行排列。这在某些情况下会产生问题。其中一个可能的问题是,如果我们绘制一个由很多三角形组成的实体对象,那么第一个绘制的三角形可能会被后面绘制的三角形覆盖。--《OpenGL超级宝典第五版》

解决办法:

油画法:对三角形进行排序,首先渲染那些较远的三角形,再在它们上方渲染那些较近的三角形。

然而这种方式非常低效:

  • 我们必须对发生几何图形重叠地方的每个像素进行两次写操作,而在存储其中进行写操作会使速度变慢。
  • 对独立的三角形进行排序的开销会更高。
正背面剔除:正面背面的区分原因之一就是为了剔除。背面剔除极大地提高了性能,也能解决上面的问题。这种方式非常高效,在渲染的图元装配阶段就整体抛弃了一些三角形,并且没有执行任何不恰当的光栅化操作。

于是我们尝试一下:

glEnable(GL_CULL_FACE);//开启正背面剔除
glDisable(GL_CULL_FACE);//关闭

看下效果:

完美!

正当我左右移动欣赏时,又发现一个问题:

出现这种现象的原因是:

在不开启深度测试时,如果我们先绘制一个距离比较近的物体,再绘制一个距离较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。

概念理解:

深度,即z值,就是像素点在3D世界中距离观察者的距离。

深度测试,在决定绘制一个物体的表面时,首先要将表面对应的像素的深度值与当前的深度缓冲区(存储z值)中的值进行比较。如果大于深度缓冲区的值,则丢弃这部分,否则利用这个值更新深度缓冲区。

所以,我们在使用GLUT设置OpenGL窗口时,应该申请一个深度缓冲区。

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );//GLUT_DEPTH:深度缓冲区

开启深度测试:

glEnable(GL_DEPTH_TEST);//如果没有申请深度缓冲区,则开启无效

再看一下甜甜圈:

完美!

然而,深度值的范围在(0,1)之间,观察者在z轴负方向时,值越小表示越靠近观察者,值越大表示越远离观察者。在深度值精确度的限制下,当两个值相差非常小的情况下,会导致深度测试的结果不可预测。显示出来的现象是交错闪烁。

这种现象称为ZFighting闪烁问题

当然有对应的解决方案:

①启用polygon offset:让深度值之间产生间隔.如果2个图形之间有间隔,是不是意味着就不会产生干涉.可以理解为在执行深度测试前将立方体的深度值做一些细微的增加.于是就能让重叠的2个图形深度值之间有所区分.

glEnable(GL_POLYGON_OFFSET_FILL);

②指定偏移量

通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units 每个Fragment 的深度值都会增加如下所示的偏移量量: Offset = ( m * factor ) + ( r * units);

m : 多边形的深度的斜率的最大值,理解一个多边形越是与近裁面平行,m就越接近于0.

r : 能产生于窗口坐标系的深度值中可分辨的差异最小值.r是由具体是由具体OpenGL 平台指定的一个常量.

一个大于0的Offset会把模型推到离你(摄像机)更远的位置,相应的⼀个小于0的Offset 会把模型拉近.⼀般⽽言,只需要将-1.0 和 -1 这样简单赋值给glPolygonOffset基本可以满足需求.

③关闭glPolygonOffset

glDisable(GL_POLYGON_OFFSET_FILL);

预防出现闪烁问题:

  • 不要将两个物体靠的太近,避免渲染时三角形叠加在一起
  • 尽可能将近裁剪面设置的离观察者远一点
  • 使用更高位数的缓冲区,通常使用24位的,硬件支持的可以提高到32位,提高精确度