OpenGL-09-绘制甜甜圈及正背面剔除

我们通过现成API实现一个甜甜圈的图案,并且使用默认光源着色器进行存储,通过转动观察者视角,但物体本身不动的方式来看一下绘制的图案所出现的bug

惯例,放一下流程图(代码实现直接放在文章末尾):


image.png

效果图:


image.gif

黑色部分是什么?如何解决?

黑色部分其实是隐藏面

1、油画算法

油画算法,我们之前的文章介绍过:先绘制场景中的离观察者较远的物体,再绘制较近的物体。

那么,这种⽅法在计算机图形处理中是⾮常低效的。
1、我们必须对任何发⽣⼏何图形重叠的地⽅每个像素进⾏2 次写操作,⽽在存储其中进⾏写操作会使速度变慢
2、对独⽴三⻆形进⾏排序的开销过⾼
3、油画算法 有瓶颈期. ⽐如绘制图像交叠时, 没有明确的先后顺序就⽆从下⼿绘制了
如果有多个三⻆形叠加在一起的情况,油画算法将⽆法处理.

2、正背面剔除法(Face Culling)

背景:
首先,我们观察一个3D物体,不管从任何⼀个⽅向去观察,最多可以看到⼏个⾯?
答案是:最多3⾯。从⼀个⽴⽅体的任意位置和⽅向上看,你不可能看到多于3个⾯.。
那么, 我们为何要多余的去绘制那根本看不到的3个⾯?
如果我们能以某种⽅式去丢弃这部分数据,OpenGL 在渲染的性能即可提⾼超过50%.

  • 如何知道某个⾯在观察者的视野中不会出现?
    任何平⾯都有2个⾯,正⾯/背⾯.意味着你⼀个时刻只能看到⼀⾯。

  • OpenGL 可以做到检查所有正⾯朝向观察者的⾯,并渲染它们.从⽽丢弃背⾯朝向的⾯. 这样可以节约⽚元着⾊器的性能。

  • 那如何告诉OpenGL,我们绘制的图形 哪个是正面哪个是反面?
    通过分析顶点数据的顺序就能知道(正面:逆时针 反面:顺时针)

  • 对于正⾯、背⾯三⻆形进⾏区分的原因之⼀,就是为了进⾏剔除。

因此,我们可以给平⾯定义正⾯和背⾯,OpenGL可以做到检查所有正⾯朝向观察者的⾯,并渲染它们,从⽽丢弃 背⾯朝向的⾯。OpenGL渲染的性能即可提⾼超过50%

原理:
我们不去绘制那些根本看不到的⾯,以某种⽅式去丢弃这部分数据

API:

    //开启
    glEnable(GL_CULL_FACE);
    //关闭
    glDisable(GL_CULL_FACE);
    //设置哪一面为正面, GL_CW 、GL_CCW ,默认GL_CCW
    glFrontFace(GL_CCW);
    //设置剔除哪一面, GL_FRONT 、 GL_BACK 、GL_FRONT_AND_BACK ,默认GL_BACK
    glCullFace(GL_BACK);
    
    /*
     思考:默认剔除背面,那么我们想剔除正面该怎么做呢?
     第一种:
     glCullFace(GL_BACK);
     glFrontFace(GL_CW);
     第二种:
     glCullFace(GL_FRONT);
     */

那么通过正背面剔除的技巧,把不显示的背面剔除之后,我们来看一下效果图:


image.gif

新的问题又来了:再转动过程中,会发现有的地方感觉像是被吃了一口似的。是什么原因呢?又如何解决呢?

原因:其实是两个正面叠加在一起,出现了混合, 此时OpenGL不能清楚分辨 哪个图层在前 哪个图层在后,于是就会出现甜甜圈像被啃⼀⼝的现象

深度测试

深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的.颜⾊缓存区存储像素的颜⾊信
息,⽽深度缓冲区存储像素的深度信息. 在决定是否绘制⼀个物体表⾯时, ⾸先要将表⾯对应的像
素的深度值与当前深度缓冲区中的值进⾏⽐较. 如果⼤于深度缓冲区中的值,则丢弃这部分.否则
利⽤这个像素对应的深度值和颜⾊值.分别更新深度缓冲区和颜⾊缓存区. 这个过程称为”深度测
试”

API:

    //开启
    glEnable(GL_DEPTH_TEST);
    //关闭
    glDisable(GL_DEPTH_TEST);

最后我们再看下打开了深度测试的效果:


image.gif

哇,完美解决了!

思考与拓展

我们使用了默认光源着色器,出现了隐藏面。那么如果使用平面着色器,还会出现隐藏面吗?

  • 答案是:会出现,由于都是红色的,没有办法区别谁是正面,谁是背面,所以导致肉眼无法察觉。而在光源下,被照射的部分呈现红色,无法照射的部分会呈现黑色,可以非常直观的通过肉眼看出谁是正面,谁是反面。

上面的动图可以看出,右键菜单栏有改变状态的选项,我们先看下一效果图:


image
    //修改填充方式
    //充满
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    //线
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    //点
    glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);

修改填充方式之后,也会出现隐藏面消除的问题,只是肉眼观察不到。

源码来啦!

#include "GLTools.h"
//矩阵
#include "GLMatrixStack.h"
//位置
#include "GLFrame.h"
//投影方式
#include "GLFrustum.h"
//变换管道,快速传输矩阵
#include "GLGeometryTransform.h"
#include <math.h>

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

//观察者视角
GLFrame viewFrame;
//投影方式
GLFrustum viewFrustum;
//容器类
GLTriangleBatch torusBatch;
//模型视图矩阵
GLMatrixStack modelViewMatrix;
//投影矩阵
GLMatrixStack projectionMatrix;

GLGeometryTransform transformPipeline;
GLShaderManager shaderManager;

//记录正背面剔除
int iCull = 0;
//记录深度测试
int iDepth = 0;



//窗口变化时候的回调函数
void changeSize(int w,int h)
{
    //防止宽高比中除数为0的bug
    if(h==0) h=1;
    
    //1、创建窗口
    glViewport(0, 0, w, h);
    //2、创建投影矩阵->透视投影(角度、宽高比、最小距离、最大距离)
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 500.0f);
    //3、拿到这个投影矩阵,把它加载到我们声明的透视矩阵
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    //4、在我们的视图模型矩阵 顶部载入单元矩阵,相当于初始化。(**注意**  这个默认是有的。可以不写 )
    modelViewMatrix.LoadIdentity();
    //5、设置变换管道
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    
}

//渲染场景
void RenderScene()
{
    //1、清除缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //2、判断是否开启正背面剔除
    if (iCull) {
        glEnable(GL_CULL_FACE);
        //扩展
        //设置哪一面为正面, GL_CW 、GL_CCW ,默认GL_CCW
        glFrontFace(GL_CCW);
        //设置剔除哪一面, GL_FRONT 、 GL_BACK 、GL_FRONT_AND_BACK ,默认GL_BACK
        glCullFace(GL_BACK);
        
        /*
         默认剔除背面,那么我们想剔除正面该怎么做呢?
         第一种:
         glCullFace(GL_BACK);
         glFrontFace(GL_CW);
         第二种:
         glCullFace(GL_FRONT);
         */
        
    }else{
        glDisable(GL_CULL_FACE);
    }
    
    //3、是否开启深度测试
    if(iDepth){
        glEnable(GL_DEPTH_TEST);
    }else{
        glDisable(GL_DEPTH_TEST);
    }
    
    
    //4、压栈
    modelViewMatrix.PushMatrix(viewFrame);
    
    //5、使用默认光源着色器
    GLfloat vColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vColor);
    
    //6、绘制
    torusBatch.Draw();
    
    //7、出栈
    modelViewMatrix.PopMatrix();
    
    //8、提交缓冲区
    glutSwapBuffers();
}


void SetupRC(){
    
    //1、背景色
    glClearColor(0.1, 0.1, 0.1, 1.0);
    //2、初始化着色器
    shaderManager.InitializeStockShaders();
    //3、设置观察者位置
    viewFrame.MoveForward(10.0f);
    //4、绘制甜甜圈
    /*
     gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)
     1、GLTriangleBatch容器类
     2、外边缘半径
     3、内边缘半径
     4、主半径细分单元数量
     5、从半径细分单元数量
     */
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 60, 30);
    
    //切换点状时点太小,这里提前设置
    glPointSize(5.0f);
}
//控制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();
}


void RightButtonMenu(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();
    
}
int main(int argc, char* argv[])
{
    //基本固定的初始化
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Laps");
    //函数回调
    glutReshapeFunc(changeSize);
    glutDisplayFunc(RenderScene);
    glutSpecialFunc(SpecialKeys);
    //**新方法:右键打开列表函数
    glutCreateMenu(RightButtonMenu);
    
    glutAddMenuEntry("深度测试",1);
    glutAddMenuEntry("正背面剔除",2);
    glutAddMenuEntry("充满状", 3);
    glutAddMenuEntry("线状", 4);
    glutAddMenuEntry("点状", 5);
    
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    //初始化图案信息
    SetupRC();
    
    glutMainLoop();
    return 0;
}

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