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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
Shader "Unity Shaders Book/Common/Bumped Diffuse" {

// Properties 中定义的属性会出现在材质面板中,支持进行动态控制
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1) // 控制物体的整体色调
_MainTex ("Main Tex", 2D) = "white" {} // 控制物体的漫反射纹理(MainTex)
_BumpMap ("Normal Map", 2D) = "bump" {} // 控制物体的法线纹理(BumpMap)
}
SubShader {

// RenderType - 指定当前 shader 作用的对象的渲染类型
// Opaque: 用于大多数着色器(法线着色器、环境光着色器、反射着色器以及地形的着色器)。
//
// Queue - 指定当前 shader 作用的对象的渲染顺序
// Geometry (2000) 不透明物体的渲染队列。大多数物体都应该使用该队列进行渲染,也是Unity Shader中默认的渲染队列。
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {

// LightMode 标签是 Pass 标签中的一种,它用于定义该 Pass 在 Unity 的流水线中的角色 (只有定义它才能获取到一些 Unity 内置的光照变量如 _LightColor0
// 告诉渲染管线,这个 pass 作为 ForwardBase 处理,缺少它将画不出任何东西
// ForwardBase 通道渲染一个逐像素方向光和所有的顶点/球面调和光。
// 此通道还负责渲染着色器中的环境光光照贴图。
// 在此通道中渲染的方向光可以产生阴影。
Tags { "LightMode"="ForwardBase" }

// 使用 CGPROGRAM 和 ENDCG 来包围 CG 代码片
CGPROGRAM

// multi_compile_fwdbase 是 Unity 专门为 forwardbase 预定义的 multi_compile
#pragma multi_compile_fwdbase

// 定义顶点着色器名字 - vert
#pragma vertex vert

// 定义片元着色器名字 - frag
#pragma fragment frag

// 包含 Unity 的内置文件以使用 Unity 内置的一些变量
#include "Lighting.cginc"
#include "AutoLight.cginc"

fixed4 _Color; // 获取控制面板中物体的整体色调
sampler2D _MainTex; // 获取控制面板中漫反射纹理(MainTex)的属性值
float4 _MainTex_ST; // 获取控制面板中漫反射纹理(MainTex)属性的平铺和平移值
sampler2D _BumpMap; // 获取控制面板中法线纹理(BumpMap)的属性值
float4 _BumpMap_ST; // 获取控制面板中法线纹理(BumpMap)属性的平铺和平移值

struct a2v {
float4 vertex : POSITION; // 模型空间的顶点坐标
float3 normal : NORMAL; // 模型空间的顶点法线方向
float4 tangent : TANGENT; // 模型空间的顶点切线方向,tangent.w 分量决定切线空间中的第三个坐标轴-副切线的方向性
float4 texcoord : TEXCOORD0; // 模型空间的第一套纹理坐标
};

struct v2f {
float4 pos : SV_POSITION; // 顶点在裁剪空间中的位置信息
float4 uv : TEXCOORD0; // 用于存储纹理坐标的变量,以便在片元着色器中使用该坐标进行纹理采样
float4 TtoW0 : TEXCOORD1; // 存储顶点纹理坐标信息
float4 TtoW1 : TEXCOORD2; // 存储顶点纹理坐标信息
float4 TtoW2 : TEXCOORD3; // 存储顶点纹理坐标信息
SHADOW_COORDS(4) // 存储一个名为 _ShadowCoord 的阴影纹理坐标变量
};

v2f vert(a2v v) {

v2f o; // 声明输出结构

// 将顶点位置从模型空间转换到裁剪空间中
o.pos = UnityObjectToClipPos(v.vertex);

// 贴图 UV
// xy 分量存储了漫反射纹理(MainTex)坐标
// 使用漫反射纹理(MainTex)的属性值 _MainTex_ST 对顶点漫反射纹理(MainTex)坐标进行转换,得到最终的漫反射纹理(MainTex)坐标
// 计算过程是首先使用缩放属性 _MainTex_ST.xy 对顶点漫反射纹理(MainTex)坐标进行缩放,然后再使用偏移属性 _MainTex_ST.zw 对结果进行偏移
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

// 法线 UV
// zw 分量存储了法线纹理(BumpMap)坐标
// 使用法线纹理(BumpMap)的属性值 _BumpMap_ST 对顶点法线纹理(BumpMap)坐标进行转换,得到最终的法线纹理(BumpMap)坐标
// 计算过程是首先使用缩放属性 _BumpMap_ST.xy 对顶点法线纹理(BumpMap)坐标进行缩放,然后再使用偏移属性 _BumpMap_ST.zw 对结果进行偏移
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

// 获取世界空间中的顶点位置(模型空间转世界空间)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

// 获取世界空间下的法线(模型空间转世界空间)
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);

// 获取世界空间下的切线(模型空间转世界空间)
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);

// 获取世界空间下的副法线(模型空间转世界空间)
// 副法线计算,叉积(单位法线,单位切线)*切线⽅向
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

// 构造切线空间到世界空间的旋转矩阵
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

TRANSFER_SHADOW(o); // 得到一个用于采样阴影贴图的坐标

return o; // 返回输出结果
}

fixed4 frag(v2f i) : SV_Target {

// 构建世界空间下的坐标
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);

// 计算世界空间下的光线方向
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));

// 计算世界空间下的视角方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

// 使用 UnpackNormal 对法线纹理(BumpMap)进行采样和解码(需要把法线问题的格式识别为 Normal map)
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));

// 使用 TtoW0、TtoW1、TtoW2 存储的变换矩阵把法线变换到世界空间下
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

// 使用 CG 的 tex2D 函数对漫反射纹理(MainTex)进行采样
// tex2D 函数参数一是需要被采样的漫反射纹理(MainTex)
// tex2D 函数参数二是对应顶点的漫反射纹理(MainTex)坐标
// tex2D 函数返回值是计算得到的纹素值
// 材质的反射率 albedo 是采样结果和颜色属性 _Color 的乘积
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;

// 环境光计算: ambient 是材质反射率与环境光照的乘积
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

// 漫反射计算:利用 albedo 计算漫反射光照的结果
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));

// 计算光照衰减系数:实现代码位于 AutoLight 导入文件之中,使用之前需要 #include "AutoLight.cginc" 使用这个宏后,看起来衰减不再工作了
// 参数一为返回值(光照衰减系数)
// 参数二用于阴影计算
// 参数三是世界坐标
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

// 结果 = 环境光 + 漫反射 * 光照衰减系数
return fixed4(ambient + diffuse * atten, 1.0);
}

ENDCG
}

Pass {

// LightMode 标签是 Pass 标签中的一种,它用于定义该 Pass 在 Unity 的流水线中的角色 (只有定义它才能获取到一些 Unity 内置的光照变量如 _LightColor0
// 告诉渲染管线,这个 pass 作为 ForwardBase 处理,缺少它将画不出任何东西
// 其他的像素光在附加通道中进行渲染,每个光源都需要一个通道。
// 在这些通道中渲染的光源无法产生阴影。
Tags { "LightMode"="ForwardAdd" }

// 基于叠加模式(无透明效果)
Blend One One

// 使用 CGPROGRAM 和 ENDCG 来包围 CG 代码片
CGPROGRAM

// multi_compile_fwdadd 是 Unity 专门为 ForwardAdd 预定义的 multi_compile
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows

// 定义顶点着色器名字 - vert
#pragma vertex vert

// 定义片元着色器名字 - frag
#pragma fragment frag

// 包含 Unity 的内置文件以使用 Unity 内置的一些变量
#include "Lighting.cginc"
#include "AutoLight.cginc"

fixed4 _Color; // 获取控制面板中物体的整体色调
sampler2D _MainTex; // 获取控制面板中漫反射纹理(MainTex)的属性值
float4 _MainTex_ST; // 获取控制面板中漫反射纹理(MainTex)属性的平铺和平移值
sampler2D _BumpMap; // 获取控制面板中法线纹理(BumpMap)的属性值
float4 _BumpMap_ST; // 获取控制面板中法线纹理(BumpMap)属性的平铺和平移值

struct a2v {
float4 vertex : POSITION; // 模型空间的顶点坐标
float3 normal : NORMAL; // 模型空间的顶点法线方向
float4 tangent : TANGENT; // 模型空间的顶点切线方向,tangent.w 分量决定切线空间中的第三个坐标轴-副切线的方向性
float4 texcoord : TEXCOORD0; // 模型空间的第一套纹理坐标
};

struct v2f {
float4 pos : SV_POSITION; // 顶点在裁剪空间中的位置信息
float4 uv : TEXCOORD0; // 用于存储纹理坐标的变量,以便在片元着色器中使用该坐标进行纹理采样
float4 TtoW0 : TEXCOORD1; // 存储顶点纹理坐标信息
float4 TtoW1 : TEXCOORD2; // 存储顶点纹理坐标信息
float4 TtoW2 : TEXCOORD3; // 存储顶点纹理坐标信息
SHADOW_COORDS(4) // 存储一个名为 _ShadowCoord 的阴影纹理坐标变量
};

v2f vert(a2v v) {
v2f o; // 声明输出结构

// 将顶点位置从模型空间转换到裁剪空间中
o.pos = UnityObjectToClipPos(v.vertex);

// 贴图 UV
// xy 分量存储了漫反射纹理(MainTex)坐标
// 使用漫反射纹理(MainTex)的属性值 _MainTex_ST 对顶点漫反射纹理(MainTex)坐标进行转换,得到最终的漫反射纹理(MainTex)坐标
// 计算过程是首先使用缩放属性 _MainTex_ST.xy 对顶点漫反射纹理(MainTex)坐标进行缩放,然后再使用偏移属性 _MainTex_ST.zw 对结果进行偏移
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

// 法线 UV
// zw 分量存储了法线纹理(BumpMap)坐标
// 使用法线纹理(BumpMap)的属性值 _BumpMap_ST 对顶点法线纹理(BumpMap)坐标进行转换,得到最终的法线纹理(BumpMap)坐标
// 计算过程是首先使用缩放属性 _BumpMap_ST.xy 对顶点法线纹理(BumpMap)坐标进行缩放,然后再使用偏移属性 _BumpMap_ST.zw 对结果进行偏移
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

// 获取世界空间中的顶点位置(模型空间转世界空间)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

// 获取世界空间下的法线(模型空间转世界空间)
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);

// 获取世界空间下的切线(模型空间转世界空间)
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);

// 获取世界空间下的副法线(模型空间转世界空间)
// 副法线计算,叉积(单位法线,单位切线)*切线⽅向
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

// 构造切线空间到世界空间的旋转矩阵
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

TRANSFER_SHADOW(o); // 得到一个用于采样阴影贴图的坐标

return o; // 返回输出结果
}

fixed4 frag(v2f i) : SV_Target {
// 构建世界空间下的坐标
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);

// 计算世界空间下的光线方向
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));

// 计算世界空间下的视角方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

// 使用 UnpackNormal 对法线纹理(BumpMap)进行采样和解码(需要把法线问题的格式识别为 Normal map)
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));

// 使用 TtoW0、TtoW1、TtoW2 存储的变换矩阵把法线变换到世界空间下
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

// 使用 CG 的 tex2D 函数对漫反射纹理(MainTex)进行采样
// tex2D 函数参数一是需要被采样的漫反射纹理(MainTex)
// tex2D 函数参数二是对应顶点的漫反射纹理(MainTex)坐标
// tex2D 函数返回值是计算得到的纹素值
// 材质的反射率 albedo 是采样结果和颜色属性 _Color 的乘积
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;

// 漫反射计算:利用 albedo 计算漫反射光照的结果
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));

// 计算光照衰减系数:实现代码位于 AutoLight 导入文件之中,使用之前需要 #include "AutoLight.cginc" 使用这个宏后,看起来衰减不再工作了
// 参数一为返回值(光照衰减系数)
// 参数二用于阴影计算
// 参数三是世界坐标
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

// 结果 = 漫反射 * 光照衰减系数
return fixed4(diffuse * atten, 1.0);
}

ENDCG
}
}

// 设置回调 shader 为内置 Diffuse,上述 SubShader 失败后用于回调的 Unity Shader
FallBack "Diffuse"
}