阅读 1104

OpenGL 渲染技巧:正背面剔除、深度测试

  • 甜甜圈案例

    • 案列分析

      这个项目和上一个博客的绘制金字塔、六边形的案例的绘制流程基本上是差不多的,无非就是上一个博客绘制了好几个图案可以监听空格键来切换每个图案的显示,,此案例少了空格键的监听,还有就是变化方式,上一个博客是通过变换模型矩阵来变换图形的,而此案列通过变换观察者矩阵来变换图形,绘制流程有疑问可以看博客juejin.cn/post/685041…
    • 运行结果

  • 问题面出现原因以及解决方案

    冲上面的运行结果会发现很明显在转动的过程中会出现黑色区域,出现的原因其实很简单,首先明确有光源照到的地方也就是阳面肯定是显示的是正常的颜色,没有光源照到的地方也就是阴面肯定是黑色,而在图形转动的过程中OpenGl不知道该显示阳面还是阴面,所以就导致出现红黑色交替的现象。解决方案:

    1. 油画家算法
      由远及近绘制盖住重叠的部分,但是这样也有问题就是凸显图形交叉的现象列如:
    2. 开启正背面剔除不绘制看不见的面(也就是阴面)
      首先正背面剔除性能方面就能节省50%,举例,一个正方体,最多只能看到单个面,使用正背面剔除就只绘制三个面另外三个看不见的面都不用绘制还一个就是OpenGL中默认规定了逆时针方向绘制的三角形是正面,当然你可以改为顺时针为正面,但是一般不建议这么操作,主要是由于这个设置不仅仅是作用于你的项目,而是作用于OpenGL全局的。
      正背面剔除相关方法
      • glEnable(GL_CULL_FACE);开启正背面剔除 (默认背面剔除)
      • glDisable(GL_CULL_FACE);关闭正背面
      • glFrontFace(GLenum mode);修改正面的函数GL_CW(顺时针),GL_CCW(逆时针),默认GL_CCW
      • glCullFace(GLenum mode);设置需要剔除的面GL_FRONT正面、GL_BACK反面、GL_FRONT_AND_BACK剔除正背面
  • 深度测试

    上述问题添加正背面提出之后,还是会有点小问题,再旋转看效果

    新问题又来了会发现甜甜圈会出现一个缺口,这个缺口出现的原因以及解决办法又是什么呢?

    • 原因
      因为开启了正背面剔除,所以就会留正面但是不渲染反面,但是当甜甜圈旋转到竖直方向的时候也就是出现了两个面重叠(同时正面/反面)的部分,openGL就不知道那个面改显示到前面那个面显示到后面,就导致了有缺口的问题
    • 解决方法(开启深度测试)
      • 深度测试的相关概念
        • 什么深度
          其实就是z值,就是物体距离摄像机的距离
          观察者可以放在坐标系的任意位置, 所以. 不能简单的说Z 数值越⼤或越⼩. 观察者就越靠近物体;
        • 深度缓冲区
          深度缓冲区存储在显存中,其实就是存储深度的,就是把距离观察者平⾯(近裁剪⾯)的深度值 与 窗⼝中每个像素点1对1进⾏关联以及存储;
          GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。
          深度缓存区的默认值为1.0. 表示最⼤的深度值. 深度值的范围是[0,1]之间.
        • 深度测试作用
          主要作用在于重叠区,通过z值得判断来确定那个面该丢弃,那个面要留下
      • 深度测试开启关闭以及相关参数解释
        • 清空深度缓冲区

          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        • 开启深度测试

          glEnable(GL_DEPTH_TEST);

        • 关闭深度测试

          glDisable(GL _ DEPTH _ TEST);

        • 深度测试的测试规则修改 glDepthFunc(GLenum func)函数来修改相关参数解释
          参数 说明
          GL_ALWAYS 总是通过测试
          GL_NEVER 总是不通过测试
          GL_LESS 在当前深度值 < 存储的深度值时通过
          GL_EQUAL 在当前深度值 = 存储的深度值时通过
          GL_LEQUAL 在当前深度值 <= 存储的深度值时通过
          GL_GREATER 在当前深度值 > 存储的深度值时通过
          GL_NOTEQUAL 在当前深度值 不等于 存储的深度值时通过
          GL_GEQUAL 在当前深度值 >= 存储的深度值时通过
      • 深度测试的潜在⻛险之 Z-fighting (Z冲突.闪烁)问题
        新问题又来了会有这种情况部分地方出现了闪烁的现象,这个原因又是什么呢
        • 出现的原因
          同一个位置出现了多个图层,然而每个图层的位置又非常接近(图层之间的间距值查出来深度缓冲区存储的最大精度),此时openGL无法区分图层的先后所以就造成了闪烁的问题

        • 解决方法

          1. 启用多边形偏移: 让深度值之间产⽣间隔.如果2个图形之间有间隔,是不是意味着就不会产⽣⼲涉.可以理解为在执⾏深度测试前将⽴⽅体的深度值做⼀些细微的增加.于是就能将重叠的2个图形深度值之前有所区分.
           //启⽤Polygon Offset ⽅式 
           glEnable(GL_POLYGON_OFFSET_FILL)
           参数列表:
           GL_POLYGON_OFFSET_POINT 对应模式: GL_POINT
           GL_POLYGON_OFFSET_LINE  对应模式: GL_LINE
           GL_POLYGON_OFFSET_FILL  对应模式: GL_FILL
          复制代码
          1. 指定偏移量
            • 通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units
            • 每个Fragment 的深度值都会增加如下所示的偏移量:
              Offset = ( m * factor ) + ( r * units);
              m : 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0.
              r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的 ⼀个常量.
            • ⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉 近
            • ⼀般⽽⾔,只需要将-1 和-1 这样简单赋值给glPolygonOffset 基本可以满⾜需求.

          最后记得用完就关闭多边形偏移glDisable(GL_POLYGON_OFFSET_FILL)

      • 如何预防ZFighting闪烁问题
        1. 不要将两个物体靠的太近,避免渲染时三⻆形叠在⼀起。这种⽅式要求对场景中物体插⼊⼀个少量的偏移,那么就可能避免ZFighting现象。例如上⾯的⽴⽅体和平⾯问题中,将平⾯下移0.001f就可以解决这个问题。当然⼿动去插⼊这个⼩的偏移是要付出代价的。
        2. 尽可能将近裁剪⾯设置得离观察者远⼀些。上⾯我们看到,在近裁剪平⾯附近,深度的精确度是很⾼的,因此尽可能让近裁剪⾯远⼀些的话,会使整个裁剪范围内的精确度变⾼⼀些。但是这种⽅式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪⾯参数。
        3. 使⽤更⾼位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有⼀些硬件使⽤使⽤32/64位的缓 冲区,使精确度得到提⾼
  • 完整代码

    #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类来设置透视投影
    GLFrustum           viewFrustum;
    GLTriangleBatch     torusBatch;
    GLMatrixStack       modelViewMatix;
    GLMatrixStack       projectionMatrix;
    GLGeometryTransform transformPipeline;
    GLShaderManager     shaderManager;
    
    //标记:背面剔除、深度测试
    int iCull = 0;
    int iDepth = 0;
    
    //右键菜单栏选项
    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();
    }
    
    
    // 召唤场景
    void RenderScene(void)
    {
        //清除窗口和深度缓冲区
        //可以给学员演示一下不清空颜色/深度缓冲区时.渲染会造成什么问题. 残留数据
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        //根据设置iCull标记来判断是否开启背面剔除
        if(iCull)
        {
            glEnable(GL_CULL_FACE);
            glFrontFace(GL_CCW);
            glCullFace(GL_BACK);
        }
        else
            glDisable(GL_CULL_FACE);
        
        //根据设置iDepth标记来判断是否开启深度测试
        if(iDepth)
            glEnable(GL_DEPTH_TEST);
        else
            glDisable(GL_DEPTH_TEST);
        
        //把摄像机矩阵压入模型矩阵中
        modelViewMatix.PushMatrix(viewFrame);
        
        GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
        
        //使用平面着色器
        //参数1:平面着色器
        //参数2:模型视图投影矩阵
        //参数3:颜色
        //shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
        
        //使用默认光源着色器
        //通过光源、阴影效果跟提现立体效果
        //参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
        //参数2:模型视图矩阵
        //参数3:投影矩阵
        //参数4:基本颜色值
        shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
        
        //绘制
        torusBatch.Draw();
    
        //出栈
        modelViewMatix.PopMatrix();
        
        
        glutSwapBuffers();
    }
    
    
    void SetupRC()
    {
        // 设置背景颜色
        glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
        
        //初始化着色器管理器
        shaderManager.InitializeStockShaders();
        
        //将相机向后移动7个单元:肉眼到物体之间的距离
        viewFrame.MoveForward(7.0);
        
        //创建一个甜甜圈
        //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);
    }
    
    //键位设置,通过不同的键位对其进行设置
    //控制Camera的移动,从而改变视口
    void SpecialKeys(int key, int x, int y)
    {
        if(key == GLUT_KEY_UP)
            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);
        
        //重新刷新window
        glutPostRedisplay();
    }
    
    
    void ChangeSize(int w, int h)
    {
        //1.防止h变为0
        if(h == 0)
            h = 1;
        
        //2.设置视口窗口尺寸
        glViewport(0, 0, w, h);
        
        //3.setPerspective函数的参数是一个从顶点方向看去的视场角度(用角度值表示)
        // 设置透视模式,初始化其透视矩阵
        viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
        
        //4.把透视矩阵加载到透视矩阵对阵中
        projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
        
        //5.初始化渲染管线
        transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
    }
    
    
    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("Geometry Test Program");
        glutReshapeFunc(ChangeSize);
        glutSpecialFunc(SpecialKeys);
        glutDisplayFunc(RenderScene);
        
        // Create the Menu
        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);
        
        
        GLenum err = glewInit();
        if (GLEW_OK != err) {
            fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
            return 1;
        }
        
        SetupRC();
        
        glutMainLoop();
        return 0;
    }
    
    
    复制代码
文章分类
阅读
文章标签