所要实现的效果图:
想要实现这种效果我们分为两步,1、绘制出一个三角形, 2、让三角形动起来。
一、三角形的绘制
首先导入头文件:
#include "GLShaderManager.h"
#include "GLTools.h"
#include <GLUT/GLUT.h>
定义一个着色管理器及一个简单的批次容器:
GLShaderManager shaderManager;
/** GLTools的一个简单的容器类 */
GLBatch triangleBatch;
在main
函数中初始化GLUT
库,以及注册相应的函数:
int main(int argc,char *argv[])
{
//初始化glut
glutInit(&argc, argv);
//初始化绘制的模式
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH|GLUT_STENCIL);
//初始化窗口大小
glutInitWindowSize(500, 500);
//创建窗口及设置窗口名称
glutCreateWindow("会移动的三角形");
//注册重塑函数 窗口大小发生变化时会调用该函数
glutReshapeFunc(changeSize);
//注册显示函数
glutDisplayFunc(RenderScene);
//初始化一个GLEW库,确保OpenGL API对程序完全可用。
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("error: %s\n", glewGetErrorString(status));
return 1;
}
//设置渲染环境
setupRC();
glutMainLoop();
return 0;
}
-
GLUT_DOUBLE
双缓存窗口,屏幕显示调用glutSwapBuffers()
,绘图指令实际是在离屏缓存区执行的,然后迅速转换成窗口视图,这种方式经常用来生成动画效果。既然存在双缓存窗口必然也存在单缓存窗口GLUT_SINGLE
,屏幕显示调用glFlush()
,将图像直接在当前显示缓存中进行渲染,会存在图形跳动/闪烁问题。 -
GLUT_RGBA
颜色模式。 -
GLUT_DEPTH
深度测试,标志将一个深度缓存区分配为显示的一部分,我们能够执行深度测试。 -
GLUT_STENCIL
确保我们也会有一个可用的模板缓存区。
void changeSize(int w, int h){
glViewport(0, 0, w, h);
}
其函数原型为 glViewport(GLint x,GLint y,GLsizei width,GLsizei height)
x,y 以像素为单位,指定了窗口的左下角位置。,height表示视口矩形的宽度和高度,根据窗口的实时变化重绘窗口,通常x,y 都是为0。
设置渲染环境setupRC()
:
void setupRC()
{
//设置窗口的背景色
glClearColor(0.98f, 0.40f, 0.7f, 1);
//初始化固定着色器
shaderManager.InitializeStockShaders();
//定义三角形的顶点坐标。
GLfloat vVerts[] = {
0.0f,0.2f,0.0f,//这三个值分别代表了坐标系中x,y,z值
-0.2f,0.0f,0.0f,
0.2f,0.0f,0.0f
};
triangleBatch.Begin(GL_TRIANGLES, 3);
//拷贝顶点数据
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();
}
显示函数RenderScene()
:
void RenderScene(void)
{
//1.清除一个或者多个指定的缓存区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
//2.设置一组浮点数来表示红色
GLfloat vRed[] = {1.0,1.00,0.0,0.5f};
//传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
//提交着色器
triangleBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
// glFlush();
}
运行就可以绘制出我们想要的三角形了:
二、用键位控制三角形的移动
首先看一下,三角形平移的坐标变换:
由此可知移动三角形我们只需将顶点数组数据,进行坐标平移变换,重新渲染视图即可实现,三角形的平移。
定义一个三角形移动的函数
void SpecialKeys(int key, int x, int y)
在main函数中注册该函数:
glutSpecialFunc(SpecialKeys);
通过移动顶点坐标实现,三角形的移动:
//由于在SpecialKeys函数和setupRC函数中都需要用到顶点数据 因此我们可以将其定义为全局变量
GLfloat triangleSize = 0.2f;
GLfloat vVerts[] = {
0.0f,triangleSize,0.0f,
-triangleSize,0.0f,0.0f,
triangleSize,0.0f,0.0f,
};
void SpecialKeys(int key, int x, int y){
//移动的步长
GLfloat stepSize = 0.15;(该值为标量,左减右加 上加下减)
GLfloat blockX = vVerts[0];
GLfloat blockY = vVerts[1];
//控制键值实现坐标的左右移动
if (key == GLUT_KEY_UP) blockY += stepSize;//当点击键盘上向上的箭头是 a'点的纵坐标增加stepSize
if (key == GLUT_KEY_DOWN) blockY -= stepSize;
if (key == GLUT_KEY_RIGHT) blockX += stepSize;
if (key == GLUT_KEY_LEFT) blockX -= stepSize;
//边界检测 防止移动出屏幕之外
if (blockX > 1.0f-triangleSize) blockX = 1.0f-triangleSize;
if (blockX < triangleSize-1.0f) blockX = triangleSize-1.0f;
if (blockY > 1.0f) blockY = 1.0f;
if (blockY < triangleSize-1.0f) blockY = triangleSize-1.0f;
//顶点更新
vVerts[0] = blockX;//移动后a‘点的横坐标
vVerts[1] = blockY;//移动后a’点的纵坐标
//以a点为坐标原点 则b点的坐标为(- triangleSize,- triangleSize,0) c点的坐标为( triangleSize,- triangleSize,0),则根据上面移动坐标变换可以得出b‘ c'点的坐标
vVerts[3] = blockX - triangleSize;
vVerts[4] = blockY-triangleSize;
vVerts[6] = blockX + triangleSize;
vVerts[7] = blockY - triangleSize;
triangleBatch.CopyVertexData3f(vVerts);
//顶点坐标移动后 重新绘制三角形
glutPostRedisplay();
}
运行即可得到,三角形如演示图一样的三角形的移动效果,但这样做存在着一个问题,三角形还好我们只需要移动三个点就好,但当遇到不规则的图形时,这样移动显然是不合适的。通过线性代数的知识我们知道,通过矩阵的方式,我们也可以实现坐标的平移变换。
//定义x轴y轴的偏移量
GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;
//重写SpecialKeys函数
void SpecialKeys(int key, int x, int y){
//移动的步长
GLfloat stepSize = 0.2f;
if (key == GLUT_KEY_UP) yPos += stepSize;
if (key == GLUT_KEY_DOWN) yPos -= stepSize;
if (key == GLUT_KEY_RIGHT) xPos += stepSize;
if (key == GLUT_KEY_LEFT) xPos -= stepSize;
//边界检测
if (xPos > 1.0f-triangleSize) xPos = 1.0f-triangleSize;
if (xPos < triangleSize-1.0f) xPos = triangleSize-1.0f;
if (yPos > 1.0f-triangleSize) yPos = 1.0f-triangleSize;
if (yPos < -1.0f) yPos = -1.0f;
glutPostRedisplay();
}
//重写RenderScene函数
void RenderScene(void){
//清空缓存区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//设置颜色
GLfloat color[] = {0.3, 0.4,0.8,1};
//定义一个矩阵
M3DMatrix44f mTransFromMatrix;
/** 平移矩阵
m3dTranslationMatrix44(<#float *m#>, <#float x#>, <#float y#>, <#float z#>)
第一个参数 将用来装平移矩阵的矩阵
第二个参数是x轴的偏移量
第三个参数是y轴的偏移量
第四个参数是z轴的偏移量
*/
m3dTranslationMatrix44(mTransFromMatrix, xPos, yPos, 0.0f);
//GLT_SHADER_IDENTITY是片元着色器 无法进行坐标的平移变换
shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransFromMatrix, color);
triangleBatch.Draw();
glutSwapBuffers();
}
通过运行我们依旧能得到一个会移动的三角形,但通过矩阵的方式远比顶点坐标一个个移动的方式要简单的多。