片元后处理(Per-fragment operation)
per-fragment系列操作包括模板测试,深度测试,混合测试等一系列对每个片元进行的处理,这一系列处理过程如下:
*Pixel ownership test:该测试决定像素在 framebuffer 中的位置是不是为当前 OpenGL ES 所有。也就是说测试某个像素是否对用户可见或者被重叠窗口所阻挡
*Scissor Test:剪裁测试,判断像素是否在由 glScissor 定义的剪裁矩形内,不在该剪裁区域内的像素就会被剪裁掉;
*Stencil Test:模版测试,将模版缓存中的值与一个参考值进行比较,从而进行相应的处理;
*Depth Test:深度测试,比较下一个片段与帧缓冲区中的片段的深度,从而决定哪一个像素在前面,哪一个像素被遮挡;
*Blending:混合,混合是将片段的颜色和帧缓冲区中已有的颜色值进行混合,并将混合所得的新值写入帧缓冲;
*Dithering:抖动,抖动是使用有限的色彩让你看到比实际图象更多色彩的显示方式,以缓解表示颜色的值的精度不够大而导致的颜色剧变的问题。
模板缓冲的使用
模板测试的是一种可以以一定标准丢弃片元的方法,这个标准就是借助模板缓冲和我们指定的测试函数而运作的。
模板测试是在深度测试之前进行的,可以作为一种丢弃片元的辅助方法。
如图中所示,模板缓冲中为1的地方我们选择保留图形,而其他部分则丢弃,形成最终的效果。
使用模板缓冲需要三个要素:
- 正确的时间开启和关闭深度缓冲
- 模板测试函数
- 模板测试函数失败或者成功后的执行的动作
在OpenGL中开启模板缓冲的方法如下:
- 1
同时和深度缓冲一样,需要清除,默认清除时写入0,可以通过glClearStencil设置清除的指定值。
- 1
- 2
一般地绘制模板以及利用模板选择性地绘制物体时则开启模板缓冲,绘制其他物体时关闭模板缓冲。使用模板缓存的步骤一般如下:
- 开启模板测试
- 绘制模板,写入模板缓冲(不写入color buffer和depth buffer)
- 关闭模板缓冲写入
- 利用模板缓冲中的值,绘制后续场景
与模板测试相关的函数
glStencilMask 函数用于控制模板缓冲区的写入,使用位掩码的方式决定是否可以写入模板缓冲区,使用得较多的是0x00表示禁止写入,0xFF表示允许任何写入。
glStencilFunc ,用于指定模板测试的函数,这里指定是什么情况下通过模板测试。
API void glStencilFunc(GLenum func, GLint ref, GLuint mask);
func同深度测试一样,指定函数名GL_NEVER,GL_LESS,GL_LEQUAL等函数。
ref是和当前模板缓冲中的值stencil进行比较的指定值,这个比较方式使用了第三个参数mask,例如GL_LESS通过,当且仅当
满足: ( ref & mask ) < ( stencil & mask ).GL_GEQUAL通过,当且仅当( ref & mask ) >= ( stencil & mask )。
一般地,我们将上述函数的mask置为0xFF,用于比较,则比较时计算方式比较直观。例如:
- 1
表示当前模板缓冲区中值为1的部分通过模板测试,这部分片元将被保留,其余地则被丢弃。
glStencilOp 用于指定测试通过或者失败时执行的动作,例如保留缓冲区中值,或者使用ref值替代等操作。
API void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
sfail表示深度测试失败,dpfail表示模板测试通过但是深度测试失败,dppass表示深度测试成功。GLenum部分填写的是对应条件下执行的动作,例如GL_KEEP表示保留缓冲区中值,GL_REPLACE表示使用glStencilFunc设置的ref值替换。更完整的参数列表可以参考glStencilOp。
这三个测试的关系如下图所示(来自cnblogs 迈克老狼2012 depth/stencil buffer的作用):
绘制矩形模板
首先实现上面给出的矩形模板,通过矩形模板我们将场景中不被矩形覆盖的部分丢弃,最终只显示一个矩形遮盖的区域。
绘制流程用代码简要地表示如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
需要注意的是,绘制模板时使用代码:
- 1
- 2
禁止写入颜色和深度缓冲区,因为矩形模板最终是不显示在屏幕上的。后面我们可以看到,有些时候模板也需要显示,注意在合适的时候调整这部分代码。
最终的效果如下图所示:
outline 轮廓效果
这部分参考自www.learnopengl.com Stencil testing。
outline就是轮廓的效果,在游戏场景中,例如玩家选取了附件的物体时,通过轮廓线条来表示选取了哪些物体,十分有用。实现的思路是:
-
现正常比例绘制物体,同时绘制的部分作为模板
-
适当放大比例,在刚绘制的物体上绘制一个轮廓,使用模板缓冲实现。
实现的轮廓效果如下:
这里第一次绘制了原始的立方体,通过原始立方体填充了模板缓冲区为1,第二次绘制放大一点的立方体,对比缓冲区中值等于1则丢弃,因此只显示处理轮廓部分。缓冲区比较函数实现为:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
需要注意的是绘制轮廓线条时,需要暂时关闭深度测试,否则眼前的平面会把底部的轮廓线条遮挡掉,如果忘记了关闭深度测试,那么错误的效果如下:
需要注意地是正确地开启和允许模板缓冲区的读写,例如如果在第一次主循环结束之前忘记使用代码:
- 1
- 2
开启和允许模板缓冲写操作,那么得到的错误效果如下:
planar reflections 镜面效果
镜面效果表示的是物体的原始和反射后的图像,形成的一种镜面效果。实现思路为:
- 绘制镜面模板
- 利用模板,使用镜像变换(reflection transformation),绘制反射后物体
- 使用混色(blend)绘制镜面
- 绘制原始物体
实现效果如下:
实现绘制反射的物体,主要通过向缩放操作传递负数来实现镜像变换,如果对镜像变换不熟悉,可以回过头去参考模型变换(model transformation) 。需要注意地是,在本节代码中镜面位置在y=-0.5,那么镜像变换时,需要注意镜像变换的中心问题。这里关于y=-0.5变换,而不是y=0即x轴变换,因此首先将物体从变换中心(0.0,-0.5)移动到原点(0,0),缩放后将物体再次移动到(0.0,-0.5),这一过程表示为:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
这里模板缓冲的作用,主要是使镜像看起来在镜面上,如果不使用模板缓冲,效果如下:
这里红色部分显示在镜面外面,带有明显的视觉bug,通过模板缓冲,我们决定只将镜像在镜面部分的内容显示出来,形成的镜面效果看起来才更真实。下面给出一个完整的不使用和使用模板的效果对比图:
在实现镜面效果的时候,注意镜面需要开启blend效果,就是混色的效果,通过混色,我们在镜面上显示了少许镜子的颜色(我们使用红色模拟镜面),看到了更多的是物体的镜像的颜色。如果不开启混色,那么物体的镜像将被镜面遮住,看不到镜像。使用混色(blend)绘制镜面,可以通过如下代码实现:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
关于blend的内容,下一节将会介绍,这里不再展开。
最后的说明
模板测试使用的思路很简单,先通过绘制物体写入模板缓冲,这是一个建立的过程;第二步是利用模板缓冲中的值选择性地丢弃或者保留片元,从而制造特效。不同的效果,调弄这些模板函数的方式各不相同。在使用过程中,尤其需要注意的是何时开始缓冲区,以及缓冲区写入的模式设置问题。关于planar reflections,一个比较好的参考资料点击这里。