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; 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 () { directionalLight.lightmapBakeType = LightmapBakeType.Mixed; directionalLight.shadows = LightShadows.Soft; 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(); } } foreach (GameObject obj in dynamicObjects) { MeshRenderer renderer = obj.GetComponent<MeshRenderer>(); if (renderer != null ) { 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 () { foreach (Light light in mixedLights) { light.lightmapBakeType = LightmapBakeType.Mixed; light.shadows = LightShadows.Soft; light.shadowStrength = 1.0f ; } } }
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 () { LightmapEditorSettings.realtimeResolution = lightmapResolution / 4f ; LightmapEditorSettings.bakeResolution = lightmapResolution / 10f ; RenderSettings.reflectionIntensity = indirectIntensity; 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 () { 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 ; 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 , LightLayerCharacter = 1 << 1 , LightLayerEnvironment = 1 << 2 , LightLayerEffects = 1 << 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; 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 ; else profileIndex = 0 ; #else if (SystemInfo.graphicsMemorySize > 4096 ) profileIndex = 3 ; else profileIndex = 2 ; #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; if (fps < 30f && useRealtimeMode) { SwitchToPerformanceMode(); } 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; } } 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); } }
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(); } } 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} " ); } public void NextCookie () { currentCookieIndex = (currentCookieIndex + 1 ) % cookieTextures.Length; SetCookie(currentCookieIndex); } void AnimateCookie () { transform.Rotate(Vector3.forward, rotationSpeed * Time.deltaTime); } 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; [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) { targetLight.cookie = iesProfile; if (targetLight.type == LightType.Directional) { Debug.LogWarning("IES profiles are typically not used with directional lights" ); } Debug.Log($"Applied IES profile: {iesProfile.name} " ); } } public void ToggleIES (bool enabled ) { enableIES = enabled; targetLight.cookie = enabled ? iesProfile : null ; } 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 () { GameObject volumeObj = new GameObject("Volumetric Light Volume" ); volumeObj.transform.SetParent(transform); volume = volumeObj.AddComponent<Volume>(); volume.isGlobal = false ; ConfigureVolumetricSettings(); } void ConfigureVolumetricSettings () { 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>()); foreach (Light light in allLights) { if (light.type != LightType.Directional) { 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 ; } 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 () { MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>(); foreach (MeshRenderer renderer in renderers) { if (!renderer.gameObject.isStatic) continue ; SerializedObject so = new SerializedObject(renderer); float scaleInLightmap = CalculateOptimalScale(renderer); so.FindProperty("m_ScaleInLightmap" ).floatValue = scaleInLightmap; so.FindProperty("m_ReceiveGI" ).enumValueIndex = 1 ; so.ApplyModifiedProperties(); } 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光照系统的各个方面,从基础配置到高级优化。关键要点:
光照模式选择 - 根据项目需求在Realtime、Mixed和Baked之间权衡
Light Layers - 使用光照层实现精确的光照控制和特殊效果
反射探针 - 合理配置反射探针提升材质真实感,注意性能开销
Light Cookies和IES - 创建复杂光照图案和真实光照分布
性能优化 - 实施光源LOD、距离剔除和烘焙优化策略
优化建议:
移动平台优先使用Baked光照
限制实时光源数量(建议≤4)
合理设置阴影距离和分辨率
使用Light Layers减少不必要的光照计算
定期优化光照贴图大小和压缩
下一章预告: 将详细讲解URP阴影系统,包括级联阴影贴图、阴影参数调优和移动平台阴影优化。