Opengl学习之——模板测试

模板测试介绍

上一篇介绍了深度测试,实际上深度测试执行是在模板测试之后进行的,只有通过了模板测试之后的片段才会进行深度测试。在片段着色器执行完之后,需要经过一系列的测试,如下过程:在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)
  • func:设置模板测试操作。这个测试操作应用到已经储存的模板值和glStencilFunc的ref值上,可用的选项是:GL_NEVER、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL、GL_ALWAYS。
  • ref:指定模板测试的引用值。模板缓冲的内容会与这个值对比。
  • mask:指定一个遮罩,在模板测试对比引用值和储存的模板值前,对它们进行按位与(and)操作,初始设置为1。
    glStencilFunc(GL_EQUAL, 1, 0xFF);

这个函数意思是,对遮罩0xFF按位与,然后模板中的值等于引用值1时就通过测试,否则不通过。

上面的函数告诉了怎么进行模板测试,但是如何更新模板缓冲呢?需要glStencilOp来设定。

void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
  • sfail: 如果模板测试失败将采取的动作。
  • dpfail: 如果模板测试通过,但是深度测试失败时采取的动作。
  • 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

猜你喜欢

转载自blog.csdn.net/u011371324/article/details/77605908