1 理解 Stencil Buffer

引例

stencil 是印刷工业中的版面模子,模子上抠出需要的图案,然后将模子盖在要被印刷的材质上,对洞涂或喷绘颜色。

如果将屏幕上所有像素想象成一串连续的 0 组成的矩形,那么 stencil buffer 的作用就是将某些 0 变为 1,2,3 … 255,在每个 pass 中可以决定只渲染某个特定 stencil 值的像素并抛弃对其他非该值像素的操作,就像一块模板一样扣住了所有像素,并只对当前 stencil 值的洞洞进行喷绘。

Pasted image 20221030224512

  • 左图为颜色缓冲区中的一张图,在模板缓冲区中我们会给这张图的每一个片元分配一个 0-255 的数字(8 位,默认为 0
  • 中、右图可以看到,我们修改了一些 0 为 1,通过自定义的一些准则,如输出模板缓冲区中 1 对应的片元的颜色;0 的不输出,最后通过模板测试的结果就如右图所示。

模板测试效果举例

  • ①传送门效果:可以看到左边传送门内的景象正是右侧的场景
    Pasted image 20221030224810
  • ②Minions 讲解的一些效果,例如 3D 卡牌效果、侦探镜效果等

1|200 1 1|200 1 2|200 1 3|200

  • ③每个正方体面显示不同场景(每个面作为蒙版来显示场景)
    《笼中窥梦》游戏场景:
    y7Tcu-l8zuwKKGkVacX8qg bvS1V1NTiLs9k5UHIqnUUw

  • 对于上述的例子总结一下,这些效果基本可以归结为三层组成

  • 以②中的图 4 传送门为例子,三层分别对应:门外场景、门内场景、门

  • 也就是说可以理解为:包括两层物体/场景、和一层遮罩

2 什么是模板测试

从渲染管线理解

1683366278896

在像素处理阶段的合并阶段(Merger)进行各种测试:
合并阶段是可以配置但不可编程的(对应图中为黄色背景)
Pasted image 20221030230409

流程:

  • 像素所有权测试→裁剪测试→透明度测试→模板测试→深度测试→透明度混合

  • PixelOwnershipTest(像素所有权测试):

  • 简单来说就是控制当前屏幕像素的使用权限

  • 举例:比如 unity 引擎中仅渲染 scene 和 game 窗口,即只对 scene 和 game 窗口部分的像素具有使用权限

  • ScissorTest(裁剪测试):

  • 在渲染窗口再定义要渲染哪一部分,默认全部渲染,可以自己控制。

  • 和裁剪空间一起理解,也就是只渲染能看到的部分

  • 举例:只渲染窗口的左下角部分

  • AlphaTest(透明度测试)

  • 提前设置一个透明度阈值

  • 只能实现不透明效果和全透明效果

  • 举例:设置透明度 a 为 0.5,如果片元大于这个值就通过测试,如果小于 0.5 就剔除掉

  • StencilTest(模板测试)

  • DepthTest(深度测试)

  • Blending(透明度混合)

  • 可以实现半透明效果

  • 完成接下来的其他一系列操作后,我们会将合格的片元/像素输出到帧缓冲区(FrameBuffer),最后渲染到屏幕上。

从逻辑上理解

Pasted image 20221030230942

  • 理解:

  • referenceValue:当前模板缓冲片元的参考值(0~255)

  • &readMask:与读掩码做一个“与”操作

  • stencilBufferValue:模板缓冲区里的值,初始为 0

  • 中间comparisonFunction,就是做一个比较

  • 结果:

  • 如果通过,这个片元就进入下一个阶段

  • 未通过/抛弃,停止并且不会进入下一个阶段,也就是说不会进入颜色缓冲区

  • 总结:就是通过一定条件来判断这个片元/片元属性执行保留还是抛弃的操作

从书面概念上理解

模板缓冲区 Stencil buffer

  • 模板缓冲区与颜色缓冲区和深度缓冲区类似,模板缓冲区可以为屏幕上的每一个像素点保存一个无符号整数值(通常为 8 位 int,0-255)。
  • 这个值的意义根据程序的具体应用而定。

模板测试

  • 渲染过程中,可以用这个值与预先设定好的参考值作(ReferenceValue)比较,根据结果来决定是否更新相应的像素点的颜色值。
  • 这个比较的过程就称为模板测试
  • 模板测试在透明度测试之后深度测试之前
  • 如果模板测试通过,相应的像素点更新,否则不更新。

3 使用方法

语法表示/结构解释

[[unity3d.com)](https://docs.unity3d.com/cn/2022.3/Manual/SL-Stencil.html|ShaderLab 命令:模板 - Unity 手册 (unity3d.com)]]
Pasted image 20221030231544

  • Ref:当前片元的参考值(0-255)referenceValue
  • ReadMask:读掩码
  • WriteMask:写掩码
  • Comp:比较操作函数
  • Pass:测试通过,之后进行操作(StencilOperation,后边有详细讲解)
  • Fail:测试未通过,也会进行一个操作
  • ZFail: 模板测试通过,深度测试未通过,也可以进行一个操作

默认值如下:
注:Unity 中模板缓冲区默认是 0, 即 Ref 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stencil 
{
//当前像素stencil值与0进行比较
Ref 0 //0-255
//测试条件:测试是否相等
Comp Equal //default:always
//如果测试通过对此stencil值进行的写入操作:保持当前stencil值
Pass keep //default:keep
//如果测试失败对此stencil值进行的写入操作:保持当前stencil值
Fail keep //default:keep
//如果深度测试失败对此stencil值进行的写入操作:循环递增
ZFail IncrWrap //default:keep
}

ComparisonFunction

我们可以根据需求配置
Pasted image 20221030231708

StencilOperation 更新值

有不同的更新操作,根据自己的需求进行配置
Pasted image 20221030231720

4 总结

  • 最重要 (用来比较的)两个值

  • 当前模板缓冲区值(StencilBufferValue)模板参考值(ReferenceValue)

  • 模板测试主要就是对这两个值进行特定的比较操作,例如 Never、Always、Equal 等,具体参考上文的表格

  • 模板测试后要对模板缓冲区的值进行更新操作,例如 Keep,Replace 等,具体参考上文表格

  • 模板测试之后可以根据结果对模板缓冲区做不同的更新操作,例如模板测试成功操作 Pass、模板测试失败操作 Fail、深度测试失败操作 ZFail、还有正对正面和背面精确更新操作 Passback,Passfront,Failback 等…

属性中使用一个内置的枚举,这样就可以在外边自己选择可配置的属性了
Pasted image 20221031163235

5 应用

Pasted image 20221031163637
[3条消息) Unity Shader: 理解Stencil buffer并将它用于一些实战案例(描边,多边形填充,反射区域限定,阴影体shadow volume阴影渲染)_liu_if_else的博客-CSDN博客 Unity Shader: 理解Stencil buffer并将它用于一些实战案例(描边,多边形填充,反射区域限定,阴影体shadow volume阴影渲染)_liu_if_else的博客-CSDN博客]]

描边

[[05 描边#2 模板测试描边]]

卡牌效果

ID 都为 0 时,即 unity 默认的显示效果:
Pasted image 20221031161931|300
Mask 和 Texture 的 ID 都设置为 1:
Pasted image 20221031155916|300
Pasted image 20221031160105|300
思路:蒙版 Ref[_ID]设置为 1,Texture 的 Ref[_ID]也设置为 1,这样可以显示与蒙版重叠的部分,其余部分都剔除。

kapai
以下为案例 shader,重点在于 Stencil 部分的设置,其余部分就是按正常 shader 来写,对于 3d 模型计算一下光照

①蒙版 Mask 的 shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//蒙版,ID设置为1,将外面东西全部剔除
Shader "Unlit/StencilMask"
{
Properties
{
_ID ("Mask ID", Int) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue" = "Geometry+1" }
ColorMask 0 //0:什么都不显示,全透明
//ColorMask RGBA //四个通道全部输出,其他可选RGB、R、G、B、0
Zwrite off

Stencil
{
Ref[_ID]
Comp always //默认always
Pass replace //replace:通过就把模板缓冲区的值替换成我们的ID值,默认keep

//其他不写的选项默认Keep
//Fail Keep
//ZFail keep
}
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
};

struct v2f
{
float4 pos : SV_POSITION;
};

v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
return float4(1,1,1,1); //随便给一个颜色,因为ColorMask为0,不会输出颜色
}
ENDCG
}
}
}

②被遮挡物体的 shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//被遮挡物体 
Shader "Unlit/TextureMasked"
{
Properties
{
_MainTex("BaseColor",2D) = "white"{}
_ID ("Mask ID", Int) = 1
}
SubShader
{
//在Mask之后渲染,队列次序比Mask高
Tags { "RenderType"="Opaque" "Queue" = "Geometry+2" }

Stencil
{
Ref[_ID]
Comp equal //索引值和当前模板缓冲区中的值一致时才会被绘制
}
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.uv = v.uv;
o.pos = UnityObjectToClipPos (v.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
float4 BaseColor = tex2D(_MainTex, i.uv);
return BaseColor;
}
ENDCG
}
}
FallBack "Diffuse"
}

③完善卡牌效果

分离前后 Mask:
MainTex 的 A 通道是一个 Mask,将正反面分离以定制正反面的不同效果,比如单独加 Fresnel
Pasted image 20221031170431|300
Pasted image 20221031170405

盒子不同面显示不同场景

bvS1V1NTiLs9k5UHIqnUUw

  • 和卡牌效果类似,一个用蒙版遮罩的物体,盒子每个面使用一个蒙版遮罩
  • 同样利用默认的值为 0 来做,只是面多了,蒙版和里边显示的物体也多了,ID 依次为 1、2、3、4
  • 总结:一个蒙版对应一个物体,他们使用相同的 ID,出来的效果就是:每个面显示的盒子内部物体不同

多边形填充

通过 stencil 值对几何体交叉区域进行判定与渲染。

第一个 pass 渲染一个几何体,不论任何情况都通过测试并对它所覆盖的像素区域 stencil 值加 1,后三个 pass 分别只对 stencil 值为 2,3,4 的区域进行渲染。

3f106474b6e69426ec99d0e8d45a5c6f_MD5

fold
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
Shader "Unlit/PolygonsBeta"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
ENDCG

Pass
{
Stencil {
Ref 0 //0-255
Comp always //default:always
Pass IncrWrap //default:keep
Fail keep //default:keep
ZFail IncrWrap //default:keep
}

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(0,0,0,0);
}
ENDCG
}

Pass
{
Stencil {
Ref 2 //0-255
Comp Equal //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(0.2,0.2,0.2,1);
}
ENDCG
}

Pass
{
Stencil {
Ref 3 //0-255
Comp equal //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(0.6,0.6,0.6,1);
}
ENDCG
}

Pass
{
Stencil {
Ref 4 //0-255
Comp equal //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(1,1,1,1);
}
ENDCG
}
}
}

反射区域限定

此用法主要是辅助一个反射 shader,可以比较简单的模拟出一个镜面效果。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
Shader "Unlit/TwoPassReflection"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}

Pass
{
Stencil {
Ref 1 //0-255
Comp Equal //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}
ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 normal: NORMAL;
};

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
v.vertex.xyz=reflect(v.vertex.xyz,float3(-1.0f,0.0f,0.0f));
v.vertex.xyz=reflect(v.vertex.xyz,float3(0.0f,1.0f,0.0f));
v.vertex.x+=1.5f;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Shader "Unlit/Mirror"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry-1" }
LOD 100

Stencil {
Ref 0 //0-255
Comp always //default:always
Pass IncrSat //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(0.2f,0.2f,0.2f,1.0f);
}
ENDCG
}
}
}

说明

在 TwoPassReflection. shader 中,第一个 pass 正常渲染模型,第二个 pass 对顶点进行了一个简单的反射,并将 ZTest 设为 always,然后将一个 quad 放入本体和倒影之间,它的效果是这样的:

9f9de8465e47213581a87a5e2abe562a_MD5

图 5:使用 TwoPassReflection. shader,无 mirror. shader

倒影超出了想要的范围。解决这一问题,在 quad 上使用 mirror. shader 将 quad 覆盖的像素 stencil 值改为 1,并在 TwoPassReflection 第二个 pass 中约定只在 stencil 值为 1 的区域中渲染。

效果

259dc239aafdb72d497bf1c408867f1c_MD5

图 6:quad 使用 mirror. shader

这里有个前提是 mirror 必须在倒影之前渲染以先将反射区域的 stencil 值标记好。

   

阴影体 shadow volume 阴影渲染

说明

shadow volume 阴影体算法是将‘遮光体’遮挡光源后产生的阴影实例为一个几何体,对在该阴影几何体的渲染过程中找出应该渲染阴影效果的像素。

a5ed32890776026ea1cc8b80094929fd_MD5

图 7:圆柱阴影体

检测手段有几种,本案例 shader 采用的是 Depth Fail,也叫 Carmack’s reverse 方法的思路。它的思想与步骤如下:

1,在一般物体渲染后,渲染阴影体,第一个 pass cull front,渲染内侧,在 stencil 测试阶段如果发现深度测试失败,说明该像素在阴影体内部表面或阴影体外部表面与视角之间有发生遮挡,将该像素 stencil 值加 1。

2,第二个 pass cull back,渲染外侧,如果有深度测试失败,则说明该像素在阴影体外部表面与视角之间有发生遮挡,将该像素 stencil 值减 1。

3,经过两个 pass 的 stencil 操作,只有在阴影体内部的物体且它遮挡住阴影体内部表面的部分的 stencil 值为 1。对阴影体内 stencil 值为 1 的像素进行渲染。

本文中的 shader 只为展示 stencil buffer 在此技术中的角色,缺乏正确的阴影体网格或它的动态生成手段,粗暴的用 Unity 默认几何体中的圆柱体模拟一个阴影体,并且算法中也没有考虑被阴影覆盖的物体自身的阴影体的问题以及其他细节问题。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
Shader "Unlit/SV_DepthFailBeta"
{
Properties
{
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"} //在渲染所有阴影体内物体后再渲染阴影体
LOD 100

CGINCLUDE //三个pass内着色器内容相同
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};

struct v2f
{
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(0.3,0.3,0.3,1); //影子颜色
}
ENDCG

Pass
{
Cull Front //阴影体内侧像素Z测试失败,stencil值加1
Stencil {
Ref 0 //0-255
Comp always //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail IncrWrap //default:keep
}

ColorMask 0 //关闭color buffer写入
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
ENDCG
}

Pass
{
Cull Back //阴影体外侧像素Z测试失败,stencil值减1
Stencil {
Ref 0 //0-255
Comp always //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail DecrWrap //default:keep
}
ColorMask 0 //关闭color buffer写入
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
ENDCG
}

Pass
{
Cull Back //经过前两个pass,stencil值为1的值为在此阴影体内被阴影覆盖的像素
Stencil {
Ref 1 //0-255
Comp equal //default:always
Pass keep //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
ENDCG
}
}
}

效果

60e95f131768e67ac4e3fc979a01a586_MD5

图 8:使用 SV_DepthFailBeta. shader