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

mian:程序入口

SetupRC:初始化渲染环境
-
RC:Rendering Context
,表示 OpenGL 的渲染状态机。 - 在任何 OpenGL 函数起作用之前必须要创建一个渲染环境
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:绘制甜甜圈

//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)
上面的例子都是通过创建的单个图形,而我们在实际应用场景中往往都是使用的多个图形、或者是图形之间的变化,这个例子中将会展示如何进行一个变换图形的绘制
- 从下面的图中可以清晰的看出在初始化的过程中做了哪些事情
- 第一部分:设置背景色、初始化管理器、开启深度测试等初始化操作都是为了后面的渲染
- 第二部分:矩阵堆栈,这个是为了处理图形、坐标系的变化而定义的
- 第三部分:批次类的处理,定义各种图形的初始状态

- 计算一个圆的顶点坐标
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:键位监听
在这个方法后中一般都会做哪些操作呢?从目的来反推这个过程。
- 目的:让当前图片动起来,改变下样子;
- 方式:需要重新绘制,重新需要有数据的变化才会有效果;
- 数据:修改顶点数据、修改变换矩阵、修改整个对象(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:正方形移动
中处理的顶点数据。
这些被处理过的数据都是在这个方法中被使用,然后渲染到屏幕上。
方法流程

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