在两个项目开始前,先简单的介绍一下准备工作:
1、搭建OpenGL环境(若使用Xcode搭建环境可以参考我简写搭建描述)
2、了解两个容器:着色管理器(GLShaderManager)和批次类容器(本文使用的是GLTools)
3、定义三个重要的函数
1、changeSize函数:通过glutReshaperFunc(函数名)注册为重塑函数。当屏幕大小发生变化或者第一次创建窗口时,会调用该函数改变窗口大小
2、RenderScene函数:通过glutDisplayFunc(函数名)注册为显示渲染函数。当屏幕发生改变或者开发者主动渲染会调用此函数,用来实现数据->渲染过程
3、setupRC函数:设置你需要渲染的图形的相关顶点数据、颜色数据等数据准备工作。
4、main函数:程序入口。是面向过程编程.所以你会发现利利⽤用OpenGL处理图形/图像都是链式形式.以及基于OpenGL封装的图像处理理框架也是链式编程
渲染三角形:
先上代码:
#include "GLTools.h"
#include "glew.h"
#include "GLShaderManager.h"
#include <GLUT/GLUT.h>
//定义一个着色管理器
GLShaderManager shaderManager;
//简单的批次容器,是GLTools的一个简单的容器类
GLBatch triangleBatch;
/*
在窗口大小改变时,接收新的宽度和
*/
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,1.0,0.0,0.5f};
//传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
//提交着色器
triangleBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
}
void setupRC(){
//设置清屏颜色(背景颜色)
glClearColor(0.98f,0.5f,0.6f,1.0);
//没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一个渲染管理器。
//在前面的课程,我们会采用固管线渲染,后面会学着用OpenGL着色语言来写着色器
shaderManager.InitializeStockShaders();
//指定顶点
//在OpenGL中,三角形是一种基本的3D图元绘图原素。
GLfloat vVerts[]={
-0.5f,0.0f,0.0f,
0.5f,0.0f,0.0f,
0.5f,0.5f,0.0f,
// -0.5,0.5f,0.0f
};
triangleBatch.Begin(GL_TRIANGLES, 3);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();
}
int main(int argc,char *argv[]){
//初始化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_STENCIL);
//GLUT窗口大小、窗口标题
glutInitWindowSize(800, 600);
glutCreateWindow("Triangle");
/*
GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
1)为窗口改变大小而设置的一个回调函数
2)包含OpenGL 渲染的回调函数
*/
//注册重塑函数
glutReshapeFunc(changeSize);
//注册显示函数
glutDisplayFunc(RenderScene);
/*
初始化一个GLEW库,确保OpenGL API对程序完全可用。
在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
*/
GLenum status = glewInit();
if(GLEW_OK != status){
printf("GLEW Error:%s\n",glewGetErrorString(status));
}
//设置我们的渲染环境
setupRC();
glutMainLoop();
return 0;
}
上面代码有详细的注释,这边就不再对代码进行多余的解释,我们这边介绍一下程序的执行过程(通过lldb断点调试):
程序的执行会从main函数开始执行:
1、对GLUT库初始化、初始化缓冲窗口、创建窗口大小,标题,注册重塑函数和显示函数。
2、执行setuoRC函数,执行函数内设置的数据对窗口进行渲染(清除屏幕颜色,对着色器进行初始化,设置顶点数据,批次类容器将数据传递到着色器)。
3、执行changeSize函数,新建窗口
4、执行RenderScene函数,清理缓存区,使用存储着色器绘制图形。
正方形键位移动
正方形的绘制和三角形的绘制相差不太多,主要是移动功能该如何去实现。下面我们去探究一下。
先上代码
#include "GLShaderManager.h"
#include "GLTools.h"
#include <GLUT/GLUT.h>
//定义一个着色器
GLShaderManager shaderManager;
//简单的批次容器,是GLTools的一个简单的容器类
GLBatch squareBatch;
//blockSize边长
GLfloat blockSize = 0.1f;
//正方形的4个坐标点
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;
/*
在窗口大小改变时。接收新的宽度&高度
*/
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:指示模板缓冲区
*/
#pragma mark - 方法一
/*
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
//设置一组浮点数来表示颜色
GLfloat vColor[] = {0.4,0.6,0.8,1.0f};
//传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vColor);
//提交着色器
squareBatch.Draw();
//在开始的设置openGL 窗口的时候,我们指定要一个双缓冲区的渲染环境。这就意味着将在后台缓冲区进行渲染,渲染结束后交换给前台。这种方式可以防止观察者看到可能伴随着动画帧与动画帧之间的闪烁的渲染过程。缓冲区交换平台将以平台特定的方式进行。
//将后台缓冲区进行渲染,然后结束后交换给前台
glutSwapBuffers();
*/
#pragma mark - 方法二
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
GLfloat vColor[] = {1.0f,1.0f,1.0f,0.0f};
M3DMatrix44f mFinalTransform,mTransfromMatrix,mRotationMartix;
//平移
m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0f);
//每次平移时,旋转5度
static float yRot = 0.0f;
yRot += 5.0f;
m3dRotationMatrix44(mRotationMartix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);
//将旋转和移动的矩阵结果合并到mfinalTransfrom(矩阵相乘)
m3dMatrixMultiply44(mFinalTransform, mTransfromMatrix, mRotationMartix);
//将矩阵结果提交给固定着色器(平面着色器)中绘制
shaderManager.UseStockShader(GLT_SHADER_FLAT,mFinalTransform,vColor);
squareBatch.Draw();
//执行交换缓冲区
glutSwapBuffers();
}
void setupRC(){
//设置清屏颜色(背景颜色)
glClearColor(0.9f, 0.6f, 0.2f, 1.0f);
//没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一个渲染管理器。
//在前面的课程,我们会采用固管线渲染,后面会学着用OpenGL着色语言来写着色器
shaderManager.InitializeStockShaders();
//四个顶点
squareBatch.Begin(GL_TRIANGLE_FAN, 4);
squareBatch.CopyVertexData3f(vVerts);
squareBatch.End();
}
void SpecialKeys(int key,int x,int y){
GLfloat stepSize = 0.025f;
#pragma mark - 方法一
/*
GLfloat blockx = vVerts[0];
GLfloat blocky = vVerts[10];
printf("blockx:%f,blocky:%f",blockx,blocky);
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;
}
//触碰边界
//当正方形移动超过最左边
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;
}
printf("blockX = %f\n",blockx);
printf("blockY = %f\n",blocky);
//替换坐标值
vVerts[0] = blockx;
vVerts[1] = blocky-blockSize*2;
printf("左下(%f,%f)\n",vVerts[0],vVerts[1]);
vVerts[3] = blockx + blockSize*2;
vVerts[4] = blocky - blockSize*2;
printf("右下(%f,%f)\n",vVerts[3],vVerts[4]);
vVerts[6] = blockx + blockSize*2;
vVerts[7] = blocky;
printf("右上(%f,%f)\n",vVerts[6],vVerts[7]);
vVerts[9] = blockx;
vVerts[10] = blocky;
printf("左上(%f,%f)\n",vVerts[9],vVerts[10]);
squareBatch.CopyVertexData3f(vVerts);
glutPostRedisplay();
*/
#pragma mark - 方法二
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;
}
//碰撞检测
if(xPos<(-1.0f+blockSize)){
xPos = -1.0f+blockSize;
}
if(xPos>(1.0f-blockSize)){
xPos = 1.0f-blockSize;
}
if(yPos<(-1.0f+blockSize)){
yPos = -1.0f+blockSize;
}
if(yPos>(1.0f-blockSize)){
yPos = 1.0f-blockSize;
}
glutPostRedisplay();
}
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(500, 500);
glutCreateWindow("square block");
/*
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));
}
//设置我们的渲染环境
setupRC();
glutMainLoop();
return 0;
}
在上面整个代码过程中,利用了两种方法去实现正方形的移动,第一种是通过计算各个顶点的坐标去实现正方形的移动,第二种方法在移动过程中另外加了一个旋转功能。
下面来介绍一下一些难以理解的部分代码:
1、绘制正方形我们采用了矩阵的方式去实现,首先定义了正方形的四个顶点;其中,窗口的正中间为坐标原点(0,0)。
2、第一种方法:
在RenderScene函数中,我们利用了单元着色器的笛卡尔坐标系去绘制正方形,通过改变4个顶点的坐标,然后调用glutPostRedisplay方法重绘去实现正方形的移动。
setupRC函数只是对正方形数据的渲染。
在SpecialKeys中,主要是改变四个坐标的位置去实现正方形移动和判断坐标值去判断是否到达窗口边界:通过传入的key的值,判断按键是哪一个,然后改变vVerts数组中的值。
在进行碰撞检测时,在左边的和在最上面时,我们只需要显示真实的坐标值,但是在右边和最下面时,需要用最大的边界值减去正方形的宽高。
在替换坐标值时,也是需要对y的负半轴和x的正半轴进行计算,需要利用半轴的长度减去正方形的宽高。
3、第二种方法
第二种方式使用了平面着色器绘制正方形。然后利用了矩阵的方式去移动正方形方块。
首先定义了两个偏移的全局变量,根据移动方向,计算移动距离。
在判断碰撞时,我们需要先了解偏移距离为多少时,才是到达边界,从最开始我们知道,x,y的偏移值都是为0,然后在移动过程中,我们,我们是利用xPos和yPos的值加上stepSize。因此,我们可以得出偏移的距离其实就是半个正方形的宽高加上移动的多少个stepSize的距离。因此,在每个最边界时,都是半个轴的边界值加上或者减去半个正方形的宽高。
移动执行过程,当我们按上下左右键时,首先都会调用SpecialKeys这个方法去判断坐标位置或者偏移值
然后调用RenderScene方法清除缓冲区,调用着色器,重绘图形。
网友评论