第5章 URP光照系统

Unity URP渲染管线教程 - 第5章

第5章 URP光照系统

5.1 实时光照与烘焙光照混合

URP支持三种光照模式的灵活组合,以实现性能和质量的平衡。

5.1.1 光照模式概述

三种光照模式对比:

模式 Realtime (实时) Mixed (混合) Baked (烘焙)
直接光照 实时计算 实时计算 烘焙到光照贴图
间接光照 无或实时GI 烘焙 烘焙
动态物体 完全支持 支持 仅环境光
阴影 实时 实时或烘焙 烘焙
性能消耗

配置光照模式:

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

public class LightingModeSetup : MonoBehaviour
{
public Light targetLight;

// 设置为实时光照
public void SetRealtimeMode()
{
targetLight.lightmapBakeType = LightmapBakeType.Realtime;
targetLight.shadows = LightShadows.Soft;

Debug.Log("Light set to Realtime mode");
}

// 设置为混合光照
public void SetMixedMode()
{
targetLight.lightmapBakeType = LightmapBakeType.Mixed;

// Mixed模式下的子模式选择
// 在Lighting窗口中设置:Baked Indirect / Subtractive / Shadowmask

Debug.Log("Light set to Mixed mode");
}

// 设置为烘焙光照
public void SetBakedMode()
{
targetLight.lightmapBakeType = LightmapBakeType.Baked;
targetLight.shadows = LightShadows.None; // 烘焙光不产生实时阴影

Debug.Log("Light set to Baked mode");
}
}

5.1.2 混合光照子模式详解

Baked Indirect(推荐用于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
48
49
50
51
52
53
54
55
56
57
using UnityEngine;
using UnityEngine.Rendering;

public class BakedIndirectSetup : MonoBehaviour
{
[Header("Lighting Settings")]
public Light directionalLight;

[Header("Scene Objects")]
public GameObject[] staticObjects;
public GameObject[] dynamicObjects;

void Start()
{
ConfigureBakedIndirect();
}

void ConfigureBakedIndirect()
{
// 1. 设置主光源为Mixed模式
directionalLight.lightmapBakeType = LightmapBakeType.Mixed;
directionalLight.shadows = LightShadows.Soft;

// 2. 配置静态物体
foreach (GameObject obj in staticObjects)
{
// 标记为静态
obj.isStatic = true;

// 设置为接收光照贴图
MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
if (renderer != null)
{
renderer.receiveGI = ReceiveGI.Lightmaps;

// 可选:调整光照贴图参数
SerializedObject so = new SerializedObject(renderer);
so.FindProperty("m_ScaleInLightmap").floatValue = 1.0f;
so.ApplyModifiedProperties();
}
}

// 3. 配置动态物体
foreach (GameObject obj in dynamicObjects)
{
MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
if (renderer != null)
{
// 使用Light Probes接收间接光照
renderer.receiveGI = ReceiveGI.LightProbes;
}
}

Debug.Log("Baked Indirect lighting configured");
Debug.Log("Don't forget to bake lighting: Window > Rendering > Lighting");
}
}

Shadowmask模式:

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

public class ShadowmaskSetup : MonoBehaviour
{
public Light[] mixedLights;

void ConfigureShadowmask()
{
// 在URP Asset中启用Shadowmask
// Mixed Lighting > Lighting Mode = Shadowmask

foreach (Light light in mixedLights)
{
light.lightmapBakeType = LightmapBakeType.Mixed;

// 配置阴影设置
light.shadows = LightShadows.Soft;
light.shadowStrength = 1.0f;

// Shadowmask距离设置
// 超出距离使用烘焙阴影,距离内使用实时阴影
}

// 在Lighting窗口设置Shadowmask距离
// QualitySettings.shadowDistance
}
}

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

#if UNITY_EDITOR
public class LightingBakeController : EditorWindow
{
private int lightmapResolution = 1024;
private float indirectIntensity = 1.0f;
private int bounces = 2;

[MenuItem("Tools/Lighting/Bake Controller")]
static void ShowWindow()
{
GetWindow<LightingBakeController>("Bake Controller");
}

void OnGUI()
{
GUILayout.Label("Lightmap Settings", EditorStyles.boldLabel);

lightmapResolution = EditorGUILayout.IntSlider("Resolution", lightmapResolution, 32, 4096);
indirectIntensity = EditorGUILayout.Slider("Indirect Intensity", indirectIntensity, 0f, 5f);
bounces = EditorGUILayout.IntSlider("Bounces", bounces, 1, 10);

EditorGUILayout.Space();

if (GUILayout.Button("Apply Settings"))
{
ApplyLightingSettings();
}

if (GUILayout.Button("Bake Lighting"))
{
BakeLighting();
}

if (GUILayout.Button("Clear Baked Data"))
{
ClearBakedData();
}
}

void ApplyLightingSettings()
{
// 设置Lightmap参数
LightmapEditorSettings.realtimeResolution = lightmapResolution / 4f;
LightmapEditorSettings.bakeResolution = lightmapResolution / 10f;

// 设置间接光照强度
RenderSettings.reflectionIntensity = indirectIntensity;

// 设置光线弹射次数
// LightmapEditorSettings.bounces = bounces; // 需要使用反射访问

Debug.Log("Lighting settings applied");
}

void BakeLighting()
{
// 开始烘焙
Lightmapping.BakeAsync();
Debug.Log("Lighting bake started...");
}

void ClearBakedData()
{
// 清除烘焙数据
Lightmapping.Clear();
Debug.Log("Baked data cleared");
}
}
#endif

5.2 Light Layers与Rendering Layers

Light Layers允许精确控制哪些光源照亮哪些物体,实现复杂的光照分层效果。

5.2.1 Light Layers基础配置

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

public class LightLayerManager : MonoBehaviour
{
[Header("Light Configuration")]
public Light characterLight; // 只照亮角色
public Light environmentLight; // 只照亮环境
public Light effectLight; // 只照亮特效

[Header("Object Configuration")]
public GameObject character;
public GameObject[] environmentObjects;
public GameObject[] effectObjects;

void Start()
{
SetupLightLayers();
}

void SetupLightLayers()
{
// 定义光照层
// Layer 0 = Default
// Layer 1 = Character
// Layer 2 = Environment
// Layer 3 = Effects

// 配置光源
ConfigureLight(characterLight, LightLayerEnum.LightLayerCharacter);
ConfigureLight(environmentLight, LightLayerEnum.LightLayerEnvironment);
ConfigureLight(effectLight, LightLayerEnum.LightLayerEffects);

// 配置渲染器
ConfigureRenderer(character, LightLayerEnum.LightLayerCharacter);

foreach (GameObject obj in environmentObjects)
{
ConfigureRenderer(obj, LightLayerEnum.LightLayerEnvironment);
}

foreach (GameObject obj in effectObjects)
{
ConfigureRenderer(obj, LightLayerEnum.LightLayerEffects);
}

Debug.Log("Light Layers configured successfully");
}

void ConfigureLight(Light light, LightLayerEnum layer)
{
if (light == null) return;

// 获取Universal Additional Light Data组件
UniversalAdditionalLightData lightData = light.GetComponent<UniversalAdditionalLightData>();

if (lightData == null)
{
lightData = light.gameObject.AddComponent<UniversalAdditionalLightData>();
}

// 设置光照层
lightData.renderingLayers = (uint)layer;
}

void ConfigureRenderer(GameObject obj, LightLayerEnum layer)
{
if (obj == null) return;

Renderer renderer = obj.GetComponent<Renderer>();
if (renderer != null)
{
renderer.renderingLayerMask = (uint)layer;
}
}
}

// 光照层枚举
[System.Flags]
public enum LightLayerEnum : uint
{
Nothing = 0,
LightLayerDefault = 1 << 0, // Layer 0
LightLayerCharacter = 1 << 1, // Layer 1
LightLayerEnvironment = 1 << 2, // Layer 2
LightLayerEffects = 1 << 3, // Layer 3
Everything = uint.MaxValue
}

5.2.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
86
using UnityEngine;
using UnityEngine.Rendering.Universal;
using System.Collections;

public class DynamicLightLayerController : MonoBehaviour
{
public Light targetLight;
public Renderer targetRenderer;

private UniversalAdditionalLightData lightData;

void Start()
{
lightData = targetLight.GetComponent<UniversalAdditionalLightData>();

if (lightData == null)
{
lightData = targetLight.gameObject.AddComponent<UniversalAdditionalLightData>();
}
}

// 添加光照层
public void AddLightLayer(LightLayerEnum layer)
{
lightData.renderingLayers |= (uint)layer;
Debug.Log($"Added layer: {layer}");
}

// 移除光照层
public void RemoveLightLayer(LightLayerEnum layer)
{
lightData.renderingLayers &= ~(uint)layer;
Debug.Log($"Removed layer: {layer}");
}

// 设置唯一光照层
public void SetOnlyLightLayer(LightLayerEnum layer)
{
lightData.renderingLayers = (uint)layer;
Debug.Log($"Set only layer: {layer}");
}

// 检查是否包含特定层
public bool HasLightLayer(LightLayerEnum layer)
{
return (lightData.renderingLayers & (uint)layer) != 0;
}

// 切换光照层
public void ToggleLightLayer(LightLayerEnum layer)
{
if (HasLightLayer(layer))
RemoveLightLayer(layer);
else
AddLightLayer(layer);
}

// 渐变切换光照层(带淡入淡出效果)
public IEnumerator FadeToLightLayer(LightLayerEnum newLayer, float duration)
{
float originalIntensity = targetLight.intensity;

// 淡出
float elapsed = 0f;
while (elapsed < duration / 2)
{
elapsed += Time.deltaTime;
targetLight.intensity = Mathf.Lerp(originalIntensity, 0, elapsed / (duration / 2));
yield return null;
}

// 切换层
SetOnlyLightLayer(newLayer);

// 淡入
elapsed = 0f;
while (elapsed < duration / 2)
{
elapsed += Time.deltaTime;
targetLight.intensity = Mathf.Lerp(0, originalIntensity, elapsed / (duration / 2));
yield return null;
}

targetLight.intensity = originalIntensity;
}
}

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

public class CharacterHighlightSystem : MonoBehaviour
{
[Header("Lights")]
public Light mainLight; // 场景主光
public Light highlightLight; // 角色高光

[Header("Characters")]
public GameObject[] allCharacters;
public GameObject selectedCharacter;

[Header("Settings")]
public Color highlightColor = Color.yellow;
public float highlightIntensity = 2.0f;

private UniversalAdditionalLightData mainLightData;
private UniversalAdditionalLightData highlightLightData;

void Start()
{
SetupHighlightSystem();
}

void SetupHighlightSystem()
{
// 配置主光源 - 照亮所有物体
mainLightData = mainLight.GetComponent<UniversalAdditionalLightData>();
if (mainLightData == null)
mainLightData = mainLight.gameObject.AddComponent<UniversalAdditionalLightData>();

mainLightData.renderingLayers = (uint)LightLayerEnum.Everything;

// 配置高光光源 - 只照亮选中的角色
highlightLightData = highlightLight.GetComponent<UniversalAdditionalLightData>();
if (highlightLightData == null)
highlightLightData = highlightLight.gameObject.AddComponent<UniversalAdditionalLightData>();

highlightLight.color = highlightColor;
highlightLight.intensity = highlightIntensity;
highlightLight.enabled = false;

// 所有角色默认在主光照层
foreach (GameObject character in allCharacters)
{
SetCharacterLayer(character, LightLayerEnum.LightLayerDefault);
}
}

// 选择角色
public void SelectCharacter(GameObject character)
{
// 取消之前的选择
if (selectedCharacter != null)
{
SetCharacterLayer(selectedCharacter, LightLayerEnum.LightLayerDefault);
}

// 选择新角色
selectedCharacter = character;

if (selectedCharacter != null)
{
// 添加高光层
SetCharacterLayer(selectedCharacter,
LightLayerEnum.LightLayerDefault | LightLayerEnum.LightLayerCharacter);

// 启用高光
highlightLightData.renderingLayers = (uint)LightLayerEnum.LightLayerCharacter;
highlightLight.enabled = true;

// 高光跟随角色
highlightLight.transform.position = selectedCharacter.transform.position + Vector3.up * 5f;
}
else
{
highlightLight.enabled = false;
}
}

void SetCharacterLayer(GameObject character, LightLayerEnum layer)
{
Renderer[] renderers = character.GetComponentsInChildren<Renderer>();
foreach (Renderer renderer in renderers)
{
renderer.renderingLayerMask = (uint)layer;
}
}

void Update()
{
// 高光跟随选中角色
if (selectedCharacter != null && highlightLight.enabled)
{
Vector3 targetPosition = selectedCharacter.transform.position + Vector3.up * 5f;
highlightLight.transform.position = Vector3.Lerp(
highlightLight.transform.position,
targetPosition,
Time.deltaTime * 5f
);
}
}
}

5.3 光照模式:Baked、Realtime、Mixed

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

public class LightingPerformanceAnalyzer : MonoBehaviour
{
[System.Serializable]
public class LightingProfile
{
public string name;
public LightmapBakeType lightMode;
public int maxRealtimeLights;
public bool enableShadows;
public float shadowDistance;

[Header("Performance Metrics")]
public float estimatedCost; // 1-10评分
public string targetPlatform;
}

public List<LightingProfile> profiles = new List<LightingProfile>
{
new LightingProfile
{
name = "Mobile Low",
lightMode = LightmapBakeType.Baked,
maxRealtimeLights = 1,
enableShadows = false,
shadowDistance = 20f,
estimatedCost = 2f,
targetPlatform = "Mobile"
},
new LightingProfile
{
name = "Mobile High",
lightMode = LightmapBakeType.Mixed,
maxRealtimeLights = 2,
enableShadows = true,
shadowDistance = 40f,
estimatedCost = 4f,
targetPlatform = "Mobile"
},
new LightingProfile
{
name = "PC Medium",
lightMode = LightmapBakeType.Mixed,
maxRealtimeLights = 4,
enableShadows = true,
shadowDistance = 80f,
estimatedCost = 6f,
targetPlatform = "PC"
},
new LightingProfile
{
name = "PC High",
lightMode = LightmapBakeType.Realtime,
maxRealtimeLights = 8,
enableShadows = true,
shadowDistance = 150f,
estimatedCost = 9f,
targetPlatform = "PC"
}
};

public void ApplyProfile(int profileIndex)
{
if (profileIndex < 0 || profileIndex >= profiles.Count)
{
Debug.LogError("Invalid profile index");
return;
}

LightingProfile profile = profiles[profileIndex];

// 应用配置到场景
Light[] lights = FindObjectsOfType<Light>();
int realtimeLightCount = 0;

foreach (Light light in lights)
{
if (light.type == LightType.Directional)
{
// 主光源使用配置的模式
light.lightmapBakeType = profile.lightMode;
light.shadows = profile.enableShadows ? LightShadows.Soft : LightShadows.None;
}
else
{
// 附加光源
if (realtimeLightCount < profile.maxRealtimeLights)
{
light.lightmapBakeType = LightmapBakeType.Realtime;
realtimeLightCount++;
}
else
{
light.lightmapBakeType = LightmapBakeType.Baked;
}
}
}

// 设置阴影距离
QualitySettings.shadowDistance = profile.shadowDistance;

Debug.Log($"Applied lighting profile: {profile.name}");
Debug.Log($"Estimated performance cost: {profile.estimatedCost}/10");
}

// 自动选择最佳配置
public void AutoSelectProfile()
{
int profileIndex = 0;

#if UNITY_ANDROID || UNITY_IOS
// 移动平台
if (SystemInfo.systemMemorySize > 4096)
profileIndex = 1; // Mobile High
else
profileIndex = 0; // Mobile Low
#else
// PC平台
if (SystemInfo.graphicsMemorySize > 4096)
profileIndex = 3; // PC High
else
profileIndex = 2; // PC Medium
#endif

ApplyProfile(profileIndex);
}
}

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

public class DynamicLightingModeController : MonoBehaviour
{
public Light[] sceneLights;

[Header("Mode Settings")]
public bool useRealtimeMode = true;

private LightmapBakeType[] originalLightModes;

void Start()
{
// 保存原始模式
originalLightModes = new LightmapBakeType[sceneLights.Length];
for (int i = 0; i < sceneLights.Length; i++)
{
originalLightModes[i] = sceneLights[i].lightmapBakeType;
}
}

// 切换到性能模式(烘焙)
public void SwitchToPerformanceMode()
{
foreach (Light light in sceneLights)
{
if (light.type != LightType.Directional)
{
light.enabled = false; // 禁用附加光源
}
else
{
light.lightmapBakeType = LightmapBakeType.Baked;
light.shadows = LightShadows.None;
}
}

useRealtimeMode = false;
Debug.Log("Switched to Performance Mode (Baked Lighting)");
}

// 切换到质量模式(实时)
public void SwitchToQualityMode()
{
for (int i = 0; i < sceneLights.Length; i++)
{
sceneLights[i].enabled = true;
sceneLights[i].lightmapBakeType = originalLightModes[i];

if (sceneLights[i].type == LightType.Directional)
{
sceneLights[i].shadows = LightShadows.Soft;
}
}

useRealtimeMode = true;
Debug.Log("Switched to Quality Mode (Realtime Lighting)");
}

// 根据性能动态调整
void Update()
{
float fps = 1.0f / Time.deltaTime;

// 如果帧率低于30,自动切换到性能模式
if (fps < 30f && useRealtimeMode)
{
SwitchToPerformanceMode();
}
// 如果帧率高于50,可以切换回质量模式
else if (fps > 50f && !useRealtimeMode)
{
SwitchToQualityMode();
}
}
}

5.4 Reflection Probes在URP中的使用

反射探针用于捕捉环境反射,增强材质的真实感。

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

public class ReflectionProbeManager : MonoBehaviour
{
[System.Serializable]
public class ProbeSettings
{
public ReflectionProbe probe;
public ReflectionProbeMode mode = ReflectionProbeMode.Baked;
public ReflectionProbeRefreshMode refreshMode = ReflectionProbeRefreshMode.OnAwake;
public int resolution = 256;
public bool hdr = true;
public float intensity = 1.0f;
public float blendDistance = 1.0f;
}

public ProbeSettings[] probes;

void Start()
{
ConfigureAllProbes();
}

void ConfigureAllProbes()
{
foreach (ProbeSettings settings in probes)
{
if (settings.probe == null) continue;

ConfigureProbe(settings);
}

Debug.Log($"Configured {probes.Length} reflection probes");
}

void ConfigureProbe(ProbeSettings settings)
{
ReflectionProbe probe = settings.probe;

// 基础设置
probe.mode = settings.mode;
probe.refreshMode = settings.refreshMode;
probe.resolution = settings.resolution;
probe.hdr = settings.hdr;
probe.intensity = settings.intensity;
probe.blendDistance = settings.blendDistance;

// 优化设置
probe.boxProjection = true; // 启用盒投影以获得更好的反射
probe.importance = 1; // 优先级

// 根据模式初始化
if (probe.mode == ReflectionProbeMode.Baked)
{
// 烘焙模式 - 在编辑器中烘焙
#if UNITY_EDITOR
probe.RenderProbe();
#endif
}
else if (probe.mode == ReflectionProbeMode.Realtime)
{
// 实时模式 - 设置更新频率
probe.timeSlicingMode = ReflectionProbeTimeSlicingMode.IndividualFaces;
}
}

// 批量烘焙所有Baked探针
public void BakeAllProbes()
{
#if UNITY_EDITOR
foreach (ProbeSettings settings in probes)
{
if (settings.probe != null && settings.probe.mode == ReflectionProbeMode.Baked)
{
settings.probe.RenderProbe();
Debug.Log($"Baked probe: {settings.probe.name}");
}
}
#endif
}

// 刷新所有实时探针
public void RefreshRealtimeProbes()
{
foreach (ProbeSettings settings in probes)
{
if (settings.probe != null && settings.probe.mode == ReflectionProbeMode.Realtime)
{
settings.probe.RenderProbe();
}
}
}
}

5.4.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
86
87
88
using UnityEngine;
using System.Collections;

public class DynamicReflectionSystem : MonoBehaviour
{
[Header("Probe Settings")]
public ReflectionProbe reflectionProbe;
public float updateInterval = 0.1f; // 更新间隔(秒)
public bool followTarget = true;
public Transform targetTransform;

[Header("Performance Settings")]
public int maxProbeResolution = 512;
public bool adaptiveQuality = true;
public float targetFrameRate = 60f;

private float lastUpdateTime;
private int currentResolution;

void Start()
{
if (reflectionProbe == null)
{
reflectionProbe = GetComponent<ReflectionProbe>();
}

currentResolution = reflectionProbe.resolution;

// 设置为实时模式
reflectionProbe.mode = ReflectionProbeMode.Realtime;
reflectionProbe.refreshMode = ReflectionProbeRefreshMode.ViaScripting;

StartCoroutine(UpdateProbeRoutine());
}

IEnumerator UpdateProbeRoutine()
{
while (true)
{
// 跟随目标
if (followTarget && targetTransform != null)
{
transform.position = targetTransform.position;
}

// 自适应质量
if (adaptiveQuality)
{
AdjustQuality();
}

// 渲染反射
reflectionProbe.RenderProbe();

yield return new WaitForSeconds(updateInterval);
}
}

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

if (currentFPS < targetFrameRate - 10f)
{
// 降低分辨率
currentResolution = Mathf.Max(64, currentResolution / 2);
reflectionProbe.resolution = currentResolution;
}
else if (currentFPS > targetFrameRate + 10f)
{
// 提高分辨率
currentResolution = Mathf.Min(maxProbeResolution, currentResolution * 2);
reflectionProbe.resolution = currentResolution;
}
}

// 手动触发更新
public void ForceUpdate()
{
reflectionProbe.RenderProbe();
}

// 设置更新频率
public void SetUpdateInterval(float interval)
{
updateInterval = Mathf.Max(0.016f, interval); // 最快60fps
}
}

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

public class ReflectionProbeBlendingSystem : MonoBehaviour
{
[System.Serializable]
public class ProbeZone
{
public string zoneName;
public ReflectionProbe probe;
public Bounds bounds;
public float blendWeight = 1.0f;
}

public List<ProbeZone> zones = new List<ProbeZone>();
public Transform observerTransform; // 通常是相机

void Update()
{
if (observerTransform == null) return;

UpdateProbeBlending();
}

void UpdateProbeBlending()
{
Vector3 observerPos = observerTransform.position;

foreach (ProbeZone zone in zones)
{
if (zone.probe == null) continue;

// 计算观察者到区域的距离
float distance = Vector3.Distance(observerPos, zone.bounds.center);
float maxDistance = zone.bounds.extents.magnitude + zone.probe.blendDistance;

// 计算混合权重
float weight = 1.0f - Mathf.Clamp01(distance / maxDistance);
zone.blendWeight = weight;

// 应用权重到探针强度
zone.probe.intensity = weight;
}
}

// 可视化区域
void OnDrawGizmos()
{
foreach (ProbeZone zone in zones)
{
if (zone.probe == null) continue;

Gizmos.color = Color.cyan * zone.blendWeight;
Gizmos.DrawWireCube(zone.bounds.center, zone.bounds.size);

Gizmos.color = Color.yellow * zone.blendWeight;
Gizmos.DrawWireSphere(zone.probe.transform.position, zone.probe.blendDistance);
}
}
}

5.5 Light Cookies与IES配置文件

Light Cookies可以创建复杂的光照图案,IES文件提供真实世界的光照分布。

5.5.1 Light Cookie基础使用

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

public class LightCookieController : MonoBehaviour
{
[Header("Light Settings")]
public Light targetLight;

[Header("Cookie Textures")]
public Texture2D[] cookieTextures;
private int currentCookieIndex = 0;

[Header("Animation Settings")]
public bool animateCookie = false;
public float rotationSpeed = 10f;
public float scrollSpeed = 0.5f;

private Material cookieMaterial;

void Start()
{
if (targetLight == null)
{
targetLight = GetComponent<Light>();
}

if (cookieTextures.Length > 0)
{
SetCookie(0);
}
}

void Update()
{
if (animateCookie && targetLight.cookie != null)
{
AnimateCookie();
}
}

// 设置Cookie纹理
public void SetCookie(int index)
{
if (index < 0 || index >= cookieTextures.Length) return;

currentCookieIndex = index;
targetLight.cookie = cookieTextures[index];

Debug.Log($"Set cookie: {cookieTextures[index].name}");
}

// 切换到下一个Cookie
public void NextCookie()
{
currentCookieIndex = (currentCookieIndex + 1) % cookieTextures.Length;
SetCookie(currentCookieIndex);
}

// 动画Cookie(旋转)
void AnimateCookie()
{
transform.Rotate(Vector3.forward, rotationSpeed * Time.deltaTime);
}

// 创建程序化Cookie
public Texture2D CreateProceduralCookie(int resolution, CookiePattern pattern)
{
Texture2D cookie = new Texture2D(resolution, resolution);

for (int y = 0; y < resolution; y++)
{
for (int x = 0; x < resolution; x++)
{
float u = (float)x / resolution;
float v = (float)y / resolution;

Color color = Color.white;

switch (pattern)
{
case CookiePattern.Radial:
float dist = Vector2.Distance(new Vector2(u, v), Vector2.one * 0.5f);
color = Color.Lerp(Color.white, Color.black, dist * 2f);
break;

case CookiePattern.Grid:
bool isGrid = (Mathf.FloorToInt(u * 8) + Mathf.FloorToInt(v * 8)) % 2 == 0;
color = isGrid ? Color.white : Color.black;
break;

case CookiePattern.Spots:
float noise = Mathf.PerlinNoise(u * 10, v * 10);
color = noise > 0.5f ? Color.white : Color.black;
break;
}

cookie.SetPixel(x, y, color);
}
}

cookie.Apply();
return cookie;
}
}

public enum CookiePattern
{
Radial,
Grid,
Spots
}

5.5.2 IES配置文件使用

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

public class IESLightController : MonoBehaviour
{
[Header("IES Settings")]
public Light targetLight;
public Texture iesProfile; // IES纹理

[Header("IES Parameters")]
[Range(0, 1)] public float iesStrength = 1.0f;
public bool enableIES = true;

private UniversalAdditionalLightData lightData;

void Start()
{
if (targetLight == null)
{
targetLight = GetComponent<Light>();
}

lightData = targetLight.GetComponent<UniversalAdditionalLightData>();
if (lightData == null)
{
lightData = targetLight.gameObject.AddComponent<UniversalAdditionalLightData>();
}

ApplyIESProfile();
}

void ApplyIESProfile()
{
if (iesProfile != null && enableIES)
{
// 在URP中,IES通过Light的Cookie实现
targetLight.cookie = iesProfile;

// 设置光源类型(IES通常用于Spot或Point光源)
if (targetLight.type == LightType.Directional)
{
Debug.LogWarning("IES profiles are typically not used with directional lights");
}

Debug.Log($"Applied IES profile: {iesProfile.name}");
}
}

// 切换IES效果
public void ToggleIES(bool enabled)
{
enableIES = enabled;
targetLight.cookie = enabled ? iesProfile : null;
}

// 调整IES强度
public void SetIESStrength(float strength)
{
iesStrength = Mathf.Clamp01(strength);
targetLight.intensity = targetLight.intensity * iesStrength;
}
}

5.6 体积光与光照优化策略

5.6.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class VolumetricLightController : MonoBehaviour
{
[Header("Light Settings")]
public Light targetLight;

[Header("Volumetric Settings")]
[Range(0, 1)] public float density = 0.5f;
[Range(0, 1)] public float scattering = 0.8f;
public Color volumeColor = Color.white;

[Header("Quality Settings")]
public int sampleCount = 64;
public float maxDistance = 50f;

private Volume volume;
private VolumetricFog volumetricFog;

void Start()
{
SetupVolumetricLight();
}

void SetupVolumetricLight()
{
// 创建Volume组件
GameObject volumeObj = new GameObject("Volumetric Light Volume");
volumeObj.transform.SetParent(transform);

volume = volumeObj.AddComponent<Volume>();
volume.isGlobal = false;

// 配置体积雾效果
// 注意:这是示例代码,实际URP需要自定义Volume组件
ConfigureVolumetricSettings();
}

void ConfigureVolumetricSettings()
{
// 在URP中实现体积光通常需要:
// 1. 自定义Renderer Feature
// 2. 或使用第三方插件如Aura 2

// 这里展示基本概念
Debug.Log("Volumetric light configured");
Debug.Log($"Density: {density}, Scattering: {scattering}");
}

public void UpdateVolumetricSettings()
{
if (targetLight != null)
{
// 调整光源参数以增强体积效果
targetLight.intensity *= (1.0f + density);
}
}
}

// 简化的体积雾组件(示例)
public class VolumetricFog : VolumeComponent
{
public ClampedFloatParameter density = new ClampedFloatParameter(0.5f, 0f, 1f);
public ClampedFloatParameter scattering = new ClampedFloatParameter(0.8f, 0f, 1f);
public ColorParameter fogColor = new ColorParameter(Color.white);
}

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

public class LightingOptimizationManager : MonoBehaviour
{
[Header("Light Culling")]
public Camera mainCamera;
public float cullDistance = 50f;

[Header("Light Pooling")]
public int maxActiveLights = 8;

[Header("LOD Settings")]
public float[] lodDistances = { 20f, 40f, 80f };

private List<Light> allLights = new List<Light>();
private List<LightLOD> lightLODs = new List<LightLOD>();

[System.Serializable]
class LightLOD
{
public Light light;
public float originalIntensity;
public float originalRange;
public LightShadows originalShadows;
public int currentLOD;
}

void Start()
{
InitializeLights();
}

void InitializeLights()
{
// 收集所有光源
allLights.AddRange(FindObjectsOfType<Light>());

// 为每个光源创建LOD信息
foreach (Light light in allLights)
{
if (light.type != LightType.Directional) // 主光源不参与LOD
{
LightLOD lod = new LightLOD
{
light = light,
originalIntensity = light.intensity,
originalRange = light.range,
originalShadows = light.shadows,
currentLOD = 0
};
lightLODs.Add(lod);
}
}

Debug.Log($"Initialized {lightLODs.Count} lights for optimization");
}

void Update()
{
if (mainCamera == null) return;

OptimizeLights();
}

void OptimizeLights()
{
Vector3 cameraPos = mainCamera.transform.position;
List<LightDistancePair> lightDistances = new List<LightDistancePair>();

// 计算所有光源到相机的距离
foreach (LightLOD lod in lightLODs)
{
float distance = Vector3.Distance(cameraPos, lod.light.transform.position);
lightDistances.Add(new LightDistancePair { lod = lod, distance = distance });
}

// 按距离排序
lightDistances.Sort((a, b) => a.distance.CompareTo(b.distance));

// 应用优化
for (int i = 0; i < lightDistances.Count; i++)
{
LightLOD lod = lightDistances[i].lod;
float distance = lightDistances[i].distance;

// 距离剔除
if (distance > cullDistance)
{
lod.light.enabled = false;
continue;
}

lod.light.enabled = true;

// 限制激活光源数量
if (i >= maxActiveLights)
{
lod.light.enabled = false;
continue;
}

// LOD处理
ApplyLightLOD(lod, distance);
}
}

void ApplyLightLOD(LightLOD lod, float distance)
{
int lodLevel = GetLODLevel(distance);

if (lodLevel != lod.currentLOD)
{
lod.currentLOD = lodLevel;

switch (lodLevel)
{
case 0: // 高质量(近距离)
lod.light.intensity = lod.originalIntensity;
lod.light.range = lod.originalRange;
lod.light.shadows = lod.originalShadows;
break;

case 1: // 中等质量
lod.light.intensity = lod.originalIntensity * 0.8f;
lod.light.range = lod.originalRange * 0.8f;
lod.light.shadows = LightShadows.Hard; // 硬阴影
break;

case 2: // 低质量
lod.light.intensity = lod.originalIntensity * 0.6f;
lod.light.range = lod.originalRange * 0.6f;
lod.light.shadows = LightShadows.None; // 无阴影
break;

case 3: // 最低质量(远距离)
lod.light.intensity = lod.originalIntensity * 0.3f;
lod.light.range = lod.originalRange * 0.5f;
lod.light.shadows = LightShadows.None;
break;
}
}
}

int GetLODLevel(float distance)
{
for (int i = 0; i < lodDistances.Length; i++)
{
if (distance < lodDistances[i])
return i;
}
return lodDistances.Length;
}

// 辅助类
class LightDistancePair
{
public LightLOD lod;
public float distance;
}

// 可视化调试
void OnDrawGizmos()
{
if (!Application.isPlaying || mainCamera == null) return;

Vector3 cameraPos = mainCamera.transform.position;

// 绘制距离圈
for (int i = 0; i < lodDistances.Length; i++)
{
Gizmos.color = new Color(1, 1, 0, 0.2f);
DrawCircle(cameraPos, lodDistances[i]);
}

// 绘制剔除距离
Gizmos.color = new Color(1, 0, 0, 0.2f);
DrawCircle(cameraPos, cullDistance);
}

void DrawCircle(Vector3 center, float radius)
{
int segments = 32;
Vector3 prevPoint = center + Vector3.forward * radius;

for (int i = 1; i <= segments; i++)
{
float angle = (float)i / segments * Mathf.PI * 2f;
Vector3 newPoint = center + new Vector3(
Mathf.Sin(angle) * radius,
0,
Mathf.Cos(angle) * radius
);
Gizmos.DrawLine(prevPoint, newPoint);
prevPoint = newPoint;
}
}
}

5.6.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
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class LightmapOptimizer : MonoBehaviour
{
[Header("Optimization Settings")]
public bool optimizeForMobile = false;
public int maxLightmapSize = 2048;
public float texelsPerUnit = 5f;

[Header("Compression Settings")]
public bool compressLightmaps = true;
public int compressionQuality = 50;

#if UNITY_EDITOR
[ContextMenu("Optimize Scene Lightmaps")]
public void OptimizeSceneLightmaps()
{
// 1. 设置静态对象的Lightmap参数
MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>();

foreach (MeshRenderer renderer in renderers)
{
if (!renderer.gameObject.isStatic) continue;

SerializedObject so = new SerializedObject(renderer);

// 优化Scale in Lightmap
float scaleInLightmap = CalculateOptimalScale(renderer);
so.FindProperty("m_ScaleInLightmap").floatValue = scaleInLightmap;

// 设置光照贴图参数
so.FindProperty("m_ReceiveGI").enumValueIndex = 1; // Lightmaps

so.ApplyModifiedProperties();
}

// 2. 配置Lightmap Settings
LightmapEditorSettings.bakeResolution = texelsPerUnit;
LightmapEditorSettings.maxAtlasSize = maxLightmapSize;

if (optimizeForMobile)
{
LightmapEditorSettings.bakeResolution = 2f; // 降低分辨率
LightmapEditorSettings.maxAtlasSize = 1024;
}

Debug.Log("Lightmap optimization settings applied");
Debug.Log($"Bake Resolution: {LightmapEditorSettings.bakeResolution}");
Debug.Log($"Max Atlas Size: {LightmapEditorSettings.maxAtlasSize}");
}

float CalculateOptimalScale(MeshRenderer renderer)
{
// 根据物体大小和重要性计算最优比例
Bounds bounds = renderer.bounds;
float size = bounds.size.magnitude;

if (size < 1f)
return 0.5f; // 小物体
else if (size < 5f)
return 1.0f; // 中等物体
else if (size < 20f)
return 1.5f; // 大物体
else
return 2.0f; // 非常大的物体
}

[ContextMenu("Compress Lightmaps")]
public void CompressLightmaps()
{
if (!compressLightmaps) return;

LightmapData[] lightmaps = LightmapSettings.lightmaps;

foreach (LightmapData lightmap in lightmaps)
{
if (lightmap.lightmapColor != null)
{
string path = AssetDatabase.GetAssetPath(lightmap.lightmapColor);
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;

if (importer != null)
{
importer.textureCompression = TextureImporterCompression.Compressed;
importer.compressionQuality = compressionQuality;
importer.SaveAndReimport();
}
}
}

Debug.Log("Lightmaps compressed");
}
#endif
}

5.7 总结

本章深入讲解了URP光照系统的各个方面,从基础配置到高级优化。关键要点:

  1. 光照模式选择 - 根据项目需求在Realtime、Mixed和Baked之间权衡
  2. Light Layers - 使用光照层实现精确的光照控制和特殊效果
  3. 反射探针 - 合理配置反射探针提升材质真实感,注意性能开销
  4. Light Cookies和IES - 创建复杂光照图案和真实光照分布
  5. 性能优化 - 实施光源LOD、距离剔除和烘焙优化策略

优化建议:

  • 移动平台优先使用Baked光照
  • 限制实时光源数量(建议≤4)
  • 合理设置阴影距离和分辨率
  • 使用Light Layers减少不必要的光照计算
  • 定期优化光照贴图大小和压缩

下一章预告: 将详细讲解URP阴影系统,包括级联阴影贴图、阴影参数调优和移动平台阴影优化。