我的
OpenGL
专题学习目录,希望和大家一起学习交流进步!
- OpenGL学习(一)-- 术语了解
- OpenGL学习(二)-- Xcode 搭建 OpenGL 环境
- OpenGL学习(三)-- OpenGL 基础渲染
- OpenGL学习(四)-- 正面&背面剔除和深度测试
- OpenGL学习(五)-- 裁剪与混合
- OpenGL学习(六)-- 基础纹理
- OpenGL学习(七)-- 基础变化综合练习实践总结
- OpenGL学习(八)-- OpenGL ES 初探(上)
- OpenGL学习(九)-- OpenGL ES 初探(下)GLKit
- OpenGL学习(十)-- 着色语言 GLSL 语法介绍
- OpenGL学习(十一)-- 用 GLSL 实现加载图片
- OpenGL学习(十二)-- OpenGL ES 纹理翻转的策略对比
一、前言
以下我总结了一些最近学习 OpenGL 中常用的一些函数,添加了比较多的注释,既是对自己学习的一个巩固总结,也是防止以后遗忘可以快速查看的记录,同时希望也能帮助到更多在学习 OpenGL 的朋友们。
Xcode 还没有搭建OpenGL环境的朋友可以参照我这篇文章去搭建,OpenGL学习(二)-- Xcode 搭建 OpenGL 环境。 另外还有之前的我总结的一些 OpenGL 基础文章,可以从文章上方的目录查看。
二、代码+注释总结
1、引入的头文件:
#include "GLTools.h" // OpenGL toolkit
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLGeometryTransform.h"
#include "StopWatch.h"
#include <math.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
2、一些常用全局变量的声明:
GLShaderManager shaderManager;// 固定管线管理器
GLMatrixStack modelViewMatrix;// 模型视图矩阵堆栈
GLMatrixStack projectionMatrix;// 投影视图矩阵堆栈
GLFrame cameraFrame;// 观察者位置
GLFrame objectFrame;// 世界坐标位置
GLFrustum viewFrustum;// 投影方式,学名:视景体。用来构造投影矩阵。
GLBatch triangleBatch;// 简单的批次容器,是GLTools的一个简单的容器类。
GLTriangleBatch CC_Triangle;// 三角形批次类
GLTriangleBatch sphereBatch;// 球
GLTriangleBatch torusBatch;// 环
GLTriangleBatch cylinderBatch;// 圆柱
GLTriangleBatch coneBatch;// 锥
GLTriangleBatch diskBatch;// 磁盘
GLGeometryTransform transformPipeline; // 变换管道,专门用来管理投影和模型矩阵的
GLfloat vGreen[] = {0.0f, 1.0f, 0.0f, 1.0f };// 定义一个颜色值,绿色
3、main 函数:程序入口中的设置
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("Triangle");
/*
GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
1)为窗口改变大小而设置的一个回调函数
2)包含OpenGL 渲染的回调函数
*/
//注册重塑函数
glutReshapeFunc(changeSize);
//注册显示函数
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpeacialKeys);
/*
初始化一个GLEW库,确保OpenGL API对程序完全可用。
在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
//设置我们的渲染环境
setupRC();
glutMainLoop();
return 0;
}
4、changeSize (int w, int h) 重塑函数:
重塑函数,为窗口改变大小而设置的一个回调函数,窗口大小改变时,接收新的宽度&高度。 在 main 函数中使用
glutReshapeFunc
函数进行了注册:glutReshapeFunc(changeSize);//注册重塑函数
/*
在窗口大小改变时,接收新的宽度&高度。
*/
void changeSize(int w,int h) {
/*
1、设置窗口坐标
x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
*/
// 防止h变为0
if(h == 0){
h = 1;
}
glViewport(0, 0, w, h);
// 2、
// 如果绘制的是立体图形,还需要设置透视投影
// 透视投影
// 参数1:从顶点方向看去的视场角度(用角度值表示)
// 参数2:宽和高的纵横比
// 参数3:fNear
// 参数4:fFar
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
// 3、
// 上面只是设置了但没有用,我们需要把设置的转化为矩阵,把结果带回去。
// 往投影矩阵堆栈projectionMatrix里加载一个矩阵,从我们的视景体viewFrustum里获取投影矩阵GetProjectionMatrix()
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 4、
// 模型视图矩阵是用来旋转、平移等操作的,这里并没有变化,所以只是加载一个单元矩阵。
// 往模型视图矩阵堆栈modelViewMatrix里加载一个单元矩阵
modelViewMatrix.LoadIdentity();
}
5、KeyPressFunc 点击空格函数:
每次点击空格,切换窗口的标题,并重新渲染图形
//点击空格,切换渲染图形
void KeyPressFunc(unsigned char key, int x, int y) {
if(key == 32) {
nStep++;
if(nStep > 4)
nStep = 0;
}
switch(nStep) {
case 0:
glutSetWindowTitle("Sphere");
break;
case 1:
glutSetWindowTitle("Torus");
break;
case 2:
glutSetWindowTitle("Cylinder");
break;
case 3:
glutSetWindowTitle("Cone");
break;
case 4:
glutSetWindowTitle("Disk");
break;
}
glutPostRedisplay();
}
6、SpecialKeys 函数
点击键盘的上下左右,让世界坐标系发生变化(并不是移动物体本身)
//上下左右,移动世界坐标系
void SpecialKeys(int key, int x, int y) {
if(key == GLUT_KEY_UP)
// 移动世界坐标系,而不是去移动物体。
// 将世界坐标系在X方向移动-5.0
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
if (key == GLUT_KEY_DOWN)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
if (key == GLUT_KEY_LEFT)
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
glutPostRedisplay();
}
7、SetupRC() 函数:
进行必要的初始化,设置我们的渲染环境,在 main
函数中调用。一般在这里是去描述我们要渲染的图形长什么样子。真正去绘制的时候不在这里,而是在 RenderScene()
函数 中,后面会讲解。
// 将上下文中,进行必要的初始化
void SetupRC() {
// 1、设置背景颜色
glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
// 2、初始化固定着色器管理器
shaderManager.InitializeStockShaders();
// 3、绘制立体图形的时候,需要开启深度测试
glEnable(GL_DEPTH_TEST);
// 4、通过GLGeometryTransform管理矩阵堆栈
// 使用transformPipeline 管道管理模型视图矩阵堆栈 和 投影矩阵堆栈(把两个矩阵作为两个参数放进去)
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
// 5、为了让效果明显,将观察者坐标位置Z移动往屏幕里移动15个单位位置
// 参数:表示离屏幕之间的距离。 负数,是往屏幕后面移动;正数,往屏幕前面移动
cameraFrame.MoveForward(-15.0f);
// ----------------------GLTriangleBatch类型--------------------------
// 利用“GLTriangleBatch”类型的三角形批次类构造图形对象,GLTriangleBatch类型封装了很多常用立体图形。
// 1、球
/*
gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
参数1:sphereBatch,三角形批次类对象
参数2:fRadius,球体半径
参数3:iSlices,从球体底部堆叠到顶部的三角形带的数量;其实球体是一圈一圈三角形带组成
参数4:iStacks,围绕球体一圈排列的三角形对数
建议:一个对称性较好的球体的片段数量是堆叠数量的2倍,就是iStacks = 2 * iSlices,参数4是参数3的2倍。
绘制球体都是围绕Z轴,这样+z就是球体的顶点,-z就是球体的底部。
*/
gltMakeSphere(sphereBatch, 3.0, 10, 20);
// 2、环面
/*
gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
参数1:torusBatch,三角形批次类对象
参数2:majorRadius,甜甜圈中心到外边缘的半径
参数3:minorRadius,甜甜圈中心到内边缘的半径
参数4:numMajor,沿着主半径的三角形数量
参数5:numMinor,沿着内部较小半径的三角形数量
*/
gltMakeTorus(torusBatch, 3.0f, 0.75f, 15, 15);
// 3、圆柱
/*
void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
参数1:cylinderBatch,三角形批次类对象
参数2:baseRadius,底部半径
参数3:topRadius,头部半径
参数4:fLength,圆形长度
参数5:numSlices,围绕Z轴的三角形对的数量
参数6:numStacks,圆柱底部堆叠到顶部圆环的三角形数量
*/
gltMakeCylinder(cylinderBatch, 2.0f, 2.0f, 3.0f, 15, 2);
// 4、锥
/*
void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
参数1:cylinderBatch,三角形批次类对象
参数2:baseRadius,底部半径
参数3:topRadius,头部半径
参数4:fLength,圆形长度
参数5:numSlices,围绕Z轴的三角形对的数量
参数6:numStacks,圆柱底部堆叠到顶部圆环的三角形数量
*/
//圆柱体,从0开始向Z轴正方向延伸。
//圆锥体,是一端的半径为0,另一端半径可指定。
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
// 5、磁盘
/*
void gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
参数1:diskBatch,三角形批次类对象
参数2:innerRadius,内圆半径
参数3:outerRadius,外圆半径
参数4:nSlices,圆盘围绕Z轴的三角形对的数量
参数5:nStacks,圆盘外网到内围的三角形数量
*/
gltMakeDisk(diskBatch, 1.5f, 3.0f, 13, 3);
// ------------------------GLBatch类型--------------------------
// 用上面GLTriangleBatch的代码的时候,把下面这段代码先注释掉再运行。两块儿代码本来是分开的,为了方便写在了一起。
//上面的是用的“GLTriangleBatch”类型的三角形批次类,下面我们使用最基础的“GLBatch”类型的三角形批次类绘制金字塔。
// 首先创建三角形批次类,告诉它我们需要多少个顶点,
// 1.函数BeginMesh( GLuint nMaxVerts )// 指定有多少个顶点
CC_Triangle.BeginMesh(300);// 指定有300个顶点
// 2.创建一个顶点
M3DVector3f m[] = {
0.5, 0.0, 0.0,
-0.5, 0.0, 0.0,
0.0, 0.5, 0.0
};
// 3.将顶点复制进去,可以使用copy函数,也可以使用add函数添加
// 参数1:表示顶点坐标值
// 参数2:表示法线坐标值,没有就写NULL
// 参数3:表示纹理坐标值,没有就写NULL
CC_Triangle.AddTriangle(m, NULL, NULL);
//CC_Triangle.CopyVertexData3f
CC_Triangle.End();
CC_Triangle.Draw();
// --------------------------------------------------------------
}
8、RenderScene()显示函数:
在发生任何变化,调用 glutPostRedisplay()
方法时,会调用 RenderScene()
这个函数。
在 main
函数中注册了 RenderScene
显示函数
glutDisplayFunc(RenderScene);
下面我先着重解释一下这几段代码:
- (1)压栈 PushMatrix();
modelViewMatrix.PushMatrix();
这句代码的意思是压栈,如果 PushMatix() 括号里是空的,就代表是把栈顶的矩阵复制一份,再压栈到它的顶部。如果不是空的,比如是括号里是单元矩阵,那么就代表压入一个单元矩阵到栈顶了。
- (2)矩阵相乘 MultMatrix(mObjectFrame)
将 modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到 modelViewMatrix 矩阵堆栈中。
modelViewMatrix.MultMatrix(mObjectFrame);
这句代码的意思是把 模型视图矩阵堆栈 的 栈顶 的矩阵 copy 出一份来和新矩阵进行矩阵相乘,然后再将相乘的结果赋值给栈顶的矩阵。
- (3)出栈PopMatrix();
modelViewMatrix.PopMatrix();
把栈顶的矩阵出栈,恢复为原始的矩阵堆栈,这样就不会影响后续的操作了。下面是我自己总结的一个《矩阵入栈、相乘、出栈》的流程图:
- (4)cameraFrame.GetCameraMatrix(mCamera) :
cameraFrame.GetCameraMatrix(mCamera);
这里和 OC 的语法有些不一样,它的意思是从 cameraFrame 这个 观察者坐标系 中获取矩阵,然后赋值给 mCamera。同理的还有获取 世界坐标位置 的矩阵objectFrame.GetMatrix(mObjectFrame);
。 cameraFrame 和 mCamera 都是GLFrame
类型的结构体。
接下来看一下 RenderScene() 代码:
//召唤场景
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);
//模型视图矩阵栈堆,压栈
modelViewMatrix.PushMatrix();
//获取摄像头矩阵
M3DMatrix44f mCamera;
//从camereaFrame中获取矩阵到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型视图堆栈的 矩阵与mCamera矩阵 相乘之后,存储到modelViewMatrix矩阵堆栈中
modelViewMatrix.MultMatrix(mCamera);
//创建矩阵mObjectFrame
M3DMatrix44f mObjectFrame;
//从ObjectFrame 获取矩阵到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//将modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到modelViewMatrix矩阵堆栈中
modelViewMatrix.MultMatrix(mObjectFrame);
//使用平面着色器
//参数1:类型
//参数2:通过transformPipeline获取模型视图矩阵
//参数3:颜色
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
//判断你目前是绘制第几个图形
// 注意这里传递的是地址 &sphereBatch
switch(nStep) {
case 0:
DrawWireFramedBatch(&sphereBatch);// 球
break;
case 1:
DrawWireFramedBatch(&torusBatch);// 环面
break;
case 2:
DrawWireFramedBatch(&cylinderBatch);// 圆柱
break;
case 3:
DrawWireFramedBatch(&coneBatch);// 锥
break;
case 4:
DrawWireFramedBatch(&diskBatch);// 磁盘
break;
}
// 绘制完毕了,需要把栈顶的矩阵pop出去,不要影响我下一次绘图
modelViewMatrix.PopMatrix();
// Flush drawing commands
glutSwapBuffers();
}
9、DrawWireFramedBatch 函数:
void DrawWireFramedBatch(GLTriangleBatch* pBatch) {
// 平面着色器,绘制三角形
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
// 传过来的参数,对应不同的图形Batch
pBatch->Draw();
// 画出黑色轮廓
glPolygonOffset(-1.0f, -1.0f);
// 开启线段圆滑处理
glEnable(GL_LINE_SMOOTH);
// 开启混合功能
glEnable(GL_BLEND);
// 颜色混合
// 表示源颜色乘以自身的alpha 值,目标颜色乘以1.0减去源颜色的alpha值,这样一来,源颜色的alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减 小。这种情况下,我们可以简单的将源颜色的alpha值理解为“不透明度”。这也是混合时最常用的方式。
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 通过程序点大小模式来设置点的大小
glEnable(GL_POLYGON_OFFSET_LINE);
// 多边形模型(背面、线) 将多边形背面设为线框模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 线条宽度
glLineWidth(2.5f);
// 平面着色器绘制线条
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
// 恢复多边形模式和深度测试
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:
1、《OpenGL超级宝典 第5版》
2、《OpenGL编程指南(第八版)》
转载请备注原文出处,不得用于商业传播——凡几多