Stencil 介绍
Stencil 的概念和 Stencil Buffer 联系在一起。
我们知道,屏幕有深度缓冲,在默认情况下,当正在渲染的点的距离大于深度缓冲里的值时,就会直接丢弃此时Pass。
Stencil Buffer 也是一个 Buffer,不同于深度缓冲有默认的大小对比行为,Stencil Buffer 需要我们手动约定处理方式。
官方12有一些介绍,不过可能稍微有点抽象不好理解,这里我们从一个例子出发,来解释下可以做到什么。
Stencil Test
假如本来我们有一个Cube和一个正方形面片Quad,当Quad在Cube的前面的时候,Cube会被遮住一些部分。

现在我们将Quad替换为下面的 Shader,这个 Shader 做了下列几件事:
Blend Zero One表示保持原有的颜色,这意味着渲染的物体将是透明的。ZWrite Off表示不改变深度缓冲Stencil内的内容就是模板测试,具体内容后面会再解释,这里只需要知道Comp Always表示永远通过,Pass Replace表示Pass后将 Stencil 缓冲区的值替换为Ref所表示的值。
Shader "Custom/Stencil/StencilFilter"
{
Properties
{
[IntRange] _StencilID ("Stencil ID", Range(0, 255)) = 1
}
SubShader
{
Tags {
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Geometry"
}
Blend Zero One
ZWrite Off
Stencil
{
Ref [_StencilID]
Comp Always
Pass Replace
}
Pass
{
}
}
}
现在Quad将是透明的,同时,它也改写了整个区域的Stencil Buffer的值。

然后,我们将Cube的Shader也进行替换:
- 我们只需要关注
Stencil内的内容,这里Comp Equal的意思是,如果Ref的值等于Stencil Buffer就通过测试,继续后面的Pass,否则停止渲染。
Shader "Custom/Stencil/StencilTest"
{
Properties
{
[IntRange] _StencilID ("Stencil ID", Range(0, 255)) = 0
[MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)
[MainTexture] _BaseMap("Base Map", 2D) = "white"
}
SubShader
{
Tags {
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Geometry"
}
Stencil
{
Ref [_StencilID]
Comp Equal
}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
CBUFFER_START(UnityPerMaterial)
half4 _BaseColor;
float4 _BaseMap_ST;
CBUFFER_END
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor;
return color;
}
ENDHLSL
}
}
}
将Quad和Cube的_StencilID的值都改为相同的值,比如1。
然后拖动Quad,会看到非常有意思的一幕。只有被Quad遮挡的区域,才会渲染Cube。

如果我们有2个不同_StencilID的Cube,和2个不同_StencilID的Quad,它们可以各自显示符合自己ID的Cube,但是不会显示另一个Cube。

这里稍微解释下发生了什么:
- 由于整个屏幕每次渲染默认的
Stencil Buffer的值是0,所以两个Cube在Quad没有覆盖到的地方都不会显示。 Quad渲染后改变了当前像素的Stencil Buffer的值,而Cube设置了Comp Equal,所以可以显示在改变了Stencil Buffer的区域。
渲染顺序
在上面的探讨中,可以意识到一个问题:Quad只有在Cube之前渲染,才能够先改变Stencil Buffer,然后渲染Cube。
如果它们的渲染顺序也就是Queue都是相等的,那么可能会发生闪烁。

可以将Quad的Queue设置为比Cube更小的值,此时就可以确保Quad在Cube之前进行渲染。

Cube不再会发生闪烁:

Stencil Test 语法介绍
核心语法如下:
Stencil
{
Ref <ref>
ReadMask <readMask>
WriteMask <writeMask>
Comp <comparisonOperation>
Pass <passOperation>
Fail <failOperation>
ZFail <zFailOperation>
}
其中<ref>是参考值,它如何与Stencil Buffer对比取决于Comp。
ReadMask 是对比的时候的遮罩,同理 WriteMask 是写入的时候的遮罩。
Comp定义Ref与Stencil Buffer Value如何对比,默认是Always也就是总是Pass,Equal表示相等时Pass,还有Less/LEqual/Grater/NotEqual/GEual等等。
Pass定义的是Comp通过后对Stencil Buffer Value的操作,比如Keep表示保持Stencil Buffer的值,Replace表示用Ref的值替换掉Stencil Buffer的值,还有Zero/IncrSat/DecrSat/Invert/IncrWrap/DecrWrap。
Fail定义的是Comp失败后的操作,内容和Pass一样。
ZFail定义的是Comp通过,但是深度缓冲测试未通过的情况,内容还是和Pass一样。
还可以单独定义正面和反面的Pass/Fail/ZFail,这里可以查看官方文档去了解。
一个最简单的修改Stencil Buffer的代码:
Stencil
{
Ref 1
Comp Always
Pass Replace
}
一个最简单的测试Stencil Buffer的代码:
Stencil
{
Ref 1
Comp Equal
}
Renderer Objects feature
如果每增加一个Shader,我们就需要添加Stencil Buffer,然后设置_StencilID,那无疑会是一个非常麻烦的事情,这意味着我们需要修改很多材质。
你也可以参考 impossible-geom-stencils4 了解如何使用 Renderer Objects feature 来设置Stencil。
如图,场景中有3个Cube和2个Quad,其中紫色的Cube是半透明材质。

先将2个Quad替换为前面的StencilFilter。
创建2个新的Layer,分别命名Stencil Object 1和Stencil Object 2,给3个Cube分配上这2个Layer中的任意1个。
然后找到Universal Renderer Data设置,取消掉这两层的渲染。

添加一个Render Objects Feature,Filters中的Layer Mask选择Stencil Object 1/2,这里一共需要添加4个Render Objects Feature,对应2种Queue和2个Layer Mask。
重点是在Override中,勾选Stencil,这样可以覆盖掉原有的Stencil设置,比如这里改为Equal同时设置Value为对应的值。
还有一点就是Event插入时机,如果是Opaque应该对应选择After Rendering Opaques,Transparent要对应选择After Rendering Transparents。

看一下效果:

如果修改一下场景布局,取消相机渲染天空盒,给StencilFilter加上颜色,就可以有如下的Magic Cube:

Unity, 2025, writing-shader-set-stencil, https://docs.unity3d.com/6000.2/Documentation/Manual/writing-shader-set-stencil.html ↩︎
Unity, 2025, SL-Stencil, https://docs.unity3d.com/6000.2/Documentation/Manual/SL-Stencil.html ↩︎ ↩︎
lupeng0330, 2015, UnityShader实例09:Stencil Buffer&Stencil Test , https://blog.csdn.net/u011047171/article/details/46928463 ↩︎
Daniel Ilett, 2022, impossible geom stencils, https://danielilett.com/2022-01-05-tut5-22-impossible-geom-stencils/ ↩︎
