模板测试
模板测试简单来说就是一个mask,根据你的mask来保留或者丢弃片段。
那么可以用来显示什么功能呢?剪切,镂空、透明度等操作。
和深度缓冲的关系是: 先片段着色器,然后进入深度测试,最后加入模板测试。模板测试是根据又一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer),我们可以在渲染的时候更新它来获得一些很有意思的效果。
具体流程为:
- 启用模板缓冲
- 渲染物体, loop中更新模板内容
- 禁用模板缓冲的写入
- 渲染其他物体,根据模板缓冲的内容丢弃特定的片段
所以,通过使用模板缓冲,我们可以根据场景中已绘制的其它物体的片段,来决定是否丢弃特定的片段。
这段话很有意思,其他物体的片段可以用来下一次的模板。
和深度一样,都需要先开启,然后再每次迭代前清除
glEnable(GL_STENCIL_TEST);
...
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
模板函数
和深度函数一样,我们也可以自定义控制模板测试。
但是这个函数只描述了模板缓冲内容如何变化,还需要知道如何更新缓冲
实例-物体轮廓
会通过模板测试来给物体周围加上一圈色带。就像是游戏中选中一个单位之后那个单位会高亮标出一样。步骤如下:
思路很简单,这个过程将每个物体的片段的模板缓冲设置为1,当我们想要绘制边框的时候,我们主要绘制放大版本的物体中模板测试通过的部分,也就是物体的边框的位置。我们主要使用模板缓冲丢弃了放大版本中属于原物体片段的部分。
首先重新定义一个片段着色器,用来画边框,直接输出一个颜色就好。
#version 330 core
out vec4 FragColor;
void main(){
FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}
接下来我先按照教程的思路来做,然后我自己再多测试测试其他情况。
我现在只给箱子加上边框,我不想给机器人加边框。所有我应该先画机器人再画箱子。第一遍画正常的箱子,并写入模板缓冲,然后画放大的箱子,并丢弃覆盖了之前箱子的那些片段。
首先开启模板测试
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
最后一个gl_replace,表示当深度和模板测试都通过的时候,那么我们希望将储存的模板值设置为参考值,参考值能够通过glStencilFunc来设置,我们之后会设置为1。
先来看下对于箱子如何操作。
glStencilFunc(GL_ALWAYS, 1, 0xFF); // 更新模板缓冲函数,所有的片段都要写入模板
glStencilMask(0xFF); // 启用模板缓冲写入
//正常绘制十个正方体,而后记录模板值
cube.DrawArray(myMaterial->shader, myMaterial->diffuse, myMaterial->specular, myMaterial->emission);
//现在模板缓冲在箱子被绘制的地方都更新为1了,我们将要绘制放大的箱子,也就是绘制边框
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // 禁止模板缓冲的写入
glDisable(GL_DEPTH_TEST);
border->use();
//Set Model matrix
modelMat = glm::translate(glm::mat4(1.0f), cubePositions[i - 1]);
float angle = 20.0f * (i - 1);
modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
modelMat = glm::scale(modelMat, glm::vec3(1.2, 1.2, 1.2));
glUniformMatrix4fv(glGetUniformLocation(border->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(glGetUniformLocation(border->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(border->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
//因为之前设置了GL_NOTEQUAL,它会保证我们只绘制箱子上模板值不为1的部分
cube.DrawArray(border, 1, 1, 1);
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
先通过glstenciFunc和glstenciMask来更新模板缓冲和启用模板缓存写入。
然后再draw的过程中,所有箱子绘制的地方都会被设置为1。其他地方都是0.
然后就是要画变大的箱子,但是这里就不能再写入了,而是比较。
可以看到在第二次的时候模板函数为gl_notequal 保证只绘制箱子模板值不为1的地方。可以想象一个场景,在之前的模板缓冲中,有箱子的地方都为1,其他都是0.然后现在画一个大一点的箱子上去。有1的地方就不画了,只在不为1的地方画,也就是边框之外的地方。
而且 可以看到这里再画第二次的时候禁止了深度测试。
现在来分析一下为什么会有这样的画面。
在机器人后面的那个箱子为什么露出来的部分是原来的,被机器人盖住的地方以及边框部分是纯色?为什么边框是连在一起的?
有这样的一个时间轴,在画的过程中依次进行了机器人、箱子、放大箱子的绘制。
其中,深度测试覆盖机器人和箱子,模板测试覆盖箱子和放大的箱子。
ok,现在按照时间轴走下去吧。
- 首先画了一个机器人,因为有深度测试,所以更新了深度缓冲。又因为glStencilMask(0x00); 保证我们在绘制机器人的时候不会更新模板缓冲。
- 现在轮到画箱子了,并启用了模板缓冲写入。所以在画箱子的过程中所有画着箱子的地方模板为1,其他为0.(但是因为深度测试,被遮挡的地方没有画箱子,也就是被遮挡的地方模板缓冲为0)
- 最后就是画放大的箱子。而且这时候是关闭了深度测试,也就是说 现在画的纯色是在最上面一层的。那么画在什么地方呢,画在放大的箱子覆盖的地方中模板缓冲为0的地方。
还记得之前的glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);,这个表示的意思是只有在深度和模板都开启的情况下去吧储存的模板值设置为参考值。
这也就解释了为什么箱子被盖住的地方会被画上纯色了。又因为没有了深度,纯色色块是连在一起的,没有前后之分。
一些测试
现在我们如何不把深度关上,会出现什么情况呢?
这里面由几个地方比较奇怪,可以看到蓝圈这里,前面的边框挡住了后面的木箱本体,而红圈内的边框被后面木箱挡住。
这里是个疑问点,我暂时还想不出来如何解释。唯一能解释的是因为深度的原因,红圈部分的边框深度在后面。