第20章 自定义Shader编写

第20章 自定义Shader编写

理论讲解

20.1 URP Shader模板创建

在URP中创建自定义Shader需要遵循特定的结构和规范,以确保与URP渲染管线的兼容性。URP提供了多种Shader模板,包括Lit、SimpleLit和Unlit,开发者可以根据需要选择合适的模板进行扩展。

URP Shader的基本结构:

  1. Properties块:定义Shader的可调节参数
  2. SubShader块:包含渲染通道和标签
  3. Tags:定义渲染队列和渲染管线
  4. Pass:具体的渲染通道
  5. HLSLPROGRAM:着色器程序代码

URP Shader模板类型:

  • Lit Shader:支持完整光照计算的着色器
  • SimpleLit Shader:简化的光照着色器
  • Unlit Shader:无光照着色器
  • Custom Pass Shader:自定义渲染通道着色器

URP特定标签:

  • "RenderPipeline" = "UniversalPipeline":指定使用URP
  • "RenderType" = "Opaque":渲染类型(Opaque/Transparent/Cutout等)
  • "Queue" = "Geometry":渲染队列

20.2 Input、Varyings、SurfaceData结构

在URP中,数据在顶点着色器和片元着色器之间传递需要通过特定的结构体,这些结构体定义了数据的格式和内容。

Input结构体:

Input结构体定义了从顶点着色器传递到片元着色器的数据,通常包含:

  • 顶点位置信息
  • 纹理坐标
  • 法线向量
  • 切线向量
  • 世界空间位置
  • 世界空间法线

Varyings结构体:

Varyings结构体是URP中用于在顶点和片元着色器之间传递数据的结构,它包含了:

  • 纹理坐标
  • 世界空间位置
  • 世界空间法线
  • 视图方向
  • 其他自定义数据

SurfaceData结构体:

SurfaceData结构体定义了表面的物理属性,用于光照计算:

  • 基础颜色(Albedo)
  • 法线向量
  • 金属度(Metallic)
  • 光滑度(Smoothness)
  • 发射颜色(Emission)
  • 遮挡因子(Occlusion)

20.3 顶点和片元函数编写

在URP中编写顶点和片元函数需要遵循特定的规范,以确保与URP的光照系统兼容。

顶点函数(Vertex Function):

顶点函数负责处理顶点数据,包括:

  • 顶点变换
  • 法线变换
  • 纹理坐标计算
  • 世界空间位置计算

片元函数(Fragment Function):

片元函数负责计算最终像素颜色,包括:

  • 表面属性计算
  • 光照计算
  • 颜色输出

函数签名规范:

1
2
3
4
5
// 顶点函数
Varyings vert(Attributes input)

// 片元函数
half4 frag(Varyings input) : SV_Target

20.4 与URP光照系统集成

URP提供了完整的光照系统,自定义Shader需要与该系统集成以获得正确的光照效果。

光照系统集成要点:

  1. 获取光照数据:使用URP提供的函数获取光照信息
  2. 应用光照计算:将光照计算应用到表面颜色
  3. 处理阴影:集成URP的阴影系统
  4. 支持多种光源:主光源和附加光源

主要光照函数:

  • GetMainLight():获取主光源信息
  • GetAdditionalLights():获取附加光源信息
  • LightmapSample():采样光照贴图
  • SampleSH():采样球谐光照

20.5 支持SRP Batcher

SRP Batcher是Unity的一项优化技术,可以显著提高渲染性能。为了支持SRP Batcher,Shader需要遵循特定的规范。

SRP Batcher支持要求:

  1. CBUFFER使用:正确使用CBUFFER宏
  2. 材质属性:避免在片元着色器中使用材质属性
  3. 纹理采样:纹理采样需要在特定位置进行
  4. 统一的CBUFFER布局:确保所有材质使用相同的CBUFFER布局

CBUFFER宏使用:

  • UNITY_INSTANCING_BUFFER_START:开始实例化缓冲区
  • UNITY_INSTANCING_BUFFER_END:结束实例化缓冲区
  • UNITY_DEFINE_INSTANCED_PROP:定义实例化属性

20.6 GPU Instancing支持

GPU Instancing允许在一次绘制调用中渲染多个相同网格的实例,可以显著提高性能。

GPU Instancing实现:

  1. 实例化缓冲区:定义实例化数据的缓冲区
  2. 实例ID获取:在着色器中获取当前实例ID
  3. 实例数据应用:将实例数据应用到渲染

实例化相关函数:

  • unity_InstanceID:获取当前实例ID
  • UNITY_ACCESS_INSTANCED_PROP:访问实例化属性

代码示例

20.7 自定义Lit 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
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
Shader "Custom/CustomLitShader"
{
Properties
{
[MainTexture] _BaseMap("Base Map", 2D) = "white" {}
[MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)

_BumpMap("Normal Map", 2D) = "bump" {}
_BumpScale("Normal Scale", Range(0, 2)) = 1.0

_Metallic("Metallic", Range(0, 1)) = 0.0
_Smoothness("Smoothness", Range(0, 1)) = 0.5
_SmoothnessTextureChannel("Smoothness Map Channel", Float) = 0

_SpecColor("Specular Color", Color) = (0.2, 0.2, 0.2, 1)
_SpecularHighlights("Specular Highlights", Float) = 1.0

[HDR] _EmissionColor("Emission Color", Color) = (0, 0, 0, 1)
_EmissionMap("Emission Map", 2D) = "white" {}

_OcclusionMap("Occlusion Map", 2D) = "white" {}
_OcclusionStrength("Occlusion Strength", Range(0, 1)) = 1.0

[ToggleOff] _SpecularOcclusion("Specular Occlusion", Float) = 1.0

// Advanced options
[Enum(UV0, 0, UV1, 1)] _UVSec("UV Set for secondary textures", Float) = 0
[Enum(Off, 0, On, 1)] _EnvironmentReflections("Environment Reflections", Int) = 1
[Enum(Off, 0, On, 1)] _SpecularHighlights("Specular Highlights", Int) = 1
[Enum(Off, 0, On, 1)] _ReceiveShadows("Receive Shadows", Int) = 1

// Blending options
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend("Src Blend", Int) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend("Dst Blend", Int) = 0
[Enum(Off, 0, On, 1)] _ZWrite("Z Write", Int) = 1
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"UniversalMaterialType" = "Lit"
"IgnoreProjector" = "True"
"ShaderModel" = "4.5"
}

Pass
{
Name "ForwardLit"
Tags { "LightMode" = "UniversalForward" }

Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
ZTest LEqual
Cull Back

HLSLPROGRAM
#pragma target 4.5

// -------------------------------------
// Material Keywords
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _SPECULARHIGHLIGHTS_OFF
#pragma shader_feature_local _ENVIRONMENTREFLECTIONS_OFF
#pragma shader_feature_local _SPECULAR_SETUP
#pragma shader_feature_local_fragment _RECEIVE_SHADOWS_OFF

// -------------------------------------
// Universal Render Pipeline keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _SHADOWS_SOFT
#pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION

// -------------------------------------
// Unity defined keywords
#pragma multi_compile_fog
#pragma multi_compile_instancing
#pragma instancing_options renderinglayer

#pragma vertex LitPassVertex
#pragma fragment LitPassFragment

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityGBuffer.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderGraphFunctions.hlsl"

// To prevent SRP Batcher to break, we need to define the CBUFFER
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _BumpScale;
half _Metallic;
half _Smoothness;
half _SmoothnessTextureChannel;
half _OcclusionStrength;
half _SpecularOcclusion;
half4 _SpecColor;
half4 _EmissionColor;
CBUFFER_END

// Textures
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);
TEXTURE2D(_EmissionMap); SAMPLER(sampler_EmissionMap);
TEXTURE2D(_OcclusionMap); SAMPLER(sampler_OcclusionMap);

// Input structure
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

// Varyings structure
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD0;
float3 normalWS : TEXCOORD1;
float4 tangentWS : TEXCOORD2;
float2 uv : TEXCOORD3;
float2 lightmapUV : TEXCOORD4;
half4 fogFactorAndVertexLight : TEXCOORD5;

#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
float4 shadowCoord : TEXCOORD6;
#endif

#if defined(MAIN_LIGHT_CALCULATE_SHADOWS)
float4 shadowCoord : TEXCOORD7;
#endif

UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

// Surface data structure
struct SurfaceData
{
half3 albedo;
half3 specular;
half metallic;
half3 emission;
half smoothness;
half occlusion;
half alpha;
half3 normalTS;
half3 viewDirWS;
};

// Vertex function
Varyings LitPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);

output.positionCS = vertexInput.positionCS;
output.positionWS = vertexInput.positionWS;
output.normalWS = normalInput.normalWS;
output.tangentWS = half4(normalInput.tangentWS, normalInput.sign);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.lightmapUV = input.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;

OUTPUT_LIGHTMAP_UV(input.uv1.xy, unity_LightmapST, output.lightmapUV);
OUTPUT_SH(output.normalWS.xyz);

output.fogFactorAndVertexLight = half4(0, 0, 0, 0);

#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
output.shadowCoord = TransformWorldToShadowCoord(output.positionWS);
#endif

return output;
}

// Get surface data from input
SurfaceData InitializeSurfaceData(Varyings input)
{
SurfaceData surfaceData;

// Sample base map
half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
surfaceData.albedo = baseMap.rgb * _BaseColor.rgb;
surfaceData.alpha = baseMap.a * _BaseColor.a;

// Sample normal map
#ifdef _NORMALMAP
half4 normalMap = SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv);
surfaceData.normalTS = UnpackNormalScale(normalMap, _BumpScale);
#else
surfaceData.normalTS = half3(0.0, 0.0, 1.0);
#endif

// Metallic and smoothness
surfaceData.metallic = _Metallic;
surfaceData.smoothness = _Smoothness;

// Specular color
surfaceData.specular = _SpecColor.rgb;

// Emission
half4 emissionMap = SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.uv);
surfaceData.emission = emissionMap.rgb * _EmissionColor.rgb;

// Occlusion
half4 occlusionMap = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.uv);
surfaceData.occlusion = LerpOneTo(occlusionMap.g, _OcclusionStrength);

// View direction
surfaceData.viewDirWS = GetWorldSpaceNormalizeViewDir(input.positionWS);

return surfaceData;
}

// Fragment function
half4 LitPassFragment(Varyings input) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

// Initialize surface data
SurfaceData surfaceData = InitializeSurfaceData(input);

// Transform normal from tangent space to world space
half3 normalWS = TransformTangentToWorld(surfaceData.normalTS,
half3x3(input.tangentWS.xyz,
cross(input.normalWS, input.tangentWS.xyz) * input.tangentWS.w,
input.normalWS));
normalWS = NormalizeNormalPerPixel(normalWS);

// Get main light
Light mainLight = GetMainLight();

// Calculate lighting
half3 diffuse = surfaceData.albedo * mainLight.color;
half3 specular = surfaceData.specular * mainLight.color;

// Apply shadows
#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
float4 shadowCoord = TransformWorldToShadowCoord(input.positionWS);
half shadowAtten = MainLightShadow(shadowCoord);
diffuse *= shadowAtten;
specular *= shadowAtten;
#endif

// Calculate final color
half3 finalColor = diffuse + surfaceData.emission;

// Apply fog
#ifdef LOREPLACE_fog
finalColor = MixFog(finalColor, input.fogFactorAndVertexLight.w);
#endif

return half4(finalColor, surfaceData.alpha);
}
ENDHLSL
}

// Shadow Caster Pass
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }

ZWrite On
ZTest LEqual
ColorMask 0

HLSLPROGRAM
#pragma target 4.5

#pragma multi_compile_instancing
#pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW

#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShadowCaster.hlsl"

float3 _LightDirection;

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

Varyings ShadowPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);

float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);

output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));

#if UNITY_REVERSED_Z
output.positionCS.z = min(output.positionCS.z, output.positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
output.positionCS.z = max(output.positionCS.z, output.positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif

return output;
}

half4 ShadowPassFragment(Varyings input) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
clip(baseMap.a * _BaseColor.a - 0.001);

return 0;
}
ENDHLSL
}

// Depth Only Pass
Pass
{
Name "DepthOnly"
Tags { "LightMode" = "DepthOnly" }

ZWrite On
ColorMask 0

HLSLPROGRAM
#pragma target 4.5

#pragma multi_compile_instancing

#pragma vertex DepthOnlyVertex
#pragma fragment DepthOnlyFragment

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

Varyings DepthOnlyVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
return output;
}

half4 DepthOnlyFragment(Varyings input) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
clip(baseMap.a * _BaseColor.a - 0.001);

return 0;
}
ENDHLSL
}
}

Fallback "Universal Render Pipeline/Lit"
CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"
}

20.8 自定义Unlit 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
Shader "Custom/CustomUnlitShader"
{
Properties
{
[MainTexture] _BaseMap("Base Map", 2D) = "white" {}
[MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)

_Intensity("Intensity", Range(0, 5)) = 1.0
_EmissionColor("Emission Color", Color) = (0, 0, 0, 1)
_EmissionMap("Emission Map", 2D) = "black" {}

[HDR] _TintColor("Tint Color", Color) = (1, 1, 1, 1)
_Saturation("Saturation", Range(0, 2)) = 1.0
_Brightness("Brightness", Range(0, 2)) = 1.0

// Blending options
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend("Src Blend", Int) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend("Dst Blend", Int) = 0
[Enum(Off, 0, On, 1)] _ZWrite("Z Write", Int) = 1
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("Z Test", Int) = 4
[Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull Mode", Int) = 2

// Advanced options
[Toggle(_EMISSION)] _UseEmission("Use Emission", Float) = 0
[Toggle(_TINT_COLOR)] _UseTintColor("Use Tint Color", Float) = 0
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"IgnoreProjector" = "True"
"ShaderModel" = "4.5"
}

Pass
{
Name "Unlit"
Tags { "LightMode" = "UniversalForward" }

Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
ZTest [_ZTest]
Cull [_Cull]

HLSLPROGRAM
#pragma target 4.5

// Keywords
#pragma shader_feature_local _EMISSION
#pragma shader_feature_local _TINT_COLOR
#pragma multi_compile_fog
#pragma multi_compile_instancing
#pragma instancing_options renderinglayer

#pragma vertex UnlitPassVertex
#pragma fragment UnlitPassFragment

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

// To prevent SRP Batcher to break, we need to define the CBUFFER
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _Intensity;
half4 _EmissionColor;
half4 _TintColor;
half _Saturation;
half _Brightness;
CBUFFER_END

// Textures
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_EmissionMap); SAMPLER(sampler_EmissionMap);

// Input structure
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

// Varyings structure
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float2 lightmapUV : TEXCOORD1;
half4 fogFactor : TEXCOORD2;

UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

// Vertex function
Varyings UnlitPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);

output.positionCS = vertexInput.positionCS;
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.lightmapUV = input.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;

OUTPUT_LIGHTMAP_UV(input.uv1.xy, unity_LightmapST, output.lightmapUV);

#ifdef LOREPLACE_fog
output.fogFactor = half4(0, 0, 0, 0);
output.fogFactor.w = ComputeFogFactor(vertexInput.positionCS.z);
#endif

return output;
}

// Convert RGB to HSV
float3 RGBToHSV(float3 rgb)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(rgb.bg, K.wz), float4(rgb.gb, K.xy), step(rgb.b, rgb.g));
float4 q = lerp(float4(p.xyw, rgb.r), float4(rgb.r, p.yzx), step(p.x, rgb.r));

float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

// Convert HSV to RGB
float3 HSVToRGB(float3 hsv)
{
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(hsv.xxx + K.xyz) * 6.0 - K.www);
return hsv.z * lerp(K.xxx, saturate(p - K.xxx), hsv.y);
}

// Apply color adjustments
half3 ApplyColorAdjustments(half3 color)
{
// Apply brightness
color *= _Brightness;

// Apply saturation
half3 gray = half3(dot(color, half3(0.299, 0.587, 0.114)));
color = lerp(gray, color, _Saturation);

// Apply tint color if enabled
#ifdef _TINT_COLOR
color *= _TintColor.rgb;
#endif

return saturate(color);
}

// Fragment function
half4 UnlitPassFragment(Varyings input) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

// Sample base texture
half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);

// Apply base color
half3 color = baseMap.rgb * _BaseColor.rgb;

// Apply intensity
color *= _Intensity;

// Add emission if enabled
#ifdef _EMISSION
half4 emissionMap = SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.uv);
half3 emission = emissionMap.rgb * _EmissionColor.rgb;
color += emission;
#endif

// Apply color adjustments
color = ApplyColorAdjustments(color);

// Apply alpha from base map
half alpha = baseMap.a * _BaseColor.a;

// Apply fog if enabled
#ifdef LOREPLACE_fog
color = MixFog(color, input.fogFactor.w);
#endif

return half4(color, alpha);
}
ENDHLSL
}
}

Fallback "Universal Render Pipeline/Unlit"
CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.UnlitShader"
}

20.9 支持GPU Instancing的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
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
Shader "Custom/InstancedLitShader"
{
Properties
{
[MainTexture] _BaseMap("Base Map", 2D) = "white" {}
[MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)

_BumpMap("Normal Map", 2D) = "bump" {}
_BumpScale("Normal Scale", Range(0, 2)) = 1.0

_Metallic("Metallic", Range(0, 1)) = 0.0
_Smoothness("Smoothness", Range(0, 1)) = 0.5

// Instance-specific properties
_InstanceColor("Instance Color", Color) = (1, 1, 1, 1)
_InstanceScale("Instance Scale", Vector) = (1, 1, 1, 1)
_InstanceRotation("Instance Rotation", Vector) = (0, 0, 0, 1)
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"UniversalMaterialType" = "Lit"
"IgnoreProjector" = "True"
"ShaderModel" = "4.5"
}

Pass
{
Name "ForwardLit"
Tags { "LightMode" = "UniversalForward" }

HLSLPROGRAM
#pragma target 4.5

// Material Keywords
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _SPECULARHIGHLIGHTS_OFF
#pragma shader_feature_local _ENVIRONMENTREFLECTIONS_OFF
#pragma shader_feature_local _SPECULAR_SETUP
#pragma shader_feature_local_fragment _RECEIVE_SHADOWS_OFF

// Universal Render Pipeline keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _SHADOWS_SOFT

// Unity defined keywords
#pragma multi_compile_fog
#pragma multi_compile_instancing
#pragma instancing_options renderinglayer

#pragma vertex LitPassVertex
#pragma fragment LitPassFragment

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

// Instance-specific CBUFFER
UNITY_INSTANCING_BUFFER_START(PerInstance)
UNITY_DEFINE_INSTANCED_PROP(float4, _InstanceColor)
UNITY_DEFINE_INSTANCED_PROP(float3, _InstanceScale)
UNITY_DEFINE_INSTANCED_PROP(float4, _InstanceRotation)
UNITY_INSTANCING_BUFFER_END(PerInstance)

// Material CBUFFER
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _BumpScale;
half _Metallic;
half _Smoothness;
CBUFFER_END

// Textures
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);

// Input structure
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

// Varyings structure
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD0;
float3 normalWS : TEXCOORD1;
float4 tangentWS : TEXCOORD2;
float2 uv : TEXCOORD3;
float4 shadowCoord : TEXCOORD4;

UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

// Apply instance transformations
float3 ApplyInstanceTransform(float3 positionOS, uint instanceID)
{
// Get instance properties
float4 instanceColor = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _InstanceColor);
float3 instanceScale = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _InstanceScale);
float4 instanceRotation = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _InstanceRotation);

// Apply scale
positionOS *= instanceScale;

// Apply rotation (simplified - using quaternion to matrix conversion would be more accurate)
float3 rotation = instanceRotation.xyz;
float3 rotatedPos = positionOS;

// Apply rotation around each axis
float cosX = cos(rotation.x);
float sinX = sin(rotation.x);
rotatedPos.yz = float2(
rotatedPos.y * cosX - rotatedPos.z * sinX,
rotatedPos.y * sinX + rotatedPos.z * cosX
);

float cosY = cos(rotation.y);
float sinY = sin(rotation.y);
rotatedPos.xz = float2(
rotatedPos.x * cosY + rotatedPos.z * sinY,
-rotatedPos.x * sinY + rotatedPos.z * cosY
);

float cosZ = cos(rotation.z);
float sinZ = sin(rotation.z);
rotatedPos.xy = float2(
rotatedPos.x * cosZ - rotatedPos.y * sinZ,
rotatedPos.x * sinZ + rotatedPos.y * cosZ
);

return rotatedPos;
}

// Vertex function
Varyings LitPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

// Apply instance transformations
float3 positionOS = ApplyInstanceTransform(input.positionOS.xyz, input.instanceID);

VertexPositionInputs vertexInput = GetVertexPositionInputs(positionOS);
VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);

output.positionCS = vertexInput.positionCS;
output.positionWS = vertexInput.positionWS;
output.normalWS = normalInput.normalWS;
output.tangentWS = half4(normalInput.tangentWS, normalInput.sign);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);

#if defined(_MAIN_LIGHT_SHADOWS)
output.shadowCoord = TransformWorldToShadowCoord(output.positionWS);
#endif

return output;
}

// Fragment function
half4 LitPassFragment(Varyings input) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

// Sample base map
half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);

// Get instance color
half4 instanceColor = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _InstanceColor);

// Combine base color with instance color
half3 color = baseMap.rgb * _BaseColor.rgb * instanceColor.rgb;

// Sample and apply normal map if enabled
#ifdef _NORMALMAP
half4 normalMap = SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv);
half3 normalTS = UnpackNormalScale(normalMap, _BumpScale);

// Transform normal from tangent space to world space
half3 normalWS = TransformTangentToWorld(normalTS,
half3x3(input.tangentWS.xyz,
cross(input.normalWS, input.tangentWS.xyz) * input.tangentWS.w,
input.normalWS));
normalWS = NormalizeNormalPerPixel(normalWS);
#else
half3 normalWS = input.normalWS;
#endif

// Get main light
Light mainLight = GetMainLight();

// Simple lighting calculation
half NdotL = saturate(dot(normalWS, mainLight.direction));
half3 lighting = mainLight.color * NdotL;

// Apply lighting
color *= (lighting + 0.2); // Add ambient

// Apply shadows if enabled
#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
half shadowAtten = MainLightShadow(input.shadowCoord);
color *= shadowAtten;
#endif

// Apply alpha
half alpha = baseMap.a * _BaseColor.a * instanceColor.a;

return half4(color, alpha);
}
ENDHLSL
}

// Shadow Caster Pass with instancing
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }

ZWrite On
ZTest LEqual
ColorMask 0

HLSLPROGRAM
#pragma target 4.5

#pragma multi_compile_instancing
#pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW

#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShadowCaster.hlsl"

float3 _LightDirection;

// Instance-specific CBUFFER for shadow pass
UNITY_INSTANCING_BUFFER_START(PerInstanceShadow)
UNITY_DEFINE_INSTANCED_PROP(float3, _InstanceScale)
UNITY_INSTANCING_BUFFER_END(PerInstanceShadow)

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

Varyings ShadowPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

// Apply instance scale to position
float3 instanceScale = UNITY_ACCESS_INSTANCED_PROP(PerInstanceShadow, _InstanceScale);
float3 scaledPosition = input.positionOS.xyz * instanceScale;

float3 positionWS = TransformObjectToWorld(scaledPosition);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);

output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));

#if UNITY_REVERSED_Z
output.positionCS.z = min(output.positionCS.z, output.positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
output.positionCS.z = max(output.positionCS.z, output.positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif

return output;
}

half4 ShadowPassFragment(Varyings input) : SV_Target
{
return 0;
}
ENDHLSL
}
}

Fallback "Universal Render Pipeline/Lit"
}

实践练习

20.10 练习1:创建自定义Lit Shader

目标:创建一个支持完整光照的自定义Lit Shader

步骤

  1. 创建新的Shader文件
  2. 设置正确的Properties块
  3. 实现顶点和片元着色器
  4. 集成URP光照系统
  5. 测试光照效果

实现要点

  • 正确的URP标签设置
  • 完整的光照计算
  • 阴影支持

20.11 练习2:创建Unlit Shader

目标:创建一个无光照的自定义Shader

步骤

  1. 创建Unlit Shader模板
  2. 实现基础纹理采样
  3. 添加颜色调整功能
  4. 集成雾效支持
  5. 测试渲染效果

功能特性

  • 纹理采样
  • 颜色调整
  • 雾效支持

20.12 练习3:GPU Instancing实现

目标:实现支持GPU Instancing的Shader

步骤

  1. 修改CBUFFER以支持实例化
  2. 实现实例化属性访问
  3. 在顶点函数中应用实例变换
  4. 测试实例化渲染
  5. 验证性能提升

技术要点

  • 实例化CBUFFER
  • 属性访问宏
  • 性能优化

20.13 练习4:SRP Batcher兼容性

目标:确保Shader与SRP Batcher兼容

步骤

  1. 检查CBUFFER使用规范
  2. 验证材质属性访问
  3. 优化着色器结构
  4. 测试SRP Batcher效果
  5. 验证性能表现

兼容性要求

  • CBUFFER布局一致性
  • 材质属性访问规范
  • 性能验证

20.14 练习5:综合Shader系统

目标:创建功能完整的自定义Shader系统

步骤

  1. 集成多种渲染模式
  2. 实现参数化控制
  3. 添加性能优化
  4. 测试各种功能
  5. 优化整体性能

系统特性

  • 模块化设计
  • 性能优化
  • 易用性

总结

第20章详细介绍了在URP中编写自定义Shader的方法,包括Shader模板创建、数据结构设计、顶点片元函数编写、URP光照系统集成、SRP Batcher支持和GPU Instancing实现。

关键要点总结:

  1. URP Shader结构:遵循URP规范的Shader结构和标签
  2. 数据传递:Input、Varyings、SurfaceData结构的正确使用
  3. 光照集成:与URP光照系统的无缝集成
  4. 性能优化:SRP Batcher和GPU Instancing的支持
  5. 实例化渲染:高效的大批量对象渲染

编写自定义Shader需要深入理解URP的渲染管线和着色器编程规范,正确实现这些技术可以创建出高性能、高质量的视觉效果。