美文网首页
03源码--003--常用函数使用解析

03源码--003--常用函数使用解析

作者: 修_远 | 来源:发表于2020-07-12 21:03 被阅读0次

[TOC]

TOC

mian:程序入口

main 函数流程

SetupRC:初始化渲染环境

  • RC:Rendering Context,表示 OpenGL 的渲染状态机。
  • 在任何 OpenGL 函数起作用之前必须要创建一个渲染环境

SetupRC:绘制正方形

SetupRC:绘制正方形
void SetupRC() {
    // 设置背景色
    glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
    
    // 初始化着色管理器
    shaderManager.InitializeStockShaders();
    
    // 批次处理
    triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.End();
}

SetupRC:绘制甜甜圈

SetupRC:绘制甜甜圈
//4.创建一个甜甜圈
//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);
  • gltMakeTorus 创建一个甜甜圈

SetupRC:绘制点/线/线段/线环/金字塔/六边形/圆柱(objectFrame)

上面的例子都是通过创建的单个图形,而我们在实际应用场景中往往都是使用的多个图形、或者是图形之间的变化,这个例子中将会展示如何进行一个变换图形的绘制

  • 从下面的图中可以清晰的看出在初始化的过程中做了哪些事情
  • 第一部分:设置背景色、初始化管理器、开启深度测试等初始化操作都是为了后面的渲染
  • 第二部分:矩阵堆栈,这个是为了处理图形、坐标系的变化而定义的
  • 第三部分:批次类的处理,定义各种图形的初始状态
SetupRC:绘制图元
  • 计算一个圆的顶点坐标
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;
}
圆的顶点坐标
  • 计算三角形带顶点坐标
//三角形条带,一个小环或圆柱段
//顶点下标
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++;

ChangeSize:窗口大小改变

  • 作用:窗口大小改变时接受新的宽度和高度,
  • 参数:(0, 0) 代表窗口视图中的左下角坐标
  • 参数:(w, h) 代表像素
  • 入口:通过 glutReshapeFunc(函数名) 注册为重塑函数

ChangeSize:绘制正方形

对于正方形的绘制,我们直接修改的是顶点坐标,窗口大小的修改不影响正方形的绘制,所以这里只需要重设视口大小即可:glViewport(0, 0, w, h);

void ChangeSize(int w, int h) {
    glViewport(0, 0, w, h);
}

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

ChangeSize:绘制点/线/线段/线环/金字塔/六边形/圆柱(objectFrame)

  • 使用投影矩阵
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();
}

SpecialKeys:键位监听

在这个方法后中一般都会做哪些操作呢?从目的来反推这个过程。

  1. 目的:让当前图片动起来,改变下样子;
  2. 方式:需要重新绘制,重新需要有数据的变化才会有效果;
  3. 数据:修改顶点数据、修改变换矩阵、修改整个对象(objectFrame)、修改观察者视角(cameraFrame)

从上面的这个反推过来的过程可以清楚的知道我们这个方法的目的

  • 修改顶点数据,作为新图形的顶点数据;
  • 修改变换矩阵,作为旧图形变换到新图形的过程;
  • 修改整个对象,作为新图形的布局;
  • 修改观察者视角,作为新图形的布局(相对);

在修改完成之后需要做的工作就是提交渲染:

glutPostRedisplay();

key值说明

  • 回调函数:通过 key 值的键值来判断触发的动作
void SpecialKeys(int key, int x, int y)
{
    
    if(key == GLUT_KEY_UP)
    if(key == GLUT_KEY_DOWN)
    if(key == GLUT_KEY_LEFT)
    if(key == GLUT_KEY_RIGHT)
    
    glutPostRedisplay();
}
  • key 的枚举值:源码中还有 F1-F12 的功能键,这里没有用到就没有写出来。通过枚举名可以清楚的看到key值的取值。
#define GLUT_KEY_LEFT           100
#define GLUT_KEY_UP              101
#define GLUT_KEY_RIGHT          102
#define GLUT_KEY_DOWN           103
#define GLUT_KEY_PAGE_UP        104
#define GLUT_KEY_PAGE_DOWN      105
#define GLUT_KEY_HOME           106
#define GLUT_KEY_END            107
#define GLUT_KEY_INSERT         108

SpecialKeys:正方形移动

在上一篇博客中已经详细的介绍了正方形移动的相关细节,这里就摘要关键代码进行展示如何使用这个回调的:计算每一个顶点的位置

// 移动步长
GLfloat stepSize = 0.025f;
// x/y 相对移动顶点 D (-x, y) 左上角
GLfloat blockX   = vVerts[0];
GLfloat blockY   = vVerts[10];

// 处理移动
if (key == GLUT_KEY_UP) {
    blockY += stepSize;
}

if (key == GLUT_KEY_DOWN) {
    blockY -= stepSize;
}

if (key == GLUT_KEY_LEFT) {
    blockX -= stepSize;
}

if (key == GLUT_KEY_RIGHT) {
    blockX += stepSize;
}

SpecialKeys:绘制甜甜圈

在这个案例中,通过调整观察者的视角来完成甜甜圈移动的变化。这是一个相对性的问题,如果以我们的视角作为参照系,那对面走过来的人就是移动的,同样的,如果以对面的人的视角做为参照系,那我们就是移动的。将这个问题映射到观察者和物体

  • 如果观察者不动,物体发生改变,可以看到物体的改变;
  • 如果观察者动,物体不动,也可以看出物体的不同角度的,看起来也是物体在发生改变;
//键位设置,通过不同的键位对其进行设置
//控制Camera的移动,从而改变视口
void SpecialKeys(int key, int x, int y)
{
    //1.判断方向
    if(key == GLUT_KEY_UP)
        //2.根据方向调整观察者位置
        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);
    
    //3.重新刷新
    glutPostRedisplay();
}

对于一个3D视角的物体,

  • 上下键:对应x轴的变化
  • 左右键:对应y轴的变化

SpecialKeys:绘制点/线/线段/线环/金字塔/六边形/圆柱(objectFrame)

上一个例子调整是观察者的变化,这个例子调整的是整个图形对象的变化

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

KeyPressFunc:空格监听

上面有了 SpecialKeys 的监听,那这里为什么还要有一个 KeyPressFunc 的监听呢?通过方法名进行一个区分

  • SpecialKeys:特殊键位,具体key的枚举上面已经都举出来了;
  • KeyPressFunc:从命名上可以知道这个是按键按下的一个回调,也就是键盘上的所有键位都支持。比如空格的 ascll 码为 32,可以通过 key==32 来判断是否是空格

就算这个方法监听到的键位更多,但也是监听键位,做出相应的处理,然后刷新视图绘制,跟上面的逻辑应该保持一致,所以最后也应该有一个提交重绘的操作:

glutPostRedisplay();

KeyPressFunc:绘制点/线/线段/线环/金字塔/六边形/圆柱(objectFrame)

下面的案例中,根据空格按下从次数来切换不同的“窗口名称”

//根据空格次数。切换不同的“窗口名称”
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();
}

RenderScene:重绘

方法介绍

重绘消息实际是一条传递到一个内部消息循环中的消息,在屏幕刷新的间隔中,也会发生其他事件。也就是说,在处理这个消息的过程中,我们仍然可以检测按键动作、鼠标移动、改变窗口大小和程序结束等动作。

方法触发时机

上面的几个回调方法都是跟我们的外接设备,也就是用户操作之后触发的,那这个方法的触发是怎么发生的呢?

当我们需要渲染的时候来触发这个方法,

  • 系统自动回调渲染:ChangeSize 方法之后,由系统触发
  • 用户手动触发渲染:上面提到过的glutPostRedisplay();

方法作用

接受到上面几个方法是中的数据,比如 ChangeSize:绘制甜甜圈 中创建的投影坐标系和渲染管线,比如 SpecialKeys:正方形移动 中处理的顶点数据。

这些被处理过的数据都是在这个方法中被使用,然后渲染到屏幕上。

方法流程

Render:重新绘制
  1. 清除缓存区:因为在 SetupRC 以及上一次渲染的时候,OpenGL 这个状态机里面难免会存在一些其他操作处理过的状态,所以在下一个绘制之前,应该是还原状态。
  2. 处理数据:比如第一个虚线框中的,顶点数据,使用之前保存的顶点数据进行绘制。比如第二个虚线框中的矩阵栈,使用变换后的矩阵,然后进行绘制,在这种使用方式中,需要对栈进行一个还原操作,也就是推出栈顶被使用过的矩阵,保证下一次栈顶元素是一个单位矩阵。
  3. 交换缓存区:关于双缓存区的机制,前面的文章中已经讲解的非常清楚了,这里就不累述了。

相关文章

网友评论

      本文标题:03源码--003--常用函数使用解析

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