URP渲染管线完整教程

Unity URP渲染管线完整教程


第一部分:URP基础入门

第1章 URP概述与环境搭建

1.1 什么是URP及其发展历史

Universal Render Pipeline(通用渲染管线)是Unity推出的可编程渲染管线(SRP),旨在提供跨平台的高性能渲染解决方案。

发展历程:

  • 2018年:Lightweight Render Pipeline (LWRP) 发布
  • 2019年:更名为Universal Render Pipeline (URP)
  • 2020年:URP成为Unity推荐的默认渲染管线
  • 2021-2025年:持续迭代,添加Deferred渲染、URP 2D、更多Renderer Features

核心特点:

  • 基于Scriptable Render Pipeline (SRP)框架
  • 单Pass Forward/Deferred渲染
  • 优秀的跨平台性能(移动端到主机平台)
  • 可扩展的Renderer Feature系统
  • 现代化的Shader Graph支持

1.2 URP vs Built-in vs HDRP对比分析

特性 Built-in URP HDRP
目标平台 全平台 全平台 高端PC/主机
渲染方式 Forward/Deferred Forward/Deferred Deferred/Forward+
性能 中等 优秀 高消耗
可定制性 有限 非常高
光照模型 PBR Simplified PBR Physical PBR
适用场景 传统项目 移动/跨平台 AAA级游戏

1.3 适用场景与性能特点

URP最适合:

  • 移动游戏(iOS/Android)
  • 中小型3D项目
  • 2D游戏(使用URP 2D)
  • 需要良好性能的跨平台项目
  • VR/AR应用

性能优势:

  • 单Pass渲染减少Draw Call
  • SRP Batcher自动批处理
  • 高效的光照剔除
  • 可配置的渲染质量级别
  • 优化的移动端渲染路径

1.4 创建URP项目的三种方式

方式一:使用URP模板创建新项目

1
2
3
4
// 1. 打开Unity Hub
// 2. 点击"New Project"
// 3. 选择"3D (URP)"或"2D (URP)"模板
// 4. 创建项目

方式二:在现有项目中安装URP

1
2
3
4
5
6
7
8
9
10
11
// 1. 打开Package Manager (Window > Package Manager)
// 2. 搜索"Universal RP"
// 3. 点击Install

// 或者手动编辑 Packages/manifest.json
{
"dependencies": {
"com.unity.render-pipelines.universal": "14.0.8",
"com.unity.render-pipelines.core": "14.0.8"
}
}

方式三:使用命令行/脚本批量配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEditor;
using UnityEngine.Rendering.Universal;

public class URPSetup : EditorWindow
{
[MenuItem("Tools/Setup URP")]
static void SetupURP()
{
// 创建URP Asset
var urpAsset = UniversalRenderPipelineAsset.Create();
AssetDatabase.CreateAsset(urpAsset, "Assets/Settings/URPAsset.asset");

// 创建Renderer
var rendererData = ScriptableRendererData.CreateRendererData();
AssetDatabase.CreateAsset(rendererData, "Assets/Settings/ForwardRenderer.asset");

// 设置为激活管线
UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset = urpAsset;

Debug.Log("URP Setup Complete!");
}
}

1.5 从Built-in管线迁移到URP的注意事项

着色器迁移:

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
// Built-in Shader
Shader "Custom/BuiltInShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

// ...
ENDCG
}
}
}

// URP Shader
Shader "Custom/URPShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }

Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

// ...
ENDHLSL
}
}
}

材质升级工具:

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
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class MaterialUpgrader
{
[MenuItem("Tools/Upgrade Materials to URP")]
static void UpgradeMaterials()
{
string[] guids = AssetDatabase.FindAssets("t:Material");
int upgraded = 0;

foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);

if (mat.shader.name.Contains("Standard"))
{
mat.shader = Shader.Find("Universal Render Pipeline/Lit");
upgraded++;
}
}

AssetDatabase.SaveAssets();
Debug.Log($"Upgraded {upgraded} materials to URP");
}
}

关键差异清单:

  1. 光照模式改变

    • 不再有Forward/Deferred切换(在Renderer中配置)
    • Light Mode tags变化:UniversalForward vs ForwardBase
  2. 后处理系统

    • 从Post Processing Stack v2迁移到Volume系统
    • 配置方式和参数有所不同
  3. 相机设置

    • 使用Camera Stack替代多相机设置
    • Rendering Path在Pipeline Asset中配置
  4. 性能考虑

    • 启用SRP Batcher需要修改Shader结构
    • 光源数量限制(默认8个附加光源)

1.6 总结

本章介绍了URP的基本概念、发展历史和环境搭建方法。URP作为Unity现代化渲染解决方案,提供了优秀的跨平台性能和可扩展性。通过三种创建方式,开发者可以快速开始URP项目开发。从Built-in管线迁移时需要注意着色器语法、材质升级和系统差异,使用提供的工具可以简化迁移过程。

下一章预告: 将深入讲解URP Asset的各项配置参数及其对渲染质量和性能的影响。


第2章 URP Asset配置详解

2.1 Universal Render Pipeline Asset结构解析

URP Asset是整个渲染管线的核心配置文件,控制着渲染质量、性能和功能特性。

创建URP Asset:

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
// 方法1:通过菜单创建
// Assets > Create > Rendering > URP Asset (with Universal Renderer)

// 方法2:通过代码创建
using UnityEditor;
using UnityEngine.Rendering.Universal;

public class CreateURPAsset
{
[MenuItem("Assets/Create/Custom URP Asset")]
static void Create()
{
var asset = UniversalRenderPipelineAsset.Create();
var path = "Assets/Settings/CustomURPAsset.asset";
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();

// 分配Renderer
var rendererData = ScriptableObject.CreateInstance<UniversalRendererData>();
AssetDatabase.CreateAsset(rendererData, "Assets/Settings/CustomRenderer.asset");

// 链接Renderer到Asset
SerializedObject so = new SerializedObject(asset);
so.FindProperty("m_RendererDataList").arraySize = 1;
so.FindProperty("m_RendererDataList.Array.data[0]").objectReferenceValue = rendererData;
so.ApplyModifiedProperties();
}
}

URP Asset主要组成部分:

1
2
3
4
5
6
7
URPAsset
├── General (通用设置)
├── Quality (质量设置)
├── Lighting (光照设置)
├── Shadows (阴影设置)
├── Post-processing (后处理设置)
└── Advanced (高级设置)

2.2 Rendering配置详解

深度纹理(Depth Texture):

1
2
3
4
5
6
// 启用深度纹理的影响
public class DepthTextureExample : MonoBehaviour
{
// 在Shader中访问深度纹理
// Shader代码
}

对应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
Shader "Custom/DepthVisualization"
{
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }

Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

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

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

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}

half4 frag(Varyings input) : SV_Target
{
// 采样深度纹理
float depth = SampleSceneDepth(input.uv);

// 线性化深度
float linearDepth = Linear01Depth(depth, _ZBufferParams);

return half4(linearDepth, linearDepth, linearDepth, 1.0);
}
ENDHLSL
}
}
}

不透明纹理(Opaque Texture):

配置选项:

  • Off: 不生成
  • After Opaques: 在不透明物体渲染后生成(用于折射效果)

应用示例 - 玻璃折射:

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
Shader "Custom/GlassRefraction"
{
Properties
{
_RefractionStrength ("Refraction Strength", Range(0, 1)) = 0.1
}

SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" "RenderPipeline"="UniversalPipeline" }

Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

float _RefractionStrength;

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float3 normalWS : TEXCOORD0;
float4 screenPos : TEXCOORD1;
};

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.screenPos = ComputeScreenPos(output.positionCS);
return output;
}

half4 frag(Varyings input) : SV_Target
{
// 计算折射UV偏移
float2 screenUV = input.screenPos.xy / input.screenPos.w;
float2 offset = input.normalWS.xy * _RefractionStrength;

// 采样不透明纹理
half3 sceneColor = SampleSceneColor(screenUV + offset);

return half4(sceneColor, 1.0);
}
ENDHLSL
}
}
}

HDR配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// HDR对后处理的影响示例
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class HDRController : MonoBehaviour
{
public Volume volume;

void Start()
{
// 检查HDR是否启用
bool isHDR = QualitySettings.activeColorSpace == ColorSpace.Linear;

if (volume.profile.TryGet<Bloom>(out var bloom))
{
// HDR下Bloom效果更明显
bloom.intensity.value = isHDR ? 1.0f : 0.5f;
}
}
}

2.3 Quality Settings质量配置

抗锯齿(Anti-Aliasing):

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
// 运行时切换抗锯齿
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class AAController : MonoBehaviour
{
private UniversalAdditionalCameraData cameraData;

void Start()
{
cameraData = GetComponent<UniversalAdditionalCameraData>();
}

public void SetAntiAliasing(AntialiasingMode mode, AntialiasingQuality quality)
{
cameraData.antialiasing = mode;
cameraData.antialiasingQuality = quality;
}

// MSAA示例
public void EnableMSAA()
{
SetAntiAliasing(AntialiasingMode.None, AntialiasingQuality.High);
// MSAA需要在URP Asset中配置
}

// FXAA示例
public void EnableFXAA()
{
SetAntiAliasing(AntialiasingMode.FastApproximateAntialiasing,
AntialiasingQuality.Medium);
}

// SMAA示例
public void EnableSMAA()
{
SetAntiAliasing(AntialiasingMode.SubpixelMorphologicalAntiAliasing,
AntialiasingQuality.High);
}
}

渲染缩放(Render Scale):

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
// 动态分辨率调整
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class DynamicResolution : MonoBehaviour
{
public UniversalRenderPipelineAsset urpAsset;
private float targetFPS = 60f;
private float currentScale = 1.0f;

void Update()
{
float currentFPS = 1.0f / Time.deltaTime;

// 根据帧率动态调整渲染分辨率
if (currentFPS < targetFPS - 5f && currentScale > 0.5f)
{
currentScale -= 0.05f;
urpAsset.renderScale = currentScale;
}
else if (currentFPS > targetFPS + 5f && currentScale < 1.0f)
{
currentScale += 0.05f;
urpAsset.renderScale = currentScale;
}

Debug.Log($"FPS: {currentFPS:F1}, Render Scale: {currentScale:F2}");
}
}

2.4 Lighting配置

主光源设置(Main Light):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 主光源阴影配置
using UnityEngine;

public class MainLightSetup : MonoBehaviour
{
public Light mainLight;

void ConfigureMainLight()
{
// 设置为主光源
mainLight.type = LightType.Directional;

// 阴影配置
mainLight.shadows = LightShadows.Soft;
mainLight.shadowStrength = 0.8f;
mainLight.shadowBias = 0.05f;
mainLight.shadowNormalBias = 0.4f;

// 渲染层设置
mainLight.renderingLayerMask = 1; // Default layer
}
}

附加光源(Additional Lights):

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
// 附加光源管理器
using UnityEngine;
using System.Collections.Generic;

public class AdditionalLightsManager : MonoBehaviour
{
public int maxAdditionalLights = 8;
private List<Light> additionalLights = new List<Light>();

void Start()
{
// 查找所有附加光源
Light[] allLights = FindObjectsOfType<Light>();

foreach (Light light in allLights)
{
if (light.type != LightType.Directional)
{
additionalLights.Add(light);
}
}

OptimizeLights();
}

void OptimizeLights()
{
// 按重要性排序(距离、强度)
additionalLights.Sort((a, b) =>
{
float distA = Vector3.Distance(Camera.main.transform.position, a.transform.position);
float distB = Vector3.Distance(Camera.main.transform.position, b.transform.position);
float scoreA = a.intensity / (distA + 1f);
float scoreB = b.intensity / (distB + 1f);
return scoreB.CompareTo(scoreA);
});

// 只保留最重要的N个光源
for (int i = 0; i < additionalLights.Count; i++)
{
additionalLights[i].enabled = i < maxAdditionalLights;
}
}

void Update()
{
// 每帧更新光源优先级
OptimizeLights();
}
}

Per-Object光照限制:

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
// Shader中处理多光源
Shader "Custom/MultiLightShader"
{
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }

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

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

// 支持多光源
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS

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

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD0;
float3 normalWS : TEXCOORD1;
};

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
return output;
}

half4 frag(Varyings input) : SV_Target
{
// 主光源计算
Light mainLight = GetMainLight();
half3 lighting = mainLight.color * mainLight.distanceAttenuation;

// 附加光源计算
#ifdef _ADDITIONAL_LIGHTS
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, input.positionWS);
half3 attenuatedLightColor = light.color * light.distanceAttenuation;
lighting += attenuatedLightColor;
}
#endif

half3 normal = normalize(input.normalWS);
half NdotL = saturate(dot(normal, mainLight.direction));

return half4(lighting * NdotL, 1.0);
}
ENDHLSL
}
}
}

2.5 多级Quality配置策略

创建Quality级别配置:

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
using UnityEngine;
using UnityEngine.Rendering.Universal;

[CreateAssetMenu(menuName = "Rendering/Quality Preset")]
public class QualityPreset : ScriptableObject
{
[Header("URP Asset References")]
public UniversalRenderPipelineAsset lowQualityAsset;
public UniversalRenderPipelineAsset mediumQualityAsset;
public UniversalRenderPipelineAsset highQualityAsset;

[Header("Quality Settings")]
public int shadowDistance = 50;
public ShadowResolution shadowResolution = ShadowResolution._1024;
public int msaaLevel = 4;
public float renderScale = 1.0f;

public void ApplyQuality(QualityLevel level)
{
UniversalRenderPipelineAsset targetAsset = null;

switch (level)
{
case QualityLevel.Low:
targetAsset = lowQualityAsset;
ConfigureLowQuality(targetAsset);
break;
case QualityLevel.Medium:
targetAsset = mediumQualityAsset;
ConfigureMediumQuality(targetAsset);
break;
case QualityLevel.High:
targetAsset = highQualityAsset;
ConfigureHighQuality(targetAsset);
break;
}

UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset = targetAsset;
QualitySettings.SetQualityLevel((int)level);
}

void ConfigureLowQuality(UniversalRenderPipelineAsset asset)
{
asset.shadowDistance = 30f;
asset.renderScale = 0.75f;
asset.msaaSampleCount = 0;
asset.supportsCameraDepthTexture = false;
}

void ConfigureMediumQuality(UniversalRenderPipelineAsset asset)
{
asset.shadowDistance = 50f;
asset.renderScale = 1.0f;
asset.msaaSampleCount = 2;
asset.supportsCameraDepthTexture = true;
}

void ConfigureHighQuality(UniversalRenderPipelineAsset asset)
{
asset.shadowDistance = 100f;
asset.renderScale = 1.0f;
asset.msaaSampleCount = 4;
asset.supportsCameraDepthTexture = true;
asset.supportsCameraOpaqueTexture = true;
}
}

public enum QualityLevel
{
Low,
Medium,
High
}

运行时Quality切换器:

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
using UnityEngine;
using UnityEngine.UI;

public class QualityManager : MonoBehaviour
{
public QualityPreset preset;
public Dropdown qualityDropdown;

void Start()
{
qualityDropdown.onValueChanged.AddListener(OnQualityChanged);

// 检测平台自动设置质量
AutoDetectQuality();
}

void OnQualityChanged(int index)
{
preset.ApplyQuality((QualityLevel)index);
Debug.Log($"Quality changed to: {(QualityLevel)index}");
}

void AutoDetectQuality()
{
int qualityLevel = 1; // Default: Medium

#if UNITY_ANDROID || UNITY_IOS
// 移动平台默认低质量
qualityLevel = 0;

// 根据设备性能调整
if (SystemInfo.systemMemorySize > 4096)
qualityLevel = 1;
#else
// PC平台根据显存判断
if (SystemInfo.graphicsMemorySize > 4096)
qualityLevel = 2;
else if (SystemInfo.graphicsMemorySize > 2048)
qualityLevel = 1;
#endif

qualityDropdown.value = qualityLevel;
}
}

2.6 总结

本章深入讲解了URP Asset的各项配置参数,包括渲染设置、质量选项、光照配置和多级质量策略。通过合理配置这些参数,可以在不同平台和硬件上实现最佳的渲染效果和性能平衡。关键要点:

  1. 深度和不透明纹理 - 根据需求启用,避免不必要的性能开销
  2. 质量分级 - 为不同平台创建专门的配置文件
  3. 光照优化 - 合理控制附加光源数量和阴影质量
  4. 动态调整 - 实现运行时质量切换以适应不同场景需求

下一章预告: 将探讨URP Renderer配置,包括Forward/Deferred渲染器和强大的Renderer Feature系统。


第3章 URP Renderer配置

3.1 Forward Renderer vs Deferred Renderer

Forward Renderer特点:

  • 单Pass渲染所有光源
  • 内存占用低
  • 移动平台友好
  • 不支持Screen Space效果(如SSAO)

Deferred Renderer特点:

  • 多Pass渲染,先写入GBuffer
  • 支持大量光源
  • 支持Screen Space效果
  • 更高的内存和带宽消耗

选择建议对照表:

场景类型 推荐Renderer 原因
移动游戏 Forward 性能最优
PC大型场景 Deferred 支持更多光源
VR应用 Forward 低延迟
建筑可视化 Deferred 需要SSAO等效果

创建和配置Renderer:

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
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class RendererSetup
{
[MenuItem("Tools/Create Forward Renderer")]
static void CreateForwardRenderer()
{
var rendererData = ScriptableObject.CreateInstance<UniversalRendererData>();
rendererData.name = "ForwardRenderer";

AssetDatabase.CreateAsset(rendererData, "Assets/Settings/ForwardRenderer.asset");
AssetDatabase.SaveAssets();

Debug.Log("Forward Renderer created!");
}

[MenuItem("Tools/Create Deferred Renderer")]
static void CreateDeferredRenderer()
{
// Deferred需要特殊设置
var rendererData = ScriptableObject.CreateInstance<UniversalRendererData>();
rendererData.name = "DeferredRenderer";

// 通过反射设置Deferred模式(Editor only)
SerializedObject so = new SerializedObject(rendererData);
so.FindProperty("m_RenderingMode").enumValueIndex = 1; // Deferred
so.ApplyModifiedProperties();

AssetDatabase.CreateAsset(rendererData, "Assets/Settings/DeferredRenderer.asset");
AssetDatabase.SaveAssets();

Debug.Log("Deferred Renderer created!");
}
}

运行时切换Renderer:

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
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class RendererSwitcher : MonoBehaviour
{
public UniversalRenderPipelineAsset urpAsset;
public ScriptableRendererData forwardRenderer;
public ScriptableRendererData deferredRenderer;

public void SwitchToForward()
{
SetRenderer(0); // 假设Forward是索引0
Debug.Log("Switched to Forward Renderer");
}

public void SwitchToDeferred()
{
SetRenderer(1); // 假设Deferred是索引1
Debug.Log("Switched to Deferred Renderer");
}

void SetRenderer(int index)
{
// 通过反射访问私有字段
var field = typeof(UniversalRenderPipelineAsset).GetField(
"m_DefaultRendererIndex",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
);
field?.SetValue(urpAsset, index);
}
}

3.2 Renderer Features系统介绍

Renderer Feature是URP的强大扩展机制,允许向渲染管线注入自定义渲染Pass。

Renderer Feature架构:

1
2
3
4
5
6
7
8
9
ScriptableRendererFeature (抽象基类)
├── Create() - 创建RenderPass实例
├── AddRenderPasses() - 将Pass添加到Renderer
└── SetupRenderPasses() - 配置Pass参数

ScriptableRenderPass (渲染Pass基类)
├── Configure() - 配置渲染目标
├── Execute() - 执行渲染命令
└── OnCameraCleanup() - 清理资源

创建自定义Renderer Feature基础示例:

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
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomRendererFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
public Material material;
public Color tintColor = Color.white;
}

public Settings settings = new Settings();
private CustomRenderPass customPass;

// 创建Pass实例(只调用一次)
public override void Create()
{
customPass = new CustomRenderPass(settings);
}

// 每帧调用,将Pass添加到渲染队列
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.material == null)
{
Debug.LogWarning("Material is missing!");
return;
}

// 配置Pass
customPass.Setup(renderer);

// 添加到渲染队列
renderer.EnqueuePass(customPass);
}

// 自定义RenderPass类
class CustomRenderPass : ScriptableRenderPass
{
private Settings settings;
private RTHandle tempTexture;
private ScriptableRenderer renderer;

public CustomRenderPass(Settings settings)
{
this.settings = settings;
renderPassEvent = settings.renderPassEvent;
}

public void Setup(ScriptableRenderer renderer)
{
this.renderer = renderer;
}

// 配置渲染目标和参数
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 创建临时纹理
RenderTextureDescriptor descriptor = cameraTextureDescriptor;
descriptor.depthBufferBits = 0; // 不需要深度

RenderingUtils.ReAllocateIfNeeded(
ref tempTexture,
descriptor,
FilterMode.Bilinear,
TextureWrapMode.Clamp,
name: "_TempTexture"
);
}

// 执行渲染命令
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get("CustomRenderPass");

// 获取相机颜色目标
RTHandle cameraTarget = renderer.cameraColorTargetHandle;

// 设置材质参数
settings.material.SetColor("_TintColor", settings.tintColor);

// Blit操作:从相机目标复制到临时纹理,应用材质
Blitter.BlitCameraTexture(cmd, cameraTarget, tempTexture, settings.material, 0);

// 复制回相机目标
Blitter.BlitCameraTexture(cmd, tempTexture, cameraTarget);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

// 清理资源
public override void OnCameraCleanup(CommandBuffer cmd)
{
tempTexture?.Release();
}
}
}

对应的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
Shader "Custom/RendererFeatureShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TintColor ("Tint Color", Color) = (1,1,1,1)
}

SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }

Pass
{
Name "CustomEffect"

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

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

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

TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);

CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
half4 _TintColor;
CBUFFER_END

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
return output;
}

half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
return color * _TintColor;
}
ENDHLSL
}
}
}

3.3 Render Objects功能详解

Render Objects Feature允许使用自定义材质和设置重新渲染特定对象。

应用场景:

  • X-Ray透视效果
  • 自定义描边
  • 特殊对象着色
  • 分层渲染

完整X-Ray效果实现:

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
using UnityEngine;
using UnityEngine.Rendering.Universal;

[CreateAssetMenu(menuName = "Rendering/X-Ray Renderer Feature")]
public class XRayRendererFeature : ScriptableRendererFeature
{
[System.Serializable]
public class XRaySettings
{
public string passName = "X-Ray Pass";
public RenderPassEvent renderEvent = RenderPassEvent.AfterRenderingTransparents;

[Header("Filter Settings")]
public LayerMask layerMask = -1;
public string lightModeTag = "UniversalForward";

[Header("Override Settings")]
public Material overrideMaterial;
public int overrideMaterialPassIndex = 0;

[Header("X-Ray Settings")]
public Color xrayColor = Color.green;
[Range(0, 1)] public float xrayAlpha = 0.5f;

[Header("Depth Settings")]
public bool writeDepth = false;
public CompareFunction depthTest = CompareFunction.Greater;
}

public XRaySettings settings = new XRaySettings();
private XRayPass xrayPass;

public override void Create()
{
xrayPass = new XRayPass(settings);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.overrideMaterial == null) return;

renderer.EnqueuePass(xrayPass);
}

class XRayPass : ScriptableRenderPass
{
private XRaySettings settings;
private FilteringSettings filteringSettings;
private RenderStateBlock renderStateBlock;

private static readonly ShaderTagId shaderTagId = new ShaderTagId("UniversalForward");

public XRayPass(XRaySettings settings)
{
this.settings = settings;
renderPassEvent = settings.renderEvent;

// 配置过滤设置
filteringSettings = new FilteringSettings(RenderQueueRange.all, settings.layerMask);

// 配置渲染状态
renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);

if (!settings.writeDepth)
{
renderStateBlock.depthState = new DepthState(false, settings.depthTest);
renderStateBlock.mask |= RenderStateMask.Depth;
}
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get(settings.passName);

// 设置材质参数
settings.overrideMaterial.SetColor("_XRayColor", settings.xrayColor);
settings.overrideMaterial.SetFloat("_XRayAlpha", settings.xrayAlpha);

// 创建绘制设置
var drawingSettings = CreateDrawingSettings(
shaderTagId,
ref renderingData,
SortingCriteria.CommonOpaque
);

drawingSettings.overrideMaterial = settings.overrideMaterial;
drawingSettings.overrideMaterialPassIndex = settings.overrideMaterialPassIndex;

// 执行绘制
context.DrawRenderers(
renderingData.cullResults,
ref drawingSettings,
ref filteringSettings,
ref renderStateBlock
);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
}

X-Ray 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
Shader "Custom/XRayShader"
{
Properties
{
_XRayColor ("X-Ray Color", Color) = (0, 1, 0, 1)
_XRayAlpha ("X-Ray Alpha", Range(0, 1)) = 0.5
_RimPower ("Rim Power", Range(0.1, 8.0)) = 3.0
}

SubShader
{
Tags
{
"RenderType"="Transparent"
"Queue"="Transparent"
"RenderPipeline"="UniversalPipeline"
}

Pass
{
Name "XRayPass"
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
ZTest Greater

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float3 normalWS : TEXCOORD0;
float3 viewDirWS : TEXCOORD1;
};

CBUFFER_START(UnityPerMaterial)
half4 _XRayColor;
half _XRayAlpha;
half _RimPower;
CBUFFER_END

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);

float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.viewDirWS = GetWorldSpaceViewDir(positionWS);

return output;
}

half4 frag(Varyings input) : SV_Target
{
// 计算边缘光
half3 normalWS = normalize(input.normalWS);
half3 viewDirWS = normalize(input.viewDirWS);

half NdotV = saturate(dot(normalWS, viewDirWS));
half rim = 1.0 - NdotV;
rim = pow(rim, _RimPower);

half3 finalColor = _XRayColor.rgb * rim;
half alpha = rim * _XRayAlpha;

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

3.4 Decal Renderer Feature

Decal系统允许在表面投射贴花,常用于弹孔、血迹、标记等效果。

启用Decal Feature:

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
// 运行时启用Decal
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class DecalManager : MonoBehaviour
{
public DecalProjector decalPrefab;

void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
CreateDecal(hit.point, hit.normal);
}
}
}

void CreateDecal(Vector3 position, Vector3 normal)
{
DecalProjector decal = Instantiate(decalPrefab, position, Quaternion.identity);

// 对齐到表面
decal.transform.rotation = Quaternion.LookRotation(-normal);

// 随机旋转
decal.transform.Rotate(0, 0, Random.Range(0, 360));

// 随机缩放
float scale = Random.Range(0.5f, 1.5f);
decal.transform.localScale = Vector3.one * scale;

// 5秒后销毁
Destroy(decal.gameObject, 5f);
}
}

Decal 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
Shader "Custom/DecalShader"
{
Properties
{
_BaseMap ("Base Map", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
_DecalColor ("Decal Color", Color) = (1,1,1,1)
_NormalScale ("Normal Scale", Range(0, 2)) = 1.0
_Metallic ("Metallic", Range(0, 1)) = 0.0
_Smoothness ("Smoothness", Range(0, 1)) = 0.5
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}

Pass
{
Name "DecalPass"
Tags { "LightMode" = "DecalForwardEmissive" }

ZTest Always
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

struct Attributes
{
float4 positionOS : POSITION;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float4 screenPos : TEXCOORD0;
float3 viewRay : TEXCOORD1;
};

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
TEXTURE2D(_NormalMap);
SAMPLER(sampler_NormalMap);

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _DecalColor;
half _NormalScale;
half _Metallic;
half _Smoothness;
CBUFFER_END

// Decal投影矩阵
float4x4 _DecalToWorld;
float4x4 _WorldToDecal;

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.screenPos = ComputeScreenPos(output.positionCS);

// 计算视线方向
float3 positionVS = TransformWorldToView(TransformObjectToWorld(input.positionOS.xyz));
output.viewRay = positionVS;

return output;
}

half4 frag(Varyings input) : SV_Target
{
// 采样深度
float2 screenUV = input.screenPos.xy / input.screenPos.w;
float depth = SampleSceneDepth(screenUV);

// 重建世界空间位置
float3 positionWS = ComputeWorldSpacePosition(screenUV, depth, UNITY_MATRIX_I_VP);

// 转换到Decal空间
float3 positionDS = mul(_WorldToDecal, float4(positionWS, 1.0)).xyz;

// 裁剪Decal范围外的像素
clip(0.5 - abs(positionDS));

// 计算UV
float2 uv = positionDS.xy + 0.5;

// 采样贴花纹理
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
baseColor *= _DecalColor;

return baseColor;
}
ENDHLSL
}
}
}

3.5 Screen Space Ambient Occlusion (SSAO)

SSAO通过屏幕空间计算环境光遮蔽,增强场景深度感。

配置SSAO:

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
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class SSAOController : MonoBehaviour
{
public Volume volume;
private ScreenSpaceAmbientOcclusion ssao;

void Start()
{
if (volume.profile.TryGet<ScreenSpaceAmbientOcclusion>(out ssao))
{
ConfigureSSAO();
}
}

void ConfigureSSAO()
{
// 启用SSAO
ssao.active = true;

// 基础设置
ssao.Intensity.value = 0.5f; // 强度
ssao.Radius.value = 0.25f; // 采样半径
ssao.Falloff.value = 100f; // 衰减距离

// 质量设置
ssao.SampleCount.value = ScreenSpaceAmbientOcclusionSampleCount.Medium;

// 可选:启用After Opaque
ssao.AfterOpaque.value = false;
}

// 运行时调整
public void SetSSAOIntensity(float intensity)
{
ssao.Intensity.value = Mathf.Clamp01(intensity);
}

public void SetSSAOQuality(int quality)
{
switch (quality)
{
case 0:
ssao.SampleCount.value = ScreenSpaceAmbientOcclusionSampleCount.Low;
break;
case 1:
ssao.SampleCount.value = ScreenSpaceAmbientOcclusionSampleCount.Medium;
break;
case 2:
ssao.SampleCount.value = ScreenSpaceAmbientOcclusionSampleCount.High;
break;
}
}
}

3.6 自定义Renderer Feature基础

创建一个完整的全屏模糊效果Renderer Feature:

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
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class BlurRendererFeature : ScriptableRendererFeature
{
[System.Serializable]
public class BlurSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
public Material blurMaterial;

[Range(0, 5)] public int blurIterations = 2;
[Range(0, 10)] public float blurRadius = 2f;
[Range(1, 8)] public int downSample = 2;
}

public BlurSettings settings = new BlurSettings();
private BlurRenderPass blurPass;

public override void Create()
{
blurPass = new BlurRenderPass(settings);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.blurMaterial == null)
{
Debug.LogWarning("Blur material is missing!");
return;
}

blurPass.Setup(renderer);
renderer.EnqueuePass(blurPass);
}

class BlurRenderPass : ScriptableRenderPass
{
private BlurSettings settings;
private ScriptableRenderer renderer;

private RTHandle[] blurTextures = new RTHandle[2];
private static readonly int blurRadiusID = Shader.PropertyToID("_BlurRadius");

public BlurRenderPass(BlurSettings settings)
{
this.settings = settings;
renderPassEvent = settings.renderPassEvent;
}

public void Setup(ScriptableRenderer renderer)
{
this.renderer = renderer;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 创建降采样的临时纹理
RenderTextureDescriptor descriptor = cameraTextureDescriptor;
descriptor.width /= settings.downSample;
descriptor.height /= settings.downSample;
descriptor.depthBufferBits = 0;

RenderingUtils.ReAllocateIfNeeded(ref blurTextures[0], descriptor,
FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BlurTemp0");
RenderingUtils.ReAllocateIfNeeded(ref blurTextures[1], descriptor,
FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BlurTemp1");
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get("Blur Effect");

RTHandle source = renderer.cameraColorTargetHandle;

// 设置模糊参数
settings.blurMaterial.SetFloat(blurRadiusID, settings.blurRadius);

// 降采样到第一个临时纹理
Blitter.BlitCameraTexture(cmd, source, blurTextures[0]);

// 多次模糊迭代
for (int i = 0; i < settings.blurIterations; i++)
{
// 水平模糊
settings.blurMaterial.SetVector("_BlurDirection", new Vector4(1, 0, 0, 0));
Blitter.BlitCameraTexture(cmd, blurTextures[0], blurTextures[1], settings.blurMaterial, 0);

// 垂直模糊
settings.blurMaterial.SetVector("_BlurDirection", new Vector4(0, 1, 0, 0));
Blitter.BlitCameraTexture(cmd, blurTextures[1], blurTextures[0], settings.blurMaterial, 0);
}

// 升采样回相机目标
Blitter.BlitCameraTexture(cmd, blurTextures[0], source);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void OnCameraCleanup(CommandBuffer cmd)
{
blurTextures[0]?.Release();
blurTextures[1]?.Release();
}
}
}

对应的Blur 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
Shader "Hidden/BlurShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}

SubShader
{
Tags { "RenderPipeline" = "UniversalPipeline" }

Pass
{
Name "GaussianBlur"

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

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

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

TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float4 _MainTex_TexelSize;

float _BlurRadius;
float4 _BlurDirection;

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}

// 高斯权重
static const float weights[5] = { 0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216 };

half4 frag(Varyings input) : SV_Target
{
float2 texelSize = _MainTex_TexelSize.xy * _BlurRadius;
float2 direction = _BlurDirection.xy;

// 中心采样
half3 result = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv).rgb * weights[0];

// 双向采样
for (int i = 1; i < 5; i++)
{
float2 offset = direction * texelSize * i;
result += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + offset).rgb * weights[i];
result += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv - offset).rgb * weights[i];
}

return half4(result, 1.0);
}
ENDHLSL
}
}
}

3.7 总结

本章深入讲解了URP Renderer配置和Renderer Feature系统,这是URP强大可扩展性的核心。关键要点:

  1. Forward vs Deferred - 根据目标平台和光照需求选择合适的渲染器
  2. Renderer Feature架构 - 理解ScriptableRendererFeature和ScriptableRenderPass的生命周期
  3. 内置Features - Render Objects用于特殊渲染效果,Decal用于表面贴花,SSAO增强深度感
  4. 自定义Features - 掌握创建自定义渲染Pass的完整流程
  5. 性能考虑 - Feature执行顺序影响性能,合理使用临时纹理

下一章预告: 将详细讲解URP着色器的编写方法,包括内置Shader、ShaderGraph和自定义Shader开发。


第4章 URP着色器基础

4.1 URP Shader结构与Built-in区别

URP着色器采用HLSL语法,与Built-in的CG语法有显著差异。

核心差异对照表:

特性 Built-in (CG) URP (HLSL)
着色器语言 CG/HLSL混合 纯HLSL
包含文件 UnityCG.cginc Core.hlsl, Lighting.hlsl
代码块 CGPROGRAM…ENDCG HLSLPROGRAM…ENDHLSL
变量声明 直接声明 CBUFFER包装
纹理采样 tex2D() SAMPLE_TEXTURE2D()
坐标转换 mul(UNITY_MATRIX_MVP, v) TransformObjectToHClip()
光照Tag ForwardBase/ForwardAdd UniversalForward

基础URP 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
Shader "Custom/BasicURPShader"
{
Properties
{
// 属性定义
_BaseMap ("Base Texture", 2D) = "white" {}
_BaseColor ("Base Color", Color) = (1,1,1,1)
_Smoothness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Geometry"
}

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

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

// 编译指令
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fog

// 包含URP核心库
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

// 纹理和采样器声明
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);

// 材质属性(必须使用CBUFFER以支持SRP Batcher)
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _Smoothness;
half _Metallic;
CBUFFER_END

// 顶点着色器输入结构
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
};

// 片元着色器输入结构
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 tangentWS : TEXCOORD3;
half fogFactor : TEXCOORD4;
};

// 顶点着色器
Varyings vert(Attributes input)
{
Varyings output;

// 坐标变换
VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = positionInputs.positionCS;
output.positionWS = positionInputs.positionWS;

// 法线和切线变换
VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS);
output.normalWS = normalInputs.normalWS;
output.tangentWS = float4(normalInputs.tangentWS, input.tangentOS.w);

// UV变换
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);

// 雾效
output.fogFactor = ComputeFogFactor(output.positionCS.z);

return output;
}

// 片元着色器
half4 frag(Varyings input) : SV_Target
{
// 采样基础纹理
half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
half3 albedo = baseMap.rgb * _BaseColor.rgb;
half alpha = baseMap.a * _BaseColor.a;

// 准备光照输入数据
InputData inputData = (InputData)0;
inputData.positionWS = input.positionWS;
inputData.normalWS = normalize(input.normalWS);
inputData.viewDirectionWS = GetWorldSpaceNormalizeViewDir(input.positionWS);
inputData.shadowCoord = TransformWorldToShadowCoord(input.positionWS);
inputData.fogCoord = input.fogFactor;

// 准备表面数据
SurfaceData surfaceData = (SurfaceData)0;
surfaceData.albedo = albedo;
surfaceData.alpha = alpha;
surfaceData.metallic = _Metallic;
surfaceData.smoothness = _Smoothness;
surfaceData.normalTS = half3(0, 0, 1);
surfaceData.occlusion = 1.0;
surfaceData.emission = 0;

// 计算PBR光照
half4 color = UniversalFragmentPBR(inputData, surfaceData);

// 应用雾效
color.rgb = MixFog(color.rgb, inputData.fogCoord);

return color;
}
ENDHLSL
}

// 阴影投射Pass
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }

ZWrite On
ZTest LEqual
ColorMask 0

HLSLPROGRAM
#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment

#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
ENDHLSL
}

// 深度Pass
Pass
{
Name "DepthOnly"
Tags { "LightMode" = "DepthOnly" }

ZWrite On
ColorMask 0

HLSLPROGRAM
#pragma vertex DepthOnlyVertex
#pragma fragment DepthOnlyFragment

#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
ENDHLSL
}
}

FallBack "Hidden/Universal Render Pipeline/FallbackError"
}

4.2 Lit、SimpleLit、Unlit Shader详解

URP提供了三种基础着色器类型,满足不同的渲染需求。

4.2.1 Lit Shader(完整PBR光照)

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
Shader "Custom/URPLit"
{
Properties
{
[Header(Base Settings)]
_BaseMap ("Albedo (RGB)", 2D) = "white" {}
_BaseColor ("Color", Color) = (1,1,1,1)

[Header(Surface Properties)]
_Metallic ("Metallic", Range(0,1)) = 0.0
_Smoothness ("Smoothness", Range(0,1)) = 0.5
[Normal] _BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale ("Normal Scale", Float) = 1.0

[Header(Additional Maps)]
_OcclusionMap ("Occlusion", 2D) = "white" {}
_OcclusionStrength ("Occlusion Strength", Range(0,1)) = 1.0
[HDR] _EmissionColor ("Emission Color", Color) = (0,0,0,0)
_EmissionMap ("Emission Map", 2D) = "white" {}

[Header(Rendering Options)]
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
[Toggle] _ReceiveShadows ("Receive Shadows", Float) = 1.0
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"UniversalMaterialType" = "Lit"
}

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

Cull [_Cull]

HLSLPROGRAM
#pragma target 4.5
#pragma vertex LitPassVertex
#pragma fragment LitPassFragment

// Material Keywords
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _OCCLUSIONMAP
#pragma shader_feature_local _EMISSION
#pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP
#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A

// Universal Pipeline Keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _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_fog

// GPU Instancing
#pragma multi_compile_instancing
#pragma instancing_options renderinglayer

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

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

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _Metallic;
half _Smoothness;
half _BumpScale;
half _OcclusionStrength;
half4 _EmissionColor;
CBUFFER_END

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 texcoord : TEXCOORD0;
float2 lightmapUV : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 1);
float3 positionWS : TEXCOORD2;
float3 normalWS : TEXCOORD3;
#ifdef _NORMALMAP
float4 tangentWS : TEXCOORD4;
#endif
half4 fogFactorAndVertexLight : TEXCOORD5;
#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
float4 shadowCoord : TEXCOORD6;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

Varyings LitPassVertex(Attributes input)
{
Varyings output = (Varyings)0;

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.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);

output.normalWS = normalInput.normalWS;
#ifdef _NORMALMAP
real sign = input.tangentOS.w * GetOddNegativeScale();
output.tangentWS = half4(normalInput.tangentWS.xyz, sign);
#endif

half3 vertexLight = VertexLighting(vertexInput.positionWS, normalInput.normalWS);
half fogFactor = ComputeFogFactor(vertexInput.positionCS.z);
output.fogFactorAndVertexLight = half4(fogFactor, vertexLight);

#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
output.shadowCoord = GetShadowCoord(vertexInput);
#endif

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

return output;
}

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

// 采样纹理
half4 albedoAlpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
half3 albedo = albedoAlpha.rgb * _BaseColor.rgb;
half alpha = albedoAlpha.a * _BaseColor.a;

// 法线
half3 normalTS = half3(0, 0, 1);
#ifdef _NORMALMAP
normalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv), _BumpScale);
#endif

// 遮蔽
half occlusion = 1.0;
#ifdef _OCCLUSIONMAP
occlusion = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.uv).g;
occlusion = LerpWhiteTo(occlusion, _OcclusionStrength);
#endif

// 自发光
half3 emission = 0;
#ifdef _EMISSION
emission = SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.uv).rgb * _EmissionColor.rgb;
#endif

// 准备输入数据
InputData inputData;
inputData.positionWS = input.positionWS;
inputData.viewDirectionWS = GetWorldSpaceNormalizeViewDir(input.positionWS);

#ifdef _NORMALMAP
inputData.normalWS = TransformTangentToWorld(normalTS,
half3x3(input.tangentWS.xyz,
half3(input.tangentWS.w, 0, 0) * cross(input.normalWS.xyz, input.tangentWS.xyz),
input.normalWS.xyz));
#else
inputData.normalWS = input.normalWS;
#endif

inputData.normalWS = NormalizeNormalPerPixel(inputData.normalWS);

#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
inputData.shadowCoord = input.shadowCoord;
#else
inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS);
#endif

inputData.fogCoord = input.fogFactorAndVertexLight.x;
inputData.vertexLighting = input.fogFactorAndVertexLight.yzw;
inputData.bakedGI = SAMPLE_GI(input.lightmapUV, input.vertexSH, inputData.normalWS);

// 准备表面数据
SurfaceData surfaceData;
surfaceData.albedo = albedo;
surfaceData.metallic = _Metallic;
surfaceData.specular = half3(0, 0, 0);
surfaceData.smoothness = _Smoothness;
surfaceData.normalTS = normalTS;
surfaceData.emission = emission;
surfaceData.occlusion = occlusion;
surfaceData.alpha = alpha;
surfaceData.clearCoatMask = 0;
surfaceData.clearCoatSmoothness = 0;

// 计算最终光照
half4 color = UniversalFragmentPBR(inputData, surfaceData);
color.rgb = MixFog(color.rgb, inputData.fogCoord);

return color;
}
ENDHLSL
}

UsePass "Universal Render Pipeline/Lit/ShadowCaster"
UsePass "Universal Render Pipeline/Lit/DepthOnly"
UsePass "Universal Render Pipeline/Lit/Meta"
}

CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"
}

4.2.2 SimpleLit 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
Shader "Custom/URPSimpleLit"
{
Properties
{
_BaseMap ("Texture", 2D) = "white" {}
_BaseColor ("Color", Color) = (1,1,1,1)
_Cutoff ("Alpha Cutoff", Range(0.0, 1.0)) = 0.5
_SpecColor ("Specular Color", Color) = (0.2, 0.2, 0.2, 1.0)
_Shininess ("Shininess", Range(0.01, 1)) = 0.5
[Normal] _BumpMap ("Normal Map", 2D) = "bump" {}
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}

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

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fog

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

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

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half4 _SpecColor;
half _Shininess;
half _Cutoff;
CBUFFER_END

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

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float3 tangentWS : TEXCOORD3;
float3 bitangentWS : TEXCOORD4;
half fogFactor : TEXCOORD5;
};

Varyings vert(Attributes input)
{
Varyings output;

VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz);
VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS);

output.positionCS = positionInputs.positionCS;
output.positionWS = positionInputs.positionWS;
output.normalWS = normalInputs.normalWS;
output.tangentWS = normalInputs.tangentWS;
output.bitangentWS = normalInputs.bitangentWS;
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.fogFactor = ComputeFogFactor(output.positionCS.z);

return output;
}

half4 frag(Varyings input) : SV_Target
{
// 采样基础纹理
half4 albedoAlpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
half3 albedo = albedoAlpha.rgb * _BaseColor.rgb;

// 采样法线
half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv));
float3x3 tangentToWorld = float3x3(input.tangentWS, input.bitangentWS, input.normalWS);
half3 normalWS = normalize(mul(normalTS, tangentToWorld));

// 获取主光源
Light mainLight = GetMainLight(TransformWorldToShadowCoord(input.positionWS));

// 简化的Blinn-Phong光照
half3 viewDirWS = GetWorldSpaceNormalizeViewDir(input.positionWS);
half3 halfDir = normalize(mainLight.direction + viewDirWS);

half NdotL = saturate(dot(normalWS, mainLight.direction));
half NdotH = saturate(dot(normalWS, halfDir));

// 漫反射
half3 diffuse = albedo * mainLight.color * NdotL * mainLight.distanceAttenuation * mainLight.shadowAttenuation;

// 高光
half specularPower = exp2(_Shininess * 10.0 + 1.0);
half3 specular = _SpecColor.rgb * pow(NdotH, specularPower) * mainLight.color;

// 环境光
half3 ambient = albedo * half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);

half3 color = diffuse + specular + ambient;
color = MixFog(color, input.fogFactor);

return half4(color, albedoAlpha.a * _BaseColor.a);
}
ENDHLSL
}

UsePass "Universal Render Pipeline/SimpleLit/ShadowCaster"
}
}

4.2.3 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
Shader "Custom/URPUnlit"
{
Properties
{
_BaseMap ("Texture", 2D) = "white" {}
_BaseColor ("Color", Color) = (1,1,1,1)
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 0
[Enum(Off, 0, On, 1)] _ZWrite ("Z Write", Float) = 1
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Geometry"
}

Pass
{
Name "Unlit"

Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#pragma multi_compile_instancing

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

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
CBUFFER_END

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

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

Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;

UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = positionInputs.positionCS;
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.fogFactor = ComputeFogFactor(output.positionCS.z);

return output;
}

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

half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
half3 color = texColor.rgb * _BaseColor.rgb;
half alpha = texColor.a * _BaseColor.a;

color = MixFog(color, input.fogFactor);

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

4.3 ShaderGraph在URP中的应用

ShaderGraph提供可视化的着色器创建方式,特别适合美术人员使用。

创建基础ShaderGraph示例(代码方式配置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using UnityEngine;
using UnityEditor.ShaderGraph;
using UnityEditor;

public class ShaderGraphCreator
{
[MenuItem("Assets/Create/Shader/Custom URP Shader Graph")]
static void CreateCustomShaderGraph()
{
// ShaderGraph通常通过GUI创建
// 右键 > Create > Shader Graph > URP > Lit Shader Graph

Debug.Log("Please create ShaderGraph through: Create > Shader Graph > URP");
}
}

ShaderGraph常用节点组合示例:

1. 溶解效果(Dissolve Effect):

1
2
3
4
节点流程:
Texture2D (Base Texture) → Sample Texture 2D →
Texture2D (Noise Texture) → Sample Texture 2D → Add (Dissolve Amount) → Step → Multiply (Edge) →
Final Color Output

对应的代码实现:

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
Shader "Custom/DissolveEffect"
{
Properties
{
_BaseMap ("Base Texture", 2D) = "white" {}
_BaseColor ("Color", Color) = (1,1,1,1)
_NoiseMap ("Noise Texture", 2D) = "white" {}
_DissolveAmount ("Dissolve Amount", Range(0, 1)) = 0.5
_EdgeWidth ("Edge Width", Range(0, 0.5)) = 0.1
[HDR] _EdgeColor ("Edge Color", Color) = (1,0.5,0,1)
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}

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

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog

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

TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_NoiseMap); SAMPLER(sampler_NoiseMap);

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
float4 _NoiseMap_ST;
half4 _BaseColor;
half4 _EdgeColor;
half _DissolveAmount;
half _EdgeWidth;
CBUFFER_END

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

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float2 noiseUV : TEXCOORD1;
half fogFactor : TEXCOORD2;
};

Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.noiseUV = TRANSFORM_TEX(input.uv, _NoiseMap);
output.fogFactor = ComputeFogFactor(output.positionCS.z);
return output;
}

half4 frag(Varyings input) : SV_Target
{
// 采样基础纹理
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;

// 采样噪声纹理
half noise = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, input.noiseUV).r;

// 计算溶解
half dissolve = noise - _DissolveAmount;
clip(dissolve);

// 计算边缘发光
half edge = step(dissolve, _EdgeWidth);
half3 edgeGlow = edge * _EdgeColor.rgb * _EdgeColor.a;

// 混合颜色
half3 finalColor = baseColor.rgb + edgeGlow;
finalColor = MixFog(finalColor, input.fogFactor);

return half4(finalColor, baseColor.a);
}
ENDHLSL
}
}
}

2. 顶点动画(Vertex Animation):

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
Shader "Custom/VertexWave"
{
Properties
{
_BaseMap ("Texture", 2D) = "white" {}
_BaseColor ("Color", Color) = (1,1,1,1)
_WaveAmplitude ("Wave Amplitude", Float) = 0.5
_WaveFrequency ("Wave Frequency", Float) = 2.0
_WaveSpeed ("Wave Speed", Float) = 1.0
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}

Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
float _WaveAmplitude;
float _WaveFrequency;
float _WaveSpeed;
CBUFFER_END

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

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normalWS : TEXCOORD1;
};

Varyings vert(Attributes input)
{
Varyings output;

// 计算波浪偏移
float time = _Time.y * _WaveSpeed;
float wave = sin(input.positionOS.x * _WaveFrequency + time) * _WaveAmplitude;

// 应用到顶点位置
float3 positionOS = input.positionOS.xyz;
positionOS.y += wave;

output.positionCS = TransformObjectToHClip(positionOS);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);

return output;
}

half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
return color;
}
ENDHLSL
}
}
}

3. 边缘光效果(Rim Light):

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
Shader "Custom/RimLight"
{
Properties
{
_BaseMap ("Texture", 2D) = "white" {}
_BaseColor ("Color", Color) = (1,1,1,1)
[HDR] _RimColor ("Rim Color", Color) = (0,0.5,1,1)
_RimPower ("Rim Power", Range(0.1, 10)) = 3.0
_RimIntensity ("Rim Intensity", Range(0, 5)) = 1.0
}

SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}

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

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half4 _RimColor;
half _RimPower;
half _RimIntensity;
CBUFFER_END

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

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
};

Varyings vert(Attributes input)
{
Varyings output;

VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz);
VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS);

output.positionCS = positionInputs.positionCS;
output.positionWS = positionInputs.positionWS;
output.normalWS = normalInputs.normalWS;
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);

return output;
}

half4 frag(Varyings input) : SV_Target
{
// 基础颜色
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;

// 计算边缘光
half3 normalWS = normalize(input.normalWS);
half3 viewDirWS = GetWorldSpaceNormalizeViewDir(input.positionWS);

half NdotV = saturate(dot(normalWS, viewDirWS));
half rim = 1.0 - NdotV;
rim = pow(rim, _RimPower) * _RimIntensity;

// 混合边缘光
half3 rimColor = _RimColor.rgb * rim;
half3 finalColor = baseColor.rgb + rimColor;

return half4(finalColor, baseColor.a);
}
ENDHLSL
}
}
}

4.4 常用URP Shader属性与关键字

4.4.1 材质属性类型:

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
Properties
{
// 纹理类型
_MainTex ("Texture", 2D) = "white" {} // 2D纹理
_Cube ("Cubemap", Cube) = "" {} // 立方体贴图
_3D ("3D Texture", 3D) = "" {} // 3D纹理

// 数值类型
_Float ("Float Value", Float) = 1.0
_Range ("Range Value", Range(0, 1)) = 0.5
_Int ("Integer Value", Int) = 1

// 颜色类型
_Color ("Color", Color) = (1,1,1,1)
[HDR] _HDRColor ("HDR Color", Color) = (1,1,1,1)

// 向量类型
_Vector ("Vector4", Vector) = (0,0,0,0)

// 特殊属性标签
[Normal] _NormalMap ("Normal Map", 2D) = "bump" {}
[NoScaleOffset] _Texture ("Texture", 2D) = "white" {}
[PerRendererData] _InstancedProperty ("Instanced", Float) = 0

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

// Toggle开关
[Toggle] _EnableFeature ("Enable Feature", Float) = 0
[Toggle(_NORMALMAP)] _UseNormalMap ("Use Normal Map", Float) = 0

// 关键字枚举
[KeywordEnum(Off, On, Custom)] _Mode ("Mode", Float) = 0
}

4.4.2 常用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
SubShader
{
Pass
{
HLSLPROGRAM
// ===== 光照相关 =====
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_SCREEN
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _SHADOWS_SOFT

// ===== 雾效 =====
#pragma multi_compile_fog

// ===== 光照贴图 =====
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_SHADOW_MIXING
#pragma multi_compile _ SHADOWS_SHADOWMASK

// ===== GPU Instancing =====
#pragma multi_compile_instancing
#pragma instancing_options renderinglayer

// ===== 材质关键字(Shader Feature)=====
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _ALPHATEST_ON
#pragma shader_feature_local _ALPHAPREMULTIPLY_ON
#pragma shader_feature_local _EMISSION
#pragma shader_feature_local _METALLICSPECGLOSSMAP
#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma shader_feature_local _OCCLUSIONMAP
#pragma shader_feature_local _PARALLAXMAP

// ===== 渲染层 =====
#pragma multi_compile _ _LIGHT_LAYERS

ENDHLSL
}
}

4.4.3 关键字在代码中的使用:

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
half4 frag(Varyings input) : SV_Target
{
half4 color = _BaseColor;

// 使用法线贴图
#ifdef _NORMALMAP
half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv));
// ... 计算法线
#endif

// Alpha测试
#ifdef _ALPHATEST_ON
clip(color.a - _Cutoff);
#endif

// 自发光
#ifdef _EMISSION
color.rgb += SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.uv).rgb * _EmissionColor.rgb;
#endif

// 雾效
#ifdef FOG_LINEAR || FOG_EXP || FOG_EXP2
color.rgb = MixFog(color.rgb, input.fogFactor);
#endif

return color;
}

4.5 URP材质工作流程

4.5.1 创建和管理材质:

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
using UnityEngine;
using UnityEditor;

public class MaterialManager : MonoBehaviour
{
[MenuItem("Tools/Create URP Material")]
static void CreateURPMaterial()
{
// 创建新材质
Material mat = new Material(Shader.Find("Universal Render Pipeline/Lit"));

// 设置基础属性
mat.SetColor("_BaseColor", Color.white);
mat.SetFloat("_Metallic", 0.0f);
mat.SetFloat("_Smoothness", 0.5f);

// 保存材质
AssetDatabase.CreateAsset(mat, "Assets/Materials/NewURPMaterial.mat");
AssetDatabase.SaveAssets();

Debug.Log("URP Material created!");
}

// 批量设置材质属性
[MenuItem("Tools/Batch Set Material Properties")]
static void BatchSetMaterialProperties()
{
string[] guids = AssetDatabase.FindAssets("t:Material", new[] { "Assets/Materials" });

foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);

if (mat != null && mat.shader.name.Contains("Universal"))
{
// 统一设置属性
mat.SetFloat("_Smoothness", 0.5f);
mat.SetFloat("_Metallic", 0.0f);

EditorUtility.SetDirty(mat);
}
}

AssetDatabase.SaveAssets();
Debug.Log("Batch material update completed!");
}
}

4.5.2 材质变体管理:

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
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

public class MaterialVariantManager
{
[MenuItem("Tools/Analyze Material Keywords")]
static void AnalyzeMaterialKeywords()
{
string[] guids = AssetDatabase.FindAssets("t:Material");
Dictionary<string, int> keywordCount = new Dictionary<string, int>();

foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);

if (mat != null)
{
foreach (string keyword in mat.shaderKeywords)
{
if (keywordCount.ContainsKey(keyword))
keywordCount[keyword]++;
else
keywordCount[keyword] = 1;
}
}
}

// 输出统计
Debug.Log("=== Material Keyword Statistics ===");
foreach (var kvp in keywordCount)
{
Debug.Log($"{kvp.Key}: {kvp.Value} materials");
}
}

// 优化材质关键字
[MenuItem("Tools/Optimize Material Keywords")]
static void OptimizeMaterialKeywords()
{
string[] guids = AssetDatabase.FindAssets("t:Material");
int optimized = 0;

foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);

if (mat != null)
{
// 禁用未使用的关键字
List<string> unusedKeywords = new List<string>();

foreach (string keyword in mat.shaderKeywords)
{
// 检查是否实际使用
if (keyword == "_NORMALMAP" && mat.GetTexture("_BumpMap") == null)
{
unusedKeywords.Add(keyword);
}
else if (keyword == "_EMISSION" && mat.GetColor("_EmissionColor") == Color.black)
{
unusedKeywords.Add(keyword);
}
}

// 禁用关键字
foreach (string keyword in unusedKeywords)
{
mat.DisableKeyword(keyword);
optimized++;
}

if (unusedKeywords.Count > 0)
{
EditorUtility.SetDirty(mat);
}
}
}

AssetDatabase.SaveAssets();
Debug.Log($"Optimized {optimized} unused keywords");
}
}

4.5.3 运行时材质操作:

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
using UnityEngine;
using System.Collections;

public class RuntimeMaterialController : MonoBehaviour
{
private Material material;
private Renderer targetRenderer;

void Start()
{
targetRenderer = GetComponent<Renderer>();

// 创建材质实例(避免修改共享材质)
material = targetRenderer.material;
}

// 颜色渐变
public IEnumerator FadeColor(Color targetColor, float duration)
{
Color startColor = material.GetColor("_BaseColor");
float elapsed = 0f;

while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;

Color currentColor = Color.Lerp(startColor, targetColor, t);
material.SetColor("_BaseColor", currentColor);

yield return null;
}

material.SetColor("_BaseColor", targetColor);
}

// 溶解效果
public IEnumerator Dissolve(float duration)
{
float elapsed = 0f;

while (elapsed < duration)
{
elapsed += Time.deltaTime;
float dissolveAmount = elapsed / duration;

material.SetFloat("_DissolveAmount", dissolveAmount);

yield return null;
}

gameObject.SetActive(false);
}

// 闪烁效果
public IEnumerator Flash(Color flashColor, float duration, int times)
{
Color originalColor = material.GetColor("_BaseColor");
float singleFlashDuration = duration / (times * 2);

for (int i = 0; i < times; i++)
{
// 变为闪烁色
yield return FadeColor(flashColor, singleFlashDuration);
// 恢复原色
yield return FadeColor(originalColor, singleFlashDuration);
}
}

// 材质属性动画
public void AnimateProperty(string propertyName, float targetValue, float duration)
{
StartCoroutine(AnimatePropertyCoroutine(propertyName, targetValue, duration));
}

private IEnumerator AnimatePropertyCoroutine(string propertyName, float targetValue, float duration)
{
float startValue = material.GetFloat(propertyName);
float elapsed = 0f;

while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;

float currentValue = Mathf.Lerp(startValue, targetValue, t);
material.SetFloat(propertyName, currentValue);

yield return null;
}

material.SetFloat(propertyName, targetValue);
}

// 纹理滚动
public void ScrollTexture(Vector2 scrollSpeed)
{
Vector2 offset = material.GetTextureOffset("_BaseMap");
offset += scrollSpeed * Time.deltaTime;
material.SetTextureOffset("_BaseMap", offset);
}

// 释放材质实例
void OnDestroy()
{
if (material != null)
{
Destroy(material);
}
}
}

4.5.4 Material Property Block优化:

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
using UnityEngine;

public class MaterialPropertyBlockExample : MonoBehaviour
{
private MaterialPropertyBlock propertyBlock;
private Renderer targetRenderer;

// Shader属性ID(缓存以提高性能)
private static readonly int BaseColorID = Shader.PropertyToID("_BaseColor");
private static readonly int MetallicID = Shader.PropertyToID("_Metallic");
private static readonly int SmoothnessID = Shader.PropertyToID("_Smoothness");

void Start()
{
targetRenderer = GetComponent<Renderer>();
propertyBlock = new MaterialPropertyBlock();
}

// 设置颜色(不创建新材质实例)
public void SetColor(Color color)
{
// 获取当前属性
targetRenderer.GetPropertyBlock(propertyBlock);

// 修改属性
propertyBlock.SetColor(BaseColorID, color);

// 应用属性
targetRenderer.SetPropertyBlock(propertyBlock);
}

// 设置多个属性
public void SetProperties(Color color, float metallic, float smoothness)
{
targetRenderer.GetPropertyBlock(propertyBlock);

propertyBlock.SetColor(BaseColorID, color);
propertyBlock.SetFloat(MetallicID, metallic);
propertyBlock.SetFloat(SmoothnessID, smoothness);

targetRenderer.SetPropertyBlock(propertyBlock);
}

// GPU Instancing示例
public void SetInstancedColor(Color color)
{
targetRenderer.GetPropertyBlock(propertyBlock);
propertyBlock.SetColor(BaseColorID, color);
targetRenderer.SetPropertyBlock(propertyBlock);
}
}

// 批量设置示例
public class BatchMaterialPropertySetter : MonoBehaviour
{
public GameObject[] targets;
private MaterialPropertyBlock propertyBlock;

void Start()
{
propertyBlock = new MaterialPropertyBlock();
SetRandomColors();
}

void SetRandomColors()
{
foreach (GameObject obj in targets)
{
Renderer renderer = obj.GetComponent<Renderer>();
if (renderer != null)
{
Color randomColor = new Color(
Random.value,
Random.value,
Random.value,
1.0f
);

propertyBlock.SetColor("_BaseColor", randomColor);
renderer.SetPropertyBlock(propertyBlock);
}
}
}
}

4.6 总结

本章详细讲解了URP着色器的编写方法和材质工作流程。关键要点:

  1. Shader结构差异 - URP使用HLSL语法,需要使用CBUFFER包装材质属性以支持SRP Batcher
  2. 三种基础Shader - Lit提供完整PBR,SimpleLit简化光照计算,Unlit无光照适合特效
  3. ShaderGraph应用 - 可视化创建复杂效果,常见应用包括溶解、顶点动画、边缘光等
  4. 关键字系统 - 理解multi_compile和shader_feature的区别,合理管理变体数量
  5. 材质优化 - 使用MaterialPropertyBlock避免实例化,批量管理材质属性
  6. 运行时操作 - 实现材质动画和特效,注意性能开销

实践建议:

  • 优先使用内置Shader或ShaderGraph
  • 自定义Shader时确保支持SRP Batcher
  • 合理控制关键字数量,避免组合爆炸
  • 运行时修改使用MaterialPropertyBlock
  • 定期清理未使用的材质变体

下一章预告: 将深入讲解URP光照系统,包括实时光照、烘焙光照、光照层和各种光照优化策略。