每块碎片的操作和帧缓冲
帧缓冲有一组按照二维数组排列的像素组成。数组的宽高在不同的GL实现中可能会发生变化。这里讨论的出发点是,每个在帧缓冲的像素是简单的一些数目的比特。每个像素的比特数目也会根据指定的GL实现或者上下文发生变化。
更多的,有两种类型的帧缓冲:由窗口系统提供的默认帧缓冲和应用创建的帧缓冲对象。每个OpenGL ES上下文都有一个默认的窗口系统提供的帧缓冲。应用可以选择的创建额外的非显示的帧缓冲对象。跟多关于应用创建帧缓冲对象的信息,见4.4节。
在帧缓冲中每个像素对应的比特,都被按照bitplane分组;每个比特平面包含来自每个像素的单一的一个比特。这些比特平面被分组到几个逻辑缓冲中。这些逻辑缓冲叫做color,depth,stencil缓冲。颜色缓冲实际上是由一些缓冲组成的,这些颜色缓冲用途相关但是目的略有不同,着取决于他们是被绑定到默认的窗口系统提供的帧缓冲上还是应用创造的帧缓冲上。
对于默认的窗口系统提供的帧缓冲,颜色缓冲由前(单)缓冲区和后缓冲区中的一个或者两个组成。一半的,前缓冲的内容显示在一个颜色显示器上,而后缓冲的内容是看不见的。颜色缓冲必须有相同数目的位平面,尽管上下文可能不会提供两种类型的缓冲。更多的是,实现或者上下文可能不会提供深度或者模板缓冲。
对于应用创建的帧缓冲对象,颜色缓冲是看不到的,因此颜色缓冲的名字也和显示设备没有关系。应用创建的帧缓冲对象的颜色缓冲的名字是COLOR_ATTACHMENT0。深度和模板缓冲的名字是DEPTH_ATTACHMENT和STENCIL_ATTACHMENT。更多的关于应用创建的帧缓冲对象的信息,可以看4.4.2节。作为一个完整的(见4.4.5节)帧缓冲,附着在应用创建的帧缓冲对象上的颜色缓冲必须有相同的位平面。深度和模板缓冲也可以可选的附着在应用创建的帧缓冲上。
颜色缓冲由无符号整型R,G,B,以及一个可选的A组成。在每个颜色缓冲,深度缓冲和模板缓冲中的位平面的数量是基于当前绑定的帧缓冲的。对于默认的帧缓冲,位平面的数量是被修正过的。对于应用创建的帧缓冲兑现个,位平面的数量是由逻辑缓冲给出的,如果对应的帧缓冲附着点的状态或者附着的图像发生变化,位平面的数量可能也会发生变化。
所有提供的位平面的初始化状态是未定义的。
4.1 每个碎片的操作
窗口坐标栅格化之后产生的碎片会改变基于一系列参数和条件下载那个位置上的帧缓冲对象上的像素。我们按照顺序介绍这些变化和测试,如图4.1。
4.1 像素归属者测试
第一个测试是决定在帧缓冲的位置的像素在当前是否是属于GL的(更精确的说,是否是属于当前GL的上下文的)。如果不属于,窗口系统会决定被处理的碎片的命运。可能的结果是,碎片被忽略或者每个碎片操作的操作序列的子集会被应用到碎片上。这个测试允许窗口系统来控制GL的行为,比如,当GL的窗口是模糊的。
当应用创建的帧缓冲对象被绑定在FRAMEBUFFER上是,像素归属测试总是会通过。应用创建的缓冲对象总是贵OpenGL ES所有,而不是窗口系统。只有当窗口系统提供的命名为0的帧缓冲被绑在FRAMEBUFFER上,才会有窗口系统控制像素归属。
4.1.2 裁剪测试
裁剪测试会决定是否在由四个值定义的裁剪矩形中。这些值通过下面的函数设置:
void Scissor(int left, int bottom, sizei width, sizei height);
如果且,裁剪测试就会通过。否则,测试失败,碎片就会被忽略。测试通过使用常数SCISSOR_TEST的Enable和Disable来打开和关闭。当关闭时,相当于是裁剪测试总是通过。如果宽和高都比0小,会产生INVALID_VALUE错误。这个状态要求由四个整数值和一个用来指示测试是否打开的比特位构成。初始状态时,left = bottom = 0;宽和高由GL窗口的大小决定。初始状态下,裁剪测试是关闭的。
4.1.3 多重采样的碎片操作
根据SAMPLE_ALPHA_TO_COVERAGE,SAMPLE_COVERAGE,SAMPLE_CONVERAGE_VALUE,SAMPLE_CONVERAGE_INVERT的值,这一步会修改碎片的alpha值和覆盖值。如果在这一步SAMPLE_BUFFERS的值不是1,碎片的alpha和覆盖值就不会发生变化。
SAMPLE_ALPHA_TO_COVERAGE和SAMPLE_COVERAGE通过调用Enable和Disable打开或者关闭,用cap指定两个命令指令中的一个。所有的值都是通过调用设置了cap为期望的命令指令的IsEnable函数来查询的。如果SAMPLE_ALPHA_TO_CONVERAGE是打开的,在对应的采样位置的alpha值决定的每个比特会产生一个临时的覆盖值。这个临时覆盖值会和碎片的覆盖值做AND运算。否则, 在这点的碎片的覆盖值是不变的。
将采样的alpha值转换为临时的覆盖值是没有特定的算法的。这就意味着在临时覆盖之中的1和碎片的alpha值的集合成比例,1对应所有alpha值的最大值,0对应的alpha的对小值0。这也意味着这种算法本质上是伪随机的,来避免由于规则的覆盖采样位置而产生的图像伪影。算法在不同的像素位置可能会有不同。如果确实有差异,算法应该相对于窗口定义,而不是屏幕,坐标系,这样渲染结果就会相对于窗口位置保持不变性。
最终,如果SAMPLE_COVEAGE是打开的,碎片的覆盖值就会和另外的临时的覆盖值做AND操作。这种临时覆盖值和上述一样用相同的方式产生,但是是作为SAMPLE_CONVERAGE_VALUE的值的函数的。这个函数不需要相等,但是必须要在比例和不变性上有相同的属性。如果过SAMPLE_CONVERAGE_INVERT是TRUE,临时覆盖值会在和碎片覆盖值做AND操作之前发生翻转(所有的比特位都会翻转)。
SAMPLE_COVERAGE_VALUE和SAMPLE_COVERAGE_INVERT的值是通过下面函数指定的:
void SampleCoverage(clampf value, boolean invert);
value是设置为期望的覆盖值,invert设置TRUE或者FALSE。value在被存储到SAMPLE_COVERAGE_VALUE之前被限制在[0,1]范围内。使用GetFloatv
函数,将pname设置为SAMPLE_COVERAGE_VALUE就可以查询到SAMPLE_COVERAGE_VALUE。SAMPLE_COVERAGE_INVERT可以通过调用GetBooleanv
函数,并设置pname参数为SAMPLE_COVERAGE_INVERT来查询到。
4.1.4 模板测试
基于模板缓冲在位置的值和一个参考值的比较结果,模板测试会有条件的忽略一些碎片。测试使用Enable和Disable函数来开关,使用的符号化常数是STENCIL_TEST。当关闭时,模板测试和相关的改变不会生效,碎片也总是被通过。
模板测试通过下面函数控制:
void StencilFunc(enum func, int ref, uint mask);
void StenciFuncSeparate(enum face, enum func, int ref, uint mask);
void StencilOp(enum sfail,enum dpfail, enum dppass);
void StencilOpSeparate(enum face, enum sfail, enum dpfail, enum dppass);
有两组模板相关的状态,前模板状态集和后模板状态集。模板测试和写入,在处理来自非多边形图元(点,线,位图,矩形图像)的栅格化碎片和面朝前的多边形图元时,使用前模板状态集,而处理来自面朝后的多边形图元的栅格化碎片时使用后模板状态集。出于模板测试的目的,即使多边形被栅格化时由于当前的多边形模式会当做是点或者线处理,还是会被认为是一个多边形。多边形是面朝前还是面朝后决定方式是相同的,使用面挑选机制(见3.5.1)。
函数StenciFuncSeparate
和StencilOpSeparate
的face参数可以是FRONT,BACK,FRONT_AND_BACK,它指示着那个状态集是有起作用的。StenciFunc
和StencilOp
来给相同的值设置前模板状态和后模板状态。
函数StencilFunc
和StencilFuncSeparate
用了三个参数来控制模板测试是通过还是失败。ref是一个整数参考值,用来做无符号的模板比较。模板比较操作和ref的查询把他们的值限制在区间中,s是附着在帧缓冲上的模板缓冲的比特位数目。mask参数的最小位s和参考值以及存储的模板值按位做AND操作,结果的蒙版值是由参与比较操作的函数func控制的。func是一个符号化常量,它决定了模板的比较函数;八个比较常量值分别是NEVER,ALWAYS,LESS,LEQUAL,EQUAL,GEQUAL,GREATER,NOTEQUAL。这意味着,模板测试就是一直通过,或者一直不通过,或者如果蒙版参考值比存储在模板缓冲中的蒙版值小,或者小于等于,或者等于,或者大于等于,或者大于,或者不相等时通过。
函数StencilOp
和StencilOpSeparate
用三个参数来只是如果这个或者某个接下来的测试失败或者通过时,存储的模板值发生了什么。sfail指示出如果模板测试失败会采取什么行动。符号化常量有KEEP,ZERO,REPLACE,INCR,DECR,INVERT,INCR_WRAP,DECR_WRAP。这些的意思是保持当前值,设置为0,使用参考值替换,饱和递增,饱和递减,位翻转,不饱和递增,不饱和递减。
在递增或者递减式,模板比特位被当做是无符号整形。饱和递增或者递减把模板支限制在0到最大表示值的范围中。非饱和递增或者递减限制方式为,递增最大可表示值会导致结果为0,递减0会导致结果为最大可表示值。
如果深度缓冲测试(见4.1.5)失败了(dpfial),或者通过了(dppass),同样的符号化值会被用来标识模板的行为。
如果模板测试失败了,送来的碎片就会被忽略。要求的状态由传递给StencilFunc
或者StencilFuncSeparate
或者StencilOp
或者StencilOpSeparate
函数的最近的值和一个指定模板测试是否打开或者关闭的比特位组成。在初始化状态下,模板是关闭的,前模板和后模板参考值都是0,前模板和后模板的比较函数都是ALWAYS,前后的模板的蒙版都是一个。初始化时,所有的三个前后模板操作都是KEEP。
如果没有模板缓冲,就不会有模板改变发生,就如同是模板测试全部通过,不考虑所有对StencilFunc
的调用。
4.1.5 深度缓冲测试
如果深度对比失败,深度缓冲测试将会忽略输入的碎片。对比还是通过使用符号化常量为DEPTH_TEST的通用的Enable
和Disable
函数来开关。当关闭时,深度比较和深度缓冲值的后续可能的更新会被绕过,然后碎片会被传给下一个操作。模板值像下面指示的一样发生改变,就像深度缓冲测试通过了。如果打开了,比较就会发生,深度缓冲和模板值可能会随后被修改。
比较是通过下面函数实现的
void DepthFunc(enum func);
这个函数有一个单一的符号化常量:NEVER,ALWAYS,LESS,LEQUAL,EQUAL,GREATER,GEQUAL,NOTEQUAL中的一个。因此,深度缓冲测试不会通过或者总是会通过,或者如果收到的碎片的值比在给定的受到的碎片的坐标位置上存储的深度值小,或者小于等于,等于,大于,大于等于,不等于时通过或者不通过。
如果深度测试失败了,新收到的碎片就会被忽略。碎片坐标上的模板值会根当前对深度缓冲测试失败的效果函数来更新。否则,碎片会继续下一个操作,在碎片的坐标上的深度缓冲的值会被设置为。这种情况下,模板值会根据深度缓冲测试成功的当前起作用的函数来更新。
必要的状态是一个八值整数和一个单一的比特位用来指定深度缓冲是开的还是关的。在初始状态,函数是LESS,测试是关闭的。
如果没有深度缓冲,就可以当做是深度缓冲测试总是可以通过。
4.1.6 混合
混合操作是把新来的源碎片的R,G,B,A值和在帧缓冲中存储的在碎片位置的目标R,G,B,A值结合起来。
源和目标值通过混合等式结合在一起,源和目标值的四通道中的每个的权重因子都由混合函数决定,一个常量混合颜色来获取一组新的R,G,B,A值,下面会介绍。这些浮点指针值中的每个都被限制在[0,1]中,然后把帧缓冲中的颜色通道按照2.1.2节中描述的方式在将其再转换回修正指针值。四个值的结果被送到下一步操作中。
混合操作依赖于新来的碎片的alpha值和相对应的当前存储的像素。混合操作通过带有BLEND参数的Enable和Disable函数来打开或者关闭。如果是关闭的,就继续处理下一步操作。
- 混合等式
混合由混合等式控制,下面函数定义:
void BlendEquation(enum mode);
void BlendEquationSeparate(enum modeRGB, enum modeAlpha);
函数BlendEquationSeparate
的参数modeRGB决定了RGB的混合函数,modeAlpha决定了alpha通道的混合等式。BlendEquation
的参数mode决定了RGB和alpha通道的混合等式。modeRGB和modeAlpha一定是FUNC_ADD,FUNC_SUBTRACT,FUNC_REVERSE_SUBTRACT中的一个。
根据2.1.2节中描述帧缓冲颜色通道的流程,目的(帧缓冲)通道采用修正指针值表示,就像源(碎片)一样。常量颜色通道采用浮点指针值。
在混合之前,每个修正指针值颜色通道都经历了到浮点指针值类型的隐式转换。这个转换必须保留0和1的不变性。混合通道可以当成是在浮点指针类型中执行的。
表4.1提供了对应的每个通道在每种模式下的混合等式,作用于modeRGB下的RGB通道或者modeAlpha下的alpha通道。
在这个表中,颜色通道缩写(R,G,B,A)的下标s表示新来的碎片的源颜色通道,颜色通道的缩写的下标d表示在对应的帧缓冲位置的目标颜色通道,颜色通道缩写的下标c表示常量的混合颜色通道。没有下标的颜色通道缩写表示从混合中得出的新的颜色通道。另外,是源的红,绿,蓝和透明度的权重因子,由源的混合函数决定,是目的的红,绿,蓝和透明度通道的权重因子,由目的的混合函数决定。混合函数下面介绍。
- 混合函数
混合等式使用的权重因子由混合函数决定。混合函数由下面函数指定
void BlendFuncSeparate(enum srcRGB,enum dstRGB, enum srcAlpha, enum dstAlpha);
void BlendFunc(enum src, enum dst);
函数BlendFuncSeparate
的参数srcRGB和desRGB决定源和目的RGB的混合函数,srcAlpha和dstAlpha分别决定源和目的的Alpha通道的混合函数。BlendFunc
的参数src决定了源的RGB和alpha的混合函数,dst据定了目的的RGB和Alpha的混合函数。
可能的源和目的的混合函数和他们的对应的混合计算因子总结在表4.2中。
- 混合颜色
颜色常量在混合中被使用,用下面函数指定:
void BlendColor(clampf red, clampf green, clampf blue, clampf alpha);
着四个值在存储之前被限制到区间[0,1]中。这个常量颜色可以被用在源和目的的混合函数中。
- 混合状态
混合需要的状态有,描述RGB和alpha的混合等式的两个整型数,描述源和目的RGB和alpha的混合函数的四个整型数,存储RGBA常量混合色彩的四个浮点指针值,描述混合功能是否被打开的一个比特位。初始化的RGB和alpha的混合等式都是FUNC_ADD。初始化的混合函数对于源的RGB和alpha函数是ONE,对于目的的RGB和alpha函数是ZERO。初始的常量混合色彩是(R,G,B,A)= (0,0,0,0)。最开始,混合功能是关闭的。
每个颜色缓冲当前是可写状态(见4.2.1节),每个缓冲的颜色使用颜色,混合就会发生。如果颜色缓冲没有A的值,会采用的值为1。
4.1.7 抖动
抖动会在两个值之间做出选择。把任何颜色通道的值当做是一个左边有m位比特的二进制指针的修正指针值,m是在帧缓冲中分配到那个通道的比特位的数目;每个这样的值叫做c。对于每个c,抖动选择一个值,(在挑选之后,可以被认为是一个有m位的在[0,1]区间内的修正指针值)。挑选基于像素的坐标。不论是通道还是序列号,比较合适的是,c一定不能大于在帧缓冲中的表示法的最大值。
许多抖动算法是可能的,但是抖动之后的值是由一些必须只依靠新来的值和碎片的x和y窗口坐标系通过一些算法产生的。如果抖动是关闭的,每个颜色通道会被缩短到有着和帧缓冲中的对应的通道相同的比特位数的修正指针值。
使用符号化常量DITHER的Enable
和Disable
函数控制开关抖动功能。必要的状态因此只有一个单一的比特位。初始化时,抖动是打开的。
4.1.8 额外的碎片多重采样操作
如果SAMPLE_BUFFERS是1,模板测试,深度测试,混合,抖动操作会在每一个像素样本上执行,而不是仅仅对每个碎片仅执行一次。模板或者深度测试的失败会导致采样点处理流程的终结,而不是丢弃碎片。所有的操作都在存储在多重采样缓冲(在接下来的小节会介绍)的颜色,深度,模板值上执行。这个时候的颜色缓冲的内容是不会被修改的。
对一个像素采样点,只有他的碎片的覆盖为是1的时候,才会执行模板,深度,混合和抖动操作。如果对应的覆盖位是0,在采样点上不会有任何的操作。
如果SAMPLE_BUFFERS是1,碎片可能会被上面描述的一样处理,还会有些优化的可能,因为碎片覆盖一定会被设置为全覆盖。无论如何,更多的优化是被允许的。实现可能会选择标识出一个最中心的样本,并只在这个样本上执行模板和深度操作。不考虑模板测试的结果,所有的多重采样缓冲的模板采样值会被设置合适的新的模板值。如果深度测试通过,所有的多重采样缓冲的深度采样值会被设置为碎片的最中心样本的深度值,所有的多从采样缓冲的颜色采样值会被设置为新来的碎片的颜色值。其他方面,对于多重采样的缓冲颜色或者深度值不会有任何改变。
在多重采样缓冲的所有操作都完成后,颜色采样值会合并来生成一个单一的颜色值,这个值会被写到用来写入的颜色缓冲中(见4.2.1)。实现可能会把颜色缓冲的写入推迟一段时间,但是帧缓冲的状态必须表现的就像颜色缓冲是在每个碎片处理完后更新的一样。结合的方法不是指定的,仅仅是对每个独立的推荐的颜色通道做一个简单的平均值计算。