上一篇介绍了深度测试,实际上深度测试执行是在模板测试之后进行的,只有通过了模板测试之后的片段才会进行深度测试。在片段着色器执行完之后,需要经过一系列的测试,如下过程:在Opengl 3.0 以后Alpha测试已剔除,最后的逻辑操作一般也不使用。
模板测试类似于深度测试,也有一个缓冲区,来存储模板值,叫做模板缓冲(Stencil Buffer)。模板缓冲中的模板支通常是8位的,所以每个片段共有256种不同的模板值。可以在模板缓冲中设置我们所需要的模板值,然后在做模板测试时就可以根据这个值决定片段的保留与丢弃。
举一个模板测试的例子来演示模板测试的作用,如下图。通过模板测试可以选择哪些地方可以显示出来,就像通过窗户看外面的风景一样。
该过程一般经过如下步骤:
- 开启模板测试
- 绘制模板,写入模板缓冲
- 关闭模板缓冲
- 渲染其他物体,基于该模板进行模板测试
通过GL_STENCIL_TEST来开启模板测试
glEnable(GL_STENCIL_TEST);
每次循环中也要清除模板缓冲区:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
同深度测试一样glDepthMask函数一样,模板缓冲也有一个相似函数。glStencilMask允许我们给模板值设置一个位遮罩(Bitmask),它与模板值进行按位与(AND)运算决定缓冲是否可写入。
//此时,模板缓冲可写
glStencilMask(0xFF);
//此时,模板缓冲不可写
glStencilMask(0x00);
模板测试时如何进行比较呢,*glStencilFun*c 函数提供了比较的操作。
void glStencilFunc(GLenum func, GLint ref, GLuint mask)
glStencilFunc(GL_EQUAL, 1, 0xFF);
这个函数意思是,对遮罩0xFF按位与,然后模板中的值等于引用值1时就通过测试,否则不通过。
上面的函数告诉了怎么进行模板测试,但是如何更新模板缓冲呢?需要glStencilOp来设定。
void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
这里 pass 操作涉及到了深度测试,三种操作的关系如下[流程图]
(http://www.cnblogs.com/mikewolf2002/archive/2012/05/15/2500867.html):
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);
该函数表明任何测试,无论失败或通过,均保留现有模板缓冲的值
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
将第三个参数改为GL_REPLACE后,如果模板测试和深度测试都通过后,将更新模板缓冲的值,即glStencilFunc函数的ref值。
该例子中实现的效果如下,就像从一个窗口中看物体。
主要代码设置如下:
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
//清除缓冲区
glClearColor(0.16f, 0.05f, 0.15f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilMask(0xFF);
//数据不写入颜色缓冲区,只是使用矩形来作为模板测试
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
//深度缓冲区禁止写入,不影响后面的绘制操作
glDepthMask(GL_FALSE);
//绘制矩形模板
...
----------------------------------------------
glDepthMask(GL_TRUE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_EQUAL, 1, 0xFF);
glStencilMask(0x00);//设定为只读状态
//绘制物体
---------------------------------------------------
//绘制结束后需要设定可写入状态,因为还要清除模板缓冲
glStencilMask(0xFF);
轮廓的绘制可以将一个物体展示的更显眼,游戏中常使用该方法将一个物体或人物作为选中对象,先来看一下效果:
主要思路如下:
1. 在绘制箱子时,将模板设定可更新,并更新为1。
2. 将箱子通过scale变换,放大箱子。
3. 绘制时使用纯色来绘制边缘,并设定模板值不等于1时通过测试,即只保留边框部分,注意此时不更新模板缓冲的值。
//开启深度测试和模板测试
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
/*绘制正常的两个立方体*/
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilMask(0xFF);
//绘制箱子
....
//-----------------------------------------------
//绘制放大的物体
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);//关闭深度缓冲,防止轮廓被遮挡
//矩阵变换,放大箱子
model = glm::scale(model, glm::vec3(1.1f, 1.1f, 1.1f));
//绘制放大的箱子
...
//--------------------------------
注意绘制后开启深度测试和模板写入权限
glStencilMask(0xFF);
glDisable(GL_DEPTH_TEST);
通过模板测试可以实现很多特效,如Planar reflections(镜面反射),有兴趣的可以自己尝试下。
1.http://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/02%20Stencil%20testing/
2.https://open.gl/depthstencils
3.http://blog.csdn.net/wangdingqiaoit/article/details/52143197