一、OpenGL(规范)
Open Graphic Library。
是一个由Khronos组织维护的规范(Specification),它严格规定了函数应该如何执行,输出值。这类函数是用来操作图形硬件,将3D坐标构成的模型显示到2D坐标上,并使得人眼看上去有3D的感觉。
就好比提供了一个C头文件,里面有所有函数的声明以及功能解释。
二、OpenGL(库)
OpenGL的具体实现,也可以叫OpenGL核心库,都是gl开头的函数,由各生产厂商专门针对自己的图形硬件(显卡)根据OpenGL规范开发并维护。常见的生产厂商有:
公司 | 显卡 |
---|---|
Imagination | Power VR系列 |
高通 | Adreno系列 |
ARM | Mali系列 |
NVIDIA | GeForce系列 |
AMD(ATI) | Radeon |
当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL,这就是更新显卡驱动的原因。
三、OpenGL常见工具库
1、glut
OpenGL Utility Toolkit。
OpenGL工具库,都是glut开头的函数,对gl核心库函数的部分封装,主要功能是窗口管理、鼠标键盘事件处理、封装了一些创建常见几何图形(长方体、球、茶壶)的函数。早已停止维护。
2、freeglut
glut的开源版本(重新实现),完全兼容glut,是glut的代替品,但是bug较多,稳定性欠佳。
3、glew
The OpenGL Extension Wrangler Library。
glut或者freegult主要是OpenGL 1.0的基本函数功能;glew是使用OpenGL 2.0之后的一个工具函数库。
不同的显卡公司,也会发布一些只有自家显卡才支持的扩展函数,你要想用这些涵数,不得不去寻找最新的glext.h,有了GLEW扩展库,你就再也不用为找不到函数的接口而烦恼,因为GLEW能自动识别你的平台所支持的全部OpenGL高级扩展函数。也就是说,只要包含一个glew.h头文件,你就能使用gl,glu,glext,wgl,glx的全部函数。
glew包含了OpenGL所需的核心。前面已经说过由显卡生产商来实现OpenGL,那么系统如何才能找到这些实现好的函数呢?而且不同的平台函数存放地方还不同,文件结构也不同。有没有一种方式能够自动找到OpenGL的函数?这就是glew的作用:用来找openGL的函数,并初始化,这样我们就能直接调用OpenGL的函数了。
4、glfw
Graphics Library Framework。
轻量、开源、跨平台的OpenGL工具库,代替glut/freeglut,主要功能有管理窗口和OpenGL上下文(context)、处理输入和事件。
5、glad
Multi-Language GL/GLES/EGL/GLX/WGL Loader-Generator based on the official specs。
GLAD是继GL3W,GLEW之后,当前最新的用来访问OpenGL规范接口的第三方库。glew的升级版。
简化gl函数调用
由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。取得地址的方法因平台而异,在Windows上会是类似这样:
// *******************************************************
// 不借助glad,原始地调用gl函数
// *******************************************************
// windows平台上, 如何动态调用gl核心函数
typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*); // 定义函数原型:我们要调用的函数长什么样子
// 找到正确的函数并赋值给函数指针
GL_GENBUFFERS glGenBuffers = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");
GLuint buffer = 0;
glGenBuffers(1, &buffer); // 现在函数可以被正常调用了
// *******************************************************
// 借助glad,一次性加载全部的gl函数,后面即可直接使用
// *******************************************************
if( !gladLoadGLLoader( ( GLADloadproc ) glfwGetProcAddress ) )
{
cout << "Failed to initialize GLAD" << endl;
return false;
}
else
{
// load all OpenGL function pointers successfully.
}
6、总结
- 窗口管理
- 老产品:glut、freeglut
- 替代品:glfw
- 函数加载
- 老产品:glew
- 替代品:glad
- 项目开发,通常有三种组合
- freeglut + glew,经典,比较老。
- glfw + glew
- glfw + glad,新潮。
四、Core-profile和Immediate mode
Immediate mode,立即渲染模式,也叫固定管线模式。使用方便,功能单一,不能满足现代需求。就好比提供的函数,我们只能通过输入参数来改变结果,并不是添加的自己的逻辑来动态修改结果(这可以实现很多很棒的特效和功能)。
Core-profile,核心模式应运而生,使用门槛高但功能灵活,更高效,OpenGL 3.2开始,规范文档开始废弃立即渲染模式,并鼓励开发者在OpenGL的核心模式(Core-profile)下进行开发,这个分支的规范完全移除了旧的特性。五、扩展
OpenGL支持扩展(Extension)机制,当一个显卡公司提出一个新特性或者渲染上的大优化,通常会以扩展的方式在驱动中实现。打个比方,某显卡公司开发了一个“狂霸酷炫叼炸天”的新功能,但并不在OpenGL规范中,应用程序可以通过扩展机制“尝鲜”。如果这个新功能很受欢迎,那么它将可能会被加入到下个版本的OpenGL规范中。具体如何“尝鲜”: ```cpp
if(GL_ARB_extension_name) { // 使用硬件支持的全新的现代特性 } else { // 不支持此扩展: 用旧的方式去做 }
<a name="vJqln"></a>
# 六、状态机
状态机是一组状态变量的集合,OpenGL就是通过状态机模型来追踪每个状态变量,使得一个状态值被设置之后,就会一直保持这个状态,直到下次被相关函数修改。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:
- **设置选项**
- **操作缓冲**
- **使用当前OpenGL Context渲染**
假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。<br />当使用OpenGL的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。<br />举个例子:
```cpp
GLubyte *version = glGetString(GL_VERSION); // 获得当前OpenGL版本
GLubyte *vendor = glGetString(GL_VENDOR); // 获得显卡厂商
GLubyte *renderer = glGetString(GL_RENDERER); // name of renderer,不同硬件平台不同,且不会变化。
// vendor && renderer 唯一确定平台(uniquely specify a platform)。
// 以下三个函数用于获取或者修改只有开/闭值的状态变量
void glEnable( GLenum capability ); //状态变量capability设置为打开
void glDisable( GLenum capability ); //状态变量capability设置为关闭
GLboolean glIsEnabled(GLenum capability); // 是否打开
// 以下常见函数用于获取或修改有多个可能值的状态变量
// return the value or values of a selected parameter
void glGetBooleanv (GLenum pname, GLboolean * params);
void glGetDoublev (GLenum pname, GLdouble * params);
void glGetFloatv (GLenum pname, GLfloat * params);
void glGetIntegerv (GLenum pname, GLint * params);
void glGetInteger64v(GLenum pname, GLint64 * params);
在这里可以了解到所有的OpenGL状态变量及其意义和相关操作函数。
七、对象(Object)
OpenGL库是用C语言写的,同时也支持多种语言的派生,但其内核仍是一个C库。由于C的一些语言结构不易被翻译到其它的高级语言,因此OpenGL开发的时候引入了一些抽象层。“对象(Object)”就是其中一个。
在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct):
struct object { // 一个 OpenGL 对象:一些设置的封装
float option1; // 使用这个对象,就是使用这些设置(将设置更新到状态机中)
int option2;
char[] name;
};
struct OpenGL_Context { // OpenGL上下文,当前OpenGL渲染的全部状态
...
object* object_Window_Target; // 其中一个OpenGL对象:绘制窗口的参数设置。
...
};
unsigned int objectId = 0; // 存储对象句柄:通过对象句柄(整型)来控制对象。
glGenObject(1, &objectId); // 创建对象,对象句柄保存在objectId中。
// 在OpenGL中,不会直接操作对象,而是通过对象的句柄操作。
glBindObject(GL_WINDOW_TARGET, objectId); // 绑定对象至上下文,接下来的状态设置都将保存在当前绑定的对象中。
// 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 将上下文对象设回默认,objectId句柄控制的对象依然还在,即窗口宽高设置参数,我们可以随时再使用。
glBindObject(GL_WINDOW_TARGET, 0);
// glBindObject(GL_WINDOW_TARGET, objectId); // 立即这个对象对应的设置参数立即生效。
glDeleteObject(objectId); // 不再使用这个对象 ,就删除这个对象,释放内存。
八、C/S架构
参考资料:IOS OpenGL ES开发
点击查看【processon】
OpenGL采用客户端/服务器架构,我们设计的应用程序发送以下数据或指令:
- OpenGL状态设置,state changes
- 纹理数据,texture
- 顶点数据,vertex data
- 渲染指令,rendering commands
给客户端,客户端将其转化为图形硬件所能“理解”的数据,然后再转发给GPU进行处理。客户端和服务端进行通信会带来明显的性能开销(overhead)。
为了最大限度优化程序性能,我们应该小心处理overhead,可以从如下途径解决:
- 减少client和server间的通讯次数,如增加每次通讯的数据量。
- 用硬件支持的数据格式来减少转换代价,如使用压缩纹理。
- 管理好OpenGL和app之间的数据流。