Android OpenGL ES:OpenGL

460 阅读7分钟

Android OpenGL ES系列目录

本系列文章主要是基于LearnOpenGL和对应的中文教程。与原教程主要的差异是,该系列讲解的是基于Android设备环境的OpenGL ES,并提供对应的Java示例。

原文:OpenGL

中文原文:OpenGL

OpenGL一般被认为是一个API(Application Programming Interface, 应用程序编程接口),包含了一系列可以操作图形、图像的函数。然而,OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。

OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现(Implement)的,将由实现OpenGL库的开发者自行决定。OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配即可。

实际的OpenGL库的开发者通常是显卡的生产商。你购买的显卡所支持的OpenGL版本都为这个系列的显卡专门开发的。当你使用Apple系统的时候,OpenGL库是由Apple自身维护的。在Linux下,有显卡生产商提供的OpenGL库,也有一些爱好者改编的版本。这也意味着任何时候OpenGL库表现的行为与规范规定的不一致时,基本都是库的开发者留下的bug。

由于OpenGL的大多数实现都是由显卡厂商编写的,当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL,这也是为什么总是建议你偶尔更新一下显卡驱动。

所有版本的OpenGL规范文档都被公开的寄存在Khronos那里。有兴趣的读者可以找到OpenGL ES的各个版本的规范文档。如果你想深入到OpenGL的细节(只关心函数功能的描述而不是函数的实现),这是个很好的选择。如果你想知道每个函数具体的运作方式,这个规范也是一个很棒的参考。

OpenGL与OpenGL ES

OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机和游戏主机等嵌入式设备而设计。

OpenGL ES是OpenGL的子集,但是两者的版本号并不是一一对应的。即OpenGL ES3.0并不一定就是OpenGL3.0的子集。关于OpenGL ES对应版本的功能,建议直接参考OpenGL ES各版本文档

文档一般分三类。以OpenGL ES 3.0为例:

OpenGL ES 3.0.6 规范

OpenGL ES 着色语言 3.00 规范

OpenGL ES 3.0 在线参考文档

核心模式与立即渲染模式

OpenGL有两种渲染模式:核心模式(Core-profile)与立即渲染模式(Immediate mode,也就是固定渲染管线)。不过现在OpenGL规范文档已经废弃立即渲染模式,鼓励开发者在OpenGL的核心模式下进行开发

立即渲染模式:简单易用,灵活性低,效率低。OpenGL ES 1.x为立即渲染模式

核心模式:采用可编程渲染管线。需要使用者真正理解OpenGL和图形编程。灵活性好,效率高,但难以学习。开发者需要使用OpenGL ES Shading Language(着色语言) 编码实现顶点着色器、片段着色器处理逻辑,从而渲染出自己想的展示效果。OpenGL ES 2.x以后采用的是核心模式。

扩展

OpenGL的一大特性就是对扩展(Extension)的支持,当一个显卡公司提出一个新特性或者渲染上的大优化,通常会以扩展的方式在驱动中实现。如果一个程序在支持这个扩展的显卡上运行,开发者可以使用这个扩展提供的一些更先进更有效的图形功能。通过这种方式,开发者不必等待一个新的OpenGL规范面世,就可以使用这些新的渲染特性了,只需要简单地检查一下显卡是否支持此扩展。通常,当一个扩展非常流行或者非常有用的时候,它将最终成为未来的OpenGL规范的一部分。

使用扩展的代码大多看上去如下:

if(GL_ARB_extension_name) {
    // 使用硬件支持的全新的现代特性
} else {
    // 不支持此扩展: 用旧的方式去做
}

我们很少需要使用扩展来完成大多数功能,当需要的时候,本教程将提供适当的指示。

状态机

OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。

假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。

当使用OpenGL的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。

对象

OpenGL库是用C语言写的,同时也支持多种语言的派生。本系列教程使用的OpenGL相关函数,只是Android对OpenGL ES的C函数封装后,提供的一层Java接口而已。

由于C的一些语言结构不易被翻译到其它的高级语言,因此OpenGL开发的时候引入了一些抽象层。“对象(Object)”就是其中一个。

在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct):

class object_name {
    float  option1;
    int    option2;
    String name;
}

当我们使用一个对象时,通常看起来像如下一样(把OpenGL上下文看作一个大的结构体):

// OpenGL的状态
class OpenGL_Context {
    ...
    Object object_Window_Target;
    ...     
};
// 创建对象
int[] objectId = new int[1];
glGenObject(1, objectId);
// 绑定对象至上下文
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);
// 将上下文对象设回默认
glBindObject(GL_WINDOW_TARGET, 0);

这一小段代码展现了你以后使用OpenGL时常见的工作流。我们首先创建一个对象,然后用一个int数组保存它的指针(对象非Java对象,它的数据是保存在C层的,被储存在OpenGL中)。然后我们将对象绑定至上下文的目标位置(例子中窗口对象目标的位置被定义成GL_WINDOW_TARGET)。接下来我们设置窗口的选项。最后我们将目标位置的对象id设回0,解绑这个对象。设置的选项将被保存在objectId所引用的对象中,一旦我们重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效。

上面提供的伪代码只是OpenGL如何操作的一个大致描述,后面的教程你会遇到很多实际的例子。

使用对象的一个好处是,在程序中,我们可以定义多个对象,设置不同的选项。在我们执行一个使用OpenGL状态的操作的时候,只需要绑定含有需要的选项的对象即可。比如说我们有一些对象,持有一些3D模型数据(一栋房子或一个人物)。在我们想绘制其中任何一个模型的时候,只需绑定对应的对象就可以了,便不需要再重复设置选项了。

让我们开始吧

你现在已经知道一些OpenGL的相关知识了,OpenGL规范和库,OpenGL幕后大致的运作流程,以及OpenGL使用的一些传统技巧。不要担心你还没有完全消化它们,后面的教程我们会仔细地讲解每一个步骤,你会通过足够的例子来真正掌握OpenGL。

附加资源