在第二节Alpha Test例子中,我们已经提到,片元Shader计算完成后,进入输出合并阶段,会经过一系列的测试

Alpha Test —— Stencil Test —— Depth Test —— Blending —— Frame Buffer

模板测试发生在Alpha Test之后,深度测试之前

模板测试

默认情况下,模板缓冲区的值都为零,我们强制让一个模型写入模板缓冲区(取值范围0~255),修改模板比较的规则,就能控制模型的显示。

ASE中的模板测试

在ASE中,直接通过左侧的面板勾选Stencil Buffer开始模板测试

ASE中的模板测试

Reference :当前模型设置的模板参考值

Read/Write Mask :为了和模板缓冲区做比较用的Mask

Comparison :当前模型设置的模板参考值和模板缓冲区内的值的比较方法,有大于、大于等于、小于、小于等于等多种比较公式,图上的Always表示总是能通过比较。

Pass Front :当前模型前面的模板参考值通过后,对于模板缓冲区的操作,有

  • Keep :保持当前缓冲区的值不变
  • Zero :将当前缓冲区的值归零
  • Replace :将当前模型的参考值写入缓冲区
  • IncrSat/DecrSat :未知
  • IncrWrap/DecrWrap :未知
  • Invert :未知

Fail Front :当前模型前面的模板参考值未通过,对于模板缓冲区的操作,操作值和前面一样

ZFail Front :当前模型前面的模板参考值通过,但是深度测试未通过,对于模板缓冲区的操作,操作值和前面一样

如果我们把背面剔除关闭(Cull Off),模板测试还会出现Comparison Back、Pass Back、Fail Back、ZFail Back四个选项,原理与前面相同。

镜中世界的镜子

我们希望世界能在镜子中显示,在镜子外不显示

设置镜子的ASE Shader

ASE中的模板测试

先让镜子写入模板缓冲区,让镜子区域的模板值变为1.

同时镜子不需要被渲染,在ASE中将Color Mask中的RGBA统统关闭。

同时将镜子的深度写入关闭,防止背后的世界因为深度测试而剔除。在ASE中找到Depth,将ZWrite设为Off。

将镜子的Render Queue从2000设为1999(暂时的),保证镜子最先被渲染,这样才能预先将镜子的模板值写入模板缓冲区。

设置背后世界的ASE Shader

背后世界的材质,开启Stencil Buffer,将Reference设为1,将Comparison设为Euqal。

这样背后世界的模板参考值为1,和镜子提供的模板值相等,只能在镜子内显示。

解决渲染顺序问题

如果想要镜中世界不和镜外世界的其他普通物体穿帮,我们需要设置镜子和镜中模型的渲染顺序。

在Unity中,默认的渲染队列如下:

  • Geometry :2000
  • Alpha Test :2450,这个队列用于贴图有透明区域的情况,也属于透明渲染队列
  • Transparent :3000

我们一般情况下,将2450以前的队列设为不透明渲染队列,将2450以后的队列设为透明渲染队列。

我们将镜子的渲染队列设为2460,这样就能保证镜子在场景外不透明的和有透明贴图的物体渲染完之后渲染。

我们也可以同时在ASE中设置渲染队列,在ASE中将镜子的Render Queue设为Alpha Test,然后再将Queue Index设为10,此时这个镜子的Render Queue就是2460,不需要在Inspector中单独再设置。

我们将镜子内的物体的渲染队列设为2470,还是要保证它们在镜子之后渲染。

此时又有一个问题,如果镜子外的物体和镜子有交叉,那么肯定是镜子外的物体先渲染完,再渲染镜子内的物体,我们的镜子内是透明没有背景的场景,透明的部分和镜子外交叉的话,镜子外的物体依然会出现在镜子内。

如果想要解决这个问题,我们需要一个大球模型,让这个球作为天空盒,包裹住镜子内外整个场景,然后给这个天空盒设置和镜子内物体一样的开启Stencil Buffer,将Reference设为1,将Comparison设为Euqal。将天空盒的渲染队列设置为2465。最后最重要的是,在ASE中将天空盒的ZTest Mode设置为Always。这就表示不论天空盒离我们多远,当我们渲染到天空盒时,天空盒会把在镜子内出现的在自己之前渲染的对象给覆盖掉。而在天空盒之后渲染的镜子后面的镜中世界物体,因为它们都在天空盒前面,所以依旧会正常出现在镜子里。

  1. 先渲染镜子之外的物体
  2. 渲染镜子,并把镜子的模板参考值写入模板缓冲区中
  3. 渲染天空盒,天空盒先经过模板缓冲,在通过ZTest,将镜子内出现的镜子之外的物体给清空。
  4. 渲染镜子之后的物体。

UnityFrameDebugger技巧

将场景中main camera的Clear Flags设置为Dont Clear,否则FrameDebugger的渲染顺序会出问题。

把场景中的Directional Light的Shadow Type设为No Shadows,可以让FrameDebugger关闭UpdateDepthTexture阶段。

代码

镜子的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
Shader "KerryTA/Code/06Mirror_Stencil_Code"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "Queue"="AlphaTest+10" }//引号内不要有空格,否则识别不出渲染队列
LOD 100

Pass
{
//*******************add*****************//
ColorMask 0 //ColorMask RGBA
Stencil {
Ref 1
Comp always
Pass replace
}
ZWrite Off
//*******************add*****************//
CGPROGRAM
//...
ENDCG
}
}
}

镜中世界的Shader代码

我们在ASE中,将贴图属性名命名为了Diffuse,Unity的材质就会记住这个属性名,在我们更换新的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 "KerryTA/Code/06Scene_Mirror_Code"
{
Properties
{
_Diffuse ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "Queue"="AlphaTest+20" }//+++
LOD 100

Pass
{
Stencil{//+++
Ref 1
Comp equal
}
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 _Diffuse;
float4 _Diffuse_ST;

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

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_Diffuse, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
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
Shader "KerryTA/Code/106Mirror_SkyBox_Code"
{
Properties
{
_Color ("Color", Color) = (0.0,0.0,0.0,0.0)
}
SubShader
{
Tags { "Queue"="AlphaTest+15" }//+++
LOD 100

Pass
{
ZTest Always//+++
Stencil{
Ref 1
Comp equal
}
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;
};

float4 _Color;

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

fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _Color;
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}

问题

目前的Shader无法实现物体从镜子内穿出来的效果

镜子内的物体依然会对镜子外的物体产生投影。