关于正背面剔除和深度测试的理解,我们用一个案例来解释。
我们通过OpenGL绘制一个甜甜圈。如下图
![](https://img.haomeiwen.com/i10153078/b1ec1ac2a92821c4.jpg)
如果同时通过点击上下左右按钮让它进行旋转,如果想知道怎么绘制,请移步我之前的简书(传送门 用OpenGL绘制用OpenGL绘制金字塔、圆环、扇形)。
但是有不同的几点。
- 绘制甜甜圈
//
//参数1:GLTriangleBatch 容器帮助类
//参数2:外边缘半径
//参数3:内边缘半径
//参数4、5:主半径和从半径的细分单元数量
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
这是OpenGL为我们提供的官方的方法进行甜甜圈的绘制,注意:参数4和参数5的比例是2:1。torusBatch相当于一个指针,相当于将参数放入torusBatch进行调用。
- 观察者矩阵
//键位设置,通过不同的键位对其进行设置
//控制Camera的移动,从而改变视口
void SpecialKeys(int key, int x, int y)
{
//1.判断方向
if(key == GLUT_KEY_UP)
//2.根据方向调整观察者位置
viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_DOWN)
viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_LEFT)
viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
if(key == GLUT_KEY_RIGHT)
viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
//3.重新刷新
glutPostRedisplay();
}
和之前的案例不同,我们这次将物体(ObjectFrame)固定住,让观察者进行移动,
那么在void RenderScene()中只需要将viewFrame压入模型视图矩阵(modelViewMatix)中即可。
//2.把摄像机矩阵压入模型矩阵中
modelViewMatix.PushMatrix(viewFrame);
-
默认光源着色器(GLT_SHADER_DEFAULT_LIGHT)
从图我们可以看到有的地方颜色比较红,而有的地方呈现出了黑色,那是因为使用GLT_SHADER_DEFAULT_LIGHT,在光照的作用下,甜甜圈呈现立体效果。有的地方是阳面,呈现红色;有的地方是阴面,则呈现黑色。
//使用默认光源着色器
//通过光源、阴影效果跟提现立体效果
//参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
//参数2:模型视图矩阵
//参数3:投影矩阵
//参数4:基本颜色值
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
一个甜甜圈就绘制好了。接下来,我们旋转甜甜圈。
![](https://img.haomeiwen.com/i10153078/08a0a0a3b3aa7c22.gif)
我们可以看到天天圈有的地方变成了黑色的,有的地方是红色的。那么是为什么呢?
正背面剔除
原因
在渲染一个立体图形时,我们需要决定哪些面应该管观察者看到,而哪些面不应该被观察者看到,如果不需要被观察者看到,应该直接丢弃,不应该绘制,这种情况叫做隐藏面消除。那么在上面的例子中,就是因为加载了我们不应该看到的面,所以才导致混乱。
画家算法
-
画家算法的原理
画家算法表示头脑简单的画家首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。就像画家作画一样,一层一层的图层往画布上面铺。
油画算法.png 如图中所示,先画远的假山,再画近处的树,树会把重叠的部分给覆盖住,从而会遮住我们看不到的面,就消除了隐藏面。
-
画家算法的弊端
画家算法无法处理相互重叠的多边形在有些场合下,画家算法可能无法解决可见性问题。
问题.jpg
油画算法只能由远及近一层层堆叠,但是遇到这种相互交错、重叠的时候就会有局限,因为在堆叠的时候无法判断谁先谁后。
于是,我们使用正背面剔除。
正背面剔除原理
![](https://img.haomeiwen.com/i10153078/0a477ce334d8bce3.jpg)
在我们观察一个正方体如魔方的时候,最多可以可以看到3个面,那我们看不到面怎么处理呢?那就直接丢弃掉,此时还能提高性能。
正背面的区分
- 正面:按照逆时针顶点连接顺序的三角面。
-
背面:按照顺时针顶点连接顺序的三角面。
正背面.jpg
当我们从正方形右侧观看的时候,右侧的三角形按逆时针连接,则为正面,左侧的三角形按顺时针连接,则为背面。
正背面剔除用法
- 开启正背面剔除
//打开正背面剔除
glEnable(GL_CULL_FACE);
- 关闭正背面剔除
//关闭正背面剔除
glDisable(GL_CULL_FACE);
注意,打开后记得关闭,否则会对全局产生影响。
- 设置正、背面
//设置正背面
glFrontFace(GL_CCW);
GL_CCW是一个GLenum类型的枚举值。我们默认选择GL_CCW。
参数 | 含义 |
---|---|
GL_CCW | 顶点顺序为逆时针方向的表面为正面 |
GL_CW | 顶点顺序为顺时针方向的表面为正面 |
- 剔除正、背面
//剔除背面
glCullFace(GL_BACK);
GL_BACK是一个GLenum类型的枚举值。我们默认选择GL_BACK。
参数 | 含义 |
---|---|
GL_FRONT | 正面 |
GL_BACK | 背面 |
GL_FRONT_AND_BACK | 正面和背面 |
实现
//开启正背面剔除
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
加上这3行代码后就可以开启正背面剔除了,当然,下面2行可以不用设置,因为OpenGL默认设置GL_CCW和GL_BACK。
再看效果,诶,还有问题,好像被啃了一口?我们再看原因
![](https://img.haomeiwen.com/i10153078/4d7fed7c84497656.gif)
深度测试
混乱原因
结合图来看,
![](https://img.haomeiwen.com/i10153078/811e1c79ed9d2137.png)
当我们选择到这个位置的时候,因为有2个面(正面1和正面2)对着观察者,所以就会出现2个面都是正面的情况,那么接下来再转一下,因为2个面都是正面,所以导致OpenGL因为不知道具体该显示哪个面而出现混乱,出现类似于被啃了一口的样子。
深度测试原理
-
深度
深度就是像素点在3D世界中离观察者的距离,即为Z值。如果观察者在Z轴的正方向,Z值越大越靠近观察者
如果观察者在Z轴的负方向,Z值越小越靠近观察者 -
深度缓冲区
深度缓冲区类似于顶点缓冲区,就是将像素点的Z值存在显存中,而这个专门开辟的区域就叫做深度缓冲区。
当不开启深度测试时,我们就按照绘制的顺序进行存储,后绘制的图像的Z值会将前一个Z值给顶替掉。
当开启深度测试时,就判断深度缓冲区中存在的Z值和即将要存入的Z值谁距离观察者更近,如果即将存入的值更近,则将覆盖原来的值;否则,新的值将会被丢弃。
深度测试用法
- 开启深度测试
//开启深度测试
glEnable(GL_DEPTH_TEST);
- 关闭深度测试
//关闭深度测试
glEnable(GL_DEPTH_TEST);
- 深度测试判断模式
//指定深度测试判断模式
void glDepthFunc(GLEnum mode);
参数 | 含义 |
---|---|
GL_ALWAYS | 总是通过测试 |
GL_NEVER | 总是不通过测试 |
GL_LESS | 当前深度值<存储的深度值时通过 |
GL_EQUAL | 当前深度值=存储的深度值时通过 |
GL_LEQUAL | 当前深度值<=存储的深度值时通过 |
GL_GREATER | 当前深度值>存储的深度值时通过 |
GL_NOTEQUAL | 当前深度值!=存储的深度值时通过 |
GL_GEQUAL | 当前深度值>=存储的深度值时通过 |
深度测试风险--Z-Fighting(Z 冲突)
-
Z-Fighting(Z 冲突)
在开启深度测试后,被遮挡的面就不会再进行绘制,但是由于深度缓冲区的精度所限,所以对于深度差非常小的2个图层,OpenGL并不能准确判断谁打谁小,就会导致结果的不可预测性,出现图层交错闪烁,就称之为Z-Fighting(Z 冲突)。
image.png
上图可以看到,红色跟绿色交叠出出现了很不规则的形状,这就是因为红色跟绿色靠到太近,导致OpenGL判断不了究竟谁在前谁在后。
Z-Fighting(Z 冲突)解决方案
-
多边形偏移
让深度值之间产生微妙的间隔。
//启用多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL);
参数 | 含义 |
---|---|
GL_POLYGON_OFFSET_POINT | 填充方法是点填充 |
GL_POLYGON_OFFSET_LINE | 填充方法是线填充 |
GL_POLYGON_OFFSET_FILL | 填充方法是面填充 |
Z-Fighting(Z 冲突)预防
- 不要将两个物体离得太近,避免叠加。
- 尽可能将近裁剪面设置的离观察者远一些。
- 使用更高位数的深度缓冲区,提高深度缓冲区精度,这样的比较就会更加精确。
网友评论