OpenGL绘制甜甜圈、正反面剔除问题

今天我们绘制一个甜甜圈,如下图所示:


甜甜圈

并且通过方向键来控制上下左右旋转:
由于绘制流程和上节的绘制基本图形的流程一样绘制点、线、三角形、金字塔,在此我们简略描述、只是在初始化函数SetupRC里我们要创建一个甜甜圈:

  //创建一个甜甜圈
    //void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
    //参数1:GLTriangleBatch 容器帮助类
    //参数2:外边缘半径
    //参数3:内边缘半径
    //参数4、5:主半径和从半径的细分单元数量
   
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
   //点的大小
    glPointSize(4.0f);

然后在RenderScene渲染函数中进行相应地渲染:

shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
  //绘制
 torusBatch.Draw();

在此我们为了更好的体现立体效果使用了默认光源着色器进行绘制。
这时运行就得到我们如图的甜甜圈了。
但是当我们通过控制方向键来旋转的时候我们发现了如下问题:


旋转的甜甜圈

是什么原因造成的呢?
在绘制立体甜甜圈的时候我们使用了默认光源着色器,这个时候物体就会有阳面(阳光照射的面)在此我们称之为正面(正反是相对的)、阴面(背面)
上图中蓝色的部分其实就是阳面,黑色的部分其实就是背面。
当我们在旋转的过程中,OpenGl分不清当前层面上需要显示正面还是反面,于是把本应该隐藏的反面绘制了出来就导致了上面的问题。

解决方法
画家算法

针对这个问题,第一个方案是画家算法,由远及近的绘制不同图层,近的图层就可以将远的图层的隐藏面覆盖掉,就像下图这样:


画家算法

但是画家算法也有不适用的地方,如下图:


三角形叠加情况
正背面剔除(Face Culling)

这是OpenGL中针对图形绘制的一种技巧,主要用于处理立体图形绘制时,只绘制观察者能看到的部分,看不到的部分就丢弃不绘制,因为看不见的部分不绘制,所以这种做法可以将渲染性能提高50%左右。

但是新的问题又来了,我们怎么知道哪个面是正面,哪个是背面呢?

其实在OpenGL中默认规定了逆时针方向绘制的三角形是正面,当然你可以改为顺时针为正面,但是一般不建议这么操作,主要是由于这个设置不仅仅是作用于你的项目,而是作用于OpenGL全局的。我们习惯OpenGL中默认的即可。


顺逆时针三角形

如上左图三角形绘制为顺时针所以此面为背面(默认),右三角形为逆时针绘制,所以此面为正面(默认)。
用于修改正面的函数

void glFrontFace(GLenum mode);
//model有两种:GL_CW(顺时针),GL_CCW(逆时针),
//OpenGL中的默认值:GL_CCW

在分析物体的正背面的时候OpenGl是根据三⻆形的顶点定义顺序和观察者⽅向共同决定的.随着观察者的⻆度⽅方向的改变,正⾯背⾯面也会跟着改变。

正背面剔除技巧主要涉及三个方法
1、开启正背面剔除

//开启表面剔除 (默认背面剔除)
void glEnable(GL_CULL_FACE);

2、关闭正背面剔除

//关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);

3、设置需要剔除的面

void glCullFace(GLenum mode);

GLenum mode 主要有3类:
GL_FRONT (剔除正面)
GL_BACK (剔除背面,是默认值)
GL_FRONT_AND_BACK (剔除正背面)

至此我们可以在RenderScene函数中加入剔除背面的代码:

glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);

但是为了测试方便,我们可以添加一个右键菜单通过点击菜单的选项来控制是否进行正背面剔除然后重新渲染,只需在main函数中注册有机菜单栏回调函数:

    glutCreateMenu(ProcessMenu);
    glutAddMenuEntry("Toggle depth test",1);
    glutAddMenuEntry("Toggle cull backface",2);
    glutAddMenuEntry("Set Fill Mode", 3);
    glutAddMenuEntry("Set Line Mode", 4);
    glutAddMenuEntry("Set Point Mode", 5);
    glutAttachMenu(GLUT_RIGHT_BUTTON);

通过mainloop监听右键菜单触发的消息,收到点击触发消息后,回调该函数对相应菜单进行的点击操作,主要逻辑为:

点击菜单右键---->选中剔除正反面按钮----->触发ProcessMenu函数

在ProcessMenu函数中:

//右键菜单栏选项
void ProcessMenu(int value)
{
    switch(value)
    {
        case 1:
            iDepth = !iDepth;
            break;
            
        case 2:
            iCull = !iCull;
            break;
            
        case 3:
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            break;
            
        case 4:
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            break;
            
        case 5:
            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
            break;
    }
    
    glutPostRedisplay();
}

iCull为我们定义的全局变量用来标记是否开启剔除背面测试,然后重新渲染,此时在
RenderScene函数中我们添加以下代码:

//开启/关闭正背面剔除功能
    if (iCull) {
        glEnable(GL_CULL_FACE);
        //以下两行是默认的,可以不写
        glFrontFace(GL_CCW);
        glCullFace(GL_BACK);
    }else
    {
        glDisable(GL_CULL_FACE);
    }

然后运行——>右键——>背面剔除这时我们的问题就解决了,如下图:


剔除背面的甜甜圈

当我们旋转的甜甜圈接近180度左右时我们又发现了新的问题,如下图:


缺口的甜甜圈

是被谁吃了一口嘛?嘿嘿当然不是,这个问题下一节再来讨论。

问题 总结

问题一:由于上面我们是使用的默认光源着色器绘制的,如果使用平面着色器绘制甜甜圈,会出现隐藏面消除吗?

会出现,由于都是蓝色,因此没有办法区别谁是正面,谁是背面,所以导致肉眼无法察觉,但是这个问题客观存在。

问题二:为什么使用默认光源着色器会出现隐藏面消除?

是因为在默光源着色器中,在光源无法照射的部分会呈现黑色,被照射部分呈现蓝色,可以非常直观的通过肉眼看出谁是正面,谁是反面

隐藏面消除方案 总结
  • 正背面消
    需要根据顶点数据顺序判断用户可见部分与隐藏面,隐藏面直接丢弃,不绘制,只绘制可见部分

  • 深度测试
    可以一次性解决隐藏面消除问题,原理是不管有多少图层,只显示可见图层,剩余不可见的都丢弃

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。