OpenGL ES手册翻译---2.OpenGL ES操作(一)

OpenGL ES 操作

2.1 OpenGL ES的基础框架

OpenGL ES(后面简称为“GL”),只关心数据渲染到framebuffer中以及读取在framebuffer中的数据。对其他外围的硬件设备,例如鼠标,键盘并不支持。编程者必须使用其他的机制来控制这些输入设备,比如OpenKODE API。

GL画图元的方式取决于几种选择的模式:点,线,三角形模式。每种模式都被独立的填充。一种图元的设置不会影响其他图元(尽管他们会相互作用,最终决定画在framebuffer上的是什么)。设置画图模式,指定图元,通过在函数列表中发送指令或者程序调用来描述GL的其他操作。

图元(Primitives) 被定义为一个或者一组顶点(Vertex)。一个Vertex可以是一个点,一条线段的终点,或是两条线段相交形成的三角形的角。位置坐标,颜色, 法线,纹理坐标等等,都和Vertex有关,每个Vertex处理都是独立的,按照次序做相同的操作。有一种例外就是如果一组Vertex为了让指定的图元适应特定区域的大小,而需要裁剪,这种情况下,Vertex数据将会被修改或者新的顶点数据将会被创建。而裁剪类型取决于这组顶点是那种图元。

尽管有些指令在被执行时会有一些不确定的延迟,但指令一直按照被收到的顺序执行。这意味着,举个例子,一个图元必须要完整的画完,才能有后续的操作来影响framebuffer。也说明,对于查询和读取像素的操作,返回的状态也就是上一次GL命令完成执行后的状态。概括来说,GL命令不论是作用于GL画图模式还是framebuffer,都必须执行完成后才能有后续的命令对其产生影响。

在GL中,调用时发生数据绑定。这表示,在收到命令后,数据会被传送给命令进行解释。具体来说,比如一个命令需要一组数据的指针,这些数据在命令被调用后被解释,之后,这组数据的变化将不再会影响GL,除非在后续命令中还会用到这个指针。

GL提供了对3D和2D图像基础操作的直接控制。包括,指定应用程序定义的shader执行变换,光照,纹理,和阴影操作,以及一些内置的功能,比如抗锯齿和纹理过滤。GL不提供描述或者建立复杂几何模型的方法。换句话说,GL只提供了如何给复杂的几何对象做渲染的描述,不提供描述复杂几何对象本身的机制。

GL可以被看做一个客户端-服务器模型。程序(客户端)发出请求,这些请求被GL(服务端)接受处理。服务器可以维护多个GL context,每个context是一个当前GL 状态的封装。客户端可以选择连接到其中一个context上。如果没有连接到context就向GL发送指令,会触发未定义的行为。

GL和两种类型的framebuffer相互作用:窗口系统提供的framebuffers和应用程序创建的framebuffers。只有一个窗口系统创建的framebuffer,但应用程序可以创建多个framebuffer。这两种类型的framebuffer主要通过配置和管理他们状态的接口来做区分。

窗口系统提供的framebuffer由窗口系统控制分配framebuffer资源。窗口系统决定了framebuffer的哪些部分是可以在任何时候访问的,以及告诉GL framebuffer的这些部分是什么结构。因此,窗口系统提供的framebuffer没有初始化以及配置的函数。同样的,显示器或者LCD屏上的framebuffer的显示内容也不能被GL访问地址(包括一些诸如伽马校正之类技术变换过的独立framebuffer)。framebuffer的配置和窗口系统连接在一起,和GL无关;GL上下文的初始化是在窗口系统为了给GL渲染分配窗口的时候发生的。EGL API为创建GL上下文和提供渲染窗口定义了一部分机制,这些机制允许GL和不同的平台上的窗口系统相互连接。

GL上下文的初始化发生在窗口系统为GL渲染分配了窗口的时候,同时会受到窗口系统提供的framebuffer的状态的影响。

GL可以在装配不同能力和性能的图形平台上运行。为了兼容这些不同,我们为GL操作指定了理想表现行为而不是实际的表现行为。偏离标准行为是允许的,但我们需要指定是实现时必须要遵守的规则来近似的用作理想行为。这种GL行为中被允许的不同,暗示着即使相同的输入,甚至运行在相同的framebuffer配置下,两种不同的GL实现并不对所有像素都有作用。

最后,函数名,常量,类型都以GL做前缀(gl,GL_,GL),这样可以减少和其他包的冲突。为了让文档清晰简洁些,本文中将会忽略前缀。

2.1.1数值计算

GL在执行过程中执行了一些列的数值计算。

浮点类型的指针将会被正常的执行计算,这些指针还需要满足一定的范围和精确的要求,这些定义在下面的“Floating-Point Computation”中。

这些要求仅仅应用在没有Vertex和Fragment执行的GL操作中的数值运算(对比2.10,3.8部分),比如纹理图像指定和每个片元的操作。在着色器执行期间对数值范围和精度要求的变化和定义在“OpenGL ES Shad- ing Language Specification”中。

  • 浮点运算

我们不具体说明浮点数如何别表示或者他们的操作如何被执行。我们仅仅要求浮点数部分包含足够多的位数,指数部分足够大,以保证浮点数运算的结果精度在10^5 分之1。浮点数可表示的最大数必须至少是2^32。x·0=0·x=0. 1·x=x·1=x. x+0=0+x=x. 0^0 = 1.(个别特殊的规则将会具体说明),大多数单精度浮点数格式满足这些要求。

任何可以被表示的浮点数都是作为要求浮点数做输入的GL函数,都是合法的。如果传入的不是浮点数,导致的结果是不可预料的,但一点不会导致GL中断或者停止运行。在IEEE运算中,举个例子,给GL函数提供负零,或者非规格化的数会产生可预测的结果,但如果是NAN或者无穷数,结果将不可预测。上面说的这些特例,如果x不是浮点数,则会失效。

  • 修正点运算

Vertex属性可以指定使用一个32位二进制补码来表示,其中的右边16位表示小数(分数)

  • 通用规则

一些运算要用到除法。这种情况下(包括通过向量实现的除法),0做分母将会产生不可预知的结果,但不会导致GL中断或者停止。

2.1.2数据转换

当普通的顶点属性和像素颜色或者深度部分是用整数表示的,这样的表示经常(并不总是)被称作normalized。从浮点值被转换成规范化的整数值会受到特殊的对待。

接下来的部分,我们将讨论在表格2.2中定义的整数类型,b表示表中定义的整数类型要求的最小位宽。无符号整形的转换公式也可以应用到无符号整形打包的像素通道(3.6.2部分),但在这种情况下,是由指定的打包的像素格式和被转换的通道决定b的值。

所有下面描述的转换都按照定义被执行,甚至是整形数据的范围大于最小要求的范围。

  • 整数到浮点数

被Normalized(归一化)的无符号整数的数字区间为[0,1]。转换一个归一化的无符号整型c转换成浮点f的公式是:

f=\frac{c}{2^b - 1}

被归一化有符号的整数所产生的数字区间是[-1,1]。从一个要被归一化有符号整数c到得到浮点数f的公式是:

f=\frac{2c+1}{2^b-1}
  • 浮点数到整数

浮点数f到要归一化的无符号整数c的转换首先要保证f在范围[0,1]内,然后计算:

f' = f\times(2^b - 1).

f'表示带着b位宽精度的无符号整数。

浮点数f到归一化的有符号整数c的转换,首先保证c在闭区间[-1,1]中,然后计算:

f' = \frac{f\times(2^b -1) - 1}{2}

f'表示带着b位宽精度的有符号整数。

  • 浮点数到Framebuffer修正数的转换

浮点数在写入被修正过的色彩和深度缓冲的时候,他们必须在区间[0,1]。数值被m位宽转换成修正值。m是R,G,B,A或者深度缓冲通道的位宽。我们假设修正值相当于k/(2^m-1),其中k ∈ {0,1,...,2m − 1},m则至少要比相应的framebuffer的位宽数大。如果framebuffer没有包含Alpha通道或者A通道只有1位,这种情况下,m至少为2位。

2.2 GL状态

GL有很多状态。这篇文档将枚举每种状态以及他们之间的变化以及如何如何变化。为了便于讨论,状态变量被有些武断的根据他们的功能分类。尽管我们描述了GL在framebuffer上的操作,但framebuffer不是GL状态的一部分。

我们来区分一下两种不同类型的状态。第一种状态就做GL服务器状态,依赖于GL服务器。主要的GL状态都在这个分类中。第二种类型的状态叫做GL客户端状态,依赖于GL客户端。除非有特殊的说明,一般在本文中涉及的状态都指的是GL服务器状态;GL客户端状态会被特殊标识。每个GL上下文实例都会实现一组完整的GL服务器状态;每个从客户端到服务器的连接,需要GL客户端和GL服务端都实现。

虽然GL的实现是依赖于硬件的,但这里讨论的GL实现是独立于指定的硬件的。因此,我们只在需要在精确对应GL状态的时候,考虑图形硬件的状态。

2.2.1 共享物体状态

有时会出现一组上下文共享一些状态的情况。可以通过在1.6.1部分描述过的窗口系统上绑定的API来开启上下文之间共享的操作。这些API负责创建和管理上下文,这里不多做讨论。共享对象更多的细节的讨论在附录C中。除了定义在附录中的上下文之外,所有的上下文状态都是指这个上下文自身的状态。

2.3 GL函数语法

GL命令是函数或者程式。有很多组的函数,每组函数执行相同的操作,只是给他们的参数不一样。为了方便统一这些差异,我们采用标注来描述函数和他们的参数。

GL函数的命名会根据指定的函数在后面跟最多4个字符。第一个字符表示函数必须需要的参数的个数。中间两个或者一个字符值参数的类型:32位整数,32位归一化数,单精度浮点数。最后的一个字符,如果出现,就是v,表示函数需要一个数组指针(向量)的值,而不是一系列独立的参数。举个例子:

void Uniform4f(int location, float v0, float v1, float v2, float v3);

void GetFloatv(enum value, float *data);
字母 相应的GL类型
i int
f float

表格2.1:函数后缀字母和相对应的GL参数类型。参考表2.2查看GL类型的定义。

这些例子表示了ANSI C申明的所有函数。概括来说,一个函数申明有下面形式:

rtype Name{ε1234}{ε i f}{εv}([args,]Targ1,...,TargN [,args] );

rtype是函数的返回值。大括号{}中是被选中的字符或者字符对。ε不是字符。在括号中的参数([args,]和[,args])可能出现也可能不出现。N表示有N个T类型的参数,每个参数都是表2.1中的其中一种(如果没有字母,参数类型就要被显式的给出)。如果最后的字符不是v,那么N的数字是数字1,2,3,或者4(如果没有数字,那么参数数量会被修该)。如果最后的字符是v,只有argl出现,表示N个指定类型的值组成的数组。

举个例子:

void Uniform{1234}{if}(int location, T value);

这表示有八个函数:

void Uniform1i(int location, int value);
void Uniform1f(int location, float value);
void Uniform2i(int location, int v0, int v1);
void Uniform2f( int location, float v0, float v1 ); 
void Uniform3i( int location, int v0, int v1, int v2 ); 
void Uniform3f( int location, float v1, float v2,float v2 );
void Uniform4i( int location, int v0, int v1, int v2,int v3 );
void Uniform4f( int location, float v0, float v1,float v2,float v3 );

在表2.2中总结了13中参数,而这里的参数就是这13种参数中的一种,或是指向他们的指针。

GL数据类型到指定绑定语言数据类型的映射是语言绑定定义的一部分,依赖于平台。混用不同类型的真实有效的参数时,类型转换和类型转换操作都会具体到语言绑定和平台。比如,C语言会在整数和浮点数之间自动转换,但不能自动转换int和fixed,或者float和fixed GL类型,因为fixed数据类型不是C语言的内建类型。不考虑语言绑定,枚举类型转换成规范化类型不需要放大或者缩小,整数乘以2^16转换为规范化类型。

2.4 基本的GL操作

图2.1展示了GL的总体框架。函数从左边开始进入GL。一些指令指定集合图形怎么被画上去,另一些则指定在画图的不同阶段如何控制图形。

第一阶段是对图元的操作,图元被顶点描述,有点、线、和三角形。在这个阶段,顶点的操作有变换,光照,以及裁剪,形成一块可被观测的区域,为下个阶段--光栅化做准备。光栅化产生了许多framebuffer的地址和描述点、线段或者三角形的二维数据。这样产生的每块片元在进入framebuffer之前,会在每个独立的片元上执行操作。这些操作包括新和原来存储的深度值(影响深度缓存)在进入framebuffer前的更新,新的片元颜色和原来的存储颜色的混合,还有一些对片元的其他操作,比如遮罩(查看第四章)。

数据也可以从framebuffer读取。这样传输可能会用到一些类型的解码和编码。

这个顺序结构仅仅是被用来描述GL的,并不是GL严格的实现,我们这里用它来组织展示GL的不同操作。

image

表2.2 GL数据类型。GL类型不是C类型。举个例子,GL类型int指的是GLint,也没有必要和C类型int等价。实现的时候,可能会使用比在表中提到的更多位数来代表GL类型。超过最小范围的整数值不必有正确的解释。ptrbits是bits的个数,用来表示指针类型;换句话说,intptr类型和sizeiptr一定要足够大道存储任何地址。

image

2.5 GL错误处理

GL仅仅可以检测出一部分情况下的错误。因为许多情境下,错误检查将会对无错误的程序在性能方面产生不利的影响。

enum GetError(void);

这个函数会捕捉错误信息。每个可以检测到的错误都会被分配一个数字编码。当错误被检测到,错误标志位将会被设置,错误编码会被记录。其他的错误发生时,不会被记录到错误码中。当调用GetError收,错误码返回,标志位清空,这样,其它错误才可以再次被错误码记录。如果调用GetError返回No_Error,那么从GL被初始化到现在为止,就一直没有出现过错误。

为了分开实现,会有好几对“状态-错误码”对。举个例子,在调用了GetError后,返回了错误码,而不是NO_Error,每次之后的调用都会返回清晰的错误码,直到所有的错误码都被返回。当没有错误码时,所有的状态被重置。这项任务要求由正数标志位和正整数组成的数值对来完成。最初的标志位的状态被清零,最初的错误码的值为NO_Error。

表格2.3总结了GL的错误码。当错误标志被赋值,只有在OUT_OF_MEMORY发生时,GL操作是无效的。其他情况下,产生的错误会被忽略,也不会对GL状态或者framebuffer的上下文产生影响。如果执行命令产生了一个值,他返回0.如果执行命令通过指针传参修改了值,也不会对这些值产生影响。这些错误处理的办法仅仅用在处理GL错误上,并不是适用内存访问错误之类的系统错误。扩展部分如果使用非扩展部分的实现,可能会产生一些错误。

一些错误会在每一步GL操作中隐式产生:

  • 如果函数要求传入一个枚举值,而这个枚举值不是指定的允许的类型,将会产生INVALID_ENUM错误。对于参数是指针常量的,如果所指的值不被函数允许,也会出现同样的错误。
  • 如果指定的参数类型是sizei,而传入了负数值,INVALID_VALUE也会产生。
  • 如果内存不够,产生OUT_OF_MEMORY错误。

对于其他的,只有文档中描述的指定条件下才会产生错误。

image

你可能感兴趣的:(OpenGL ES手册翻译---2.OpenGL ES操作(一))