OpenGL学习(七)-- 基础变化综合练习实践总结

1,294 阅读12分钟

我的 OpenGL 专题学习目录,希望和大家一起学习交流进步!


一、前言

以下我总结了一些最近学习 OpenGL 中常用的一些函数,添加了比较多的注释,既是对自己学习的一个巩固总结,也是防止以后遗忘可以快速查看的记录,同时希望也能帮助到更多在学习 OpenGL 的朋友们。

Xcode 还没有搭建OpenGL环境的朋友可以参照我这篇文章去搭建,OpenGL学习(二)-- Xcode 搭建 OpenGL 环境。 另外还有之前的我总结的一些 OpenGL 基础文章,可以从文章上方的目录查看。

二、代码+注释总结

1、引入的头文件:

#include "GLTools.h"	// OpenGL toolkit
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLGeometryTransform.h"
#include "StopWatch.h"

#include <math.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

2、一些常用全局变量的声明:

GLShaderManager		shaderManager;// 固定管线管理器

GLMatrixStack		modelViewMatrix;// 模型视图矩阵堆栈

GLMatrixStack		projectionMatrix;// 投影视图矩阵堆栈

GLFrame				cameraFrame;// 观察者位置

GLFrame             objectFrame;// 世界坐标位置

GLFrustum			viewFrustum;// 投影方式,学名:视景体。用来构造投影矩阵。

GLBatch             triangleBatch;// 简单的批次容器,是GLTools的一个简单的容器类。

GLTriangleBatch     CC_Triangle;// 三角形批次类

GLTriangleBatch     sphereBatch;// 球

GLTriangleBatch     torusBatch;// 环

GLTriangleBatch     cylinderBatch;// 圆柱

GLTriangleBatch     coneBatch;// 锥

GLTriangleBatch     diskBatch;// 磁盘

GLGeometryTransform	transformPipeline; // 变换管道,专门用来管理投影和模型矩阵的

GLfloat vGreen[] = {0.0f, 1.0f, 0.0f, 1.0f };// 定义一个颜色值,绿色


3、main 函数:程序入口中的设置

int main(int argc,char *argv[]) {
    //设置当前工作目录,针对MAC OS X
    /*
     `GLTools`函数`glSetWorkingDrectory`用来设置当前工作目录。实际上在Windows中是不必要的,因为工作目录默认就是与程序可执行执行程序相同的目录。但是在Mac OS X中,这个程序将当前工作文件夹改为应用程序捆绑包中的`/Resource`文件夹。`GLUT`的优先设定自动进行了这个中设置,但是这样中方法更加安全。
     */
    gltSetWorkingDirectory(argv[0]);
    
   
    //初始化GLUT库,这个函数只是传输命令参数并且初始化glut库
    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);
    
    //GLUT窗口大小、窗口标题
    glutInitWindowSize(800, 600);
    glutCreateWindow("Triangle");
    
    /*
     GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
     1)为窗口改变大小而设置的一个回调函数
     2)包含OpenGL 渲染的回调函数
     */
    //注册重塑函数
    glutReshapeFunc(changeSize);
    //注册显示函数
    glutDisplayFunc(RenderScene);
    
    glutSpecialFunc(SpeacialKeys);
    
    /*
     初始化一个GLEW库,确保OpenGL API对程序完全可用。
     在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
     */
    GLenum status = glewInit();
    if (GLEW_OK != status) {
        
        printf("GLEW Error:%s\n",glewGetErrorString(status));
        return 1;
        
    }
    
    //设置我们的渲染环境
    setupRC();
    
    glutMainLoop();

    return  0;    
}

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

重塑函数,为窗口改变大小而设置的一个回调函数,窗口大小改变时,接收新的宽度&高度。 在 main 函数中使用 glutReshapeFunc 函数进行了注册: glutReshapeFunc(changeSize);//注册重塑函数

/*
 在窗口大小改变时,接收新的宽度&高度。
 */
void changeSize(int w,int h) {
    /*
      1、设置窗口坐标
      x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
     */
    // 防止h变为0
    if(h == 0){
        h = 1;
    }
    glViewport(0, 0, w, h);

    // 2、
    // 如果绘制的是立体图形,还需要设置透视投影
    // 透视投影
    // 参数1:从顶点方向看去的视场角度(用角度值表示)
    // 参数2:宽和高的纵横比
    // 参数3:fNear
    // 参数4:fFar
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    
    // 3、
    // 上面只是设置了但没有用,我们需要把设置的转化为矩阵,把结果带回去。
    // 往投影矩阵堆栈projectionMatrix里加载一个矩阵,从我们的视景体viewFrustum里获取投影矩阵GetProjectionMatrix()
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 4、    
    // 模型视图矩阵是用来旋转、平移等操作的,这里并没有变化,所以只是加载一个单元矩阵。   
    // 往模型视图矩阵堆栈modelViewMatrix里加载一个单元矩阵
    modelViewMatrix.LoadIdentity();
}

5、KeyPressFunc 点击空格函数:

每次点击空格,切换窗口的标题,并重新渲染图形

//点击空格,切换渲染图形
void KeyPressFunc(unsigned char key, int x, int y) {
    if(key == 32) {
        nStep++;
        if(nStep > 4)
            nStep = 0;
    }
    
    switch(nStep) {
        case 0:
            glutSetWindowTitle("Sphere");
            break;
        case 1:
            glutSetWindowTitle("Torus");
            break;
        case 2:
            glutSetWindowTitle("Cylinder");
            break;
        case 3:
            glutSetWindowTitle("Cone");
            break;
        case 4:
            glutSetWindowTitle("Disk");
            break;
    }
    
    glutPostRedisplay();
}

6、SpecialKeys 函数

点击键盘的上下左右,让世界坐标系发生变化(并不是移动物体本身)

//上下左右,移动世界坐标系
void SpecialKeys(int key, int x, int y) {
    if(key == GLUT_KEY_UP)
        // 移动世界坐标系,而不是去移动物体。
        // 将世界坐标系在X方向移动-5.0
        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();
}

7、SetupRC() 函数:

进行必要的初始化,设置我们的渲染环境,在 main 函数中调用。一般在这里是去描述我们要渲染的图形长什么样子。真正去绘制的时候不在这里,而是在 RenderScene()函数 中,后面会讲解。

// 将上下文中,进行必要的初始化
void SetupRC() {
    // 1、设置背景颜色
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f );

    // 2、初始化固定着色器管理器
    shaderManager.InitializeStockShaders();

    // 3、绘制立体图形的时候,需要开启深度测试
    glEnable(GL_DEPTH_TEST);

    // 4、通过GLGeometryTransform管理矩阵堆栈
    // 使用transformPipeline 管道管理模型视图矩阵堆栈 和 投影矩阵堆栈(把两个矩阵作为两个参数放进去)
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

    // 5、为了让效果明显,将观察者坐标位置Z移动往屏幕里移动15个单位位置
    // 参数:表示离屏幕之间的距离。 负数,是往屏幕后面移动;正数,往屏幕前面移动
    cameraFrame.MoveForward(-15.0f);

// ----------------------GLTriangleBatch类型--------------------------

    // 利用“GLTriangleBatch”类型的三角形批次类构造图形对象,GLTriangleBatch类型封装了很多常用立体图形。
    // 1、球
    /*
      gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
     参数1:sphereBatch,三角形批次类对象
     参数2:fRadius,球体半径
     参数3:iSlices,从球体底部堆叠到顶部的三角形带的数量;其实球体是一圈一圈三角形带组成
     参数4:iStacks,围绕球体一圈排列的三角形对数
     建议:一个对称性较好的球体的片段数量是堆叠数量的2倍,就是iStacks = 2 * iSlices,参数4是参数3的2倍。
     绘制球体都是围绕Z轴,这样+z就是球体的顶点,-z就是球体的底部。
     */
    gltMakeSphere(sphereBatch, 3.0, 10, 20);

    // 2、环面
    /*
     gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
     参数1:torusBatch,三角形批次类对象
     参数2:majorRadius,甜甜圈中心到外边缘的半径
     参数3:minorRadius,甜甜圈中心到内边缘的半径
     参数4:numMajor,沿着主半径的三角形数量
     参数5:numMinor,沿着内部较小半径的三角形数量
     */
    gltMakeTorus(torusBatch, 3.0f, 0.75f, 15, 15);
    
    // 3、圆柱
    /*
     void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
     参数1:cylinderBatch,三角形批次类对象
     参数2:baseRadius,底部半径
     参数3:topRadius,头部半径
     参数4:fLength,圆形长度
     参数5:numSlices,围绕Z轴的三角形对的数量
     参数6:numStacks,圆柱底部堆叠到顶部圆环的三角形数量
     */
    gltMakeCylinder(cylinderBatch, 2.0f, 2.0f, 3.0f, 15, 2);
    
    // 4、锥
    /*
     void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
     参数1:cylinderBatch,三角形批次类对象
     参数2:baseRadius,底部半径
     参数3:topRadius,头部半径
     参数4:fLength,圆形长度
     参数5:numSlices,围绕Z轴的三角形对的数量
     参数6:numStacks,圆柱底部堆叠到顶部圆环的三角形数量
     */
    //圆柱体,从0开始向Z轴正方向延伸。
    //圆锥体,是一端的半径为0,另一端半径可指定。
    gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
    
    // 5、磁盘
    /*
    void gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
     参数1:diskBatch,三角形批次类对象
     参数2:innerRadius,内圆半径
     参数3:outerRadius,外圆半径
     参数4:nSlices,圆盘围绕Z轴的三角形对的数量
     参数5:nStacks,圆盘外网到内围的三角形数量
     */
    gltMakeDisk(diskBatch, 1.5f, 3.0f, 13, 3);


// ------------------------GLBatch类型--------------------------
// 用上面GLTriangleBatch的代码的时候,把下面这段代码先注释掉再运行。两块儿代码本来是分开的,为了方便写在了一起。

//上面的是用的“GLTriangleBatch”类型的三角形批次类,下面我们使用最基础的“GLBatch”类型的三角形批次类绘制金字塔。
    // 首先创建三角形批次类,告诉它我们需要多少个顶点,
    
    // 1.函数BeginMesh( GLuint nMaxVerts )// 指定有多少个顶点
    CC_Triangle.BeginMesh(300);// 指定有300个顶点
    
    // 2.创建一个顶点
    M3DVector3f m[] = {
        0.5, 0.0, 0.0,
        -0.5, 0.0, 0.0,
        0.0, 0.5, 0.0
    };
    
    // 3.将顶点复制进去,可以使用copy函数,也可以使用add函数添加
    // 参数1:表示顶点坐标值
    // 参数2:表示法线坐标值,没有就写NULL
    // 参数3:表示纹理坐标值,没有就写NULL
    CC_Triangle.AddTriangle(m, NULL, NULL);
    //CC_Triangle.CopyVertexData3f
    
    CC_Triangle.End();
    
    CC_Triangle.Draw();
// --------------------------------------------------------------
}

8、RenderScene()显示函数:

在发生任何变化,调用 glutPostRedisplay() 方法时,会调用 RenderScene() 这个函数。 在 main 函数中注册了 RenderScene 显示函数 glutDisplayFunc(RenderScene);

下面我先着重解释一下这几段代码:

  • (1)压栈 PushMatrix();

modelViewMatrix.PushMatrix(); 这句代码的意思是压栈,如果 PushMatix() 括号里是空的,就代表是把栈顶的矩阵复制一份,再压栈到它的顶部。如果不是空的,比如是括号里是单元矩阵,那么就代表压入一个单元矩阵到栈顶了。

  • (2)矩阵相乘 MultMatrix(mObjectFrame)

modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到 modelViewMatrix 矩阵堆栈中。 modelViewMatrix.MultMatrix(mObjectFrame); 这句代码的意思是把 模型视图矩阵堆栈栈顶 的矩阵 copy 出一份来和新矩阵进行矩阵相乘,然后再将相乘的结果赋值给栈顶的矩阵。

MultMatrix.png

  • (3)出栈PopMatrix();

modelViewMatrix.PopMatrix(); 把栈顶的矩阵出栈,恢复为原始的矩阵堆栈,这样就不会影响后续的操作了。

下面是我自己总结的一个《矩阵入栈、相乘、出栈》的流程图:

矩阵入栈相乘出栈.png

  • (4)cameraFrame.GetCameraMatrix(mCamera) :

cameraFrame.GetCameraMatrix(mCamera); 这里和 OC 的语法有些不一样,它的意思是从 cameraFrame 这个 观察者坐标系 中获取矩阵,然后赋值给 mCamera。同理的还有获取 世界坐标位置 的矩阵 objectFrame.GetMatrix(mObjectFrame);cameraFramemCamera 都是 GLFrame 类型的结构体。

接下来看一下 RenderScene() 代码:

//召唤场景
void RenderScene(void) {
    //1.清除一个或者一组特定的缓存区
    /*
     缓冲区是一块存在图像信息的储存空间,红色、绿色、蓝色和alpha分量通常一起分量通常一起作为颜色缓存区或像素缓存区引用。
     OpenGL 中不止一种缓冲区(颜色缓存区、深度缓存区和模板缓存区)
      清除缓存区对数值进行预置
     参数:指定将要清除的缓存的
     GL_COLOR_BUFFER_BIT :指示当前激活的用来进行颜色写入缓冲区
     GL_DEPTH_BUFFER_BIT :指示深度缓存区
     GL_STENCIL_BUFFER_BIT:指示模板缓冲区
     */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //模型视图矩阵栈堆,压栈
    modelViewMatrix.PushMatrix();
    //获取摄像头矩阵
    M3DMatrix44f mCamera;
    //从camereaFrame中获取矩阵到mCamera
    cameraFrame.GetCameraMatrix(mCamera);
    //模型视图堆栈的 矩阵与mCamera矩阵 相乘之后,存储到modelViewMatrix矩阵堆栈中
    modelViewMatrix.MultMatrix(mCamera);
    
    //创建矩阵mObjectFrame
    M3DMatrix44f mObjectFrame;
    //从ObjectFrame 获取矩阵到mOjectFrame中
    objectFrame.GetMatrix(mObjectFrame);
    //将modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到modelViewMatrix矩阵堆栈中
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //使用平面着色器
    //参数1:类型
    //参数2:通过transformPipeline获取模型视图矩阵
    //参数3:颜色
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    
    //判断你目前是绘制第几个图形
// 注意这里传递的是地址 &sphereBatch
    switch(nStep) {
        case 0:
            DrawWireFramedBatch(&sphereBatch);// 球
            break;
        case 1:
            DrawWireFramedBatch(&torusBatch);// 环面
            break;
        case 2:
            DrawWireFramedBatch(&cylinderBatch);// 圆柱
            break;
        case 3:
            DrawWireFramedBatch(&coneBatch);// 锥
            break;
        case 4:
            DrawWireFramedBatch(&diskBatch);// 磁盘
            break;
    }
    
    // 绘制完毕了,需要把栈顶的矩阵pop出去,不要影响我下一次绘图
    modelViewMatrix.PopMatrix();
    
    // Flush drawing commands
    glutSwapBuffers();
}

9、DrawWireFramedBatch 函数:

void DrawWireFramedBatch(GLTriangleBatch* pBatch) {
    // 平面着色器,绘制三角形
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
   
    // 传过来的参数,对应不同的图形Batch
    pBatch->Draw();
    
    // 画出黑色轮廓
    glPolygonOffset(-1.0f, -1.0f);
    
    // 开启线段圆滑处理
    glEnable(GL_LINE_SMOOTH);
    
    // 开启混合功能
    glEnable(GL_BLEND);
    
    // 颜色混合
    // 表示源颜色乘以自身的alpha 值,目标颜色乘以1.0减去源颜色的alpha值,这样一来,源颜色的alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减 小。这种情况下,我们可以简单的将源颜色的alpha值理解为“不透明度”。这也是混合时最常用的方式。
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    // 通过程序点大小模式来设置点的大小
    glEnable(GL_POLYGON_OFFSET_LINE);
    
    // 多边形模型(背面、线) 将多边形背面设为线框模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    
    // 线条宽度
    glLineWidth(2.5f);
    
    // 平面着色器绘制线条
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    
    pBatch->Draw();
    
    // 恢复多边形模式和深度测试
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
}

以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:

1、《OpenGL超级宝典 第5版》

2、《OpenGL编程指南(第八版)》

转载请备注原文出处,不得用于商业传播——凡几多