开场白
使用OpenGL简单实现了屏幕上展示正方形,并且用键盘控制移动和旋转。
效果展示:
![](https://img.haomeiwen.com/i20196568/f91a9efcf2d35c69.gif)
代码解释
1、定义全局变量
1.1 着色器管理类
定义一个全局着色器管理类变量(其实是当前文件的局部变量,由于demo中所有的内容都写在同一个文件中,所以这个变量也就相当于是全局变量了)
//定义一个着色器
GLShaderManager shaderManager;
1.2 批处理容器
定义一个批处理容器,这个是GLTools中封装好提供的
//批次容器,GLTools的一个简单容器类。
GLBatch triangleBatch;
1.3 正方形相关
//正方形的边长的一半,
GLfloat blockSize = 0.1f;
//正方形4个点 0~2:左下点 3~5:右下点 6~8:右上点 9~11:左上点
GLfloat vVerts[] = {
-blockSize, -blockSize, 0.0f,
blockSize, -blockSize, 0.0f,
blockSize, blockSize, 0.0f,
-blockSize, blockSize, 0.0f,
};
//移动时使用
GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;
//旋转时使用
GLfloat lRot = 0.0f;
- blockSize:正方形的边长的一半,此边长是相对值,相对于屏幕的size。
OpenGL默认坐标原点是屏幕的正中心,屏幕的xy范围是(-1,-1)到(1,1)。
‘’ 0.1‘’可以理解为屏幕正值方向总长度的的10%,也就是(正值方向+负值方向)的总长度的5%
后续代码中会看到,设置的glut初始化窗口是500x500,所以用这种方式设置边长是正方形,如果修改成500x600,就不是正方形了。 - vVerts:数组表示正方形的4个顶点,OpenGL中的坐标系是3D坐标系,所以每三个数据代表正方形的一个顶点。数据中分别从左下点开始(-blockSize,-blockSize,0.0)逆时针开始到左上点(-blockSize,blockSize,0.0)结束。
- xPos:记录变换后当前x轴的值。
- yPos:记录变换后当前y轴的值。
- lRot:记录向左方向旋转变换后的值。(左旋转为正,有选装为负)。
2、程序入口函数
2.1 程序入口main函数
int main(int argc,char *argv[]) {
//设置工作路径。
gltSetWorkingDirectory(argv[0]);
//初始化glut库,将app的入口函数main中的参数传递给glut
glutInit(&argc, argv);
//设置glut显示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//glut初始化窗口 500x500
glutInitWindowSize(500, 500);
//glut创建窗口,并设置窗口title
glutCreateWindow("Triangle");
/**
GLUT内部运行一个本地消息循环,拦截适当的消息。然后调用注册的回调函数。
*/
//注册重塑函数,为窗口改变size的时候进行回调处理
glutReshapeFunc(changeSize);
//注册显示函数,渲染回调
glutDisplayFunc(renderScene);
//注册特殊函数,触发键盘操作
glutSpecialFunc(SpeciaKeys);
/**
初始化一个GLEW库,确保OpenGL API是可用有效的。
在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n", glewGetErrorString(status));
return 1;
}
//初始化设置我们的渲染环境
setupRC();
//glut启动
glutMainLoop();
return 0;
}
代码流程:
- gltSetWorkingDirectory(argv[0]); 设置工作路径。GLTools中定义的函数。在Mac中,将工作目录修改为app中的Resource目录。而windows中不需要,因为windows中默认的把工作目录与程序的可执行程序的目录设置为相同。
- glutInit(&argc, argv);初始化glut。将工程接收到的启动参数传递给glut中。
- 设置glut显示模式glutInitDisplayMode,其中参数的含义:
- GLUT_DOUBLE: 双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式经常用来生成动画效果
- GLUT_RGBA:表明欲建立RGBA模式的窗口。
- GLUT_DEPTH:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试
- GLUT_STENCIL:确保我们也会有一个可用的模板缓存区。
- 初始化窗口size以及窗口title,此处设置的窗口size是500x500,上面提到过,只要设置显示的正方形边长为0.1就可以了,如果设置成500x600,就不是正方形了。
- 注册相关的回调方法,分别是重塑回调、显示回调和特殊指定回调这个在后面进行详细说明。
- setupRC()函数,我们相关的初始化代码写在这里,后面进行详细说明。
- glut启动mainLoop
2.2 setupRC函数
void setupRC() {
//设置清屏颜色(背景颜色)
glClearColor(0.98, 0.4, 0.7, 1);
//初始化着色器管理类
shaderManager.InitializeStockShaders();
//开始批处理
triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
//拷贝顶点数据
triangleBatch.CopyVertexData3f(vVerts);
//结束批处理
triangleBatch.End();
}
- 设置背景色
- 初始化着色器管理类
- 将正方形的顶点提交给批处理容器中,批量处理顶点数据。
3、回调方法
main函数最后一步调用glutMainLoop()函数,这个可以理解glut是一个内部有一个main loop,我们在其中注册了三个回调,大致的逻辑如图:
![](https://img.haomeiwen.com/i20196568/b55cfdfea68c4357.png)
glut的API中提供了注入回调的方法,分别是:
- 重塑回调glutReshapeFunc,我们实现的回调是changeSize函数
- 渲染回调glutDisplayFunc,我们实现的回调renderScene函数
- 特殊回调glutSpecialFunc,我们实现的回调SpeciaKeys函数
3.1 changeSize
//修改窗口大小回调
void changeSize(int w, int h) {
//设置视口
glViewport(0, 0, w, h);
}
实现中主要设置了视口的设置,第一次运行的时候会调用一次。当调整窗口大小的时候,也会调用。
3.2 renderScene
//渲染显示回调
void renderScene(void) {
/*
clear缓冲区
缓冲区是一块存在图像信息的储存空间,红色、绿色、蓝色和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);
GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 0.0f};
M3DMatrix44f mFinalTransform, mTransfomrmMatrix, mRotationMartix;
//平移
m3dTranslationMatrix44(mTransfomrmMatrix, xPos, yPos, 0.0f);
//旋转
m3dRotationMatrix44(mRotationMartix, m3dDegToRad(lRot), 0.0f, 0.0f, 1.0f);
//旋转和移动的矩阵结果 合并到mFinalTransform(矩阵相乘)
m3dMatrixMultiply44(mFinalTransform, mTransfomrmMatrix, mRotationMartix);
//shaderManager调用固定着色器方法,参数指定着色器类型
// shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
//提交着色器
triangleBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
}
启动后会调用一次,
- 每次调用需要先清空一下缓冲区,代码中的实现将颜色缓冲区、深度缓冲区和模板缓冲区都进行了清空
- 定义了几个矩阵变量,由于示例是矩阵移动旋转的方式,而非点移动的方式,所以需要这些变量。
- mFinalTransform:最终的矩阵,是mTransfomrmMatrix和mRotationMartix的乘积。
- mTransfomrmMatrix:移动矩阵
- mRotationMartix:旋转矩阵
- 平移和旋转处理,以及将两种处理的结果合并到最终变换矩阵中(mFinalTransform)
- shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
- 参数1:shaderManager调用固定着色器,由于是用矩阵的方式,所以使用GLT_SHADER_FLAT类型的着色器。
- 参数2:传入变换后的矩阵
- 参数3:传入传染的颜色
- triangleBatch.Draw();着色器定义好后,批处理进行绘制
- glutSwapBuffers();交换缓冲区。先在后台进行渲染,执行交换缓存区,将后台缓冲区进行渲染,成功后切换到前台。
3.3 SpeciaKeys
void SpeciaKeys(int key, int x, int y) {
//设置移动步长
GLfloat stepSize = 0.025f;
/**
键盘响应相关处理,对应 上下左右
GLUT_KEY_UP
GLUT_KEY_DOWN
GLUT_KEY_LEFT
GLUT_KEY_RIGHT
*/
if (key == GLUT_KEY_UP) {
yPos += stepSize;
}
if (key == GLUT_KEY_DOWN) {
yPos -= stepSize;
}
if (key == GLUT_KEY_LEFT) {
xPos -= stepSize;
lRot += 5;
}
if (key == GLUT_KEY_RIGHT) {
xPos += stepSize;
lRot -= 5;
}
//边界处理
if (xPos < -1.0 + blockSize) {
xPos = -1.0 + blockSize;
}
if (xPos > 1.0 - blockSize) {
xPos = 1.0 - blockSize;
}
if (yPos > 1.0 - blockSize) {
yPos = 1.0 - blockSize;
}
if (yPos < -1.0 + blockSize) {
yPos = -1.0 + blockSize;
}
glutPostRedisplay();
}
- stepSize确定移动步长
- 当按上下键时,设置yPos变量,每次增加stepSize
- 当按左右键时,设置xPos变量的同时,也设置lRot变量。注意左旋转为正,有旋转为fu。
- 边界处理。
- glutPostRedisplay();发送重绘指令。会调用渲染回调glutDisplayFunc,也就是我们实现renderScene函数。
移动边界碰撞逻辑
矩阵移动的方式是比较普遍的方式,相比于计算每个点的移动方式来说代码量会减少很多,代码逻辑更加清晰。而且GPU比较擅长进行矩阵的计算方式,底层代码中,使用矩阵方式,能够更好的利用GPU的性能。
矩阵的方式,可以理解为整个矩阵就是一个整体,初始时在原点坐标,如图:
![](https://img.haomeiwen.com/i20196568/97133a88ace0be99.png)
遇到边界时:
利用中心点与边长一半(blockSize)的加减法进行判断就可以了。
举个有边界处理的例子:
if (xPos > (1.0f - blockSize)) {
xPos = 1.0f - blockSize;
}
![](https://img.haomeiwen.com/i20196568/522b2b2e21c7c8df.png)
总结
刚刚接触OpenGL不久,很多API的名字很难记忆和理解。如果文章中有错误还请各位指出,我会及时进行修改。
GitHub上完整代码
网友评论