OpenGL入门 (五) —— 图元绘制实战

1,837 阅读6分钟

前言

这篇文章主要是讲如何绘制不同的简单图元,并且初次接触矩阵的代码。这篇重点是图元,关于矩阵变换在接下来的文章中会详细介绍。

绘制图元

OpenGL常用的图元有七种(用的最多的是三角形,因为理论上所有的图形都能够由三角形绘制):

1.引入相关工具类

这里主要是通过注释单独介绍引入的各个工具类是做什么的。

#include "GLTools.h"	//矩阵工具类,用来快速设置正/透视投影矩阵,完成坐标从3D->2D映射过程.
#include "GLMatrixStack.h" //矩阵工具类,可以利于GLMatrixStack 加载单元矩阵/矩阵/矩阵相乘/压栈/出栈/缩放/平移/旋转
#include "GLFrame.h" //矩阵工具类,表示位置
#include "GLFrustum.h" //矩阵工具类,用来快速设置正/透视投影矩阵,完成坐标从3D->2D映射过程
#include "GLBatch.h" //批次类, 帮助类,利用它可以传输顶点/光照/纹理/颜色数据到存储着色器中
#include "GLGeometryTransform.h" //变换管道类,用来快速在代码里传输视图矩阵/投影矩阵/视图变换矩阵等

// 在Mac 系统下,`#include<glut/glut.h>`
// 在Windows 和 Linux上,我们使⽤free glut的静
// 态库版本并且需要添加⼀个宏
#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;

//容器类(7种不同的图元对应7种容器对象)
GLBatch				pointBatch;
GLBatch				lineBatch;
GLBatch				lineStripBatch;
GLBatch				lineLoopBatch;
GLBatch				triangleBatch;
GLBatch         triangleStripBatch;
GLBatch         triangleFanBatch;

//几何变换的管道(存储投影/视图/投影视图变换矩阵)
GLGeometryTransform	transformPipeline;

3.注册各种回调函数及初始化

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    //申请一个颜色缓存区、深度缓存区、双缓存区、模板缓存区
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    //设置window 的尺寸
    glutInitWindowSize(800, 600);
    //创建window的名称
    glutCreateWindow("GL_POINTS");
    //注册回调函数(改变尺寸)
    glutReshapeFunc(ChangeSize);
    //点击空格时,调用的函数
    glutKeyboardFunc(KeyPressFunc);
    //特殊键位函数(上下左右)
    glutSpecialFunc(SpecialKeys);
    //显示函数
    glutDisplayFunc(RenderScene);
    
    //判断一下是否能初始化glew库,确保项目能正常使用OpenGL 框架
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    //绘制
    SetupRC();
    
    //runloop运行循环
    glutMainLoop();
    return 0;
}

4. 设置投影矩阵并压栈保存

// 窗口已更改大小,或刚刚创建。无论哪种情况,我们都需要
// 使用窗口维度设置视口和投影矩阵.
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();
}

viewFrustum.SetPerspective是设置投影矩阵的四个值,如下图,是透视投影和正投影的区别

继续看下图,n和f是视角到这两个平面(近平面和远平面)的距离,也是上面代码里参数的1.0f500.0ffloat(w) / float(h)是这两个平面的宽高比,35.0f是垂直方向的视场角度(如下图两个平面和原点形成的角度是一样的)

5. 根据方向键旋转视图

void SpecialKeys(int key, int x, int y)
{
    
    if(key == GLUT_KEY_UP)
        //围绕一个指定的X,Y,Z轴旋转。
        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();
}

6. 根据空格切换通过调用重绘方法

//根据空格次数。切换不同的“窗口名称”
void KeyPressFunc(unsigned char key, int x, int y)
{
    if(key == 32)
    {
        nStep++;
        
        if(nStep > 6)
            nStep = 0;
    }
    switch(nStep)
    {
        case 0:
            glutSetWindowTitle("GL_POINTS");
            break;
        case 1:
            glutSetWindowTitle("GL_LINES");
            break;
        case 2:
            glutSetWindowTitle("GL_LINE_STRIP");
            break;
        case 3:
            glutSetWindowTitle("GL_LINE_LOOP");
            break;
        case 4:
            glutSetWindowTitle("GL_TRIANGLES");
            break;
        case 5:
            glutSetWindowTitle("GL_TRIANGLE_STRIP");
            break;
        case 6:
            glutSetWindowTitle("GL_TRIANGLE_FAN");
            break;
    }
    glutPostRedisplay();
}

7.setupRC设置渲染环境

由于篇幅原因下面的代码我只展示了两种图元的绘制(末尾提供完整demo),重点是理解GLBatch,关于矩阵变化在后面的文章我会在详细讲到。

// 此函数在呈现上下文中进行任何必要的初始化。.
// 这是第一次做任何与opengl相关的任务。
void SetupRC()
{
    // 灰色的背景
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
    shaderManager.InitializeStockShaders();
    glEnable(GL_DEPTH_TEST);
    //设置变换管线以使用两个矩阵堆栈
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    cameraFrame.MoveForward(-15.0f);
    /*
     常见函数:
     void GLBatch::Begin(GLenum primitive,GLuint nVerts,GLuint nTextureUnits = 0);
      参数1:表示使用的图元
      参数2:顶点数
      参数3:纹理坐标(可选)
          
     //负责顶点坐标
     void GLBatch::CopyVertexData3f(GLFloat *vNorms);
        
     //结束,表示已经完成数据复制工作
     void GLBatch::End(void);
     */
    //定义一些点,三角形形状。
    GLfloat vCoast[9] = {
        3,3,0,0,3,0,3,0,0
        
    };
    //用点的形式
    pointBatch.Begin(GL_POINTS, 3);
    pointBatch.CopyVertexData3f(vCoast);
    pointBatch.End();

    //通过三角形创建金字塔
    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};
 
    //GL_TRIANGLES 每3个顶点定义一个新的三角形
    triangleBatch.Begin(GL_TRIANGLES, 12);
    triangleBatch.CopyVertexData3f(vPyramid);
    triangleBatch.End();
 
}

8. RenderScene完成渲染

这里主要是矩阵的处理及调用了批次类的Draw()函数,这里使用的是平面着色器

// 召唤场景
void RenderScene(void)
{
    // Clear the window with current clearing color
    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);
    
    /* GLShaderManager 中的Uniform 值——平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
     --transformPipeline.GetModelViewProjectionMatrix() 获取的
     GetMatrix函数就可以获得矩阵堆栈顶部的值
     参数3:颜色值(黑色)
     */
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    
    switch(nStep) {
        case 0:
            //设置点的大小
            glPointSize(4.0f);
            pointBatch.Draw();
            glPointSize(1.0f);
            break;
        //中间省略...
        case 6:
            DrawWireFramedBatch(&triangleStripBatch);
            break;
    }
    
    //还原到以前的模型视图矩阵(单位矩阵)
    modelViewMatrix.PopMatrix();
    
    // 进行缓冲区交换
    glutSwapBuffers();
}

总结

有看过之前文章的朋友,会发现OpenGL的这个渲染流程是大体相似的。相比之前画正方形的文章,本文只是新增了矩阵的变化以及展示了常用的基本图元是如何绘制的,重点还注释清楚了各种工具类以及使用的方法,目的是让大家继续熟悉绘制流程及相关API。 点击查看本文完整demo

OpenGL入门 (一) —— OpenGL专业名词解析

OpenGL入门 (二) —— OpenGL Mac环境搭建

OpenGL入门 (三) —— 快速画一个正方形

OpenGL入门 (四) —— 渲染流程解析

OpenGL入门 (五) —— 图元绘制实战

OpenGL入门 (六) —— 矩阵基础变化实战

OpenGL入门 (七) —— 隐藏面消除详解

OpenGL入门 (八) —— 纹理坐标解析