美文网首页
OpenGL的渲染技巧--正背面剔除

OpenGL的渲染技巧--正背面剔除

作者: SK_Wang | 来源:发表于2020-07-20 10:07 被阅读0次

    今天通过一个小案例,来讲解OpenGL的正背面剔除以及深度测试的应用。

    圆环的绘制

    我们使用GLTools中的void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)方法,简单实现一下圆环的绘制。

    代码实现

    //
    //  main.cpp
    //  OpenGLDemo
    //
    //  Created by SK on 2020/7/3.
    //  Copyright © 2020 SK. All rights reserved.
    //
    
    #include "GLShaderManager.h"
    #include "GLTools.h"
    #include <GLUT/GLUT.h>
    #include "GLMatrixStack.h"
    #include "GLFrame.h"
    #include "GLFrustum.h"
    #include "GLGeometryTransform.h"
    
    
    //定义一个,着色管理器
    GLShaderManager shaderManager;
    GLFrame viewFrame;
    GLFrustum viewFrustum;
    GLTriangleBatch torusBatch;
    GLMatrixStack modelViewMatix;
    GLMatrixStack projectionMatrix;
    GLGeometryTransform transformPipeline;
    
    void changeSize(int w, int h) {
        glViewport(0, 0, w, h);
        viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
        projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
        transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
    }
    
    void renderScene(void) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
        modelViewMatix.PushMatrix(viewFrame);
        shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
        torusBatch.Draw();
        modelViewMatix.PopMatrix();
        glutSwapBuffers();
    }
    
    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);
        
        glutPostRedisplay();
    }
    
    void setupRC() {
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        shaderManager.InitializeStockShaders();
        
        viewFrame.MoveForward(7.0);
        gltMakeTorus(torusBatch, 1.0f, 0.3f, 60, 30);
        glPointSize(4.0f);
    }
    
    int main(int argc, char *argv[]) {
        gltSetWorkingDirectory(argv[0]);
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
        glutInitWindowSize(500, 500);
        glutCreateWindow("OpenGL Demo");
        glutReshapeFunc(changeSize);
        glutDisplayFunc(renderScene);
        glutSpecialFunc(specialKeys);
        
        GLenum err = glewInit();
        if (err != GLEW_OK) {
            fprintf(stderr, "glew error: %s\n", glewGetErrorString(err));
            return 1;
        }
        setupRC();
        glutMainLoop();
        return 0;
    }
    
    

    实现的效果如下图所示:


    image.png

    看起来是一个很漂亮的圆环,当我们缓慢的转到圆环,我们会看到下面的情况:


    2020-07-17 14-45-39.2020-07-17 14_51_33.gif

    No,为什么会有这些黑色的出行?为什么开始显示的好好的,一转动就出现了这样的情况?

    其实在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,哪些部分是对观察者不可见的。对于不可见的部分,应该及早丢弃。例如在一个不透明的墙壁后,就不应该渲染。这种情况叫做隐藏面消除(Hidden surface elimination)

    消除隐藏面的解决方案

    油画算法

    先绘制场景中的离观察者较远的物体,在绘制较近的物体。
    例如下面的图例:先绘制红色部分,在绘制黄色部门,最后绘制灰色部门,即可解决隐藏面消除的问题。


    image.png

    油画算法的弊端

    如果场景里的物体出现了交叉叠加的情况,油画算法将无法处理。

    正背面剔除(Face Culling)

    当我们从任意一个方向去观察一个3D图形,最多可以看到几个面?答案是最多3个面,从一个立方体的任意位置和方向上看,不可能看到多余3个面,那么我们在绘制的过程中,为何要多余的绘制那些看不到的面呢?如果我们能以某种方式去丢弃这部分数据,OpenGL在渲染的性能上可以提高50%。

    OpenGL可以做到检查所有正面朝向观察者的面,并渲染它们,从而丢弃背面朝向的面。这样可以节约片元着色器的性能。

    正面和背面是有三角形的顶点定义顺序和观察者⽅向共同决定的。随着观察者的⻆度方向的改变,正面背面也会跟着改变。

    在OpenGL中开启正背面剔除的相关方法如下:

    //开启表面剔除(默认背面剔除)
    void glEnable(GL_CULL_FACE);
    //关闭表面剔除(默认背面剔除)
    void glDisable(GL_CULL_FACE);
    
    //用户选择剔除那个面(正面/背面)
    //mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
    void glCullFace(GLenum mode);
    
    //mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK 用户指定绕序那个为正面
    void glFrontFace(GLenum mode);
    
    //剔除正面实现例1
    glCullFace(GL_BACK);
    glFrontFace(GL_CW);
    
    //剔除正⾯实现例2
    glCullFace(GL_FRONT);
    

    我们开启正背面剔除功能,来看一下实现效果:

    2020-07-17 16-25-01.2020-07-17 16_25_33.gif
    开启了正背面剔除功能,我们已经基本的达到了预期的展示效果,但是当我们继续旋转时发现,出现了新的问题,如何解决这样的问题呢?这就引出了一个新的概念 深度测试

    深度测试

    那么什么是深度?
    深度 其实就是该像素点在3D世界中距离摄像机的距离,也就是Z值。
    深度缓存区就是一块内存区域,专门存储着每个像素点(绘制在屏幕上的)深度值。深度值(Z值)越⼤, 则离摄像机就越远。

    在不使⽤深度测试的时候,如果我们先绘制⼀个距离比较近的物体,再绘制距离较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。有了深度缓冲区后,绘制物体的顺序就不那么􏰁要了。实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写入到缓冲区中。除⾮调⽤ glDepthMask(GL_FALSE)来禁⽌写⼊。

    相关文章

      网友评论

          本文标题:OpenGL的渲染技巧--正背面剔除

          本文链接:https://www.haomeiwen.com/subject/eolekktx.html