OpenGL之矩阵堆栈绘制立体图元

绘制流程

一、main函数

  • 初始化双缓冲窗口:glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
  • 设置window初始化:glutInitWindowSize;
  • 注册重塑函数(changeSize)、显示函数(renderScene)、特殊键位回调函数(glutSpecialFunc)、普通键位回调函数(glutKeyboardFunc)、设置渲染函数等;

二、changeSize (int w, int h) 重塑函数

  • 设置视口:窗口刚刚创建或者已更改大小;
  • 设置投影方式:透视投影,并获得投影矩阵,载入投影矩阵堆栈中;
  • 模型视图矩阵初始化,加载一个单元矩阵。

三、SetupRC() 函数

  • 初始化存储着色器;
  • 设置变换管线使用两个矩阵堆栈(模型视图矩阵、投影矩阵);
  • 设置图元装配方式;

四、RenderScene函数

  • 清除窗口和深度缓存区;
  • 模型视图矩阵push一个单元矩阵(栈中存在两个单元矩阵,其主要是为了绘制完成还原矩阵);
  • 用cameraFrame生成观察者矩阵
  • 栈顶单元矩阵与观察者矩阵相乘,生成新的观察者矩阵,然后将其压栈
  • 用objectFrame构建物体矩阵
  • 用栈顶单元矩阵与物体矩阵相乘,生成模型视图矩阵,然后将其压栈;
  • 设置着色器类型,然后绘制图元;绘制完毕之后,还原矩阵,交换缓冲区;

五、KeyPressFunc 空格点击函数和SpecialKeys 函数的处理;

具体代码实现

  • 需要用到的工具类,已做详细的注释说明:
// GLTool.h头⽂文件包含了大部分GLTool中类似C语言的独立函数
#include "GLTools.h"
// 矩阵的⼯具类.可以利于GLMatrixStack 加载单元矩阵/矩阵/矩阵相乘/压栈/出栈/缩放/平移/旋转
#include "GLMatrixStack.h"
// 矩阵工具类,表示位置.通过设置vOrigin,vForward,vUps等
#include "GLFrame.h"
// 矩阵⼯具类,⽤来快速设置正/透视投影矩阵.完成坐标从3D->2D的映射过程
#include "GLFrustum.h"
// 三⻆形批次类,帮助类,利用它可以传输顶点/光照/纹理/颜⾊数据到存储着⾊器中
#include "GLBatch.h"
// 变换管道类,⽤来快速在代码中传输视图矩阵/投影矩阵/视图投影变换矩阵等
#include "GLGeometryTransform.h"

//  在Mac 系统下,`#include<glut/glut.h>`在Windows 和 Linux上,我们使用freeglut的静态库版本并且需要添加一个宏
#include <math.h> // 数学库
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
  • 定义全局变量:
/*
 GLMatrixStack 变化管线使用矩阵堆栈
 GLMatrixStack 构造函数允许指定堆栈的最大深度、默认的堆栈深度为64.这个矩阵堆在初始化时已经在堆栈中包含了单位矩阵。
 GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
 
 //通过调用顶部载入这个单位矩阵
 void GLMatrixStack::LoadIndentiy(void);
 
 //在堆栈顶部载入任何矩阵
 void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
 */

GLShaderManager        shaderManager;      // 固定管线管理器
GLMatrixStack          modelViewMatrix;    // 模型视图矩阵(变换)
GLMatrixStack          projectionMatrix;   // 投影矩阵
GLFrame                cameraFrame;        // 观察者位置
GLFrame                objectFrame;        // 世界坐标位置

GLFrustum              viewFrustum;        // 投影矩阵
GLGeometryTransform    transformPipeline;  // 几何变换的管道

// 不同的图元对应容器对象
GLBatch                triangleBatch;
GLBatch                triangleStripBatch;
GLBatch                triangleFanBatch;

GLfloat vGreen[] = {1.0f, 0.0f, 0.0f, 1.0f};// 图元面颜色
GLfloat vBlack[] = {0.0f, 0.0f, 0.0f, 1.0f};// 图元棱颜色

// 跟踪控制效果步骤
int stepNumber = 0;

  • main函数
int main(int argc ,char *argv[]) {
    
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    
    /*
     初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区  
     --GLUT_DOUBLE`:双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式,经常用来生成动画效果;
     --GLUT_DEPTH`:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试;
     --GLUT_STENCIL`:确保我们也会有一个可用的模板缓存区。
     */
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    
    // 设置window的大小和名称
    glutInitWindowSize(800, 600);
    glutCreateWindow("四棱锥");
    
    // 注册回调函数(window改变尺寸)
    glutReshapeFunc(changeSize);
    // 注册特殊键位函数(上下左右)
    glutSpecialFunc(specialKeys);
    // 注册键盘普通键位的调用函数
    glutKeyboardFunc(keyPressFunc);
    // 显示函数
    glutDisplayFunc(renderScene);

    // 判断是否能初始化glew库,确保项目能正常使用OpenGL框架;在视图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
    GLenum statue = glewInit();
    if (GLEW_OK != statue) {
        printf("GLEW Error:%s\n",glewGetErrorString(statue));
        return 1;
    }
    
    // 设置渲染环境
    setupRC();
    glutMainLoop();

    return 0;
    
}
  • changeSize (int w, int h) 重塑函数
// 窗口刚刚创建或者已更改大小,都需要使用窗口维度设置视口和投影矩阵
void changeSize(int w, int h) {
    
    // 设置视口
    glViewport(0, 0, w, h);
    
    // 设置投影矩阵,载入投影矩阵堆栈中
    // fFov:从顶点方向看去的视场角度(用角度值表示)
    // fAspect:宽和高的纵横比(w/h)
    // fNear
    // fFar
    viewFrustum.SetPerspective(40.0f, float(w)/float(h), 1.f, 500.f);
    // 往投影矩阵堆栈projectionMatrix里加载一个矩阵,从视景体viewFrustum里获取投影矩阵GetProjectionMatrix()
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    // 模型视图矩阵是用来旋转、平移等操作的,并没有变化,所以只需要加载一个单元矩阵;
    modelViewMatrix.LoadIdentity();
}

  • KeyPressFunc 点击空格函数
// 点击“空格”处理:根据点击空格的次数,切换不同的”窗口名称“,重新渲染
void keyPressFunc(unsigned char key, int x, int y) {
    if (key == 32) {
        stepNumber += 1;
        if (stepNumber > 2) {
            stepNumber = 0;
        }
    }
    switch (stepNumber) {
        case 0:
            glutSetWindowTitle("四棱锥");
            break;
        case 1:
            glutSetWindowTitle("圆环");
            break;
        case 2:
            glutSetWindowTitle("三角形扇");
            break;
        default:
            glutSetWindowTitle("");
            break;
    }
    glutPostRedisplay();
}

  • SpecialKeys 函数
// 特殊键位的逻辑处理(上、下、左、右)
void specialKeys(int key, int x, int y) {
    
    // objectFrame记录旋转的物体
    if (key == GLUT_KEY_UP) {
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.f, 0.f, 0.f);
    }
    if (key == GLUT_KEY_DOWN) {
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.f, 0.f, 0.f);
    }
    if (key == GLUT_KEY_LEFT) {
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.f, 1.0f, 0.f);
    }
    if (key == GLUT_KEY_RIGHT) {
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.f, 1.0f, 0.f);
    }
    glutPostRedisplay();
}

  • SetupRC() 函数
void setupRC() {
    
    // 设置窗口背景色
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    
    shaderManager.InitializeStockShaders();
    // 设置变换管线两个矩阵堆栈(模型视图矩阵、投影矩阵)
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    // 修改观察者的位置
    cameraFrame.MoveForward(-20.f);
    
    /*
      常见函数:
      void GLBatch::Begin(GLenum primitive,GLuint nVerts,GLuint nTextureUnits = 0);
      参数1:表示使用的图元
      参数2:顶点数
      参数3:纹理坐标(可选)
     * 复制顶点坐标
       void GLBatch::CopyVertexData3f(GLFloat *vNorms);
     * 结束,表示已经完成数据复制工作
       void GLBatch::End(void);
     */
    
    // 绘制"四棱锥"
    GLfloat vPyramid[12][3] = {
        -2.0f, 0.0f, -2.0f,
        2.0f, 0.0f, -2.0f,
        0.0f, 4.0f, 0.0f,

        2.0f, 0.0f, -2.0f,
        2.0f, 0.0f, 2.0f,
        0.0f, 4.0f, 0.0f,

        2.0f, 0.0f, 2.0f,
        -2.0f, 0.0f, 2.0f,
        0.0f, 4.0f, 0.0f,

        -2.0f, 0.0f, 2.0f,
        -2.0f, 0.0f, -2.0f,
        0.0f, 4.0f, 0.0f
    };
    triangleBatch.Begin(GL_TRIANGLES, 12);
    triangleBatch.CopyVertexData3f(vPyramid);
    triangleBatch.End();
    
    // 绘制“三角形扇”
    GLfloat vPoints[100][3];
    int nVerts = 0;
    GLfloat r = 3.0f;
    vPoints[nVerts][0] = 0.0f;
    vPoints[nVerts][1] = 0.0f;
    vPoints[nVerts][2] = 0.0f;
    
    // M3D_2PI 就是2Pi,绘制圆形
    for(GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) {
        
        //数组下标自增(每自增1次就表示一个顶点)
        nVerts++;
        /*
         弧长=半径*角度,这里的角度是弧度制,不是平时的角度制
         既然知道了cos值,那么角度=arccos,求一个反三角函数就行了
         */
        //x点坐标 cos(angle) * 半径
        vPoints[nVerts][0] = float(cos(angle)) * r;
        //y点坐标 sin(angle) * 半径
        vPoints[nVerts][1] = float(sin(angle)) * r;
        //z点的坐标
        vPoints[nVerts][2] = -0.5f;
    }
    
    // 结束扇形,一共绘制7个顶点(包括圆心,然后添加闭合的终点
    nVerts++;
    vPoints[nVerts][0] = r;
    vPoints[nVerts][1] = 0;
    vPoints[nVerts][2] = 0.0f;
    // 加载:GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形
    triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
    triangleFanBatch.CopyVertexData3f(vPoints);
    triangleFanBatch.End();
    
    // "圆环":三角形条带,一个小环或圆柱段
    int iCounter = 0;
    GLfloat radius = 3.0f;
    // 从0度~360度,以0.3弧度为步长
    for(GLfloat angle = 0.0f; angle <= (2.0f*M3D_PI); angle += 0.3f) {
        // 获取圆形的顶点的X,Y
        GLfloat x = radius * sin(angle);
        GLfloat y = radius * cos(angle);
        
        // 绘制2个三角形(他们的x,y顶点一样,只是z点不一样)
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = -0.5;
        iCounter++;
        
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = 0.5;
        iCounter++;
    }
    
    // 关闭循环,结束循环,在循环位置生成2个三角形
    vPoints[iCounter][0] = vPoints[0][0];
    vPoints[iCounter][1] = vPoints[0][1];
    vPoints[iCounter][2] = -0.5;
    iCounter++;
    
    vPoints[iCounter][0] = vPoints[1][0];
    vPoints[iCounter][1] = vPoints[1][1];
    vPoints[iCounter][2] = 0.5;
    iCounter++;
    
    // GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形
    triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter);
    triangleStripBatch.CopyVertexData3f(vPoints);
    triangleStripBatch.End();   
}

  • RenderScene()显示函数:将模型视图矩阵和投影矩阵放到变换管道中,变换管道的作用是能帮助快速进行矩阵相乘,在函数中可以直接通过变换管道的Get方法得到相应的矩阵(GetNormalMatrix()(法线矩阵),GetProjectionMatrix()(投影矩阵),GetModelViewMatrix()(模型视图矩阵),GetModelViewProjectionMatrix()(模型视图投影矩阵))
// 场景绘制
void renderScene() {
    
    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;
    // 只要使用GetMatrix函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。
    // 用来使用GLShaderManager的使用。或者是获取顶部矩阵的顶点副本数据
    objectFrame.GetMatrix(mObjectFrame);
    // 矩阵相乘堆栈的顶部矩阵,相乘的结果随后简存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mObjectFrame);
    // GetNormalMatrix()(法线矩阵),GetProjectionMatrix()(投影矩阵),GetModelViewMatrix()(模型视图矩阵),GetModelViewProjectionMatrix()(模型视图投影矩阵)
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    
    switch (stepNumber) {
        case 0:
            DrawWireFramedBatch(&triangleBatch);
            break;
        case 1:
            DrawWireFramedBatch(&triangleStripBatch);
            break;
        case 2:
            DrawWireFramedBatch(&triangleFanBatch);
            break;
        default:
            break;
    }
    // 还原到以前的模型视图矩阵(单位矩阵)
    modelViewMatrix.PopMatrix();
    // 进行缓冲区交换
    glutSwapBuffers();     
}

  • DrawWireFramedBatch(GLBatch* pBatch) 主要是绘制图元中的红色部分:
void DrawWireFramedBatch(GLBatch* pBatch) {
    /*------------画红色部分----------------*/
    /* GLShaderManager 中的Uniform 值——平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
          --transformPipeline 变换管线(指定了2个矩阵堆栈)
     参数3:颜色值
    */
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
    pBatch->Draw();
    
    /*-----------边框部分-------------------*/
    /*
        glEnable(GLenum mode); 用于启用各种功能。功能由参数决定
        参数列表:http://blog.csdn.net/augusdi/article/details/23747081
        注意:glEnable() 不能写在glBegin() 和 glEnd()中间
        GL_POLYGON_OFFSET_LINE  根据函数glPolygonOffset的设置,启用线的深度偏移
        GL_LINE_SMOOTH          执行后,过虑线点的锯齿
        GL_BLEND                启用颜色混合。例如实现半透明效果
        GL_DEPTH_TEST           启用深度测试 根据坐标的远近自动隐藏被遮住的图形(材料
     
     
        glDisable(GLenum mode); 用于关闭指定的功能 功能由参数决定
     
     */
    
    // 画黑色边框
    glPolygonOffset(-1.0f, -1.0f);// 偏移深度,在同一位置要绘制填充和边线,会产生z冲突,所以要偏移
    glEnable(GL_POLYGON_OFFSET_LINE);
    
    // 画反锯齿,让黑边好看些
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    // 绘制线框几何黑色版 三种模式,实心,边框,点,可以作用在正面,背面,或者两面
    // 通过调用glPolygonMode将多边形正面或者背面设为线框模式,实现线框渲染
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    //设置线条宽度
    glLineWidth(2.5f);
    
    /* GLShaderManager 中的Uniform 值——平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
         --transformPipeline.GetModelViewProjectionMatrix() 获取的
          GetMatrix函数就可以获得矩阵堆栈顶部的值
     参数3:颜色值(黑色)
     */
    
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();

    // 复原原本的设置
    // 通过调用glPolygonMode将多边形正面或者背面设为全部填充模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);   
}

效果展示

  • 点击空格键切换图元的切换显示,点击方向键则可以控制图元的旋转;
    在这里插入图片描述

矩阵堆栈

  • 在发生任何变化,调用glutPostRedisplay()方法时,会调用 RenderScene() 这个函数。从上面的renderScene()代码中我们可以看到有这么一段代码,这是一个什么操作呢?
    modelViewMatrix.PushMatrix();
    
    // 观察者矩阵
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    // 矩阵相乘,放入模型视图矩阵
    modelViewMatrix.MultMatrix(mCamera);
    
    // 物体矩阵
    M3DMatrix44f mObjectFrame;
    // 只要使用GetMatrix函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。
    // 用来使用GLShaderManager的使用,或者是获取顶部矩阵的顶点副本数据
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    ........... // 绘制图元
    // 还原到以前的模型视图矩阵(单位矩阵)
    modelViewMatrix.PopMatrix();
    // 进行缓冲区交换
    glutSwapBuffers(); 
    
  • modelViewMatrix.PushMatrix():就是压栈,如果 PushMatix() 括号里是空的,就代表是把栈顶的矩阵复制一份,再压栈到它的顶部。如果不是空的,比如是括号里是单元矩阵,那么就代表压入一个单元矩阵到栈顶了。
  • 矩阵相乘 MultMatrix(mObjectFrame):把模型视图矩阵堆栈的栈 的矩阵copy出一份来和新矩阵进行矩阵相乘,然后再将相乘的结果赋值给栈顶的矩阵;
  • 出栈modelViewMatrix.PopMatrix():把栈顶的矩阵出栈,恢复为原始的矩阵堆栈;
  • cameraFrame.GetCameraMatrix(mCamera) :从 cameraFrame 这个观察者坐标系中获取矩阵,然后赋值给 mCamera。同理的还有获取世界坐标位置的矩阵objectFrame.GetMatrix(mObjectFrame)。
  • 矩阵堆栈的操作示意流程图如下:入栈,是为了保存当前的矩阵栈状态;出栈,是为了恢复入栈前的矩阵栈状态。
    在这里插入图片描述

完整代码和具体逻辑传送门:OpenGL之矩阵堆栈绘制立体图元

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/107240144
今日推荐