OpenGL ES(OpenGL for Embeded System)是OpenGL(Open Graphics Library)的精简子集,是以手持和嵌入式设备为目标的高级3D图形API,如现在火爆的智能手机,支持桌面系统,还是浏览器3D图形标准WebGL的基础,支持多个平台,如桌面端的Linux、Windows,移动端的Android、iOS等,无处不在。
OpenGL ES是Khronos组织创立的API套装之一,最新版本为OpenGL ES 3.0,且向后兼容OpenGL ES 2.0版本,过时的另外两个版本OpenGL ES 1.0和1.1(1.x版本不被2.0/3.0兼容)采用固定功能管线,分别从OpenGL 1.3和1.5衍生而来,且需要考虑一些限制,如屏幕分辨率、CPU性能、内存等。OpenGL ES 2.0采用可编程功能管线,从OpenGL 2.0衍生而来。OpenGL ES 3.0同样采用可编程功能管线,从OpenGL 3.3衍生而来,但引入了更多功能,如阴影贴图、volume渲染、基于GPU的粒子动画、几何形状实例化、纹理压缩、伽玛矫正等。
OpenGL ES与OpenGL相比,需要考虑下面几个因素:
(1)OpenGL API规模庞大且复杂,OpenGL ES工作组的目标是创建适合于受限设备的API,为了实现这一目标,工作组从OpenGL API中删除任何冗余,在相同操作可以多种方式执行的情况下,采用最实用的方法,将多余的技术删除,指定几何形状就是一个好的例子,在OpenGL中应用程序可以使用立即模式、显示列表或者顶点数组,在OpenGL ES中,只存在顶点数组,而删除了立即模式和显示列表。
(2)删除冗余是个重要的目标,但是维护与OpenGL的兼容性也很重要,OpenGL ES尽一切可能设计成可以运行用OpenGL功能的嵌入式子集编写的程序,这是一个重要的目标,因为这使得开发人员可以使用两套API并开发使用相同功能子集的应用程序和工具。
(3)引入新功能来处理手持和嵌入式设备的特定限制,例如,为了降低电源消耗,提高着色器的性能,在着色语言中引入精度限定符。
(4)OpenGL ES的设计者旨在确保实现图像质量的最小功能集,在早期的手持式设备中,屏幕尺寸有限,必须尽可能保证在屏幕上绘制的像素质量。
(5)OpenGL ES工作组希望确保任何OpenGL ES实现都能够满足图像质量、正确性和稳定性的某种可接受和公认的标准,这通过开发相关的相容性测试来实现,OpenGL ES实现必须通过这些测试才能被视为兼容。
OpenGL ES 3.0实现了具有可编程着色功能的图形管线,如下图所示,图中带有阴影的方框表示OpenGL ES 3.0中管线的可编程阶段,有专门的OpenGL ES SL(3.0)着色语言进行处理。
下面对上图中图形管线的各个阶段简单介绍一下。
顶点着色器——
顶点着色器实现了顶点操作的通用可编程方法,其输入包括执行顶点操作的着色器程序、顶点数组提供的着色器属性(输入变量)、着色器使用的uniform变量、着色器使用纹理的采样器,输出包括一些自定义的可变变量(输出变量)及内建变量。下面是顶点着色器的一个简单示例,对顶点着色器先有个感性的认识。
#version 300 es
// matrix to convert a_position from model space to normalized device space
uniform mat4 u_mvpMatrix;
// attributes input to the vertex shader
in vec4 a_position; // position value
in vec4 a_color; // input vertex color
// output of the vertex shader and input to fragment shader
out vec4 v_color; // output vertex color
void main()
{
v_color = a_color;
gl_position = u_mvpMatrix * a_position;
}
插值——
在图元光栅化阶段,为每个生成的片段计算顶点着色器的输出值,并作为输入传递给片段着色器,用于从分配给每个图元顶点的顶点着色器输出生成每个片段值的机制称为插值。
变换反馈——
OpenGL ES 3.0新增功能,使顶点缓冲区输出可以选择性地写入输出缓冲区。
图元装配——
图元包括点、直线、三角形等几何对象,在图元装配期间,顶点着色器中的顶点组合成图元,不在视锥体内的图元被裁剪,裁剪之后,顶点位置被转换为屏幕坐标,还可以根据图元面向前方还是面向后方执行一次淘汰操作,之后,图元进入下一个光栅化阶段。
光栅化——
光栅化将图元转化为一组二维片段,代表着可在屏幕上绘制的像素,包括屏幕坐标、颜色、纹理坐标等,由后面的片段着色器处理。
片段着色器——
片段着色器为片段上的操作实现了通用的可编程方法,位于光栅化阶段之后,其输入包括片段上执行操作的着色器程序、光栅化阶段通过插值为每个片段生成的顶点着色器输出作为片段着色器的输入、uniform变量、着色器使用纹理的采样器,其输出包括一个或多个(多重渲染目标)颜色值,也可以抛弃片段,除此之外,输入输出还包括一些内建变量。下面是片段着色器的一个简单示例,对片段着色器先有个感性的认识。
#version 300 es
precision mediump float;
in vec4 v_color; // input vertex color from vertex shader
out vec4 fragColor; // output fragment color
void main()
{
fragColor = v_color;
}
逐片段操作——
光栅化阶段生成的颜色、深度、模板和屏幕坐标作为逐片段操作的输入,逐片段操作包括像素归属测试(确定帧缓冲区中某个位置的像素目前是不是归OpenGL ES所有,不属于当前OpenGL ES上下文时则不显示这些像素,在OpenGL ES内部进行)、裁剪测试(确定某个位置是否位于作为OpenGL ES状态的一部分的裁剪矩形范围内,没有则被抛弃)、模板测试(在模板值上进行测试以确定片段是否应该被拒绝)、深度测试(在深度值上进行测试以确定片段是否应该被拒绝)、混合(将新生成的片段颜色值与保存在帧缓冲区对应位置的颜色值进行组合)、抖动(最小化因为使用有限精度在缓冲区中保存颜色值而产生的伪像),最后前往帧缓冲区,光栅化生成的片段只能修改帧缓冲区中对应位置的像素。在逐片段操作中,对于未被拒绝的片段,可以在帧缓冲区的对应位置写入颜色、深度和模板值,是否写入及如何写入还取决于启用的相应写入掩码。OpenGL ES 3.0提供了从帧缓冲区读回像素的接口,删除了逐片段操作阶段的OpenGL ES 2.0和1.x中的Alpha测试和逻辑操作,由于片段可能被片段着色器抛弃,所以Alpha测试可以在片段着色器中进行,而逻辑操作很少被应用程序使用。
上面在逐片段操作阶段提到了OpenGL ES 3.0与之前版本的区别,下面介绍一下OpenGL ES 3.0新增的主要功能:
(1)纹理:允许应用程序进行伽马矫正渲染的sRGB纹理(相对于线性空间)和帧缓冲区,保存一组2D纹理的2D纹理数组(如应用于纹理动画,之前需要在单个2D纹理中平铺动画帧并修改纹理坐标改变动画帧来实现),3D纹理(之前通过扩展才能支持3D纹理,如应用于医学成像等三维渲染程序),深度纹理与阴影比较(深度纹理的最常见用途是渲染阴影),无缝立方图(立方图可以进行采样如过滤来使用相邻面的数据并删除接缝处的伪像),浮点纹理(支持16位半浮点纹理并可以过滤,支持32位全浮点纹理但不能过滤),ETC2/EAC纹理压缩(纹理压缩利用纹理缓存提供了更好的性能,减少了GPU内存占用,之前使用供应商提供的专用纹理压缩格式,现在提供了标准的纹理压缩格式),整数纹理(支持渲染和读取保存为未规范化有符号或无符号8位、16位、32位整数纹理),非2幂次纹理(NPOT,之前纹理尺寸都为2的幂次),纹理细节级别(LOD,流化mipmap)功能,纹理调配(引入新的纹理对象状态以允许独立控制纹理数据每个通道R、G、B、A在着色器中的映射),不可变纹理(应用程序在加载数据之前指定纹理格式和大小),最小尺寸增大(远大于之前的纹理资源限制)。除了前面提到的格式之外,还包括对11-11-10RGB浮点纹理、共享指数RGB9-9-9-5纹理、10-10-10-2整数纹理以及8位分量有符号规范化纹理的支持。
(2)着色器:二进制程序文件(离线,运行时不需要链接),强制的着色器在线编译器,非方矩阵(可以减少执行变换所需的指令),全整数支持(整数标量、向量、操作、内建函数、纹理、颜色缓冲区等) ,质心采样(为了避免在多重采样时产生伪像,可以用质心采样声明顶点着色器的输出变量和片段着色器的输入),平面/平滑插值程序(显式声明顶点着色器输出、片段着色器输入的插值方式为平面或平滑),统一变量块(统一变量组成的块,可以更高效地加载,也可以在多个着色器程序间共享),布局限定符(显式绑定顶点着色器输出、片段着色器输入的布局,也可以控制统一变量块的内存布局),实例和顶点ID(在顶点着色器中访问顶点索引、实例渲染ID),片段深度(片段着色器显式控制片段深度而不是依赖于深度插值),宽松的限制(着色器不再限于指令长度,完全支持变量为基础的循环和分支,并支持数组索引),新的API等。
(3)几何形状:变换反馈(可以在缓冲区对象中捕捉顶点着色器的输出,如GPU粒子动画),布尔遮挡查询(查询任何像素是否通过深度测试),实例渲染(有效渲染几何形状类似但属性不同的对象,如人群),图元重启(如为新图元使用三角形条带时使用特殊的索引值即可而不用专门生成退化的三角形),新顶点格式等。
(4)缓冲区对象:统一变量缓冲区对象(为存储、绑定大的统一变量块提供高效的方法,可以减少将统一变量值绑定到着色器的性能代价),顶点数组对象(提供绑定和在顶点数组状态之间切换的高效方法),采样器对象(将采样器状态即纹理循环模式和过滤与纹理对象分离,可以更高效地在纹理中共享采样器状态),同步对象(检查OpenGL ES操作是否在GPU上完成,fence),像素缓冲区对象(支持对像素操作和纹理传输操作的异步数据传输,优化CPU、GPU间的数据传输),缓冲区子界映射(映射缓冲区的一个子区域供CPU访问),缓冲区对象拷贝(从一个缓冲区对象向另一个缓冲区对象传输数据,不需要CPU干预)。
(5)帧缓冲区:多重渲染目标(MRT,同时渲染到多个颜色缓冲区,用于许多高级的渲染算法,如延迟着色),多重采样渲染缓冲区(使应用程序能够渲染到具备多重采样抗锯齿功能的屏幕外帧缓冲区,多重采样缓冲区不能直接绑定到纹理,但是可以使用新引入的帧缓冲区块移动Blit解析为单采样纹理),帧缓冲区失效提示(许多实现使用基于块状渲染TBR的GPU,TBR常常在必须为了进一步渲染到帧缓冲区而毫无必要地恢复图块内容时导致很高的性能代价,帧缓冲区失效为应用程序提供了通知驱动程序不再需要帧缓冲区内容的机制,这使驱动程序能够采取优化步骤,跳过不必要的图块恢复操作,这一操作对于许多应用程序实现峰值性能很重要,特别是那种进行大量屏幕外渲染的程序),新的混合方程式(支持最大值、最小值函数作为混合方程式)。
OpenGL ES命令需要渲染上下文Context和绘制表面Surface,但不包括如何创建渲染上下文,如何将渲染上下文连接到原生窗口系统,EGL则提供了这个功能,EGL是Khronos渲染API(如OpenGL ES)和原生窗口系统之间的接口。OpenGL ES在渲染之前,需要EGL查询并初始化设备商可用的显示器,创建渲染表面,创建渲染上下文。使用Opengl ES 3.0时,需要包含相应的头文件并链接对应的库,库包括libGLESv2和libEGL,头文件如下:
#include
#include
#include
#include
EGL API以egl开始,后面每个单词首字母大写,EGL数据类型以EGL开始,后面每个单词首字母大写,但EGLint和EGLenum除外,下图是EGL的数据类型:
OpenGL ES API以gl开始,后面每个单词首字母大写,OpenGL ES数据类型以GL开始,后面每个单词首字母小写,另外,有些API可能采样不同风格的参数,形如glXxxNumType,Num即参数个数,Type即参数类型,具体类型如下图所示:
若不正确使用OpenGL ES API,应用程序会生成一个错误代码,可以用如下函数查询:
GLenum glGetError(void);
glGetError返回当前错误代码,并将当前错误代码复位为GL_NO_ERROR,除了GL_OUT_OF_MEMORY错误之外,其它的生成错误的命令会被忽略,且不会影响OpenGL ES状态。基本错误代码如下:
同理,EGL也有类似查询错误的函数如下:
EGLint eglGetError(void);
图形管线的每个阶段都有一个可以启用或者禁用的状态,每个上下文维护相应的状态值,启用或者禁用状态使用如下函数:
void glEnable(GLenum cap);
void glDisable(GLenum cap);
函数中的参数cap表示某个状态,可以是:
GL_BLEND
GL_CULL_FACE
GL_DEPTH_TEST
GL_DITHER
GL_POLYGON_OFFSET_FILL
GL_PRIMITIVE_RESTART_FIXED_INDEX
GL_RASTERIZER_DISCARD
GL_SAMPLE_ALPHA_TO_COVERAGE
GL_SAMPLE_COVERAGE
GL_SCISSOR_TEST
GL_STENCIL_TEST
如果cap不是有效的状态枚举值,则生成错误代码GL_INVALID_ENUM。在初始化OpenGL ES上下文(EGLContext)时,除了GL_DITHER被设置为GL_TRUE之外,其它功能的初始化值均被设置为GL_FALSE。可以用如下函数glIsEnabled检查某个状态目前是启用还是禁用,返回GL_TRUE或GL_FALSE,如果cap不是有效的状态枚举值,则生成错误代码GL_INVALID_ENUM。另外,对于某些特殊状态值,还可以使用形如glGetXxx的函数查询。
GLboolean glIsEnabled(GLenum cap);