模板测试与深度测试类似,但在渲染管线中发生在深度测试之前。模板测试也会丢弃掉一些片段,只是丢弃的片段数量比深度测试少。
同时该测试也是基于另一个缓冲区 --- 模板缓冲区(stencil buffer),同理该缓冲区也是由我们创建窗口库创建的,我使用的库是GLFW库。该模板缓冲区中的模板值大小为8位所以每个像素有256种不同的模板值,就好比RGBA每一个中颜色有256 = 2^8种,所以总的大小为32 位.所以我们就根据模板值来决定是否丢弃或保留。
模板测试例子:
首先要使用模板测试,必须先开启glEnable(GL_STENCIL_TEST),然后清除上一帧的模板缓冲glClear(GL_STENCIL_BUFFER_BIT)设置所有片段的模板值为0,然后开启矩形片段用1填充。场景中的模板值为1的那些片段才会被渲染,其他的都被丢弃。
使用模板缓冲的过程:
- 开启模板缓冲写入。glEnable(GL_STENCIL_TEST)
- 渲染物体,更新模板缓冲。模板缓冲函数后面介绍
- 关闭模板缓冲写入。glDisable(GL_STENCIL_TEST)
- 渲染(其他)物体,这次基于模板缓冲内容丢弃特定片段。
模板缓冲的写入:
与深度测试函数管理glDepthMask()相似,模板缓冲有个函数glStencilMask()来设置是否可以对缓冲区进行写入,其原理是给模板值设置一个位遮罩(Bitmask),它与模板值进行按位与(and)运算决定缓冲是否可写。默认参数设置的位遮罩是0xFF(1),这样就不会影响输出,但是如果我们设置为0x00,所有写入深度缓冲最后都是0,说明模板缓冲区不可写入
配置模板测试函数:
void glStencilFunc(GLenum func, GLint ref, GLuint mask)
函数有三个参数:
- func:设置模板测试操作。这个测试操作应用到已经储存的模板值和
glStencilFunc
的ref
值上即判断模板测试通过的条件,例如我func设置为GL_EQUAL,ref为1,那么模板测试通过的条件就是模板值等于1,可用的选项是:GL_NEVER
、GL_LEQUAL
、GL_GREATER
、GL_GEQUAL
、GL_EQUAL
、GL_NOTEQUAL
、GL_ALWAYS,
它们的语义和深度缓冲的相似。 - ref:指定模板测试的引用值。模板值会与这个值按func方式进行对比。
- mask:指定一个遮罩,在 模板测试 对比 引用值ref和储存的模板值 前,对它们进行按位与(and)操作,初始设置为1。
glStencilFunc
只描述了OpenGL对模板缓冲做什么,而不是描述我们如何更新缓冲区,此时就出现了glStencilOp()
void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
函数包含三个选项,我们可以指定每个选项的动作:
- sfail: 如果模板测试失败将采取的动作。
- dpfail: 如果模板测试通过,但是深度测试失败时采取的动作。
- dppass: 如果深度测试和模板测试都通过,将采取的动作。(此处有点迷惑就是深度测试不是发生在模板测试之后吗,为什么会出现这个选项)
glStencilOp
函数默认设置为 (GL_KEEP, GL_KEEP, GL_KEEP) ,所以任何测试的任何结果,模板缓冲都会保留它的值。总之使用glStencilFunc
和glStencilOp
,我们就可以指定在什么时候以及我们打算怎么样去更新模板缓冲了,我们也可以指定何时让测试通过或不通过。
模板测试的应用:制作物体的轮廓(就好比在策略游戏中鼠标点击的物体身边会出现光圈)
具体的步骤如下:
- 在绘制物体前,把模板方程设置为
GL_ALWAYS
,用1更新物体将被渲染的片段。//使用glStencilOp选项中的GL_REPLACE将模板值设置成glStencilFunc中的ref值 - 渲染物体,写入模板缓冲。
- 关闭模板写入和深度测试。
- 每个物体放大一点点。
- 使用一个不同的片段着色器用来输出一个纯颜色。
- 再次绘制物体,但只是当它们的片段的模板值不为1时才进行。
- 开启模板写入和深度测试。
代码如下:
代码分析:
1.首先开启模板测试
2.如果任何测试失败我们值保持深度缓冲中当前所储存着的值。如果模板测试和深度测试都成功了,我们就将储存着的模板值替换为1
,我们要用glStencilFunc(等到该函数执行时还功能才实现)
来做这件事。
3.清楚上一帧的颜色缓冲,深度缓冲,模板缓冲值
4.绘制地板时禁止对模板缓冲的写入
5,6.绘制地板
7.设置模板测试都能通过,且ref为1即将模板缓冲区值设置成1
8.设置模板缓冲区是可写入的
9.绘制两个容器
10.我们把模板方程设置为GL_NOTEQUAL
,它保证我们只箱子上不等于1的部分,这样只绘制前面绘制的箱子外围的那部分
11.注意,我们也要关闭深度测试,这样放大的的箱子也就是边框才不会被地面覆盖。
12.再次开启深度缓冲。