什么是正背面剔除和深度测试,这里以甜甜圈为案例来说明。
首先来绘制一个甜甜圈。这里绘制甜甜圈非常简单,因为OpenGL中自带有甜甜圈的模型,所以在这里我们只需要在SetupRC中调用OpenGL的
void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
这个函数就可以了,并且在使用固定管线绘制时加入一个默认光源,其他的main、SetupRC、ChangeSize、RenderScene、SpecialKeys等函数参考之前的文章。
gltMakeTorus这个函数中参数含义如下:
torusBatch:GLTriangleBatch 容器帮助类
majorRadius:外边缘半径
minorRadius:内边缘半径
numMajor:主半径细分单元数
numMinor:从半径细分单元数
例:gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26); //torusBatch代码中定义的帮助类
执行之后我们可以得到一个甜甜圈,如下:
绘制成功之后,我们通过键盘的上下左右键来让甜甜圈旋转会看到这样一个现象,在甜甜圈中会出现黑色的部分。像这样:
那么出现这种情况的原因是什么呢?
这是因为每个物体在面对光时都有两个面,一个是正对着光的面,一个是背对着光的面,在上面的甜甜圈案例中红色就是光照面,黑色的是背光面,而正常情况下背光面是不应该被观察者所看到的,但是在旋转甜甜圈时OpenGL不知道按个是光照面那个是背光面,所以在旋转时就把背光的那一面暴露了出来,所以就出现了上面那个问题。
对于处理这个问题的解决方案有油画法、正背面剔除、深度测试。
油画法
都知道油画法是由远及近的绘制,但是他有很大的弊端:
1、当物体发生重合时,实际上只需要绘制眼睛能看到的那个,而油画法是每个都绘制,浪费资源。
2、需要对独立三角形进行排序,排序开销过大。
3、如果物体处在交叠的情况,那么OpenGL会不知道先绘制哪个。
正背面剔除
正背面剔除是OpenGL中的一种渲染技巧,它将物体的正面(能被看到的那面)和背面(不能被看到的那面)区分开来,在绘制时,只绘制正面将背面的那部分数据丢弃,这样不仅可以解决上面甜甜圈黑色部分的问题,还能提升性能。
正背面剔除的使用方法:
1、glEnable(GL_CULL_FACE); //开启正背面剔除
2、glCullFace(GL_BACK); //指定要剔除的面,这里的参数有3种:GL_BACK背面 GL_FRONT正面 GL_FRONT_AND_BACK正面和背面
3、glDisable(GL_CULL_FACE); //关闭正背面剔除
那么这样上面黑色的问题就已经解决了,但是在旋转甜甜圈是会发现有出现了一个新的问题,那就是当甜甜圈前后两个环形部分都正对着观察者时会发现甜甜圈被啃了一块,有一个缺口,如下:
这个问题出现的原因是什么呢?
当甜甜圈旋转到两个部分重叠时,OpenGL是不能分辨哪个图层在前哪个图层在后的,那么就会有甜甜圈被吭了一口的情况出现。那么很显然正背面剔除这个方法并不能完美的解决我们的问题,而我们消除隐藏面并不只有正背面剔除这一个方法,还有深度测试。下面就来了解一下深度测试。
深度测试
深度测试深度就是在OpenGL坐标系中物体像素点Z坐标距离观察者的距离。因为观察者是可以随意移动的所以不能绝对的说Z的数值越大或越小,观察者就月靠近物体。
我们可以这样来区分,观察者和物体的远近:
如果观察者在Z轴的正方向,Z值越大就越靠近观察者;
如果观察者在Z轴的负方向,Z值越小就越靠近观察者;
首先我们来了解一下深度缓冲区:
深度缓冲区
深度缓冲区存储在显存中,它的原理就是把距离观察者平面(近裁剪面)的深度值与窗口每个像素点1对1进行关联以及存储。
在了解完深度缓冲区后,再来谈谈什么是深度测试;
深度测试:OpenGL中深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是对应的,颜色缓冲区存储像素的颜色值,而深度缓冲区则是存储像素的深度值。再决定是否绘制一个物体表面时,首先要将表面像素对应的深度值与当前深度缓冲区中的值进行比较,如果大于深度缓冲区的值则丢弃,如果小于就将这个像素对应的深度值和颜色值更新到深度缓冲区和颜色缓冲区中,这个过程就叫深度测试。
下面是深度测试的使用方法:
1、首先清空深度缓冲区和颜色缓冲区: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2、开启深度测试:glEnable(GL_DEPTH_TEST);
3、关闭深度测试:glDisable(GL_DEPTH_TEST);
以下是深度测试之后的结果:
深度测试存在的问题
虽然上面深度测试完美的解决了甜甜圈的问题,但是并不代表深度测试就不会发生任何问题。
Z-Fighting(Z冲突,闪烁)问题
这是深度测试存在的潜在问题,并不是一定会发生,比较容易出现在老旧手机上,现在的手机配置都比较高,出现的概率很低。
那么这个问题出现的原因是什么呢?
在开启深度测试后,OpenGL就不会去绘制被挡住的部分。但是由于深度缓存区存的精度有限制,比如一个是0.001另一个是0.0001,而深度缓存区的精度只有两位,那么这个时候OpenGL会判定这个两个图层处于同一层,就有可能出现绘制错乱。因为不知道到底哪个图层在前哪个在后,就会产生Z-Fighting问题。如下图:
多边形偏移
如果产生了Z-Fighting问题可以用多边形来处理;
多边形偏移:Z-Fighting产生的原因是精度制不够,那么我们可以在绘制时可以人为的去改变图层的精度,让OpenGL可以分辨出来,这样就不会判定为处于同一图层上了,就不会出现Z-Fighting。
开启多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL);以下是多边形偏移参数说对应的填充模式:
指定偏移量
通过glPolygonOffset (GLfloat factor, GLfloat units);函数来指定偏移量
偏移量的计算公式:Offset = (m * factor) + (r * units)
m:多边形的深度的斜率的最大值,一个多边形也会是与近裁剪面平行,m就越接近0;
r:能产生于窗口坐标系的深度在中可分辨的差异最小值。具体是由OpenGL平台指定的。
一般而言我们只需要将参数设置为-1和-1就可以满足需求。
开启之后关闭多边形偏移glDisable(GL_POLYGON_OFFSET_FILL)。
Z-Fighting的预防
1、不要将两个物体靠的太近,避免渲染时三角形叠在一起。
2、尽可能将近裁剪面设置得离观察者远一些。
3、使用更高位数的深度缓冲区,24位上会出现问题,32/64位可能就不会出现问题了。