在绘制正方形图形跟绘制三角形图形基本上是一样的流程,要用键盘控制图形移动,还是要了解 OpenGL 下键位是如何控制的。
键位的宏
/* function keys */
#define GLUT_KEY_F1 1
#define GLUT_KEY_F2 2
#define GLUT_KEY_F3 3
#define GLUT_KEY_F4 4
#define GLUT_KEY_F5 5
#define GLUT_KEY_F6 6
#define GLUT_KEY_F7 7
#define GLUT_KEY_F8 8
#define GLUT_KEY_F9 9
#define GLUT_KEY_F10 10
#define GLUT_KEY_F11 11
#define GLUT_KEY_F12 12
/* directional keys */
#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
可以很清晰看出每个键位的功能,在 OpennGL 下不光有单键位控制,还有组合键位控制。
下面介绍两种控制图形移动的方式
- 顶点坐标法
- 矩阵变换法
顶点坐标法
顶点坐标法是一个以点即点的方法,为什么这么说呢,因为要换算每一个顶点坐标,通过每一个顶点坐标计算获取最终移动后的顶点坐标。可以想象对于坐标点很多的图形,此方法是不是显得过于笨拙呢。直接上代码:
- 首先还是要初始化配置
GLShaderManager shaderManager;
// 正方形批次容器
GLBatch quaBatch;
// 距离坐标轴的边长
GLfloat blockSize = 0.1;
// 正方形4个顶点坐标
GLfloat vVerts[] = {
-blockSize, -blockSize, 0.0f,
blockSize, -blockSize, 0.0f,
blockSize, blockSize, 0.0f,
-blockSize, blockSize, 0.0f
};
int main(int argc, char *argv[]) {
// 区分顶点坐标法和矩阵变换法
normal = false;
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
/*
* 设置双缓冲区
* GLUT_DOUBLE 双缓冲窗口
* GLUT_RGBA RGBA颜色模式
* GLUT_DEPTH 深度测试
* GLUT_STENCIL 模板缓冲区
*/
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
// 设置窗口大小
glutInitWindowSize(1000, 1000);
// 设置窗口名称
glutCreateWindow("Qua");
// 注册重塑函数
glutReshapeFunc(changeSize);
// 注册显示函数
glutDisplayFunc(renderScene);
// 注册键盘监听
glutSpecialFunc(dealKeys);
/*
* 初始化一个GLEW库,确保OpenGL API对程序完全可用
* 在渲染之前, 检查驱动程序的初始化过程没有问题
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
// 设置渲染环境
setupRC();
// 开启事件循环
glutMainLoop();
return 0;
}
每个方法是做什么的,注释已经写的很清楚了
- 窗口改变设置
/*
* 在窗口大小改变时,接收新的宽度&高度。
*/
void changeSize(int w, int h) {
// 通常x、y都为0
glViewport(0, 0, w, h);
}
- 渲染场景设置
// 每一次图形移动都会调用
void renderScene() {
/*
* 清除一个或者一组特定的缓冲区
* 缓冲区是一块存在图像信息的储存空间,RGBA通常一起作为颜色缓冲区或像素缓冲区引用
* 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.0, 0.0, 0.0, 1.0};
/*
* 传递到存储着色器(固定管线)
* GLT_SHADER_IDENTITY 顶点着色器
*/
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
// 提交着色器
quaBatch.Draw();
// 将后台缓冲区进行渲染,完成后交给前台
glutSwapBuffers();
}
- 注册键位监听
void dealKeys(int key, int x, int y) {
/*
* 可以监听键盘的案件很多包括单键、组合键等
* 我们只需要监听上下左右就可以
* 加上边界测试
*/
// 移动多远
GLfloat stepSize = 0.025;
/*
* d-----c
* | |
* | |
* a-----b
*
* a和d、b和c x轴坐标相等 a和b、c和d y轴坐标相等
*/
// 取d点坐标 通过d点坐标换算a、b、c坐标
GLfloat blockX = vVerts[0];
GLfloat blockY = vVerts[10];
switch (key) {
case GLUT_KEY_UP:
blockY += stepSize;
break;
case GLUT_KEY_DOWN:
blockY -= stepSize;
break;
case GLUT_KEY_LEFT:
blockX -= stepSize;
break;
case GLUT_KEY_RIGHT:
blockX += stepSize;
break;
default:
break;
}
// 左移
if (blockX < -1.0f) {
blockX = -1.0f;
}
// 右移 减去2倍边长
if (blockX > (1.0 - blockSize * 2)) {
blockX = 1.0 - blockSize * 2;
}
// 上移
if (blockY > 1.0f) {
blockY = 1.0f;
}
// 下移
if (blockY < -1.0f + blockSize * 2) {
blockY = -1.0f + blockSize * 2;
}
// 为啥不处理vVerts[2]/vVerts[5]/vVerts[8] 因为是z轴,z轴没有变化
// a
vVerts[0] = blockX;
vVerts[1] = blockY - blockSize * 2;
//b
vVerts[3] = blockX + blockSize * 2;
vVerts[4] = blockY - blockSize * 2;
//c
vVerts[6] = blockX + blockSize * 2;
vVerts[7] = blockY;
//d
vVerts[9] = blockX;
vVerts[10] = blockY;
//需要重新提交渲染
quaBatch.CopyVertexData3f(vVerts);
glutPostRedisplay();
}
可以会有疑问,为什么边长是0.1?在 OpenGL 坐标系中 x、y 、z 轴的范围都是(-1,1),所以边长就是0.1了,是一个比例值。具体的实现流程代码中已经解释的很清楚了,值得注意的是:函数
glutPostRedisplay() 是提交重新渲染,因为图形移动了就要重新绘制,当然还是回再次调用函数 renderScene()。这也是函数 glutMainLoop() 的意义所在。
- 设置渲染环境
void setupRC() {
// 设置背景颜色
glClearColor(1.0, 0.7, 0.7, 1.0);
// 初始化着色器, 没有着色器是没有办法渲染的
shaderManager.InitializeStockShaders();
// 批次提交
quaBatch.Begin(GL_TRIANGLE_FAN, 4);
quaBatch.CopyVertexData3f(vVerts);
quaBatch.End();
};
OpenGL 支持三角形图元装配,示意图如下:
- GL_TRIANGLES 绘制一系列单独的三角形
- GL_TRIANGLES_STRIP 构建当前三角形的顶点的连接顺序依赖于要和前面已经出现过的2个顶点组成三角形的当前顶点的序号的奇偶性。如果是顶点是偶数v2,以v0-v1-v2这样顺序排列。如果是奇数v3,以v2-v1-v3这样顺序排列。这种顺序是为了保证所有的三角形都是按照相同的方向绘制。
- GL_TRIANGLES_FAN 绘制各三角形形成一个扇形序列,以v0为起始点,以v0-v1-v2、v0-v2-v3、v0-v3-v4这样的顺序排列。
为什么说一下三角形图元装配呢,在代码中,用的是GL_TRIANGLES_FAN 宏绘制的正方形,当然也可以使用 GL_QUADS 宏绘制正方形。
这样一个可移动的正方形的实现就完成了。下面看一下效果图
总结一下绘制流程
矩阵变换法
矩阵变换法相当于以点即面,不需要换算每一个顶点坐标。通过一个中心点去换算全部的顶点坐标。绘制的流程跟顶点坐标法差不多,只是有几个地方不同。直接上代码:
- 定义全局变量
// 中心点
GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;
- 坐标变换
void dealKeys(int key, int x, int y) {
/*
* 可以监听键盘的案件很多包括单键、组合键等
* 我们只需要监听上下左右就可以
* 加上边界测试
*/
// 移动多远
GLfloat stepSize = 0.025;
/*
* d-----c
* | |
* | |
* a-----b
*
* a和d、b和c x轴坐标相等 a和b、c和d y轴坐标相等
*/
switch (key) {
case GLUT_KEY_UP:
yPos += stepSize;
break;
case GLUT_KEY_DOWN:
yPos -= stepSize;
break;
case GLUT_KEY_LEFT:
xPos -= stepSize;
break;
case GLUT_KEY_RIGHT:
xPos += stepSize;
break;
default:
break;
}
// 左移
if (xPos < -1.0f + blockSize) {
xPos = -1.0f + blockSize;
}
// 右移
if (xPos > 1.0 - blockSize) {
xPos = 1.0 - blockSize;
}
// 上移
if (yPos > 1.0f - blockSize) {
yPos = 1.0f - blockSize;
}
// 下移
if (yPos < -1.0f + blockSize) {
yPos = -1.0f + blockSize;
}
glutPostRedisplay();
}
这里就不需要 quaBatch.CopyVertexData3f(vVerts) 复制顶点数据了,因为是整体再改变。
- 图形绘制
// 每一次图形移动都会调用
void renderScene() {
/*
* 清除一个或者一组特定的缓冲区
* 缓冲区是一块存在图像信息的储存空间,RGBA通常一起作为颜色缓冲区或像素缓冲区引用
* 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.0, 0.0, 0.0, 1.0};
/*
* 矩阵变换 定义一个4*4的矩阵 M3DMatrix44f 是一个一维数组
* 从类型就能看出来 44代表4*4
* 红 绿 蓝 缩放比例
*/
M3DMatrix44f mTransfromMatrix;
/*
* 得到矩阵变化后的坐标
**/
m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0f);
/*
* GLT_SHADER_FLAT 平面着色器
*/
shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransfromMatrix, vRed);
// 提交着色器
quaBatch.Draw();
// 将后台缓冲区进行渲染,完成后交给前台
glutSwapBuffers();
}
这里增加了矩阵函数,把顶点着色器的宏 GLT_SHADER_IDENTITY 换成了 平面着色器的宏 GLT_SHADER_FLAT。这样就完成了图形矩阵坐标变换。
总结一些绘制流程: