模版测试的主要作用是利用模版缓冲区(Stencil Buffer)所保存的模版值决定当前的片段是否丢弃的过程,模版测试发生在深度测试之前。
模板缓冲区是一个逐像素掩码,也就是每个Pixel 都会和模板缓冲区的值比较,决定是否要绘制,模板缓冲区保存的是每个像素是否要被更新的标志位。
模板缓冲区的使用分为两步:
模板测试的一般步骤:
启用模板测试 glEnable(GL_STENCIL_TEST),
清空模板缓冲区 glClear( GL_STENCIL_BUFFER_BIT);
模版缓冲区的更新Mask(注意和参数的操作是按位与)
用于控制当前模版缓冲区是否可写
// 模板值与其按位与的运算结果是模板值,模板缓冲区可写
glStencilMask(0xFF);
// 模板值与其按位与的运算结果是0,模板缓冲区不可写
glStencilMask(0x00);
void glStencilFunc(GLenum func, GLint ref, GLuint mask);
// 参数说明:
// func: 设置模板测试操作,这个操作应用到已经存储模板值和 glStencilFunc 的 ref 值上,可用的选项包括:
#define GL_LESS 0x0201
#edfine GL_EQUAL 0x0202
#define GL_LEQUAL 0x0203
#define GL_GREATER 0x0204
#define GL_NOTEQUAL 0x0205
#define GL_GEQUAL 0x0206
#define GL_ALWAYS 0x0207
#define GL_KEEP 0x1E00
ref: 指定模板测试的引用值,模板缓冲区的模板值会和这个值进行对比
mask:指定一个遮罩,在模板测试对比引用值和存储的模板值前,对它们进行按位与的操作,初始值设置为 1
void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
参数说明: s:stencil dp: depth
sfail:如果模板测试失败将如何更新模板值;
dpfail:如果模板测试通过,但是深度测试失败时将如何更新模板值;
dppass:如果深度测试和模板测试都通过,将如何更新模板值
参数的可选操作:
操作 | 描述 |
---|---|
GL_KEEP | 保存现在的模板值 |
GL_ZERO | 将模板值设置为0 |
GL_REPLACE | 将模板值设置为用 glStencilFunc 函数设置的ref值 |
GL_INCR | 如果模板值不是最大值就将模板值 +1 |
GL_INCR_WRAP | 与 GL_INCR 一样将模板值 +1 ,如果模板值已经是最大值则设为 0 |
GL_DECR | 如果模板值不是最小值就将模板值 -1 |
GL_DECR_WRAP | 与 GL_DECR 一样将模板值 -1 ,如果模板值已经是最小值则设为最大值 |
GL_INVERT | 按位反转当前模板缓冲区的值 |
绘制物体轮廓是模板测试的常见应用,其步骤一般如下:
实际原理就是用实际物体的大小作为模版缓冲区,略微放大一下物体,此时比原物体大的部分作为后面的轮廓,再使用模版测试,将模版值不为 1 的地方进行渲染,就可以得到物体的轮廓。
实际代码如下:
//启动深度测试和模板测试,清空模板和深度缓冲
glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 0xFF); //所有片段都要写入模板缓冲
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);//若模板测试和深度测试都通过了,将片段对应的模板值替换为1
// 开启模版缓冲区写入
glStencilMask(0xFF);
//绘制物体
glBindVertexArray(m_VaoId);
glUseProgram(m_ProgramObj);
glUniform3f(m_ViewPosLoc, 0.0f, 0.0f, 3.0f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
UpdateMatrix(m_MVPMatrix, m_ModelMatrix, m_AngleX, m_AngleY , 1.0, glm::vec3(0.0f, 0.0f, 0.0f), ratio);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glUniformMatrix4fv(m_ModelMatrixLoc, 1, GL_FALSE, &m_ModelMatrix[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);//当片段的模板值不为 1 时,片段通过测试进行渲染
//禁用模板写入和深度测试
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
//绘制物体轮廓
glBindVertexArray(m_VaoId);
glUseProgram(m_OutlineProgramObj);
//放大 1.05 倍
UpdateMatrix(m_MVPMatrix, m_ModelMatrix, m_AngleX, m_AngleY, 1.05, glm::vec3(0.0f, 0.0f, 0.0f), ratio);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glUniformMatrix4fv(m_ModelMatrixLoc, 1, GL_FALSE, &m_ModelMatrix[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
//开启模板写入和深度测试
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);