03源码--002--绘制正方形并移动
[TOC]
一、案例效果
- 图形绘制:绘制一个正方形
- 图形移动:可以通过上下左右键盘控制正方形的移动
- 碰撞检测:不能超出屏幕边缘
二、实现流程
键盘控制正方形移动-
准备工作
-
OpenGL 环境搭建
03源码--001--OpenGL 环境搭建 -
准备工具类
- 着色管理器
GLBatch triangleBatch;
- 批次容器类
GLShaderManager shaderManager;
-
准备顶点数据
- 顶点数组:笛卡尔坐标对
GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f, // 左下(-x, -y) blockSize, -blockSize, 0.0f, // 右下(x , -y) blockSize, blockSize, 0.0f, // 右上(x , y) -blockSize, blockSize, 0.0f, // 左上(-x, y) };
- 顶点距离:顶点到坐标轴的距离
GLfloat blockSize = 0.1f;
-
-
初始化工作
-
初始化 GLUT 库:
glutInit(&argc, argv);
-
初始化双缓存窗口:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
-
初始化窗口大小、标题:
glutInitWindowSize(800, 600);
glutCreateWindow("Triangle");
-
-
注册回调函数
- 重塑函数:
glutReshapeFunc(ChangeSize);
- 渲染函数:
glutDisplayFunc(RenderScene);
- 特殊键函数:
glutSpecialFunc(SpecialKeys);
- 重塑函数:
-
设置渲染环境函数
void SetupRC() {
// 设置背景色
glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
// 初始化着色管理器
shaderManager.InitializeStockShaders();
// 批次处理
triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();
}
- RC(Rendering Context):表示渲染环境
- 在任何 OpenGL 函数起作用之前必须创建一个渲染环境
- SetupRC():这是一个 OpenGL 状态机的句柄,具体使用会在后面的章节中讲到
-
启动运行循环函数
glutMainLoop()
- 开启运行循环函数
- 在主窗口被关闭之前都不会返回(退出main函数)
- 一个应用程序中只需要调用一次
- 负责处理所有操作系统特定的消息、按键动作等,知道关闭程序为止
- 确保注册的回调函数能够准备被调用
2-5
这四个个步骤跟环境搭建篇章中的内容基本一致,这里就不再累述。下面主要看看关于图形移动这一块的内容
三、图形移动
1. 处理键盘输入
- 定义步长:
GLfloat stepSize = 0.025f;
- 选择计算顶点:顶点D,左上角顶点,坐标:
(blockX, blockY)
- 计算偏移之后顶点D的位置:
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;
}
2. 处理边缘碰撞问题
-
在上一步处理中,我们选取的顶点是左上角顶点D;
-
当触碰到左上边界时,直接根据D的坐标进行判断处理即可;
blockX = -1.0f; blockY = 1.0f;
-
触碰到右边界时,判断条件是:
blockX + 2*stepSize > 1.0
-
触碰到下边界时,判断条件是:
blockY - blockSize * 2 < -1.0f
//当正方形移动超过最左边的时候
if (blockX < -1.0f) {
blockX = -1.0f;
}
//当正方形移动到最右边时
//1.0 - blockSize * 2 = 总边长 - 正方形的边长 = 最左边点的位置
if (blockX > (1.0 - blockSize * 2)) {
blockX = 1.0f - blockSize * 2;
}
//当正方形移动到最下面时
//-1.0 - blockSize * 2 = Y(负轴边界) - 正方形边长 = 最下面点的位置
if (blockY < -1.0f + blockSize * 2 ) {
blockY = -1.0f + blockSize * 2;
}
//当正方形移动到最上面时
if (blockY > 1.0f) {
blockY = 1.0f;
}
3. 重新计算四个顶点的位置
- A点
- x:与D点x轴一致
- y:在D点下方,
-blockSize*2
vVerts[0] = blockX;
vVerts[1] = blockY - blockSize*2;
- B点
- x:在D点右方,
+blockSize*2
- y:在D点下方,
-blockSize*2
vVerts[3] = blockX + blockSize*2;
vVerts[4] = blockY - blockSize*2;
- C点
- x:在D点右方,
+blockSize*2
- y:与D点y轴一致
vVerts[6] = blockX + blockSize*2;
vVerts[7] = blockY;
- D点
vVerts[9] = blockX;
vVerts[10] = blockY;
计算好正方形的所有点之后,需要通过批次处理类将数据保存
triangleBatch.CopyVertexData3f(vVerts);
4. 提交渲染
这个方法会触发渲染函数
glutPostRedisplay();
四、提交重绘
- 清除缓存区:清除一个或者一组特定的缓存区
- 设置颜色:设置一组浮点数来表示红色
- 提交着色器
- 交换缓存区:将后台缓冲区进行渲染,然后结束后交换给前台
void RenderScene(void)
{
//1.清除一个或者一组特定的缓存区
/*
缓冲区是一块存在图像信息的储存空间,红色、绿色、蓝色和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);
//2.设置一组浮点数来表示红色
GLfloat vRed[] = {1.0,0.0,0.0,1.0f};
//传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
//提交着色器
triangleBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
}
五、平移矩阵变换
上面的demo中,正方形只有4个点,这是很幸运的一件事,因为我们在2D平面中只需要计算8个值。但是如果是400个点的图形,我们需要计算800个值,这对程序员来说就太难了,聪明的程序员肯定有其他解决办法。是的,矩阵变换解决了这个问题。
- 把这个正方形当成一个点,比较胖的点;
- 相对位置是坐标原点 (0, 0);
- 要保证这个胖点不超出屏幕,就是这个胖点的身体的任何一个地方都不能超出屏幕;
点的边界
- 左边:中心点+左边身体(blockSize) 不能超出左边界(-1.0f):
- 右边:中心点+右边身体(blockSize) 不能超出右边界(1.0f)
- 上边:中心点+上边身体(blockSize) 不能超出上边界(1.0f)
- 下边:中心点+下边身体(blockSize) 不能超出下边界(-1.0f)
GLfloat leftBound = -1.0f + blockSize; //左
GLfloat rightBound = 1.0f - blockSize; //右
GLfloat topBound = 1.0f - blockSize; //上
GLfloat bottomBound = -1.0f + blockSize; //下
1. 计算平移距离
有了上面的经验,这里就直接将特殊键回调的内容都贴出来了
- 处理移动
- 碰撞处理
- 提交重绘
void SpecialKeys(int key, int x, int y) {
// 移动步长
GLfloat stepSize = 0.025f;
// 处理移动
if (key == GLUT_KEY_UP) {
yPos += stepSize;
}
if (key == GLUT_KEY_DOWN) {
yPos -= stepSize;
}
if (key == GLUT_KEY_LEFT) {
xPos -= stepSize;
}
if (key == GLUT_KEY_RIGHT) {
xPos += stepSize;
}
// 处理边界问题
GLfloat leftBound = -1.0f + blockSize; //左
GLfloat rightBound = 1.0f - blockSize; //右
GLfloat topBound = 1.0f - blockSize; //上
GLfloat bottomBound = -1.0f + blockSize; //下
// 左边界
if (xPos < leftBound) {
xPos = leftBound;
}
// 右边界
if (xPos > rightBound) {
xPos = rightBound;
}
// 上边界
if (yPos > topBound) {
yPos = topBound;
}
// 下边界
if (yPos < bottomBound) {
yPos = bottomBound;
}
// 手动触发重新渲染
glutPostRedisplay();
}
2. 使用矩阵
- 上面的
单位着色器(GLT_SHADER_IDENTITY)
没有处理矩阵的能力,所以这里要使用平面着色器(GLT_SHADER_FLAT)
- 创建平移矩阵
M3DMatrix44f mTransfromMatrix;
m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0f);
- 使用平移矩阵
shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransfromMatrix, vRed);
- 方法实现如下
void RenderScene(void) {
// 清楚一个或一组特定的缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// 设置一组浮点数来表示红色
GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
// mTransfromMatrix: 平移矩阵
M3DMatrix44f mTransfromMatrix;
// 平移
m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0f);
// 将矩阵结果 提交给固定着色器(平面着色器)中绘制
shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransfromMatrix, vRed);
// 提交着色器
triangleBatch.Draw();
// 将在后台缓冲区进行渲染, 然后在结束时交换到前台
glutSwapBuffers();
}
六、混合矩阵的用法
上面的例子中只表示了平移这一个特性,而我们在处理动画的时候,往往是比较复杂的场景,例如:平移、旋转、缩放等更多操作的组合。
1. 创建三个矩阵变量
- mFinalTransform: 结果矩阵,用来存放多种变化组合的结果
- mTransfromMatrix: 平移矩阵,用来存放平移的变化
- mRotationMartix: 旋转矩阵,用来存放旋转的变化
M3DMatrix44f mFinalTransform, mTransfromMatrix, mRotationMartix;
2. 处理平移矩阵
m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0f);
3. 处理旋转矩阵
static GLfloat yRot = 0.0f;
yRot += 5.0f;// 每次平移时,选择5度
m3dRotationMatrix44(mRotationMartix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);
4. 合并矩阵
将旋转和移动的矩阵结果 合并到mFinalTransform (矩阵相乘)
m3dMatrixMultiply44(mFinalTransform, mTransfromMatrix, mRotationMartix);
5. 使用矩阵
将矩阵结果 提交给固定着色器(平面着色器)中绘制
shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
后面的步骤和上一个例子一样,就不再累述。
带旋转角度的正方形