我们来利用OpenGL来画一个正方形,并且上其跟随键盘的上下左右键进行移动
效果:
大概思路就是:
1-初始化OpenGL环境
1.1 glutInit()
函数初始化GLUT库
1.2 glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指
双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
1.3 glutInitWindowSize(800, 600); glutCreateWindow("移动的四边形");
初始化窗口大小和窗口标题
1.4 注册自定义函数
1.4.1 glutReshapeFunc(changeSize);
注册重塑函数:窗口改变时候触发changeSize方法
1.4.2 glutDisplayFunc(RenderScene)
注册显示函数:系统触发或者用户手动触发渲染事件,就会调用RenderScene方法,可以写渲染细节
1.4.3 glutSpecialFunc(SpecialKeys);
注册 特殊函数:当用户点击尚上下左右任意某一按键时候,会触发SpecialKeys函数
1.5 判断初始化环境成功与否
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
1.6 setupRC()
设置渲染会环境:setupRC函数写具体代码
1.7 glutMainLoop();
设置GLUT的主循环:GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。
2-设置OpenGL渲染环境
即上面的1.6阐述
2.1 glClearColor(0.98f, 0.40f, 0.7f, 1);
设置清屏颜色(背景颜色)
2.2 shaderManager.InitializeStockShaders();
需要提前注册着色管理器GLShaderManager shaderManager;
2.3 GLfloat vVerts[] = { -blockSize,blockSize,0.0f,//d blockSize,blockSize,0.0f, //a blockSize,-blockSize,0.0f,//b -blockSize,-blockSize,0.0f,//c };
四边形的顶点数据数据,一般可以写这里也可写全局
2.4
triangleBatch.Begin(GL_POLYGON, 4);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();
提交批次的顶点信息到存储着色器中,下面是几种常见的宏定义
GL_TRIANGLE:三角形
GL_TRIANGLE_FAN:三角形组成的扇子,就是多边形
GL_POLYGON 多边形
等等;下篇会详细介绍
3-设置向GL中注册的自定义函数
3.1 changeSize
/*
在窗口大小改变时,接收新的宽度&高度。
*/
void changeSize(int w,int h)
{
/*
x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
*/
glViewport(0, 0, w, h);
}
3.2 SpecialKeys
void SpecialKeys(int key,int x,int y)
为什么3个参数?是根据定义 SpecialKeys方法的 glutSpecialFunc函数内部去看
思路一:移动某个点,让其他3个点跟着运动;
特点:好理解
确点:多个点需要处理多次,麻烦
.....省略的伪代码,详情见下面代码
思路一或者思路二互斥,只能两者存一。
思路二: 利用矩阵,移动图像内部中的任意某点,其他点跟着矩阵运动,
特点:只用移动某一点,简便
.....省略的伪代码,详情见下面代码
3.3 RenderScene
3.3.1 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
清除一个或者一组特定的缓存区
【3.3.2* 思路一写法】shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed)
【思路一的写法】:传递到存储着色器,【其中GLT_SHADER_IDENTITY
单元着色器,只是简单地使⽤用默认笛卡尔坐标系(坐标范围(-1.0, 1.0))。所有的⽚片段都应⽤用同⼀一种颜⾊色,⼏几何图形为 实⼼心和未渲染的。】
【 3.3.2** 思路二写法】
M3DMatrix44f mTransformMatrix_f,mRotationMatrix_f,mResultMatrix;
思路二:使用矩阵移动,所以不使用单元存储着色器;
-
matrix
:矩阵。计算机中矩阵用数组表示,一般使用一维数组表示,效率高 -
44f
:表示4*4: 4行4列. f 表示float
m3dTranslationMatrix44(mTransformMatrix_f, xPosition, yPosition, 0.0f);
上下左右移动 就是平移操作 。平移关键字:Translation。旋转关键字;Rotat.... m3dTranslationMatrix44(平移后数据存储在那个矩阵中,x轴方向的平移,y轴方向的平移,z轴方向的平移)
3.3.3 triangleBatch.Draw();
提交着色器
3.3.4 glutSwapBuffers();
将后台缓冲区进行渲染,然后结束后交换给前台 (因为 在开始的设置openGL 窗口的时候,glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。)
详情见代码
#include "GLShaderManager.h"
/*
`#include<GLShaderManager.h>` 移入了GLTool 着色器管理器(shader Mananger)类。没有着色器,我们就不能在OpenGL(核心框架)进行着色。着色器管理器不仅允许我们创建并管理着色器,还提供一组“存储着色器”,他们能够进行一些初步䄦基本的渲染操作。
*/
#include "GLTools.h"
/*
`#include<GLTools.h>` GLTool.h头文件包含了大部分GLTool中类似C语言的独立函数
*/
#include <GLUT/GLUT.h>
/*
在Mac 系统下,`#include<glut/glut.h>`
在Windows 和 Linux上,我们使用freeglut的静态库版本并且需要添加一个宏
*/
//定义一个,着色管理器
GLShaderManager shaderManager;
//简单的批次容器,是GLTools的一个简单的容器类。
GLBatch triangleBatch;
//边长的 1/2
GLfloat blockSize = 0.1f;
//四边形的顶点数据数据
GLfloat vVerts[] = {
-blockSize,blockSize,0.0f,//d
blockSize,blockSize,0.0f, //a
blockSize,-blockSize,0.0f,//b
-blockSize,-blockSize,0.0f,//c
};
/**
y+
|
d | a
-------|----------x+
c | b
|
*/
//移动思路二:
//定义移动默认观察点,原点(0,0)
GLfloat xPosition = 0.0f;
GLfloat yPosition = 0.0f;
/*
在窗口大小改变时,接收新的宽度&高度。
*/
void changeSize(int w,int h)
{
/*
x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
*/
glViewport(0, 0, w, h);
}
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);
//思路二:使用矩阵移动,所以不使用单元存储着色器
//思路二的时候就不能使用 GLT_SHADER_IDENTITY单元存储着色器了,
//matrix:矩阵。计算机中矩阵用数组表示,一般使用一维数组表示,效率高。
//44f:表示4*4: 4行4列. f 表示float
M3DMatrix44f mTransformMatrix_f,mRotationMatrix_f,mResultMatrix;
//上下左右移动 就是平移操作 。平移关键字:Translation。旋转关键字;Rotat.... m3dTranslationMatrix44(平移后数据存储在那个矩阵中,x轴方向的平移,y轴方向的平移,z轴方向的平移)
m3dTranslationMatrix44(mTransformMatrix_f, xPosition, yPosition, 0.0f);
/* 思路二的 拓展: 平移加旋转
//如果要再加上选择效果,就增加一个旋转的矩阵
//参数一:表示结果存储在那个矩阵中;参数二:弧度。参数三/四/五:绕那个轴旋转,0.0f表示NO,1.0f表示YES
static GLfloat zDeg = 0.0f; //static 保证每次程序运行时候,初始值都是0.0f;
m3dRotationMatrix44(mRotationMatrix_f, m3dDegToRad(zDeg), 0.0f, 0.0f, 1.0f);
zDeg++;
//将平移和旋转效果叠加,并且存储在 mResultMatrix 矩阵中
m3dMatrixMultiply44(mResultMatrix, mTransformMatrix_f, mRotationMatrix_f);
shaderManager.UseStockShader(GLT_SHADER_FLAT,mResultMatrix, vRed);
//数学角度和弧度关系:
度:两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆周长的360分之一时,两条射线的夹角的大小为1度。
弧度:两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度。单位:rad,常省略不写。
π是π弧度,180是180度。我要化成什么单位,就要把有这个单位的放在分子上。也就是说我要化成弧度,就要把π弧度放在分子上--乘以π/180
角度=180°×弧度÷π = 弧度÷π × 180° =》 m3dRadToDeg(弧度)
弧度=角度×π÷180° =》 m3dDegToRad(角度)
π/6 = π弧度/6 =(π/π * 180)/6 = 30° = 30度
*/
//C++中的表示:GLShaderManager::UserStockShader(GLT_SHADER_FLAT,GLfloat mvp[16],GLfloat vColor[4]);
//GLT_SHADER_FLAT:平面着色器;GLfloat mvp[16]:一维数组,也就是4*4的矩阵。经常被称为“模型视图投影矩阵”
shaderManager.UseStockShader(GLT_SHADER_FLAT,mTransformMatrix_f, vRed);
/**
常用的着色器:
GLT_SHADER_IDENTITY = 0,
单位着色器:只是简单地使⽤用默认笛卡尔坐标系(坐标范围(-1.0, 1.0))。所有的⽚片段都应⽤用同⼀一种颜⾊色,⼏几何图形为 实⼼心和未渲染的。
GLT_SHADER_FLAT,
平面着色器:单元着色器拓展,允许使用矩阵
GLT_SHADER_SHADED,
上色着色器:在⼏几何图形中应⽤用的变换矩阵。需要设置存储着⾊色器器的GLT_ATTRIBUTE_VERTEX(顶点分量量) 和GLT_ATTRIBUTE_COLOR(颜⾊色分量量)2个属性。颜⾊色值将被平滑地插⼊入顶 点之间(平滑着⾊色)
GLT_SHADER_DEFAULT_LIGHT,
默认光源着色器: 一般是4个参数 参数2:模型视图矩阵 参数3:投影矩阵 参数4:颜⾊色值这种着⾊色器器,是对象产⽣生阴影和关照的效果。需要设置存储着⾊色器器的GLT_ATTRIBUTE_VERTEX(顶点分量量) 和GLT_ATTRIBUTE_NORMAL(表⾯面法线)
GLT_SHADER_POINT_LIGHT_DIFF,
点光源着色器: 使用此着色器,一般5个参数,参数2:模型视图矩阵 参数3:投影矩阵 参数4:视点坐标光源位置 参数5:颜⾊色值
点光源着⾊色器器和默认光源着⾊色器器很相似,区别在于:光源位置是特定的。 同样需要设置存储着⾊色器器的 GLT_ATTRIBUTE_VERTEX(顶点分量量) 和 GLT_ATTRIBUTE_NORMAL(表⾯面法线)
GLT_SHADER_TEXTURE_REPLACE,
纹理替换矩阵
着⾊色器器通过给定的模型视图投影矩阵,使⽤用绑定到 nTextureUnit (纹理理 单元) 指定纹理理单元的纹理理对⼏几何图形进⾏行行变化。 ⽚片段颜⾊色:是直接从纹理理样本中直接获取的。
需要设置存储着⾊色器器的 GLT_ATTRIBUTE_VERTEX(顶点分量量) 和 GLT_ATTRIBUTE_NORMAL(表⾯面法线)
GLShaderManager::UserStockShader(GLT_SHADER_DEFAULT_LIGHT_DIEF ,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3] ,GLfloat vColor[4]);
GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_REPLACE,GL float mvMatrix[16],GLint nTextureUnit);
GLT_SHADER_TEXTURE_MODULATE,
纹理理调整着⾊色器器
将⼀一个基本⾊色 乘以 ⼀一个取⾃自纹理理单元 nTextureUnit 的纹理理。 需要设置存储着⾊色器器的 GLT_ATTRIBUTE_VERTEX(顶点分量量) 和 GLT_ATTRIBUTE_TEXTURE0(纹理理坐标)
GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_MODULATE,G Lfloat mvMatrix[16],GLfloat vColor[4],GLint nTextureUnit);
GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
纹理理光源着⾊色器器
将一个纹理理通过漫反射照明计算机进⾏调整(相乘)。光线在视觉空间中 的位置是给定的。
需要设置存储着⾊色器器的 GLT_ATTRIBUTE_VERTEX(顶点分量量) 和
GLT_ATTRIBUTE_TEXTURE0(纹理理坐标)、GLT_ATTRIBUTE_NORMAL(表⾯面法 线)
GLShaderManager::UserStockShader(GLT_SHADER_TEXTURE_POINT_LIGH T_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLight Pos[3],GLfloat vBaseColor[4],GLint nTextureUnit);
参数1:纹理理光源着⾊色器器 参数2:投影矩阵 参数3:视觉空间中的光源位置 参数4:⼏几何图形的基本⾊色 参数5:将要使⽤用的纹理理单元
GLT_SHADER_TEXTURE_RECT_REPLACE,
GLT_SHADER_LAST
*/
//提交着色器
triangleBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
}
///> 为什么3个参数?是根据定义 SpecialKeys方法的 glutSpecialFunc函数内部去看
void SpecialKeys(int key,int x,int y){
/**
思路一:移动某个点,让其他3个点跟着运动;
特点:好理解
确点:多个点需要处理多次,麻烦
//移动一步的长度(按一次上,下,左,右移动的标准距离)
GLfloat stepSize = 0.025f;
//任意取某个点的值作为移动点,例如: d点
GLfloat blockX = vVerts[0];
GLfloat blockY = vVerts[1];
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;
}
//边界碰撞检测(碰到边界后,就不能移动出边界了)
//坐标系(-1,1)
//移动到最左侧时候
if (blockX < -1.0f) {
blockX = -1.0f;
}
//最右侧
if (blockX > 1.0f-2*blockSize) {
blockX = 1.0f - 2*blockSize;
}
//最上侧
if (blockY > 1.0f) {
blockY = 1.0f;
}
//最下侧
if (blockY < -1.0+ 2*blockSize) {
blockY = -1.0 + 2*blockSize;
}
//d
vVerts[0] = blockX;
vVerts[1] = blockY;
//其他的点
//a 点
vVerts[3] = blockX + 2*blockSize;
vVerts[4] = blockY;
//b
vVerts[6] = blockX + 2*blockSize;
vVerts[7] = blockY-2*blockSize;
//c
vVerts[9] = blockX;
vVerts[10] = blockY - 2*blockSize;
//批次容器 复制各个顶点 保存起来
triangleBatch.CopyVertexData3f(vVerts);
//触发渲染,即下面代码会触发 RenderScene方法,进行图像渲染
glutPostRedisplay();
*/
/**
思路二: 利用矩阵,移动图像内部中的任意某点,其他点跟着矩阵运动,
特点:只用移动某一点,简便
*/
//移动一步的长度(按一次上,下,左,右移动的标准距离)
GLfloat stepSize = 0.025f;
switch (key) {
case GLUT_KEY_UP:
yPosition += stepSize;
break;
case GLUT_KEY_DOWN:
yPosition -= stepSize;
break;
case GLUT_KEY_LEFT:
xPosition -= stepSize;
break;
case GLUT_KEY_RIGHT:
xPosition += stepSize;
break;
default:
break;
}
//边界碰撞检测(碰到边界后,就不能移动出边界了)
//坐标系(-1,1)
//移动到最左侧时候
if (xPosition < -1.0f + blockSize) {
xPosition = -1.0f + blockSize;
}
//最右侧
if (xPosition > 1.0f - blockSize) {
xPosition = 1.0f - blockSize;
}
//最上侧
if (yPosition > 1.0f - blockSize) {
yPosition = 1.0f - blockSize;
}
//最下侧
if (yPosition < -1.0+ blockSize) {
yPosition = -1.0 + blockSize;
}
//此处不需要复制各个顶点信息保存起来,但是需要在渲染的地方处理矩阵信息
//触发渲染,即下面代码会触发 RenderScene方法,进行图像渲染
glutPostRedisplay();
}
void setupRC()
{
//设置清屏颜色(背景颜色)
glClearColor(0.98f, 0.40f, 0.7f, 1);
//没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一个渲染管理器。
//在前面的课程,我们会采用固管线渲染,后面会学着用OpenGL着色语言来写着色器
shaderManager.InitializeStockShaders();
//指定顶点
//在OpenGL中,三角形是一种基本的3D图元绘图原素。
// GLfloat vVerts[] = {
// -0.5f,0.0f,0.0f,
// 0.5f,0.0f,0.0f,
// 0.0f,0.5f,0.0f
// };
//画 四边形 需要不同的宏定义
//GL_TRIANGLE:三角形。 GL_TRIANGLE_FAN:三角形组成的扇子,就是多边形
//GL_TRIANGULAR_NV: TRIANGULAR也是三角形的意思,这个宏定义,用在此处不显示图形
//GL_TRIANGLE_STRIP:三角形_剥离,脱去. 能行成图形,但是不是正常四边形,而是四边形的一个边有一个三角形
//GL_POLYGON: POLYGON:多边形的意思
triangleBatch.Begin(GL_POLYGON, 4);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();
}
int main(int argc,char *argv[])
{
//设置当前工作目录,针对MAC OS X
/*
`GLTools`函数`glSetWorkingDrectory`用来设置当前工作目录。实际上在Windows中是不必要的,因为工作目录默认就是与程序可执行执行程序相同的目录。但是在Mac OS X中,这个程序将当前工作文件夹改为应用程序捆绑包中的`/Resource`文件夹。`GLUT`的优先设定自动进行了这个中设置,但是这样中方法更加安全。
*/
gltSetWorkingDirectory(argv[0]);
//初始化GLUT库,这个函数只是传说命令参数并且初始化glut库
glutInit(&argc, argv);
/*
初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指
双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
--GLUT_DOUBLE`:双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式,经常用来生成动画效果;
--GLUT_DEPTH`:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试;
--GLUT_STENCIL`:确保我们也会有一个可用的模板缓存区。
深度、模板测试后面会细致讲到
*/
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
//GLUT窗口大小、窗口标题
glutInitWindowSize(800, 600);
glutCreateWindow("移动的四边形");
/*
GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
1)为窗口改变大小而设置的一个回调函数
2)包含OpenGL 渲染的回调函数
*/
//注册重塑函数
glutReshapeFunc(changeSize);
//注册显示函数
glutDisplayFunc(RenderScene);
//注册 特殊函数
glutSpecialFunc(SpecialKeys);
/*
初始化一个GLEW库,确保OpenGL API对程序完全可用。
在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
//设置我们的渲染环境
setupRC();
glutMainLoop();
return 0;
}