# 无顺序摘录

[toc]

# 模板测试渲染管线

片元着色器处理完之后到帧缓冲输出之间,有一个逐片元操作部分,这个部分分为很多个测试。模板测试就在其中

  • Pixel Ownership Test: 控制像素显示权限
  • Scissor Test: 裁剪测试,可以控制渲染那部分,比如只渲染左上角 / 右下角
  • Aplha Test: 根据 Alpha 值进行偏远裁剪
  • 模板测试
  • 深度测试
  • 透明度混合 (半透)

逐片元阶段不可编程,但高度可配置

# 模板测试是什么

从逻辑上理解

1
2
3
4
5
if(referenceValue&readMask comparisonFunction stencilBufferValue&readMask){
通过像素
}else{
抛弃像素
}
  • referenceValue: 当前片元参考值
  • stencilBufferValue: 模板缓冲区里的参考值
  • comparisonFunction: 将上面这两个值进行比较,通过则通过,失败则抛弃

# 模板缓冲区

模板缓冲区可以为屏幕上的每个像素点保存一个无符号整数值 (通常是个 8 位整数)。
在渲染中,可以用这个值与一个预先设定好的参考值进行比较,根据比较的结果来决定是否更新相应的像素点的颜色值。

# 语法

1
2
3
4
5
6
7
8
9
stencil{
Ref referenceValue //0-255
ReadMask readMask
WriteMask writeMask
Comp compairsonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation //模板测试通过,但是深度测试失败
}

其中 ComparisonFunction 包括:

  • Greater 相当于 ">" 操作,即仅当左边 > 右边,通过
  • GEqual 相当于 ">="
  • Less 相当于 "<"
  • LEqual 相当于 "<="
  • Equal 相当于 "="
  • NotEqual 相当于 "!="
  • Always 总是通过
  • Never 永远不通过

其中 stencilOperation 包括:

  • Keep 保留当前缓冲区内容,即 stencilBufferValue 不变
  • Zero 将 0 写入缓冲区,即 stencilBufferValue 值变为 0
  • Replace 将参考值写入缓冲区,即将 referenceValue 赋值给 stencilBuffer
  • IncrSat stencilBufferValue 加 1, 如果 stencilBufferValue 超过 255 了,那么不再增加
  • DesrSat: stencilBuffer 减一,如果低于 0, 不变
  • Invert: stencilBuffer 按位取反
  • IncrWrap: 缓冲值加一,如果缓冲值超过 255,那么变成 0,
  • DescrWrap: 与上面相反

# 以 Minions Art 的一个效果作为例子

可以发现,有些地方是被剔除掉的,就是可以实现一个类似于传送门的效果 那么他是怎么实现的呢?在渲染管线中,有一个标记是专门供模板测试来使用的标记.
这个标记是放在像素上的
然后模板测试都是针对于这个标记来进行展开的。比如上面的实现

# 常驻标记 (一般作为传送门的门口)

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
Shader "FX/StencilMask" {
Properties{

_ID("Mask ID", Int) = 1
}
SubShader{
Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+1" }
ColorMask 0
ZWrite off
Stencil{
Ref[_ID]
Comp always
Pass replace
}
Pass{
CGINCLUDE
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};


v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target{

return half4(1,1,1,1);
}
ENDCG

}
}
}

我们可以看到上面主要分为几点

  • 把 ColorMask 置为 0, 这样但凡经过该 Shader 渲染的都不输出颜色
  • 关闭 ZBuffer, 没有深度影响

其中

1
2
3
4
5
Stencil{
Ref[_ID]
Comp always
Pass replace
}

这一句是标准的模板测试使用的方法,我们看愿代码可以看到声明了一个_ID 的变量,这个变量就是我们要写入 模板位 中的值 Ref 代表了要拿哪个变量和模板位中的值进行比较
Comp 代表了比较方式,always 是总是通过
Pass 通过后执行 replace, 即替换 所以这句话的意思就是把 _ID的值 作为当前像素点的值写入缓存

# 可显示部分

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
Shader "Toon/Lit StencilMask" {
Properties {
_Color ("Main Color", Color) = (0.5,0.5,0.5,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_Ramp ("Toon Ramp (RGB)", 2D) = "gray" {}
_ID("Mask ID", Int) = 1

}

SubShader {
Tags { "RenderType"="Opaque" "Queue" = "Geometry+2"}
LOD 200

Stencil {
Ref [_ID]
Comp equal
}


CGPROGRAM
#pragma surface surf ToonRamp

sampler2D _Ramp;

// custom lighting function that uses a texture ramp based
// on angle between light direction and normal
#pragma lighting ToonRamp exclude_path:prepass
inline half4 LightingToonRamp (SurfaceOutput s, half3 lightDir, half atten)
{
#ifndef USING_DIRECTIONAL_LIGHT
lightDir = normalize(lightDir);
#endif

half d = dot (s.Normal, lightDir)*0.5 + 0.5;
half3 ramp = tex2D (_Ramp, float2(d,d)).rgb;

half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
c.a = 0;
return c;
}


sampler2D _MainTex;
float4 _Color;

struct Input {
float2 uv_MainTex : TEXCOORD0;
};

void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG

}

Fallback "Diffuse"
}

关注和模板缓冲区相关的部分

1
2
3
4
Stencil {
Ref [_ID]
Comp equal
}

这句是代表以_ID 为参考值,与缓存内的模板值对比,如果想等,则允许渲染,否则丢弃

# 模板测试扩展