美文网首页
OpenGL--如何使用正背面剔除解决问题

OpenGL--如何使用正背面剔除解决问题

作者: HardCabbage | 来源:发表于2020-07-11 17:39 被阅读0次

在渲染过程中可能产生的问题

  • 在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者是哪些部分是对观察者不可见的,对于不可见的部分,我们应该及早的丢弃。例如在一个不透明的墙壁后面,就不应该渲染,这种情况叫做隐藏面消除 甜甜圈的背面效果
解决办法隐藏面消除的办法:油画算法

油画算法
那么什么是油画算法呢?所谓的油画算法就是画家在画画的过程中,一般是先绘制场景中离观察者较远的物体,再绘制较近的物体。
例如下面的图例,先绘制红色的部分,再绘制黄色的部分,最后再绘制灰色的部分,即可解决隐藏消除的问题。
但是油画算法也有一定的弊端,比如从上面我的已经知道了使用油画算法,只要将场景按照物理距离观察者的距离由远及近排序即可,那么使用油画算法会出现什么样的问题呢?例子:如果三个三角形是叠加的效果,优化算法将无法处理。

三个三角形叠加效果
从上面的问题中,我们又有一种解决方案,那就是使用正背面剔除(Face Culling)
  • 我们知道一个3d的图形,无论你从任何一个方向去观察,最多可看到不可能多余3个面,如果我们能以某种方式丢弃这部分的数据,OpenGL在渲染的性能即可提高超过50%。接下来面临着新的问题:如何知道某个面在观察者的视野中不会出现呢?

如何知道某个面在观察者的视野中不会出现呢

  • 任何平面都是2个面,正面和背面。当某一个时刻观察时你只能看到一个面。
  • OpenGL为我们提供了方法,能够直接检查所有的正面朝向观察者面,并渲染它们,从而丢弃背面朝向的面,这样可以节约片元着色器的性能。
OpenGL是如何知道哪个是正面哪个是背面的呢?
  • OpenGL通过分析顶点数据的顺序


    顺时针(Clockwise)1->2->3
    逆时针(Counter-clockwise)1->3->2
如何判断正面和背面
  • 正面:按照逆时针顶点连接顺序的三角形面。
  • 背面:按照顺时针顶点连接顺序的三角形面。
正面和背面判断相关的案例:分析立方体中的正背面
分析立方体正背面:左侧三⻆形顶点顺序为: 1—> 2—> 3 ; 右侧三⻆形的顶点顺序为: 1—> 2—> 3
  • 当观察者在右侧时,则右边的三⻆形⽅向为逆时针⽅向则为正⾯,⽽左侧的三⻆形为顺时针则为⾯
  • 当观察者在左侧时,则左边的三⻆形为逆时针⽅向判定为正⾯,⽽右侧的三⻆形为顺时针判定为背⾯
    总结:正面和背面是有三角形的顶点定义顺序和观察者方向共同决定的,随着观察者角度方向的改变,正背面也会跟着改变。

正背面剔除功能的代码示例

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

如何绘制一个甜甜圈,并使用正背面剔除功能

一、 窗口改变方法的实现过程核心:
  • 1,设置透视模式,初始化其透视矩阵viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
  • 2, 把透视矩阵加载到透视矩阵对阵中projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
  • 3,初始化渲染管线
    transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
二、键位设置,通过不同的键位对其进行设置,控制camera的移动,从而改变视口,注意:观察者移动,物体不动,在void SpecialKeys(int key, int x, int y)方法中,
  • 1,我们需要知道如何根据方向调整观察者的位置,这里以上键为例 viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
  • 2,每次移动位置都需要重新渲染刷新,使用glutPostRedisplay();方法。
三、甜甜圈相关设置
  • 1,使用glClearColor()设置背景颜色
  • 2,初始化着色器管理器shaderManager.InitializeStockShaders();
  • 3, 使用MoveForward(7.0);设置肉眼到物体之间的距离,将相机向后移动7个单元。
  • 4, 创建一个甜甜圈的方法:
  • void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);,该方法中
  • GLTriangleBatch& torusBatch为容器帮助类;
  • majorRadius外边缘半径;
  • minorRadius内边缘半径;
  • numMajor主半径细分单元数量;
  • numMinor从半径的细分单元数量。
  • 5,设置点的大小(方便点填充时,便于肉眼观察)glPointSize(4.0f);
四、渲染场景
  • 清除窗口和深度缓冲区
  • 开启/关闭正背面剔除功能glEnable(GL_CULL_FACE); glFrontFace(GL_CCW); glCullFace(GL_BACK);
  • 把摄像机矩阵压入模型矩阵中modelViewMatix.PushMatrix(viewFrame);注意栈做什么用?记录当前的位置等状态,保存临时结果,回退就出栈
  • 设置绘图颜色
  • 使用默认光源着色器GLT_SHADER_DEFAULT_LIGHT
  • 开始使用Draw方法进行绘制
  • 绘制完成,将压栈的状态进行pop操作,绘制完成恢复
  • 使用glutSwapBuffers();交换缓存区
使用了正背面剔除和未使用正背面剔除的效果比较
  • 未使用正背面剔除效果图


    未使用正背面剔除.gif
  • 使用了正背面剔除效果图


    正背面剔除.gif

以下是详细代码

//演示了OpenGL背面剔除,深度测试,和多边形模式
#include "GLTools.h"    
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"

#include <math.h>
#ifdef ____
#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 RenderScene()
{
    //1.清除窗口和深度缓冲区
    //可以给学员演示一下不清空颜色/深度缓冲区时.渲染会造成什么问题. 残留数据
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //开启/关闭正背面剔除功能
    if (iCull) {
        glEnable(GL_CULL_FACE);
        glFrontFace(GL_CCW);
        glCullFace(GL_BACK);
    }else
    {
        glDisable(GL_CULL_FACE);
    }
    //压栈:栈做什么用?记录当前的位置等状态,保存临时结果,回退就出栈
    
    //2.把摄像机矩阵压入模型矩阵中
    modelViewMatix.PushMatrix(viewFrame);
    
    //3.设置绘图颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    
    //4.
    //使用平面着色器
    //参数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);
    
    //5.绘制
    torusBatch.Draw();

    //6.出栈 绘制完成恢复
    modelViewMatix.PopMatrix();
    
    //7.交换缓存区
    glutSwapBuffers();
}

void SetupRC()
{
    //1.设置背景颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
    
    //2.初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    //3.将相机向后移动7个单元:肉眼到物体之间的距离
    viewFrame.MoveForward(7.0);
    
    //4.创建一个甜甜圈
    //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);
    
    //5.点的大小(方便点填充时,肉眼观察)
    glPointSize(4.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 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);
}

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();
}


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;
}

相关文章

  • OpenGL--深度测试、多边形偏移以及颜色混合原理解析

    一、深度测试 1.1 深度测试解决什么问题 由前一篇OpenGL--如何使用正背面剔除解决问题博客,我们知道,我们...

  • OpenGL--如何使用正背面剔除解决问题

    在渲染过程中可能产生的问题 在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者是哪些部分是对观察者不...

  • OpenGL--正背面剔除

    1. 背景 在绘制3D场景时,为了尽可能的逼真,需要有远小近大的效果,而且可能会出现互相遮盖的情况。我们需要决定哪...

  • OpenGL 渲染技巧之深度测试

    在上篇的 正背面剔除的文章中我们使用正背面剔除的的方式解决了甜甜圈正背面显示错乱的问题,但是呢同时引出了新的问题,...

  • OpenGL 基础渲染(深度测试)

    OpenGL 基础渲染(正背面剔除) - 简书 在上文案例使用了正背面剔除后,出现了新的问题。如下图所示,那我们要...

  • OpenGL深度测试

    在上一篇OpenGL正背面剔除中提到了正背面剔除的弊端:如果前后两个点都是正面或是背面,这时OpenGL无法区分哪...

  • OpenGL正背面剔除

    一、在渲染过程中可能产生的问题 1. 原因 在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部...

  • OpenGL 正背面剔除

    在渲染过程中可能存在的问题 在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部分是对观察者不可...

  • OpenGL: 正背面剔除

    案例分析 1.用平面着色器绘制一个环并旋转, 效果如下: 一切看似没有问题. 2.再用光源着色器绘制一个立体环并旋...

  • OpenGL正背面剔除

    1.在渲染过程中可能产生的问题 在绘制3D场景的时候,我们需要决定哪些部分是对观察者 可⻅见的,或者哪些部分是对观...

网友评论

      本文标题:OpenGL--如何使用正背面剔除解决问题

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