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

4-1.OpenGL渲染技巧-正背面剔除

作者: Pufus | 来源:发表于2020-09-27 20:43 被阅读0次

绘制一个3D图形——甜甜圈

相关代码

#include <stdio.h>
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"
#include <GLUT/GLUT.h>


GLFrame viewFrame; // 物体
GLFrame cameraFrame; // 观察者
GLFrustum viewFrustum; // 管擦着投影矩阵
GLTriangleBatch torusBatch; // 甜甜圈批次类
GLMatrixStack modelViewMatrix; // 模型视图矩阵
GLMatrixStack projectionMatrix; // 投影矩阵
GLGeometryTransform transformPipeline; // 集合变换管道
GLShaderManager shaderManager; // 着色器管理类


GLfloat vRed[] = { 0.1f, 0.3f, 0.8f, 1.0f };


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 setupRC() {
    // 设置背景颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
    
    // 初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    // 从初始化渲染管线
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    
    // 将相机向后移动7个单元:肉眼到物体之间的距离
    viewFrame.MoveForward(10);
    
    // 创建一个甜甜圈
    // 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);
}

void renderScene() {
    //1.清除窗口和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //2.把摄像机矩阵压入模型矩阵中
    modelViewMatix.PushMatrix(viewFrame);
    //3.设置绘图颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    //4.使用默认光源着色器
    //通过光源、阴影效果跟提现立体效果
    //参数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 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();
}

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("OpenGL渲染方式");
   
    glutReshapeFunc(changeSize);
    glutDisplayFunc(renderScene);
    glutSpecialFunc(specialKeys);
    
    GLenum status = glewInit();
    if (status != GLEW_OK) {
        printf("error: %s\n", glewGetString(status));
        return 1;
    }
    
    setupRC();
    
    glutMainLoop();
    return 0;
}

代码解析

  • main函数:程序入口
  • ChangeSize函数:主要是设置视口及投影方式
  • SetupRC函数:图形数据配置,主要是顶点数据及图元连接方式
  • RenderScene函数:主要用于图形的绘制,可以系统触发,也可以开发者手动触发
  • SpecialKeys函数:对特殊键位的回调处理

main函数

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("OpenGL渲染方式");
    //注册响应的回调函数。
    glutReshapeFunc(changeSize);
    glutDisplayFunc(renderScene);
    glutSpecialFunc(specialKeys);
    
    GLenum status = glewInit();
    if (status != GLEW_OK) {
        printf("error: %s\n", glewGetString(status));
        return 1;
    }
    //执行SetupRC函数。
    setupRC();
    //开启glutMainLoop。
    glutMainLoop();
    return 0;
}
  • 初始化环境变量。
  • 注册响应的回调函数。
  • 执行SetupRC函数。
  • 开启glutMainLoop。

SetupRC

void setupRC() {
    // 设置背景颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
    
    // 初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    // 从初始化渲染管线
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    
    // 将相机向后移动7个单元:肉眼到物体之间的距离
    viewFrame.MoveForward(10);
    
    // 创建一个甜甜圈
    // 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);
}
  • 设置背景颜色。
  • 初始化着色器管理器。
  • 设置相机(观察者)的位置。
  • 调用gltMakeTorus创建一个甜甜圈。
  • 设置点的大小(方便点填充时,肉眼观察)glPointSize。

ChangeSize

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);
}
  • 设置视口。
  • 设置透视模式,初始化其透视矩阵。
  • 把透视矩阵加载到透视矩阵对阵中。
  • 初始化渲染管线。

RenderScene

void renderScene() {
    //1.清除窗口和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //2.把摄像机矩阵压入模型矩阵中
    modelViewMatix.PushMatrix(viewFrame);
    //3.设置绘图颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    //4.使用默认光源着色器
    //通过光源、阴影效果跟提现立体效果
    //参数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();
}
  • 清除窗口和深度缓冲区。
  • 把摄像机矩阵压入模型矩阵中。
  • 设置绘图颜色。
  • 使用默认光源着色器来渲染3D视图。
  • 绘制。
  • 绘制完成后将模型视图矩阵恢复。
  • 交换缓冲区。

运行代码,得到甜甜圈

甜甜圈.png

看起来甜甜圈正常,当我们进行旋转时就出现了问题


旋转时的甜甜圈.png

情况分析

默认情况下,我们所渲染的每个点、线或三角形都会在屏幕上进行光栅化,并按照在组合图元批次时指定的顺序排列,这在某些情况下会产生问题
在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部分是对观察者不可见的.对于不可见的部分,应该及早丢弃.例如在一个不透明的墙壁后,就不应该渲染.这种情况叫做”隐藏面消除”(Hidden surface elimination)
上面出现的问题:
甜甜圈在旋转过程中,OpenGL不知道该显示哪些界面,导致本来是观察者不应该看到且该丢弃部分,不仅看到了,而且没有将隐藏部分丢弃。
没有对不可见部分进行处理,导致渲染了背面的黑暗面,在甜甜圈转动的过程中就会把暗面显示出来,达不到我们想要的效果。

解决办法

1. 油画算法

所谓油画算法先绘制场景中的离观察者较远的物体,再绘制较近的物体。

油画算法.png

先绘制红⾊部分,再绘制⻩色部分,最后再绘制灰色部分,即可解决隐藏⾯消除的问题。

但这种方法在图形处理中效率很低,必须在任何发生重叠的地方对每个像素进行两次写操作,速度会变慢。
油画算法也有它的弊端,比如当图层之间相互交叉的时候,油画法就无法区分谁远谁近了,如下图

图层交叉.png

图中无法界定那个图形远近的问题,因此油画算法就无法解决隐藏面的问题。

2. 正背面剔除

正背面剔除是OpenGL的一种图形绘制技巧,主要用于处理立体图形绘制时,只绘制观察者能看到的部分,看不到的部分就丢弃不绘制。
正背面剔除的主要思想就是:只绘制用户看到的部分,对于看不到的部分直接丢弃。

尝试想象⼀个3D图形,你从任何⼀个方向去观察,最多可以看到⼏个⾯?


3D图形.png

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

那么如何知道某个面在观察者的视口中不会出现?
任何平⾯都有2个⾯,正⾯和背⾯。意味着你一个时刻只能看到一⾯面。OpenGL 可以做到检查所有正面朝向观察者的⾯,并渲染它们.从⽽丢弃背⾯朝向的⾯。这样可以节约⽚元着⾊器的性能。

如何告诉OpenGL 你绘制的图形,哪个⾯是正⾯,哪个面是背⾯?
答案:通过分析顶点数据的顺序。

正⾯背⾯.png

分析立方体中的正背面

立方体中的正背面.png
左侧三⻆形顶点顺序为: 1—> 2—> 3 ;
右侧三⻆形的顶点顺序为: 1—> 2—> 3 .
• 当观察者在右侧时,则右边的三角形⽅向为逆时针方向则为正⾯,⽽左侧的三角形为顺时针则为背面。
• 当观察者在左侧时,则左边的三⻆形为逆时针方向判定为正⾯,⽽右侧的三⻆形为顺时针判定为背⾯。
总结:正面和背面是有三角形的顶点定义顺序和观察者⽅向共同决定的。随着观察者的⻆度⽅向的改变,正面背⾯也会跟着改变。

正背面剔除相关代码

//开启表面剔除(默认背面剔除)
glEnable(GL_CULL_FACE);

//关闭表面剔除(默认背面剔除)
glDisable(GL_CULL_FACE);

//选择剔除那个面(正面/背面)
// mode参数为: GL_FRONT, GL_BACK, GL_FRONT_AND_BACK,默认GL_BACK
glCullFace(GLenum mode);

//用户指定绕序那个为正面
//mode参数为: GL_CW, GL_CCW,默认值:GL_CCW
glFrontFace(GL enum mode);

//剔除正面实现①
glCullFace(GL_BACK);
glFrontFace(GL_CW); 

//剔除正面实现②
glCullFace(GL_FRONT);
glFrontFace(GL_CCW);

下面加入到代码里面,首先声明一个全局变量标记

//标记:背面剔除
int iCull = 0;

再来添加监听菜单栏点击方法

// 监听菜单栏点击
void ProcessMenu(int value)
{
    switch(value)
    {
        case 1:
            iCull = !iCull;
            break;
    }
    //通过glutPostRedisplay主动触发渲染。
    glutPostRedisplay();
}

然后在mian()函数中添加右击菜单栏方法

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);
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    SetupRC();
    
    glutMainLoop();
    return 0;
}

最后在 RenderScene()中添加开启和关闭正背面剔除功能

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

运行代码,通过键盘方向键控制甜甜圈的旋转,可以发现,没有黑色的填充了,但是当甜甜圈的侧面正对着我们的时候,出现了另外一个问题。

另外一个问题.png
出现这样的一个缺口的原因是存在两个重叠的正面。如图所示,正面1和正面2重叠,这时候系统不知道该渲染哪个面,就在重叠部分出现了这样的一个缺口。要解决这个问题可以通过深度测试方法
缺口的原因.png

相关文章

网友评论

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

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