OpenGL–渲染图形实战

写在前面

pic (1).gif

pic (2).gif

本文主要介绍如何将固定位置上的顶点数据以6种不同的图元展示,并且使用键盘控制视图的变换。

详见:github.com/cjf56455885…

1.渲染流程图

截屏2021-08-12 16.04.25.png

此案例整个渲染流程大体如图所示。

  • main函数:程序入口,主要初始化OpenGL需要的库,设置自定义的回调函数,并开启运行循环

  • ChangeSize函数:窗口更改大小或刚刚创建都需要调用,使用窗口的维度来设置视口和投影矩阵

  • SteupRC函数:此函数在呈现上下文中进行任何必要的初始化,OpenGL绘制需要的初始化操作都在这里进行。

  • SpecialKeys函数:特殊键位处理(上、下、左、右移动)

  • KeyPressFunc函数:根据空格次数切换不同的图元方式

  • RenderScene函数:召唤场景,最终的渲染函数

  • DrawWireFrameBatch函数:用于立体图形的填充及边框绘制

以下对几个重点函数做出进一步说明。

2.ChangeSize函数

void ChangeSize(int w, int h){

    //设置视口的位置和大小
    glViewport(0, 0, w, h);

    //创建投影矩阵,并将它载入投影矩阵堆栈中
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);

    //通过设置的投影方式获得投影矩阵,并将其存入投影矩阵中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

    //调用顶部载入单元矩阵
    modelViewMatrix.LoadIdentity();
}
复制代码
  • 1.设置视口的位置和大小
glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
复制代码

前两个参数代表x,y坐标点,后两个参数代表宽和高,案例使用和窗口大小的宽高一致。

视口:可视区域的大小,一般和窗口大小一致,也可特殊指定,视口之外的顶点会被裁剪

  • 2.设置投影矩阵
    • 参数1:垂直方向上的视场角度,可以理解成眼睛张开的角度
    • 参数2:视口纵横比 = w/h
    • 参数3:近裁剪面距离
    • 参数4:远裁剪面距离
viewFrustum.SetPerspective(float fFov, float fAspect, float fNear, float fFar);
复制代码

截屏2021-08-09 20.21.12.png

fNearfFar可以根据实际情况调整大小,实现的效果就类似于把鸡蛋装在盒子和装在行李箱中的区别,只要保证容器装得下物体即可。

这里因为是显示立体图形,所以采用透视投影。

  • 3.通过设置的投影方式获得投影矩阵,并将其存入投影矩阵中
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
复制代码

窗口改变时,本例的视口也跟着改变。所以每次改变时都要获取投影矩阵.

  • 4.对模型视图矩阵堆栈压入一个单元矩阵
modelViewMatrix.LoadIdentity();
复制代码

每个场景渲染完,需要回到最初的状态准备渲染下一个场景。所以事先压入一个单元矩阵,绘制完后弹出矩阵堆栈进行还原。

模型视图矩阵:模型矩阵和视图矩阵的组合。模型矩阵相当于物体本身的变换,视图矩阵相当于观察者的变换。

模型矩阵:把定义的局部坐标转到世界坐标。

视图矩阵:把世界坐标转到摄像机坐标下。

这两者一般组合使用。试想,当想要拍摄什么东西时,可以有两种做法。

1.摄像机不动,移动物体到可视范围

2.物体不动,移动摄像机的位置保证物体到可视范围

这两个变化的目的是一样的,所以它们一般结合使用,决定你能看到什么东西。

在图形学中,为了方便处理,一般规定摄像机在原点位置,更多的是调整物体的位置。

3.SetupRC函数

准备工作的函数,主要进行背景颜色的初始化,固定着色器管理类的初始化,设置观察者方向,给定初始化的顶点数据和指定批次类。

void SetupRC(){
    // 灰色的背景
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f);

    //固定着色器管理类初始化
    shaderManager.InitializeStockShaders();
    
    //设置变换管线以使用两个矩阵堆栈
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    cameraFrame.MoveForward(-15.0f);
    ...
    //下面是给出初始化的顶点数据和指定批次类
    
    //用点的形式
    pointBatch.Begin(GL_POINTS, 3);
    pointBatch.CopyVertexData3f(vCoast);
    pointBatch.End();

    //通过线的形式
    lineBatch.Begin(GL_LINES, 3);
    lineBatch.CopyVertexData3f(vCoast);
    lineBatch.End();

    //通过线段的形式
    lineStripBatch.Begin(GL_LINE_STRIP, 3);
    lineStripBatch.CopyVertexData3f(vCoast);
    lineStripBatch.End();

    //通过线环的形式
    lineLoopBatch.Begin(GL_LINE_LOOP, 3);
    lineLoopBatch.CopyVertexData3f(vCoast);
    lineLoopBatch.End();
}
复制代码
  • 1.设置背景颜色
glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
复制代码
  • 2.固定着色器管理类初始化
shaderManager.InitializeStockShaders();
复制代码

固定着色器是OpenGL提供的,使用固定着色器前,必须调用初始化。

  • 3.变换管线使用矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
复制代码

modelViewMatrixprojectionMatrix矩阵堆栈交给变换管线处理,后面可以直接通过变换管线的get方法得到模型视图投影矩阵。

变换管线:管理矩阵和矩阵变换的流水线。里面保存了矩阵和矩阵相乘的结果。

  • 4.设置观察者位置
cameraFrame.MoveForward(-15.0f);
复制代码

观察者位置有三种设置方式:

方法 效果
void MoveForward(float fDelta) 前后移动,修改z值
void MoveUp(float fDelta) 上下移动,修改y值
void MoveRight(float fDelta) 左右移动,修改x值

4.响应特殊键位的函数

SpecialKeys:特殊键位函数,控制视图旋转

KeyPressFunc:空格键位函数,切换视图图元

void SpecialKeys(int key, int x, int y){

    if(key == GLUT_KEY_UP)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    if(key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    if(key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    if(key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
        
    glutPostRedisplay();
}
复制代码

GLUT_KEY_UP,GLUT_KEY_DOWN,GLUT_KEY_LEFT,GLUT_KEY_RIGHT是系统提供的响应上下左右键位的字段

  • 1.设置旋转
//可设置旋转角度和围绕的坐标轴
objectFrame.RotateWorld(**float** fAngle, **float** x, **float** y, **float** z);
复制代码
  • 2.提交重新渲染
glutPostRedisplay();
复制代码

5.RenderScene函数

真正干活的函数。

void RenderScene(void){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    //压栈
    modelViewMatrix.PushMatrix();

    //观察者矩阵压入矩阵堆栈
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.MultMatrix(mCamera);

    //模型视图矩阵压入矩阵堆栈
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);

    //使用平面着色器,矩阵参数取自变换管道
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

    //根据空格次数,切换图元
    **switch**(nStep) {
       case 0:
            //设置点的大小
            glPointSize(4.0f);
            pointBatch.Draw();
            glPointSize(1.0f);
            break;
        case 1:
            //设置线的宽度
            glLineWidth(2.0f);
            lineBatch.Draw();
            glLineWidth(1.0f);
            break;
        case 2:
            glLineWidth(2.0f);
            lineStripBatch.Draw();
            glLineWidth(1.0f);
            break;
        case 3:
            glLineWidth(2.0f);
            lineLoopBatch.Draw();
            glLineWidth(1.0f);
            break;
        case 4:
            DrawWireFramedBatch(&triangleBatch);
            break;
        case 5:
            DrawWireFramedBatch(&triangleStripBatch);
            break;
        case 6:
            DrawWireFramedBatch(&triangleFanBatch);
            break;
    }

    //还原到以前的模型视图矩阵(单位矩阵)
    modelViewMatrix.PopMatrix();

    // 进行缓冲区交换
    glutSwapBuffers();
}
复制代码
  • 1.清空缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
复制代码

每次渲染时,都要对使用到的缓冲区进行清空操作,防止上一帧的内容在缓冲区还有残留。

  • 2.模型视图矩阵压栈单元矩阵
modelViewMatrix.PushMatrix();
复制代码

压入单元矩阵,保证上一帧渲染结束后,出栈得到原始的状态。单元矩阵互乘还是单元矩阵,所以为了保险起见,矩阵堆栈可多次压入单元矩阵无影响。

  • 3.矩阵堆栈压入对应的矩阵

这里存在两个矩阵堆栈的概念。观察者矩阵堆栈和模型视图矩阵堆栈在SetupRC中,都已交给变换管线处理。

M3DMatrix44f mCamera;  
cameraFrame.GetCameraMatrix(mCamera); 
modelViewMatrix.MultMatrix(mCamera); 

M3DMatrix44f mObjectFrame;     
objectFrame.GetMatrix(mObjectFrame);     
modelViewMatrix.MultMatrix(mObjectFrame);
复制代码

分别把观察者矩阵压入观察者矩阵堆栈,模型视图矩阵压入模型视图矩阵堆栈。

  • 4.使用平面着色器绘制
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
复制代码

平面着色器所需的第二个参数是投影矩阵,这里使用transformPipeline.GetModelViewProjectionMatrix()从变换管道中取出处理后的模型视图投影矩阵。

  • 5.根据空格次数,切换图元

这里代码相似度高,取其中一个说明。

glLineWidth(2.0f);
lineLoopBatch.Draw();
glLineWidth(1.0f);
复制代码

使用线环批次类进行绘制。

lineLoopBatch.Begin(GL_LINE_LOOP, 3);
lineLoopBatch.CopyVertexData3f(vCoast);
lineLoopBatch.End();
复制代码

线环批次类的定义是GL_LINE_LOOPSetupRC已给出。其他同理。

这里通过点击空格的次数来控制图元装配方式,而不同的图元装配方式则会影响物体的形状。

  • 6.模型视图矩阵堆栈还原
modelViewMatrix.PopMatrix();
复制代码

每次渲染结束后,需要还原模型视图矩阵堆栈,保证下一次渲染无污染。

  • 7.交换缓冲区
glutSwapBuffers();
复制代码

因为存在双缓冲区,需要交换缓冲区保证屏幕刷新。

6.矩阵堆栈

矩阵堆栈是负责构造和管理在3d空间中建立复杂场景的矩阵,对应的类是GLMatrixStack

矩阵堆栈在初始化的时候默认包含了单位矩阵。默认的堆栈深度是64

相关的命令如下:

  • GLMatrixStack::LoadIdentity(): 顶部载入单元矩阵
函数名 说明
GLMatrixStack::LoadIdentity() 顶部载入单元矩阵
GLMatrixStack.LoadMatrix(const float * mMatrix) 顶部载入任意矩阵
GLMatrixStack::MultMatrix(const float * mMatrix) 用一个矩阵乘以顶部矩阵,得到的结果存储在堆栈的顶部
modelViewMatrix.GetMatrix() 获取矩阵堆栈顶部的值
modelViewMatrix.PushMatrix(**const** **float** *mMatrix) push矩阵到矩阵堆栈栈顶
modelViewMatrix.PopMatrix() 栈顶矩阵pop出矩阵堆栈
modelViewMatrix.PopMatrix() 栈顶矩阵pop出矩阵堆栈
modelViewMatrix.Rotate(GLfloat PFNGLPNTRIANGLESFATIPROC, GLfloat x, GLfloat y, GLfloat z) 栈顶矩阵应用旋转变换
modelViewMatrix.Translate(GLfloat x, GLfloat y, GLfloat z) 栈顶矩阵应用平移变换
modelViewMatrix.Scale(GLfloat x, GLfloat y, GLfloat z) 栈顶矩阵应用缩放变换

可以看出,矩阵堆栈具备压栈出栈和仿射变换的能力。其本质还是堆栈结构,只是堆栈内存储着矩阵。

矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。任何复杂的运动都可以拆解成简单的单步变换,因为所有矩阵操作函数只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其它矩阵就不受影响。

写在后面

以上是OpenGL渲染图形的基础案例。涉及视口模型视图矩阵投影矩阵变换管线固定着色器矩阵堆栈等常见概念。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享