美文网首页OpenGL & Metal
正背面剔除、深度测试

正背面剔除、深度测试

作者: 黑眼豆豆_ | 来源:发表于2020-07-13 14:07 被阅读0次

关于正背面剔除和深度测试的理解,我们用一个案例来解释。

我们通过OpenGL绘制一个甜甜圈。如下图

甜甜圈.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);

一个甜甜圈就绘制好了。接下来,我们旋转甜甜圈。


旋转甜甜圈.gif

我们可以看到天天圈有的地方变成了黑色的,有的地方是红色的。那么是为什么呢?

正背面剔除

原因

在渲染一个立体图形时,我们需要决定哪些面应该管观察者看到,而哪些面不应该被观察者看到,如果不需要被观察者看到,应该直接丢弃,不应该绘制,这种情况叫做隐藏面消除。那么在上面的例子中,就是因为加载了我们不应该看到的面,所以才导致混乱。

画家算法

  • 画家算法的原理
    画家算法表示头脑简单的画家首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。就像画家作画一样,一层一层的图层往画布上面铺。

    油画算法.png 如图中所示,先画远的假山,再画近处的树,树会把重叠的部分给覆盖住,从而会遮住我们看不到的面,就消除了隐藏面
  • 画家算法的弊端
    画家算法无法处理相互重叠的多边形在有些场合下,画家算法可能无法解决可见性问题。

    问题.jpg
    油画算法只能由远及近一层层堆叠,但是遇到这种相互交错、重叠的时候就会有局限,因为在堆叠的时候无法判断谁先谁后。

于是,我们使用正背面剔除

正背面剔除原理

魔方.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。

再看效果,诶,还有问题,好像被啃了一口?我们再看原因


选择甜甜圈.gif

深度测试

混乱原因

结合图来看,

image.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 冲突)预防

  • 不要将两个物体离得太近,避免叠加。
  • 尽可能将近裁剪面设置的离观察者远一些。
  • 使用更高位数的深度缓冲区,提高深度缓冲区精度,这样的比较就会更加精确。

相关文章

网友评论

    本文标题:正背面剔除、深度测试

    本文链接:https://www.haomeiwen.com/subject/mfwmcktx.html