本文同时发布在我的个人博客上:https://dragon_boy.gitee.io
模板测试
在片元着色器处理片段后,模板测试可以被执行,类似于深度测试,有一些丢弃片段的选项。之后,剩余的片段进行深度测试,OpenGL接着丢弃更多的片段。模板测试基于一种新的缓冲--模板缓冲。一个模板缓冲的值的大小为8bit,每个像素总共由256个不同的模板值。注意:GLFW会自动创建模板缓冲,其它的不一定,根据实际情况而定。下面是模板缓冲的一个例子:
模板缓冲首先用0填充,之后部分位置用一个开放矩形的1阵列填充。在模板测试之后,只有模板值为1的片段会被渲染出来。
使用模板测试的流程大致如下:
- 开启模板测试,允许写入模板缓冲。
- 渲染对象,更新模板缓冲。
- 禁止写入模板缓冲。
- 渲染对象,这一次基于模板缓冲丢弃特定的片段。
我们通过glEnable(GL_STENCIL_TEST)来开启模板测试:
glEnable(GL_STENCIL_TEST);
接着,记得在渲染循环中清除模板缓冲来更新值:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
和深度测试一样,我们可以使用slStencilMask来设置掩码,掩码的值将与模板值进行与运算后写入模板缓冲。默认是设置为0xFF,不影响模板值,当然也可以设置为0x00来将模板值全部置为0(相当于禁止写入):
glStencilMask(0xFF); // 不变
glStencilMask(0x00); // 置为0
模板方法
和深度测试一样,模板测试也有一些特定的方法可以使用来控制模板测试。有两个方法可以使用:glStencilFunc和glStencilOp。
glStencilFunc(GLenum func, GLint ref, GLuint mask)有三个参数:
- func:设置决定模板测试如何丢弃片段的参数,有以下几个选项:GL_NEVER, GL_LESS, GL_LEQUAL,GL_GREATER,GL_GEQUAL,GL_EQUAL,GL_NOTEQUAL,GL_ALWAYS。这些选项的解释和深度测试一样。
- ref:设置比较值,模板缓冲中的值会与该值比较。
- mask:在进行比较前和ref、模板缓冲值进行与运算的值,默认为0xFF。
例如,可以这么使用这个方法:
glStencilFunc(GL_EQUAL, 1, 0xFF);
glStencilFunc只说明了如何通过模板测试和丢弃片段,glStencilOp则说明了如何更新缓冲。
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)的三个参数的说明:
- sfail:如果模板测试失败的选项。
- dpfail:如果模板测试通过,但深度测试失败的选项。
-
dppass:如果模板测试和深度测试都通过的选项。
下面是可选的一些选项:
默认情况下为glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)。
物体描边
模板测试的其中一个应用是描边,效果如下:
这个应用在游戏中选择物体时非常有用。做法大致如下:
- 开启模板缓冲写入。
- 将模板的Op操作置为GL_ALWAYS,然后用1填满模板缓冲。
- 渲染物体。
- 禁用模板缓冲写入和深度测试。
- 稍微放大要作为轮廓的物体。
- 用额外的片元着色器为作为轮廓的物体赋予颜色。
- 绘制轮廓物体。
- 开启深度测试并将模板测试方法改为GL_KEEP。
按照这样的流程,我们按常规建立一个轮廓物体的片元着色器(shaderSingleColor)并赋予一个颜色:
void main()
{
FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}
在绘制立方体时,先开启模板测试:
glEnable(GL_STENCIL_TEST);
接着在每一帧,如果模板测试和深度测试都成功,我们都替换模板缓冲中的值(接下来由glStencilFunc设置):
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
我们将物体的模板值均设为1:
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xFF); // 所有片段均通过模板测试
glStencilMask(0xFF); // 开启模板缓冲写入
normalShader.use();
DrawTwoContainers();
接着绘制放大的物体来作为外轮廓,让外轮廓那一圈通过模板测试,并禁止模板缓冲写入:
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // 禁止模板缓冲写入
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
DrawTwoScaledUpContainers();
完整流程如下:
glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glStencilMask(0x00); //确保绘制地面不会更新模板缓冲
normalShader.use();
DrawFloor()
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
DrawTwoContainers();
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
DrawTwoScaledUpContainers();
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glEnable(GL_DEPTH_TEST);
结果如下:
这里给出原文参考代码:Code。
模板测试还有许多其它的用处,比如在后视镜中绘制纹理来确保是镜像纹理,或者实时渲染阴影(体积阴影技术)。
最后,请多多参考原文:https://learnopengl.com/Advanced-OpenGL/Stencil-testing