【OpenGL】模板测试(StencilTest)
目录
一、模板测试(StencilTest)的引入
二、相关API
1、模板测试的开启/关闭
2、模板测试通过/失败后
3.效果实现
三、模板测试的封装
前言:总结于b站赵新政老师的OpenGL课程,含个人理解,如有错误,很高兴大家可以指出,我会尽快修改的OwO(赵新政的个人空间-赵新政个人主页-哔哩哔哩视频)
一、模板测试(StencilTest)的引入
现在,我们想要为一个物体进行“勾边”,让他更为突出。
该效果实际上是由两个盒体进行实现,首先先渲染box本体,然后边框特效则是将原本的盒体扩大一点,替换成白色材质进行渲染。
可是呢,当我们进行渲染的时候,很显然后渲染的边框效果会遮盖住前面渲染的本体。而为了解决这个问题,就要确保边框效果不会覆盖掉本体内容。因而,就出现了模板测试(StencilTest)。
首先了解模板缓冲(StencilBuffer):与深度缓冲相似(一张画布),记录了当前绘制的物体覆盖了哪些像素区域(8bit,一般而言8bit就足够使用了)。
借助模板缓冲,可以知道那些像素被绘制过,从而指定规则,只有通过本规则测试的像素才能绘制出来。
在上一物体进行渲染的时候,我们将会根据颜色缓冲更新模板缓冲,在进行下一个物体的渲染的时候,会将当前的颜色缓冲与模板缓冲进行比较,根据设置的模板测试类型来决定是否需要对当前颜色进行渲染。
二、相关API
1、模板测试的开启/关闭
void glEnable(GL_STENCIL_TEST);//开启模板测试
void glDisable(GL_STENCIL_TEST);//关闭模板测试
模板测试条件:设置当前渲染Mesh通过模板测试的条件
void glStencilFunc(GLenum func,GLint ref,GLuint mask);
func:使用模板缓存中的数值与ref如何对比才算通过测试;
ref:参考数值
mask:缓存数值与参考值进行比较前,都要先与mask做AND(&)操作
func:
GL_NEVER:总是不允许通过测试
GL_ALWAYS:总是通过测试
GL_EQUAL:相等才通过测试
GL_NOTEQUAL:不等才通过测试
GL_LESS:小于才通过测试
GL_GREATER:大于才通过测试
假如我们使用GL_EQUAL
那么假如当前的模板缓冲中的值为value=3,mask=1。
此时如果我们的模板数值ref=1
那么value&mask=1 , ref&mask=1
两个结果相同,那么当前通过了模板测试,可以进行渲染。
2、模板测试通过/失败后
void glStencilOp(GLenum sfail,GLenum zfail,GLenum zpass);
- sfail:模板测试失败的时候执行什么操作
- zfail:模板测试通过但深度测试失败时执行的操作
- zpass:模板测试和深度测试都通过时执行的操作
GL_KEEP:保持当前的模板值
GL_ZERO:将模板值设置为0
GL_REPLACE:将模板值设置为glStencilFunc中指定的参考值
GL_INCR:如果当前模板值小于最大值,则将模板值+1
GL_INCR_WRAP:类似于GL_INCR,但是如果增加后超过了最大值则会绕回0。
GL_DECR:如果当前模板值大于0,则将模板值减1
GL_DECR_WRAP:类似于GL_DECR,但是如果减小后小于0则会绕回最大值。
GL_INVERT:按位翻转当前的模板值
类似于深度缓冲写入,我们也可以决定是否对模板缓冲进行写入。
·开启模板测试情况下,禁止模板缓冲写入
void glStencilMask(GLuint mask)
mask:掩码的每一位对应于模板缓冲区中的每一位,,每当一个位是1时,模板缓冲区的相应位可以被写入;当一个位是0时,相应位不能被写入。
0x00:任何bit位都不允许写入
0xFF:任何bit位都允许写入
举例来讲,如果当前模板缓冲的参考值ref=3,且已经通过了模板测试,若mask=2,则其进行模板缓冲写入的时候,当前新的模板缓冲值=ref&mask=2,所以,如果mask=0x00,那么模板缓冲写入始终为0,而当mask=0xFF的时候,就会直接写入ref。
3.效果实现
因而,想要实现上述的效果,对于box本体而言,其应当开启模板测试之后,设置总是可以通过模板测试(GL_ALWAYS),并设置好ref值,模板测试mask和模板缓冲写入的mask设置为0xFF,其通过模板测试和深度测试的操作应当设置为替换(GL_REPLACE),其余为保持(GL_KEEP)
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS,1,0xFF);
glStencilMask(0xFF);
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
而对于白色的框体而言,就应该在开启模板测试之后设置只有当模板测试值不等(GL_NOTEQUAL)的时候才进行渲染,ref值取1,模板测试的mask设置为0xFF,模板缓冲写入的mask设置为0x00(不进行写入)
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NOTEQUAL,1,0xFF);
glStencilMask(0x00);
三、模板测试的封装
从设置模板测试的变量去看,我们需要为每一个材质material加入如下变量
//Stencil模板测试
bool m_stencil_test{ false };
GLenum m_stencil_func{ GL_ALWAYS };
GLint m_stencil_ref{ 0 };
GLuint m_stencil_func_mask{ 0xFF };GLenum m_fail{ GL_KEEP };
GLenum m_zfail{ GL_KEEP };
GLenum m_zpass{ GL_KEEP };GLuint m_stencil_mask{ 0xFF };
于是,在渲染阶段,就需要根据材质material中的变量,来对模板测试进行设置,包括:
(1)是否要开启模板检测 glEnable/glDisable(GL_STENCIL_TEST)
(2)通过测试的操作设置 gl_StencilOp(fail,zfail,zpass)
(3)模板测试的比较方式 gl_StencilFunc(func,ref,mask)
(4)是否写入模板缓冲 gl_StencilMask(mask)
void Renderer::set_stencil_test(Material* material)
{if (material->m_stencil_test){glEnable(GL_STENCIL_TEST);glStencilOp(material->m_fail, material->m_zfail, material->m_zpass);glStencilFunc(material->m_stencil_func, material->m_stencil_ref, material->m_stencil_func_mask);glStencilMask(material->m_stencil_mask);}else{glDisable(GL_STENCIL_TEST);}
}
我们可以考虑去实现如下场景,对两个粘连在一起的正方体进行勾边:
在这种情况下,我们会发现,其中一个正方体的勾边遮住了另外一个正方体,在正常情况下我们是不希望这样的。
所以,为了解决这个问题,就需要关闭勾边的深度检测,这样,就可以确保勾边不会影响到原来的像素。