混合是源和目标像素进行的,抖动是在混合后进行的,上面的操作数据都还是放置在临时缓存区中,写入掩码和逻辑操作后才是最终的将结果像素和目标像素进行逻辑写入默认是GL_COPY。深度缓存和模板缓存的写入也受到掩码和操作函数控制,stencil测试公式是:stencil ref &mask op stencil value &mask,sicssor测试更快;alpha测试,深度测试,默认都是禁用的,启用后测试结果受到自己指定的测试比较函数控制。
现代可编程渲染管道中,后期的硬件裁剪测试,alpha测试,模板测试,深度测试(遮挡查询和条件渲染), 混合,抖动,写入掩码和写入逻辑操作,都可以在fragment shader中设置。
透明效果,抗锯齿效果(在多重采样处理边缘 alpha处理也需要),都是要依赖混合的。
累积缓存的这些图像操作(单次或多次绘制物体然后组合结果(时间上的),单次对一副图像多次附近周围采样组合结果(空间上)效果:全景抗锯齿被alpha和多重采样覆盖代替,其它操作被OGL 3.0引入的帧缓冲区对象中的浮点像素格式很容易实现了。
一、帧缓冲区基本概念
帧缓存区包括:
1.颜色缓冲区,有前后台缓存区和辅助缓存区,OGL3.0以上为浮点颜色缓存区,取消了累积缓冲区的支持。
2.模板缓冲区。
3.深度缓冲区。
颜色模板深度缓存区像素位置是一一对应的。
各种缓冲区的成分位数:
GLint rBits, gBits, bBits, aBits;
glGetIntegerv(GL_RED_BITS, &rBits);//8
glGetIntegerv(GL_GREEN_BITS, &gBits);//8
glGetIntegerv(GL_BLUE_BITS, &bBits);//8
glGetIntegerv(GL_ALPHA_BITS, &aBits);//8
GLint iBits, dBits, sBits;
//颜色缓存区中的颜色索引数据, Intel是3.1版本是32; AMD是4.2版本是0,颜色索引数据取消了
glGetIntegerv(GL_INDEX_BITS, &iBits);
glGetIntegerv(GL_DEPTH_BITS, &dBits);//24
glGetIntegerv(GL_STENCIL_BITS, &sBits);//8
// Intel是3.1版本; 0 AMD是4.2版本OGL实现都废弃了ACCUM累积缓存
// 因为OGL 3.0引入了支持本地浮点值的颜色缓存区,ACCUM缓存相关的事情很容易在浮点缓冲区上实现。
GLint acRBits, acGBits, acBBits, acABits;
glGetIntegerv(GL_ACCUM_RED_BITS, &acRBits);//0
glGetIntegerv(GL_ACCUM_GREEN_BITS, &acGBits);//0
glGetIntegerv(GL_ACCUM_BLUE_BITS, &acBBits);//0
glGetIntegerv(GL_ACCUM_ALPHA_BITS, &acABits);//0
颜色缓冲:
// 当前OGL是否立体渲染(3D渲染)屏幕;Intel是3.1版本; 0 AMD是4.2版本都不支持
// 立体渲染, 也就是前-左,前-右,后-左,后-右缓存区; VR中应该就是这样使用。
// 但每个OGL实现都必须支持前-左缓存区。GL_FRONT_LEFT
GLint nStereoSupport;
glGetIntegerv(GL_STEREO, &nStereoSupport); // Win7 OGL 3.1不支持
// 双缓存交换链:前台缓存,后台缓存;还是单缓存只有前台缓存;Intel是3.1版本; 0 AMD是4.2版本启用了都支持
GLint nDoubleFrameBufferSupport;
glGetIntegerv(GL_DOUBLEBUFFER, &nDoubleFrameBufferSupport);// Win7 OGL 3.1支持
// 辅助缓存;Intel是3.1版本不支持; AMD是4.2版本支持4个辅助颜色缓存
// 辅助缓存区,OGL并没有指定这类缓存的特定用途,因此可以用来保存自己需要的帧缓存信息
// 例如从后台缓存区glCopyPixels到这里,下次渲染从这里拿到数据,避免重绘。
GLint nAluColorBuffer;
glGetIntegerv(GL_AUX_BUFFERS, &nAluColorBuffer);// Win7 OGL 3.1不支持,只有0个颜色辅助缓存
深度缓冲:
存储的是NDC坐标规范化后(基于观察坐标系的距离),OGL z值在[-1,1], DX在[0,1]; 然后在视口变换期间,将该z值用默认的glDepthRange函数将z值缩放到[0,1]之间,当然也可以用glDepthRange指定需要缩放到的深度值。
OGL会将变换后的深度值缓存起来,后面光栅化,FragmentShader后,经过了depth Test 可以指定比较函数,那么该位置的深度值就会和当前的深度缓存值比较,通过了则更新该位置的深度值。
模板缓冲:
方便实现规则或不规则形状的遮罩效果。
一般需要二次绘制,设置模板缓存初始值和写入策略,第一次绘制时候将指定形状(一般是图像指定)的数据写入模板缓存对应位置,后面再次渲染场景物体时候根据设置的模板比较函数,确定是否写入到后台缓存中,并确定是否更新模板缓存。
累积缓冲:
累积缓存也用于存储RGBA颜色数据(不能存储颜色索引模式下的索引值),不能直接写入,而是从颜色缓存区拷贝到累积缓存区或者从累积缓存区拷贝到颜色缓存区(以矩形块为单位操作的);通常用于把一系列的图像合成一副图像,经典应用是对图像进行超量采样(多重采样时超量采样的特例,多重多边缘采样,超量是对整副图像采样),然后对样本求平均值,并且将结果写入到颜色缓冲区实现场景抗锯齿效果。也可以实现运动模糊,和模拟照片景深效果。OGL3.0以上为浮点颜色缓存区实现这些效果,取消了累积缓冲区或辅助缓存区的支持。
运动模糊,是将一副周围场景图像,进行一定的比例缩放颜色成分后,再次写入到颜色缓存区,然后绘制主运动物体,得到运动模糊效果。
景深效果,用accPerspective控制主焦聚平面,模糊程度,多次从不同的角度绘制场景,累积写入到累积缓存,后面一次返回得到一个景深的图像效果。
柔和阴影,是每次用一盏灯绘制场景,多次绘制,累积组合到累积缓存中,然后结果输出到颜色缓存中。
抗锯齿微小移动,在边缘周围多次采样,累积采样组合后的效果输出,类似高斯模糊,得到抗锯齿效果,不需要均匀采样,在相邻的像素上采样效果更好。
累积缓存的这些图像操作(单次或多次绘制物体然后组合结果(时间上的),单次对一副图像多次附近周围采样组合结果(空间上):全景抗锯齿被alpha和多重采样覆盖代替,其它操作被OGL 3.0引入的帧缓冲区对象中的浮点像素格式很容易实现了。
二、缓冲区的基本操作:
1.清除缓冲区
先设置缓冲区的值,然后每次清理的时候将会采用该值。
// default value is 0.0f
glClearColor(0.0, 0.0, 0.0, 0.0);
// default value is 1.0f
glClearDepth(1.0f);
// default value is 0
glClearIndex(0.0f);
// default value is 0
glClearStencil(0);
// 清理
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//(GLenum buffer, GLint drawBuffer, GLfloat depth, GLint stencil);
glClearBufferfi(GL_DEPTH_STENCIL, 0, 1, 0);
2.选择用于读取和写入的颜色缓冲区
1)选择用于写入或清除的颜色缓存区,并禁用以前设置的写入的颜色缓存区
glDrawBuffer(GL_FRONT); // 默认情况下单缓存是GL_FRONT, 双缓存是GL_BACK。
OGL 3.0增加了帧缓冲区对象,当OpenGL绑定到一个用户指定的帧缓冲区,那么用GL_COLOR_ATTACHMENTi来指定那个颜色渲染缓冲区是绘制目标,i值在0和GL_MAX_COLOR_ATTACHMENTS之间。
GLint num = 0;
glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &num); // 输出8
GLenum buffers[] = { GL_FRONT, GL_FRONT_LEFT, GL_BACK_RIGHT }
glDrawBuffers(3, buffers);
#define GL_FRONT_LEFT 0x0400
#define GL_FRONT_RIGHT 0x0401
#define GL_BACK_LEFT 0x0402
#define GL_BACK_RIGHT 0x0403
#define GL_FRONT 0x0404
#define GL_BACK 0x0405
#define GL_LEFT 0x0406
#define GL_RIGHT 0x0407
#define GL_FRONT_AND_BACK 0x0408
#define GL_AUX0 0x0409
#define GL_AUX1 0x040A
#define GL_AUX2 0x040B
#define GL_AUX3 0x040C
2选择读取的颜色缓存区,用于glReadPixels(),glCopyPixels(), glCopyTexImage*(), glCopyTexSubImage*()和glCopyConvolutionFilter*()的像素读取来源,并禁用以前调用的glReadBuffer时所启用的缓存区。
glReadBuffer (GLenum mode);// 默认情况下单缓存是GL_FRONT, 双缓存是GL_BACK
OGL 3.0增加了帧缓冲区对象,当OpenGL绑定到一个用户指定的帧缓冲区,那么用GL_COLOR_ATTACHMENTi来指定那个颜色渲染缓冲区是读取目标,i值在0和GL_MAX_COLOR_ATTACHMENTS之间。
3.缓冲区的屏蔽写入
在对启用的帧缓冲区进行写入之前,会对数据执行屏蔽操作,所有的掩码都使用逻辑AND操作进行组合(非布尔类型的需要要写入的值和对应位置的掩码值进行AND操作),以写入对应的数据。
//mask中出现1那么写入,0拒绝
glIndexMask(GLuint mask);
// GL_TRUE表示对应的颜色成分写入,GL_FALSE拒绝
glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
glColorMaski(GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
// GL_TRUE深度缓冲可以写入,GL_FALSE不写入;只是决定写入深度缓存区,通过了测试才到这步
glDepthMask(GLboolean flag);
// mask中出现1那么写入,0拒绝;只是对当前模板缓存区中的值进行控制,通过了测试才到这步
glStencilMask(GLuint mask);
// 控制对应的面掩码,(GLenum face, GLuint mask); mask中出现1那么写入,0拒绝
glStencilMaskSeparate();
三、帧缓存区的测试,颜色混合抖动逻辑操作
其实现代很多的后期操作,都放到了fragment shader中设置。
但传统的,在片段Fragment着色处理后(抗锯齿,光照辅助颜色,雾化处理后),还需要进行进一步的操作,操作顺序是:
1)scissor test
默认情况下scissor测试被禁用。
// 裁剪测试可以用硬件快速执行,比Stencil快很多,但是只能是矩形的
// 对应Unity中的Mask2D, Stencil对应Unity中的Mask可以是按照遮罩形状来遮罩
glEnable(GL_SCISSOR_TEST);// 默认是禁用的
// 屏幕坐标系下的x,y,width, height
glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
glDisable(GL_SCISSOR_TEST);
GLboolean scissor = glIsEnabled(GL_SCISSOR_TEST);// 默认不启用
GLint box[4];
glGetIntegerv(GL_SCISSOR_BOX, box);// 默认是屏幕的大小
2)alpha test
默认情况下alpha测试被禁用。
如果启用,它就把源片断的alpha值和一个参考值进行比较,并根据比较结果接受或拒绝这个片断。
在默认情况下这个参考值是0,比较函数式GL_AWAYS, alpha测试被禁用。OGL3.1后alpha test被废弃了,代替操作是在fragment shader中通过discard操作来废弃片断。用途是过滤一定透明度的物体。贴花可以采用alpha过滤技术。
glEnable(GL_ALPHA_TEST);
glDisable(GL_ALPHA_TEST);
glAlphaFunc(GL_ALWAYS, 0.5f);
#define GL_NEVER 0x0200
#define GL_ACCUM_BUFFER_BIT 0x00000200
#define GL_LESS 0x0201
#define GL_EQUAL 0x0202
#define GL_LEQUAL 0x0203
#define GL_GREATER 0x0204
#define GL_NOTEQUAL 0x0205
#define GL_GEQUAL 0x0206
#define GL_ALWAYS 0x0207
3)stencil test
默认情况下模板测试也是禁用的。
(1).模板测试和模板测试函数:
glStencilFunc (GLenum func, GLint ref, GLuint mask);
glStencilFuncSeparate(GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask);
模板缓存的测试公式是:
当前模板ref&mask op 颜色缓存对应的当前模板缓存value&mask, ref为左值,value为右值,mask是设置模板缓存比较函数时候指定的。如果op比较函数为true那么通过测试,false则不通过。
func值为:
#define GL_NEVER 0x0200
#define GL_LESS 0x0201
#define GL_EQUAL 0x0202
#define GL_LEQUAL 0x0203
#define GL_GREATER 0x0204
#define GL_NOTEQUAL 0x0205
#define GL_GEQUAL 0x0206
#define GL_ALWAYS 0x0207
(2)模板缓存的写入:
模板缓存不能直接写入,但是可以通过将不规则形状写入颜色缓存draw call,然后更新模板缓存中对应位置的值。
glStencilOp (GLenum fail, GLenum zfail, GLenum zpass);
glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass);
// fail,zfail,zpass的值可以是:
#define GL_KEEP 0x1E00 // 保持模板缓存中的值
#define GL_REPLACE 0x1E01 // 用参考值替换当前模板缓存中的值
#define GL_INCR 0x1E02// 增加当前模板缓存中的值,超过最大值为最大值或者重置0
#define GL_DECR 0x1E03// 减少当前模板缓存中的值,超过最小值为最小值或者重置0
原理:渲染了一个物体,写入了模板缓存值。渲染第二个物体,时候会用当前的模板缓存值进行模板测试判断,这样通过第二个物体的渲染依赖于一个物体得到想要的结果,这是模板缓存的核心思想。
等值, 代表模板测试失败模板缓冲区操作,模板测试成功深度测试失败模板缓冲区操作,模板测试和深度测试都成功的模板缓冲区操作。
示例:
// 清空模板缓存
glClearStencil(0x0);
// 启用模板缓存
glEnable(GL_STENCIL_TEST);
// glDisable(GL_STENCIL_TEST);
// 模板测试函数和参考值写入到模板缓存。
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc (GL_ALWAYS, 0x1, 0x1);
glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
// 绘制1物体,模板测试函数,和参考值写入到模板缓存中
glStencilFunc (GL_EQUAL, 0x1, 0x1);
glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
// 绘制2物体,模板测试函数,和参考值写入到模板缓存中
glStencilFunc (GL_NOTEQUAL, 0x1, 0x1);
glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
(3)模板缓存区的应用
绘制不规则的物体,用贴图来实现。
奇偶跳转的模板缓存:
使用模板缓存绘制填充的凹多边形: 方法是对多边形引入一个点该点与原来多边形按顺序的两个点组成一个凸多边形,绘制每个多边形对模板缓存区进行奇偶选择写入,例如奇数次绘制该区域则写入,偶数将其清除,最后禁止模板缓存写入,开启颜色缓存写入,则可以将凹多边形绘制出来。
多个位数的模板缓存:
寻找冲突区域:对重叠的三维物体的冲突检测,重叠冲突会对画面产生偶尔的闪烁,且绘制空间位置也出现了问题,需要检测可以从空间角度引入一个裁剪平面来进行物体和该裁剪平面的相交检测。也可以在模板缓存区,维护多个位的模板缓存,对需要检测的一个物体在该位置有像素那么写入1位,其它物体物体是否有像素写入1位,另外一个位可以写入他们的并集或交集位是否为1。
4)depth test
draw call之间的,默认也是不启用的,但是绘制复杂场景时一般要求启用深度测试。
一般用于背面消隐,深度缓存中存储的距离是[0,1]的表明摄像机到观察物体之间的距离。
禁止深度测试,禁止深度缓存写入可以实现一些想要的图层效果。
GLint depth;
glEnable(GL_DEPTH_TEST);
glGetIntegerv(GL_DEPTH_TEST, &depth); // 默认是不启用的,但是一般要求启用深度测试
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS); // 可以指定深度测试函数的,一般是小于,有时候要特殊效果一般用GL_GREATER
遮挡查询:
在渲染物体之前,判断它是否可见,是复杂程序有效提高性能的好技巧,一般大型程序使用场景空间管理技术,而简单些的应用可以使用深度缓存(也就是遮挡查询)来判断它是否可见,使用遮挡查询需要先绘制一次会暂停OGL服务器的绘制,第二次绘制时候可以可以剔除掉绘制该物体,特别适用于静态物体,动态物体每次都要绘制两遍(先简单绘制一次,再最终绘制)很复杂的动态物体也是可以提高性能的。遮挡查询步骤:
(1)生成遮挡查询id
glGenQuery(GLsizei n, GLuint *ids);
GLboolean glIsQuery(GLuint id);
(2)对遮挡查询进行初始化
glBeginQuery(GLenum target, GLuint id); // target必须是GL_SAMPLES_PASSED
// draw call
glEndQuery(GLenum target);// target必须是GL_SAMPLES_PASSED
(3)提取通过了遮挡查询的样本数量
glGetQueryObjectiv(GLenum id, GLenum pname, GLint *params);
glGetQueryObject
returns in
params
a selected parameter of the query object specified by
id
.
pname
names a specific query object parameter.
pname
can be as follows:
GL_QUERY_RESULT
params
returns the value of the query object's passed samples counter. The initial value is 0.表示通过了深度测试的片段数量,大于0则绘制,否则二次真正绘制丢弃该物体。
GL_QUERY_RESULT_NO_WAIT
If the result of the query is available (that is, a query of
GL_QUERY_RESULT_AVAILABLE
would return non-zero), then
params
returns the value of the query object's passed samples counter, otherwise, the data referred to by
params
is not modified. The initial value is 0.
GL_QUERY_RESULT_AVAILABLE
params
returns whether the passed samples counter is immediately available. If a delay would occur waiting for the query result,
GL_FALSE
is returned. Otherwise,
GL_TRUE
is returned, which also indicates that the results of all previous queries are available as well.
GL_QUERY_RESULT_AVAILABLE 只能表示是不是一个有效的查询,不一定有遮挡需要GL_QUERY_RESULT进一步判断;但是一个无效的查询结果一定是无遮挡的。
(4)删除遮挡查询对象
glDeleteQueries(GLsizei n, GLuint *ids);
条件渲染
OGL 遮挡查询会暂停OGL服务器的绘制,通常会给性能带来灾难性的影响,条件渲染允许丢弃遮挡查询产生的OGL渲染命令,不暂停OGL服务器。
把执行查询的任务完全移到了OGL 服务器端,消除了性能的最大障碍,也很高效。
步骤是在遮挡查询内,第二次绘制物体时候使用:
//
GL_QUERY_NO_WAIT,GL_QUERY_BY_REGION_WAIT,
// GL_QUERY_BY_REGION_WAIT
glBeginConditionalRender(Queryid, GL_QUERY_WAIT);
glDrawCall
glEndConditionalRender();
5)混合
片段通过了所有的测试之后,最简单的是直接覆盖颜色缓存,但是要实现半透明或抗锯齿效果,需要对源像素和目标缓存像素该位置上的值进行混合。混合函数,混合因子取决于源像素和目标像素,还有设置的混合设置。
OGL 3.0后 可以对每个缓存区设置启动关闭混合
// target必须是GL_BLEND, index是0到GL_MAX_DRAW_BUFFERS之间的值。
glEnablei(GLenum target, GLuint index);
glDisablei(GLenum target, GLuint index);
glIsEnabledi(GLenum target, GLuint index);用于判断是否对该缓存开启了混合。
现代很多混合操作设置,都是在Fragment Shader中设置的,Shader会编译到该阶段的汇编指令进行处理。
6)抖动
在可用颜色数量较少的系统中,可能需要对颜色值进行抖动,在适当损失颜色质量的情况下增加可使用的颜色数量。是对混合后的结果进行的。抖动是其它颜色的成分按照一定的比例混合作为结果输出,在RGBA模式和颜色索引模式下都是可用的,且默认情况下抖动是启用的,抖动由硬件来实现,但在计算机已经具有了很高的颜色分辨率下,启用了抖动也不会引发抖动操作。
抖动算法很多,但是任何一种抖动都只依赖于源片断的颜色值(应该是混合后的,在临时缓存空间中的值),以及它的x,y坐标值。
7)颜色缓存的写入掩码和逻辑操作
颜色缓存区的写入,受到颜色掩码的控制glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);,默认情况下是每个颜色成分都写入。
也受到最后的逻辑操作的控制,逻辑操作是通过所有测试后的,源像素和目标像素混合,抖动后的在临时缓存区中的结果像素,写入颜色缓存的掩码控制glColorMask后的像素和真正存储在当前颜色缓存中的目标像素进行逻辑操作,可以通过glEnable(GL_INDEX_LOGIC_OP), glEnable(GL_COLOR_LOGIC_OP), 也可以使用OGL 1.0版本的glEnable(GL_LOGIC_OP),
操作设置函数是:glLogicOp(GLenum opcode);默认的逻辑操作是GL_COPY。不启用写入逻辑操作也会采用GL_COPY方式写入。
深度缓存的写入,模板缓存的写入,也受到写入掩码,写入操作函数设置的控制。
物体过滤和丰富图像效果的实现:
物体绘制过滤处理:
很多时候绘制一次物体和图像得不到想要的效果。需要进行多道渲染算法,也就是Shader中的多个Pass。
通过将信息记录在深度缓存中,或者记录在应用程序中。
启用深度测试,深度缓存写入,启用模板测试和模板缓存写入,禁止颜色缓存写入,通过绘制表面翻转,关闭一些状态开启一些状态
得到模板缓存中的值。
开启颜色缓存写入,
然后根据模板缓存中的值,绘制物体,得到想要的过滤效果。
丰富的图像效果:
多道渲染,结合浮点数的Shader过滤,移动,旋转,缩放,镜像变换,FBO帧缓存对象对图像的读写缓存(累积缓冲区), 可以得到很多丰富的图像效果。