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/ ↩︎