OpenGL从入门到放弃(3)—— 渲染问题以及解决方法

527 阅读5分钟

一.  案例分享,先来一个甜甜圈,并且用方向键控制甜甜圈的旋转

1.1 甜甜圈的绘制以及注意点

1.  openGLInit进行基本库的初始化,以及回调函数注册

    //注册重塑函数
    glutReshapeFunc(changeSize);
    //注册显示函数
    glutDisplayFunc(renderScene);
    //注册特殊函数
    glutSpecialFunc(specialKeys);

2. 渲染环境设置setupRC,使用固定着色器进行渲染

- 1. 需要注意的是,我们需要把相机移动一定的像素位置,即肉眼到渲染物体的距离感受

- 2. 使用 gltMakeTorus进行甜甜圈绘制

//1. 设置清屏颜色(背景颜色)
        glClearColor(0.3f, 0.4f, 0.5f, 1);
        
        // 2.  初始化着色管理器
        //使用固定管线渲染,固定着色器
        shaderManager.InitializeStockShaders();
        
        // 3. 将相机后移 7 个单元:肉眼到物体之间的距离
        viewFrame.MoveForward(7.0);
        
        // 4. 创建甜甜圈,通过gltMakeTorus创建
        /*
         参数1: GLTriangleBatch 容器帮助类
         参数2:外边缘半径
         参数3:内边缘半径的
         参数4:主半径的细分单元数量
         参数5:从半径的细分单元数量
         */
        gltMakeTorus(torusBatch, 1, 0.3, 100, 40);
        
        // 5.点的大小,为了当我们采用点填充方式时,可以观察到
        glPointSize(5.0);


3. 在renderScene中进行绘制时,需要进行入栈出栈,以及这里使用默认的光源着色器,来让甜甜圈看起来比较形象

// 2. 把摄像机押入模型矩阵
    modelViewMatrix.PushMatrix(viewFrame);
    
    // 3. 设置画笔颜色
    GLfloat vRed[] = {148/255.0, 117/255.0, 56/255.0, 1.0};
    
    // 4. 这里使用默认光源着色器,来让甜甜圈看的更有食欲
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    // 5. 提交绘制
    torusBatch.Draw();
    
    // 6. 用完即仍,模型矩阵出栈
    modelViewMatrix.PopMatrix();


4. changeSize回调中,我们需要设置透视模式,以及加载透视模式,并且初始化渲染管线

void changeSize(int w, int h) {
    if (h == 0) h = 1;
    
    // 1. 窗口尺寸
    glViewport(0, 0, w, h);
    
    // 2. 设置透视模式,初始化他的透视矩阵
    viewFrustum.SetPerspective(35, w*1.0/h, 1, 100);
    
    // 3. 将透视矩阵加载到透视投影中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 4. 初始化渲染管线
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

5. 使用方向键控制旋转时,使用RotateWorld调整观察者位置,形成旋转现象

// 按键:上,调整观察者位置
    if (key == GLUT_KEY_UP) {
        viewFrame.RotateWorld(m3dDegToRad(-5), 1, 0, 0);
    }



1.2 甜甜圈的控制,以及渲染问题

1. 编译之后,先看一下完整图,绘制的很棒,没有问题


2. 使用方向键控制左右或者上下旋转


3. 发现问题,出现了很多黑色部分,那么这些黑色部分是如何产生的

   3.1 在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的(阳面),而哪些是不可见的(阴面),对于不可见的部分应用及时丢弃掉,例如:在一个不透明的物体背后,就不应该渲染,这种叫做“隐藏面消除”

   3.2 在旋转的过程中,因为系统并不知道甜甜圈的哪些面是阳面,哪些是阴面,所以导致了我们看到了被丢弃的未渲染部分,也就是黑色部分



二.解决隐藏面消除的方法

1.油画算法:

1.1 先绘制场景中离观察者相对较远的位置的物体,再绘制较近的物体,即可解决隐藏面消除问题


1.2 油画算法存在的问题:当物体互相叠加,层次没法比较的时候,就无法解决,类似以下这种,针对这种情况,我们可以使用正背面剔除或者深度测试来解决



2.正背面剔除(Face Culling):

1. 一个正方体从任意一个角度观察过去,最多可以观察到3个面,所以我们只需要绘制能被观察到的三个面即可,能大幅度提升性能

2. 如果才能知道哪些面是可以被观察到的,通过正背面来区分一个物体的两面,能被观察的则为正面,反之则为背面

3. 打开正背面剔除:glEnable(GL_CULL_FACE);

4. 关闭:glDisable(GL_CULL_FACE);

5. 测试,openGLinit中,注册一个菜单响应的方法,并且创建N个按钮,用来控制是否打开正背面剔除,在renderScene方法中,渲染时判断是否打开

关键代码:

// renderScene方法    
// 正背面剔除
    if (iCull) {
        glEnable(GL_CULL_FACE);
        glFrontFace(GL_CCW);
        glCullFace(GL_BACK);
    }else
    {
        glDisable(GL_CULL_FACE);
    }
    
    // 深度测试
    if(iDepth) {
        glEnable(GL_DEPTH_TEST);
    } else {
        




// openGLInit方法
glutCreateMenu(menuClicked);
glutAddMenuEntry("深度测试",1);
glutAddMenuEntry("正背面剔除",2);
glutAttachMenu(GLUT_RIGHT_BUTTON);



3.深度测试:

通过刚才的正背面剔除,我们发现,甜甜圈的黑色面的确是被消除了,但是在我们旋转的过程中,发现缺失了一块


3.1 原理

  • 深度缓冲区和颜色缓冲区是对应的,其中颜色缓冲区存储像素的颜色信息,而深度缓冲区则存放具体的深度信息(深度:像素点在3D世界中距离观察者的距离,z值)
  • 在进行绘制之前,需要将表面对应的像素的深度值和当前深度缓冲区中存放的深度值作对比,如果大于,则丢弃这部分,反之,就更新深度缓冲区和颜色缓冲区,
  • 这个过程叫做深度测试。(解释起来就是,缓冲区只保留离观察者摄像机更近的值)

3.2 执行:和刚刚正背面剔除一样创建相应菜单,并且在renderScene中进行逻辑判断

  • 开启深度测试:glEnable(GL_DEPTH_TEST);
  • 关闭深度测试:glDisable(GL_DEPTH_TEST);


3.3  潜藏的问题,深度测试受存储的值的精度影响,当精度无法满足需求时,就会出现显示异常的现象,即ZFighting现象,这种情况通常很少出现,因为现在的机器的存储精度足够的高了,当然也可以使用glDisable(GL_POLYGON_OFFSET_FILL), 在需要的时候进行解决