前言

​ 在设计毕设的战棋游戏时,因为想要让每个格子(Quad)显示其外轮廓/内轮廓线,而standard并不支持,因此参考了一些思路实现了背面法线外扩,但是对于平面来说,以矩形举例,其四周的法线存在多条,并且在剔除正面的情况下不存在背面的Vert,所以这种思路显然是不行的。所以对于平面来说,最好的方法是多使用一个Pass,在pass的vert中进行顶点缩放,绘制出比当前顶点大/小的平面,且对z轴进行细微调整防止zfighting,这样其与实际内层Pass套在一起就能营造出外轮廓的感觉。

​ 其次就是菲涅尔现象,用到了Schilick菲涅尔近似等式,即F(v,n)=F0+(1-F0)(1-dot(v,n))^5,其中v是视角方向,n是表面法线。

​ 最后是内发光现象,其思路其实与菲尼尔类似,通过1-dot(v,n)的结果与_RimColor(即外发光颜色)进行乘积后获取,拓展到外发光,其实就是外轮廓(cull front后外延)+内发光中frag的处理。

代码实现

Effect.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
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//MARKER:当模型本身有复杂的遮挡关系或者是包含了复杂的非凸网格的时候,纯粹依靠transparent队列来进行排序的方法有可能会因为排序错误产生错误的结果
//MARKER:因此我们可以采用开启深度写入的半透明效果的方法来实现
Shader"Map/MapCell"
{
Properties{
_InColor("In Color",Color)=(1,1,1,1)
_OutColor("Out Color",Color)=(1,1,1,1)
_MainTex("Main Tex",2D)="white"{}
_OutAlphaScale("Out Alpha Scale",Range(0,1))=1
_InAlphaScale("In Alpha Scale",Range(0,1))=1
_Scale("Out Scale",Range(1,2))=1 //缩放
//_Outline("Outline",Range(0,1))=0.1 //轮廓线宽度
//_OutlineColor("Outline Color",Color)=(0,0,0,1) //轮廓线颜色
//_RimColor("Rim Color",Color)=(1,1,1,1) //边缘颜色
//_RimPower("Rim Power",range(0,10))=2 //边缘强度

//_FresnelColor("FresnelColor",Color)=(1,1,1,1)
//_FresnelAmount("FresnelAmount",Range(0,1))=1
//_FresnelPower("FresnelPower",Range(0,5))=5

}
//MARKER:SubShader中多PASS的注意点:pass的执行顺序是依据pass在shader中定义的先后顺序来确定的
SubShader{
Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}


//Extra pass that renders to depth buffer only
Pass{
ZWrite On
ColorMask 0
//MARKER:ColorMask用于设置颜色通道的写掩码(write mask),当ColorMask为0时,意味着该Pass不写入任何颜色通道,即不会输出任何颜色。
//MARKER:写入深度缓冲可以剔除被自身遮挡的片元
}

//MARKER:关键在于先通过pass把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合
//MARKER:由于上一个的pass已经得到了逐像素的正确的深度信息,该pass就可以按照像素级别的深度排序结果进行透明渲染
Pass{

ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM

#pragma vertex vert
#pragma fragment frag


fixed3 _OutColor;
fixed _Scale;
//fixed3 _OutlineColor;
fixed _OutAlphaScale;

struct appdata
{
float4 vertex:POSITION;
float4 normal:NORMAL;
};

struct v2f
{
float4 pos:SV_POSITION;
};

float4x4 Scale(float scale)
{
return float4x4(scale,0,0,0,
0,scale,0,0,
0,0,0,0,
0,0,0,0);
}

v2f vert(appdata v){
v2f o;
v.vertex=mul(Scale(_Scale),v.vertex);

//SINGAL:这里直接通过乘算来缩放也可以
//v.vertex*=_Scale;

o.pos=UnityObjectToClipPos(v.vertex);
o.pos.z-=0.0001; //防止zfighting
return o;
}

fixed4 frag(v2f i):SV_Target{
return fixed4(_OutColor,_OutAlphaScale);
}
ENDCG
}
// Pass{
// NAME "OUTLINE"
// Cull Front //剔除正面
//
// CGPROGRAM
//
// #pragma vertex vert
// #pragma fragment frag
//
// float _Outline;
// fixed3 _OutlineColor;
// fixed _AlphaScale;
//
// struct appdata
// {
// float4 vertex:POSITION;
// float4 normal:NORMAL;
// };
//
// struct v2f
// {
// float4 pos:SV_POSITION;
// };
//
// v2f vert(appdata v){
// //顶点和法线变换到视角空间,达到最好效果
// v2f o;
// float4 viewPos=mul(UNITY_MATRIX_MV,v.vertex);
// float4 viewNormal=mul(UNITY_MATRIX_IT_MV,v.normal);
// viewNormal.z=-0.5;//对法线z分量进行处理,扁平化
// viewPos=viewPos+normalize(viewNormal)*_Outline;
// o.pos=mul(UNITY_MATRIX_P,viewPos); //转换到投影空间
//
// return o;
// }
//
// fixed4 frag(v2f i):SV_Target{
// return fixed4(_OutlineColor,_AlphaScale);
// }
// ENDCG
// }

Pass{

//和8.4节同样的代码
Tags{"LightMode"="ForwardBase"}

//Src(该片元产生的颜色),Dst(已经存在于颜色缓存的颜色)
//不开启深度写入
//需要开启颜色混合,调节最后的颜色缓冲输出
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include"Lighting.cginc"

//与定义的Properties进行关联
fixed4 _InColor;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _InAlphaScale;
//fixed4 _FresnelColor;
//fixed _FresnelAmount;
//fixed _FresnelPower;
//fixed3 _RimColor;
//fixed _RimPower;

//定义顶点着色器
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0; //用第一套纹理坐标来初始化texcoord(自身理解的话像是每一个顶点的映射,然后在像素着色阶段时可以是每一个像素的映射)

};

struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
float2 uv:TEXCOORD2; //记录下透明纹理对应的uv坐标
float3 worldViewDir:TEXCOORD3;
};

v2f vert(a2v v)
{
v2f o;
//顶点和法线变换到观察空间,达到最好效果
o.pos=UnityObjectToClipPos(v.vertex);

o.worldNormal=UnityObjectToWorldNormal(v.normal);

//MARKER:一定一定注意区分好pos和worldPos的区别,一个是转换到齐次裁剪坐标,一个是采用纹理来记录下顶点世界坐标的位置
o.worldPos=mul(unity_ObjectToWorld,v.vertex);

o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
o.worldViewDir=UnityWorldSpaceViewDir(o.worldPos);


return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed4 texColor = tex2D(_MainTex, i.uv);

fixed3 albedo = texColor.rgb * _InColor.rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLightDir));

//△ 菲涅尔处理
//float fresnel=_FresnelAmount+(1-_FresnelAmount)*pow((1-max(0,dot(i.worldViewDir,i.worldNormal))),_FresnelPower);
//fixed3 color=lerp(ambient+diffuse,_FresnelColor,max(0,fresnel));

//△ 外发光处理
//fixed3 rim=_RimColor*pow(1-saturate(dot(i.worldViewDir,i.worldNormal)),_RimPower);
return fixed4(ambient+diffuse, texColor.a * _InAlphaScale);
}

ENDCG
}
}
}