Stencil Buffer简单应用
写在前面:
本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。
由于本人水平有限难免出现错误,还请评论区指出,多多指教。
部分图元和素材来源于网络,如有侵权请联系本人删除。
参考资料与链接会在文章末尾贴出。
=======================================================================
还记得深度缓冲区吗?它帮助我们比较对象的深度,以确保它们有正确相互遮挡关系。而本文会简单介绍模板缓冲区,模板缓冲区主要用于仅渲染对象的一部分而丢弃其他对象。
1 Writing from the Stencil Buffer
模板缓冲区为每个像素存储一个 8 位整数值(0-255)在帧缓冲区中。在执行片段着色器之前对于给定的像素,GPU 可以将模板缓冲区中的当前值与给定的参考值进行比较,这称为模板测试(Stencil Test)。如果模板测试通过,GPU 将执行深度测试。如果模板测试失败,GPU 会跳过该像素的其余处理。这意味着我们可以使用模板缓冲区作为掩码来告诉 GPU 哪些像素要绘制,哪些像素要丢弃。
模板测试是可配置的,所有的模板操作都是通过我们的 hlsl 代码之外的一个小的模板代码块来完成的。像大多数shaderlab的东西一样,我们可以在我们的子着色器中编写它们以将它们用于整个subshader或仅在某个shader pass中使用它们。
模板测试的基本公式是:
整一块模板测试的语义:
看着非常多,但其实我们并不需要为所有的signature指定值,因为很多signature都有默认值,本文的例子中我们只需要指定几个参数就能做出我们想要的效果。
本文例子将会有两个shader,一个write shader,一个read shader。
stencil 操作最重要的参数是 Ref,它标记了我们操作的模板参考值。默认值为 0,这是缓冲区在写入任何内容之前的默认值。我们首先将它设置为 0,这目前不会改变任何东西,但是现在输入它会使代码结构更清晰一些,且后面更改起来更轻松。
我们需要的模板操作的另一个参数是 Comp,它定义了模板操作何时通过。默认值为 Always,这意味着无论我们使用什么参考值,对象都将始终被绘制。对于read shader,我们将使用 Equal,意思是我们标记的Ref值与该位置的模板缓冲区Ref值相等时才绘制对象。
场景中我们简单搭建了一个窗框,窗框前后各有一个plane,现在把write shader的材质赋给front plane,并在fragment shader 中return 0,意料之中没有什么改变:
这是因为现在所有默认的stencil ref都是0,假如我们将ref改为1:
现在我们将ref作为属性暴露在外方便后续调试,[IntRange] attribute是保证我们的值为整数值。
目前的效果仍然是0为可见,其他的值全都不可见。
2 Reading to the Stencil Buffer
首先我们会完善之前的write shader,我们暴露Ref值,并把Comp改为always,这样它总是会通过模板测试,在通过模板测试后会把当前模板缓冲区的值改为自己当前的值,还有就是把深度写入关闭,不然会遮挡后面的物体:
接下来我们需要编写read shader,我们将之前光照模型的shader作为基础,再加上Stencil部分即可:
这里Comp改为Equal,即当前ref值与模板缓冲区ref值相等才会通过模板测试。
这样说明可能会更好理解一点,我们的plane实际上就是提前为屏幕中某片区域设置好了stencil value(这里为1),我们这里叫作write(Stencil Ref)shader;而真正想要渲染的物体也设定一个当前的Stencil value,但只有当此物体与模板缓冲区的值一致时才会渲染,假如我们设为1,那么因为默认为0,该物体无法通过模板测试自然不会渲染。当时当我们透过之前的plane观察此物体时,由于这个plane覆盖区域的stecil value被设为1,该物体Stencil value与之相等,因此就会被渲染。
先上效果图:
可以看到,假如不是透过plane的话是无法看到此物体的,因为没有通过模板测试不会被渲染。
接下来我们需要编写read shader
为了真正使用从模板缓冲区读取的着色器,我们将编写第二个从模板缓冲区读取的着色器。第二个着色器本身不会写入屏幕,而是在第一个着色器之前渲染,因此我们确保模板缓冲区在读取时已经写入了正确的值。
对于这个着色器,我们从一个基本的无光照着色器开始,就像在基础中一样,因为它非常简单而且我们不需要太多。为了让它根本不渲染到屏幕上,而只是操纵模板缓冲区,我们将向它添加一些其他的小细节。
我们可以继续完善一下write shader,因为我们在fragment shader中return0,所以看到背景是黑色的,我们可以把ColorMask设为0,这样就完全不输出颜色;此外,按照上面的逻辑我们需要write shader在read shader之前渲染,因为我们把其渲染队列改为Geometry-1,而read shader则保持Geometry:
我们把画框的另一面也做同样的配置,只不过前面的ref值为1,后面的ref值为2:
实际上两个物体处于相近的位置,却因为我们利用了模板缓冲区,呈现了这种效果。