OpenGL-17-01-模版测试
基本说明
模版测试执行时机:当片段着色器处理完一个一个片段后,模版测试就会进行。
模版测试和深度测试一样,是有可能丢弃片段的。
模版测试是根据模版缓冲进行的。
如何使用模版测试
通过这行代码启动模版测试
glEnable(GL_STENCIL_TEST);
注意,和颜色和深度缓冲一样,你也需要在每次迭代之前清除模板缓冲。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
和深度测试的glDepthMask
函数一样,模板缓冲也有一个类似的函数。glStencilMask
允许我们设置一个位掩码(Bitmask
),它会与将要写入缓冲的模板值进行与(AND)运算。默认情况下设置的位掩码所有位都为1,不影响输出,但如果我们将它设置为0x00
,写入缓冲的所有模板值最后都会变成0.这与深度测试中的glDepthMask
(GL_FALSE)是等价的。
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
大部分情况下你都只会使用0x00
或者0xFF
作为模板掩码(Stencil Mask)
,但是知道有选项可以设置自定义的位掩码总是好的。
通过这个函数,我们可以控制是否写入模版缓存
通过函数控制模版测试
glStencilFunc(GLenum func, GLint ref, GLuint mask)
一共包含三个参数:
func
:设置模板测试函数(Stencil Test Function)
。这个测试函数将会应用到已储存的模板值上和glStencilFunc
函数的ref
值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。ref
:设置了模板测试的参考值(Reference Value)
。模板缓冲的内容将会与这个值进行比较。mask
:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。
这个函数用来控制通过模版测试的条件,也就是把模版缓存值与函数设置的ref
进行比较,并判断条件
这里注意和深度测试区别:深度测试是将当前片段的z
值与缓存值进行比较,而模版测试中,片段本身没有值
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
一共包含三个选项,我们能够设定每个选项应该采取的行为:
sfail
:模板测试失败时采取的行为。dpfail
:模板测试通过,但深度测试失败时采取的行为。dppass
:模板测试和深度测试都通过时采取的行为。
每个选项都可以选用以下的其中一种行为:
行为 | 描述 |
---|---|
GL_KEEP | 保持当前储存的模板值 |
GL_ZERO | 将模板值设置为0 |
GL_REPLACE | 将模板值设置为glStencilFunc 函数设置的ref 值 |
GL_INCR | 如果模板值小于最大值则将模板值加1 |
GL_INCR_WRAP | 与GL_INCR一样,但如果模板值超过了最大值则归零 |
GL_DECR | 如果模板值大于最小值则将模板值减1 |
GL_DECR_WRAP | 与GL_DECR一样,但如果模板值小于0则将其设置为最大值 |
GL_INVERT | 按位翻转当前的模板缓冲值 |
通过这个函数,我们可以控制,出现对应的情况时,如何更新模版缓存的值。只用通过这个函数,才能更新这个模版缓存值。默认的行为下(GL_KEEP, GL_KEEP, GL_KEEP)
也就是默认情况下无法更新模版缓存值。
注意:如果你想要更新模版缓存的值,那么这个函数你不能使用默认情况,并且,模版缓存位掩码必须位0xFF
,否则也同样无法写入缓存。
也就是,设置允许写入模版缓存,如果设置了禁用写入,那么即使我们设置了上面这个函数对模版缓存的更新方式也无效
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
物体轮廓
现在,我们已经能够精确的去控制模版测试了
想要在渲染时渲染出物体轮廓,我们的思路是:
首先开启模版测试,并设置缓存更新方式
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
我们想要渲染第一个物体时,让他通过深度测试并且通过模版测试时使用1
来更新模版缓存值
这样一来,这个物体的片段上的模版缓存值就为1
了。
其次,然后我们禁用模版写入,并且重新设置缓存通过条件,我们设置为模版缓存值不等于1时,通过。为什么这样做?我们马上会解释。这一次我们放大物体,没错,将其微微放大,那么,这次的片段就会覆盖之前物体的片段,但是之前物体的片段的模版缓存值已经设置为1了,而我们却将模版测试的条件设置为不等于1,因此所有和之前物体位置重复的片段都会被丢弃,只会渲染最外层的边界,而这就是我们想要的物体轮廓,然后我们使用固定的颜色去渲染这个轮廓,而不是使用物体颜色,不然看起来就不是轮廓了。
代码实现
首先开启模版测试
并设置更新模版缓存值的方式
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
// floor,不写入地面的模型缓存
glStencilMask(0x00);
//渲染地面
渲染地面时,我们不写入模版缓存(禁用模版缓存写入),因为我们要画的是物体的边框,如果在渲染地面的时候写入模版缓存,边框就不对了。
然后,我们在渲染第一个物体时,首先设置模版测试通过条件为,直接通过,并设置值为1
由于我们在上面已经设置了,如果通过深度测试和模版测试,就会将ref
写入模版缓存,而此时,就会将1写入模版缓存
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
//渲染第一个物体
然后,我们在渲染第二个物体之前,禁用模版缓存写入,并更改模版测试通过条件即可
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);//设置不等于1则通过测试
glStencilMask(0x00);//禁用模版写入
//渲染第二个物体(也就是轮廓)
最后,重新开启模版缓存写入
glStencilMask(0xFF);
这里要注意一点,模版缓存如果禁用了,那么对于模版缓存的清空(因为清空的本质就是把缓存值更改为0)也会被禁用,也就是,如果最后我们不重新开启模版缓存写入,会导致第二次循环无法清空模版缓存,就会出现不可预料的问题。
还有就是,在第一次循环没有任何模版缓存写入时,所有片段的缓存值为0
如果不设置模版测试的条件,默认未知。所以最好循环开始前手动设置模版测试条件。另外在循环结束时最好恢复最初的模版测试条件
glStencilFunc(GL_ALWAYS,1,0xFF);