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度左右时我们又发现了新的问题,如下图:


缺口的甜甜圈

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

问题 总结

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

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

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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358