US3S11L5——Unity自带全局雾效

全局雾效

全局雾效(Global Fog)是一种视觉效果,用于在3D场景中模拟大气中的雾气对远处物体的遮挡,
它通过在场景中加入雾的效果,使得距离摄像机较远的物体看起来逐渐被雾气覆盖,从而创造出一种朦胧、模糊的视觉效果。

image

image

Unity自带的全局雾效

Unity 当中本身就存在一个全局雾效功能、我们可以在 Window —> Rendering —> Lighting 窗口中的 Environment 环境页签中进行开启

image

image

  • Fog 是否开启全局雾效

    • Color 雾的颜色

    • Mode 雾的计算方式

      其中,雾的计算模式有三种,它们分别是:

      • Linear(线性)
      • Exponential(指数)
      • Exponential Squared(指数的平方)

      关于雾的计算公式,详见下文

    • Density 雾的浓度

    • 这里参数根据雾的模式的不同而不同

不同模式的雾的混合因子计算公式

其中,雾的计算模式有三种,它们分别是

  • Linear(线性)
  • Exponential(指数)
  • Exponential Squared(指数的平方)

它们都是用来计算雾的混合因子 ff 的,有了混合因子,会用雾的颜色和物体本来的颜色进行混合计算

最终的颜色=(1f)×物体的颜色+f×雾的颜色最终的颜色 = (1-f) \times 物体的颜色 + f \times 雾的颜色

也就是说得到的混合因子越大,雾的颜色占比越大,表现效果就是雾越浓,混合因子越小,物体本来的颜色占比越大,表现效果就是雾越淡

  • Linear(线性)混合因子计算公式:

    f=enddendstartf = \frac{end - |d|}{end-start}

    其中:

    • dd 代表里摄像机的距离
    • startstart 代表雾开始的距离(可控)
    • endend 代表雾最强时的距离(可控)

    image

    这里的距离都是相对于摄像机的

  • Exponential(指数)混合因子计算公式:

    f=1edensitydf = 1 - e^{-density \cdot |d|}

    • dd 代表里摄像机的距离
    • ee 是自然对数的底约等于 2.71828;
    • densitydensity 代表雾的浓度(可控)

    image

    这里的距离都是相对于摄像机的

  • Exponential Squared(指数的平方)的计算公式:

    f=1e(densityd)2f=1-e^{-(density-|d|)^2}

    • dd 代表里摄像机的距离
    • ee 是自然对数的底约等于 2.71828;
    • densitydensity 代表雾的浓度(可控)

    image

    这里的距离都是相对于摄像机的

Unity 自带的全局雾效的实现原理

Unity 自带的全局雾效的实现原理,是场景上每个物体进行渲染时,
根据物体与摄像机的距离计算雾的影响,并将雾的效果直接应用到物体的材质上(具体如何计算,取决你选择的计算模式)

需要注意的是:如果想要让物体响应 Unity 自带的全局雾效,我们需要在对应物体的 Shader 中加入相关的 CG 代码。

关键的几句 CG 代码是(创建顶点片元着色器时自带):

  1. 编译指令 #pragma multi_compile_fog

  2. 内置文件 #include "UnityCG.cginc"

  3. v2f​ 结构体中加入用于计算雾效坐标信息(通常是计算深度信息)的宏 UNITY_FOG_COORDS(数字)

    后面的数字和 SHADOW_COORDS​(阴影坐标宏)一样,前面有几个纹理坐标语义,这里就写几

  4. 顶点着色器中加入用于计算雾效数据的宏 UNITY_TRANSFER_FOG(v2f结构体, v2f结构体.顶点);

  5. 片元着色器中加入用于应用雾效的宏 UNITY_APPLY_FOG(v2f结构体.fogCoord, 颜色);

image

只需要在自定义 Shader 中加入这几句代码,那么使用该 Shader 的物体便可以受到 Unity 全局雾效的影响,
我们可以在 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
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
Shader "TeachShader/BumpedDiffuse"
{
Properties
{
_Color("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }

// 基础渲染通道 Base Pass
Pass
{
Name "BasePass"

Tags { "LightMode" = "ForwardBase" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// 让Unity全局雾效可以工作
#pragma multi_compile_fog

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; // 使用float4同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float4 tangentToWorld0: TEXCOORD1; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第一行
float4 tangentToWorld1: TEXCOORD2; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第二行
float4 tangentToWorld2: TEXCOORD3; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第三行
SHADOW_COORDS(4) // 阴影坐标变量宏,因为前面有4个TEXCOORD变量,故这里参数填写4
UNITY_FOG_COORDS(5) // Unity雾的坐标宏,因为前面有5个TEXCOORD变量(包括SHADOW_COORDS声明的),故这里填5
};

float4 _Color; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度

v2f vert (appdata_full v)
{
v2f data;

data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 得到世界空间下的顶点位置,用于之后在片元中计算视角方向(基于世界空间下)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);

// 将模型空间下的法线、切线转换到世界空间下,并计算世界空间下的副切线
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
// 将切线空间到世界空间的转换矩阵,以及世界坐标存储到三个贴图变量内
data.tangentToWorld0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
data.tangentToWorld1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
data.tangentToWorld2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
// 阴影坐标转换宏
TRANSFER_SHADOW(data)
// Unity雾效坐标转换宏
UNITY_TRANSFER_FOG(data, data.pos);

return data;
}

fixed4 frag (v2f i) : SV_Target
{
// 计算世界空间下光的方向和视角方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 通过法线纹理采样并解压缩,再乘以凹凸系数,得到切线空间下法线数据
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 将切线空间下法线数据转换到世界空间下
float3 worldNormal = float3(
dot(i.tangentToWorld0.xyz, tangentNormal),
dot(i.tangentToWorld1.xyz, tangentNormal),
dot(i.tangentToWorld2.xyz, tangentNormal)
);

// 根据以上数据计算光照模型颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _Color.rgb; //反射率
// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, lightDir));
// 计算光照和阴影综合衰减值
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
// 最终颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten;
// 对最终颜色加上雾效计算的颜色,其中i.fogCoord是v2f内UNITY_FOG_COORDS自动声明的
UNITY_APPLY_FOG(i.fogCoord, color);

return fixed4(color.rgb, 1);
}
ENDCG
}

// 附加渲染通道 Additional Pass
Pass
{
Name "AdditionalPass"

Tags { "LightMode" = "ForwardAdd" }
Blend One One

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
// 让Unity全局雾效可以工作
#pragma multi_compile_fog

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; // 使用float4同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float4 tangentToWorld0: TEXCOORD1; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第一行
float4 tangentToWorld1: TEXCOORD2; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第二行
float4 tangentToWorld2: TEXCOORD3; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第三行
SHADOW_COORDS(4) // 阴影坐标变量宏,因为前面有4个TEXCOORD变量,故这里参数填写4
UNITY_FOG_COORDS(5) // Unity雾的坐标宏,因为前面有5个TEXCOORD变量(包括SHADOW_COORDS声明的),故这里填5
};

float4 _Color; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度

v2f vert (appdata_full v)
{
v2f data;

data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 得到世界空间下的顶点位置,用于之后在片元中计算视角方向(基于世界空间下)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);

// 将模型空间下的法线、切线转换到世界空间下,并计算世界空间下的副切线
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
// 将切线空间到世界空间的转换矩阵,以及世界坐标存储到三个贴图变量内
data.tangentToWorld0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
data.tangentToWorld1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
data.tangentToWorld2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
// 阴影坐标转换宏
TRANSFER_SHADOW(data)
// Unity雾效坐标转换宏
UNITY_TRANSFER_FOG(data, data.pos);

return data;
}

fixed4 frag (v2f i) : SV_Target
{
// 计算世界空间下光的方向和视角方向
float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
#if defined(_DIRECTIONAL_LIGHT)
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 平行光的光源方向
#else
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - worldPos); // 非平行光的光源方向
#endif
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 通过法线纹理采样并解压缩,再乘以凹凸系数,得到切线空间下法线数据
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 将切线空间下法线数据转换到世界空间下
float3 worldNormal = float3(
dot(i.tangentToWorld0.xyz, tangentNormal),
dot(i.tangentToWorld1.xyz, tangentNormal),
dot(i.tangentToWorld2.xyz, tangentNormal)
);

// 根据以上数据计算光照模型颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _Color.rgb; //反射率
// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, lightDir));
// 计算光照和阴影综合衰减值
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
// 最终颜色
fixed3 color = lambertColor * atten;
// 对最终颜色加上雾效计算的颜色,其中i.fogCoord是v2f内UNITY_FOG_COORDS自动声明的
UNITY_APPLY_FOG(i.fogCoord, color);

return fixed4(color.rgb, 1);
}
ENDCG
}
}

Fallback "Diffuse"
}
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
Shader "TeachShader/BumpedSpecular"
{
Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_SpecularNum("SpecularNum", Range(0, 20)) = 18
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }

// 基础渲染通道 Base Pass
Pass
{
Name "BasePass"

Tags { "LightMode" = "ForwardBase" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
// 让Unity全局雾效可以工作
#pragma multi_compile_fog

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; // 使用float4同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float4 tangentToWorld0: TEXCOORD1; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第一行
float4 tangentToWorld1: TEXCOORD2; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第二行
float4 tangentToWorld2: TEXCOORD3; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第三行
SHADOW_COORDS(4) // 阴影坐标变量宏,因为前面有4个TEXCOORD变量,故这里参数填写4
UNITY_FOG_COORDS(5) // Unity雾的坐标宏,因为前面有5个TEXCOORD变量(包括SHADOW_COORDS声明的),故这里填5
};

float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度

v2f vert (appdata_full v)
{
v2f data;

data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 得到世界空间下的顶点位置,用于之后在片元中计算视角方向(基于世界空间下)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
// 将模型空间下的法线、切线转换到世界空间下,并计算世界空间下的副切线
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
// 将切线空间到世界空间的转换矩阵,以及世界坐标存储到三个贴图变量内
data.tangentToWorld0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
data.tangentToWorld1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
data.tangentToWorld2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
// 阴影坐标转换宏
TRANSFER_SHADOW(data)
// Unity雾效坐标转换宏
UNITY_TRANSFER_FOG(data, data.pos);

return data;
}

fixed4 frag (v2f i) : SV_Target
{
// 计算世界空间下光的方向和视角方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 通过法线纹理采样并解压缩,再乘以凹凸系数,得到切线空间下法线数据
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 将切线空间下法线数据转换到世界空间下
float3 worldNormal = float3(
dot(i.tangentToWorld0.xyz, tangentNormal),
dot(i.tangentToWorld1.xyz, tangentNormal),
dot(i.tangentToWorld2.xyz, tangentNormal)
);

// 根据以上数据计算光照模型颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _MainColor.rgb; //反射率
// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, lightDir));
// 高光反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
float3 halfA = normalize(viewDir + lightDir); //半角向量
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfA)), _SpecularNum);
// 计算光照和阴影综合衰减值
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
// 最终颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + (lambertColor + specularColor) * atten;
// 对最终颜色加上雾效计算的颜色,其中i.fogCoord是v2f内UNITY_FOG_COORDS自动声明的
UNITY_APPLY_FOG(i.fogCoord, color);

return fixed4(color.rgb, 1);
}
ENDCG
}

// 附加渲染通道 Additional Pass
Pass
{
Name "AdditionalPass"

Tags { "LightMode" = "ForwardAdd" }
Blend One One

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
// 让Unity全局雾效可以工作
#pragma multi_compile_fog

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; // 使用float4同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float4 tangentToWorld0: TEXCOORD1; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第一行
float4 tangentToWorld1: TEXCOORD2; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第二行
float4 tangentToWorld2: TEXCOORD3; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第三行
SHADOW_COORDS(4) // 阴影坐标变量宏,因为前面有4个TEXCOORD变量,故这里参数填写4
UNITY_FOG_COORDS(5) // Unity雾的坐标宏,因为前面有5个TEXCOORD变量(包括SHADOW_COORDS声明的),故这里填5
};

float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度

v2f vert (appdata_full v)
{
v2f data;

data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 得到世界空间下的顶点位置,用于之后在片元中计算视角方向(基于世界空间下)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
// 将模型空间下的法线、切线转换到世界空间下,并计算世界空间下的副切线
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
// 将切线空间到世界空间的转换矩阵,以及世界坐标存储到三个贴图变量内
data.tangentToWorld0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
data.tangentToWorld1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
data.tangentToWorld2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
// 阴影坐标转换宏
TRANSFER_SHADOW(data)
// Unity雾效坐标转换宏
UNITY_TRANSFER_FOG(data, data.pos);

return data;
}

fixed4 frag (v2f i) : SV_Target
{
// 计算世界空间下光的方向和视角方向
float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
#if defined(_DIRECTIONAL_LIGHT)
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 平行光的光源方向
#else
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - worldPos); // 非平行光的光源方向
#endif
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 通过法线纹理采样并解压缩,再乘以凹凸系数,得到切线空间下法线数据
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 将切线空间下法线数据转换到世界空间下
float3 worldNormal = float3(
dot(i.tangentToWorld0.xyz, tangentNormal),
dot(i.tangentToWorld1.xyz, tangentNormal),
dot(i.tangentToWorld2.xyz, tangentNormal)
);

// 根据以上数据计算光照模型颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _MainColor.rgb; //反射率
// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, lightDir));
// 高光反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
float3 halfA = normalize(viewDir + lightDir); //半角向量
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfA)), _SpecularNum);
// 计算光照和阴影综合衰减值
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
// 最终颜色
fixed3 color = (lambertColor + specularColor) * atten;
// 对最终颜色加上雾效计算的颜色,其中i.fogCoord是v2f内UNITY_FOG_COORDS自动声明的
UNITY_APPLY_FOG(i.fogCoord, color);

return fixed4(color.rgb, 1);
}
ENDCG
}
}

Fallback "Specular"
}

显示效果:

image