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

2020-07-12  本文已影响0人  修_远

[TOC]

TOC

mian:程序入口

main 函数流程

SetupRC:初始化渲染环境

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

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:窗口大小改变

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值说明

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();
}
#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视角的物体,

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 的监听呢?通过方法名进行一个区分

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

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:绘制甜甜圈 中创建的投影坐标系和渲染管线,比如 SpecialKeys:正方形移动 中处理的顶点数据。

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

方法流程

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

猜你喜欢

热点阅读