第5章 URP光照系统
理论讲解
实时光照与烘焙光照混合
在URP中,光照系统是渲染管线的核心组成部分之一。URP支持多种光照模式,包括实时光照、烘焙光照以及两者的混合使用。理解这些光照模式的工作原理对于创建高质量的视觉效果至关重要。
实时光照(Real-time Lighting)
实时光照是指在运行时动态计算的光照效果。在URP中,实时光照包括:
- 主光源(Main Light):通常是场景中最主要的定向光源,如太阳光
- 附加光源(Additional Lights):包括点光源、聚光灯等其他光源
- 环境光(Ambient 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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class RealTimeLightingController : MonoBehaviour { [Header("Main Light Configuration")] public Light mainLight; public float mainLightIntensity = 1.0f; public Color mainLightColor = Color.white; [Header("Additional Lights")] public Light[] additionalLights; public float additionalLightIntensity = 0.5f; [Header("Environment Lighting")] public float ambientIntensity = 0.2f; public Color ambientColor = Color.gray; private UniversalRenderPipelineAsset urpAsset; void Start() { SetupRealTimeLighting(); } void SetupRealTimeLighting() { if(mainLight != null) { mainLight.type = LightType.Directional; mainLight.intensity = mainLightIntensity; mainLight.color = mainLightColor; mainLight.lightmapBakeType = LightmapBakeType.Realtime; } foreach(var light in additionalLights) { if(light != null) { light.intensity = additionalLightIntensity; light.lightmapBakeType = LightmapBakeType.Realtime; } } RenderSettings.ambientIntensity = ambientIntensity; RenderSettings.ambientLight = ambientColor; RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight; } public void AdjustMainLightIntensity(float intensity) { if(mainLight != null) { mainLight.intensity = intensity; } } public void AdjustMainLightColor(Color color) { if(mainLight != null) { mainLight.color = color; } } public void SetLightMode(LightmapBakeType bakeType) { if(mainLight != null) { mainLight.lightmapBakeType = bakeType; } foreach(var light in additionalLights) { if(light != null) { light.lightmapBakeType = bakeType; } } } }
|
烘焙光照(Baked Lighting)
烘焙光照是预先计算并存储在光照贴图中的光照信息。在URP中,烘焙光照通过Unity的光照烘焙系统生成。
烘焙光照的优势:
- 性能高效:运行时不需要计算复杂的光照效果
- 视觉质量:可以实现高质量的间接光照效果
- 一致性:光照效果在不同设备上保持一致
烘焙光照的局限性:
- 静态性:烘焙后的光照无法动态变化
- 预处理时间:需要较长的烘焙时间
- 内存占用:需要存储光照贴图
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;
public class BakedLightingController : MonoBehaviour { [Header("Baked Lighting Configuration")] public bool enableBakedLighting = true; public Light[] staticLights; public Renderer[] staticObjects; [Header("Lightmap Settings")] public float lightmapScale = 1.0f; void Start() { ConfigureBakedLighting(); } void ConfigureBakedLighting() { if(enableBakedLighting) { foreach(var light in staticLights) { if(light != null) { light.lightmapBakeType = LightmapBakeType.Baked; light.gameObject.isStatic = true; } } foreach(var obj in staticObjects) { if(obj != null) { var meshRenderer = obj.GetComponent<MeshRenderer>(); if(meshRenderer != null) { meshRenderer.lightmapScaleOffset = new Vector4( lightmapScale, lightmapScale, 0, 0 ); } } } } } public bool IsObjectBakeable(Renderer renderer) { if(renderer == null) return false; if(!renderer.gameObject.isStatic) return false; var material = renderer.sharedMaterials; foreach(var mat in material) { if(mat.HasProperty("_MainTex")) { if(renderer.lightmapIndex == -1) return false; } } return true; } }
|
混合光照(Mixed Lighting)
混合光照结合了实时光照和烘焙光照的优点,允许某些光照效果实时计算,而其他效果烘焙到光照贴图中。
在URP中,混合光照模式包括:
- Baked Indirect:间接光照烘焙,直接光照实时计算
- Subtractive:烘焙光照作为基础,实时光照减去环境光
- 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 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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class MixedLightingController : MonoBehaviour { [Header("Mixed Lighting Configuration")] public Light[] mixedLights; public MixedLightingMode mixedMode = MixedLightingMode.BakedIndirect; [Header("Light Probes")] public LightProbeGroup lightProbeGroup; public bool useLightProbes = true; void Start() { SetupMixedLighting(); } void SetupMixedLighting() { foreach(var light in mixedLights) { if(light != null) { light.lightmapBakeType = LightmapBakeType.Mixed; light.mixedLightingMode = mixedMode; if(mixedMode == MixedLightingMode.Subtractive) { light.shadowType = LightShadows.Soft; } else if(mixedMode == MixedLightingMode.BakedIndirect) { light.bounceIntensity = 1.0f; } } } if(lightProbeGroup != null && useLightProbes) { var dynamicObjects = FindObjectsOfType<Renderer>(); foreach(var obj in dynamicObjects) { if(obj.GetComponent<LightProbeProxyVolume>() == null) { obj.probeAnchor = lightProbeGroup.transform; } } } } public void SwitchMixedMode(MixedLightingMode newMode) { mixedMode = newMode; foreach(var light in mixedLights) { if(light != null) { light.mixedLightingMode = newMode; } } } public string GetMixedLightingInfo() { string info = "Mixed Lighting Configuration:\n"; info += $"Mode: {mixedMode}\n"; info += $"Light Count: {mixedLights.Length}\n"; info += $"Light Probes: {(lightProbeGroup != null ? "Enabled" : "Disabled")}\n"; return info; } }
|
Light Layers与Rendering Layers
Light Layers是URP中用于控制光照影响范围的功能,允许开发者指定哪些光源可以照亮哪些物体。
Light Layers工作原理
Light Layers通过位掩码(bitmask)的方式工作,每个光源和每个渲染器都可以分配到不同的层中。
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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class LightLayersController : MonoBehaviour { [Header("Light Layers Configuration")] public Light[] lights; public Renderer[] renderers; [System.Serializable] public class LightLayerAssignment { public Light light; public int layerMask = 1; } public LightLayerAssignment[] lightLayerAssignments; void Start() { ConfigureLightLayers(); } void ConfigureLightLayers() { foreach(var assignment in lightLayerAssignments) { if(assignment.light != null) { SetLightLayer(assignment.light, assignment.layerMask); } } foreach(var renderer in renderers) { if(renderer != null) { SetRendererLightLayer(renderer, 1); } } } void SetLightLayer(Light light, int layerMask) { Debug.Log($"Setting light {light.name} to layer mask: {layerMask}"); } void SetRendererLightLayer(Renderer renderer, int layerMask) { Debug.Log($"Setting renderer {renderer.name} to light layer: {layerMask}"); } public void CreateLightObjectPair(Light light, GameObject[] objects, int layerMask) { foreach(var obj in objects) { var renderer = obj.GetComponent<Renderer>(); if(renderer != null) { SetRendererLightLayer(renderer, layerMask); } } SetLightLayer(light, layerMask); } }
|
光照模式:Baked、Realtime、Mixed
URP支持三种主要的光照模式,每种模式都有其特定的用途和性能特征:
1. Baked Mode(烘焙模式)
在烘焙模式下,所有光照信息都被预先计算并存储在光照贴图中。
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
| using UnityEngine;
public class BakedLightModeController : MonoBehaviour { [Header("Baked Light Configuration")] public Light[] bakedLights; public GameObject[] staticObjects; [Header("Baking Parameters")] public float indirectIntensity = 1.0f; public float lightmapResolution = 40.0f; void ConfigureBakedLights() { foreach(var light in bakedLights) { if(light != null) { light.lightmapBakeType = LightmapBakeType.Baked; light.bounceIntensity = indirectIntensity; light.gameObject.isStatic = true; } } foreach(var obj in staticObjects) { if(obj != null) { obj.isStatic = true; var renderer = obj.GetComponent<MeshRenderer>(); if(renderer != null) { renderer.lightmapScaleOffset = Vector4.one; } } } } public bool CheckBakingRequirements() { bool allStatic = true; bool hasUVs = true; foreach(var obj in staticObjects) { if(!obj.isStatic) allStatic = false; var meshFilter = obj.GetComponent<MeshFilter>(); var meshRenderer = obj.GetComponent<MeshRenderer>(); if(meshFilter != null && meshRenderer != null) { var mesh = meshFilter.sharedMesh; if(mesh != null && mesh.uv.Length == 0) hasUVs = false; } } return allStatic && hasUVs; } }
|
2. Realtime Mode(实时模式)
在实时模式下,所有光照效果都在运行时动态计算。
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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class RealtimeLightModeController : MonoBehaviour { [Header("Realtime Light Configuration")] public Light[] realtimeLights; public GameObject[] dynamicObjects; [Header("Realtime Parameters")] public float maxRealtimeLights = 4; public bool enableShadows = true; void ConfigureRealtimeLights() { int lightCount = 0; foreach(var light in realtimeLights) { if(light != null && lightCount < maxRealtimeLights) { light.lightmapBakeType = LightmapBakeType.Realtime; light.shadows = enableShadows ? LightShadows.Soft : LightShadows.None; lightCount++; } } } public void AdjustRealtimeLighting(float intensityMultiplier) { foreach(var light in realtimeLights) { if(light != null) { light.intensity *= intensityMultiplier; } } } public string GetRealtimeLightInfo() { int activeLights = 0; float totalIntensity = 0f; foreach(var light in realtimeLights) { if(light != null && light.isActiveAndEnabled) { activeLights++; totalIntensity += light.intensity; } } return $"Active Realtime Lights: {activeLights}\n" + $"Max Allowed: {maxRealtimeLights}\n" + $"Total Intensity: {totalIntensity:F2}"; } }
|
3. Mixed Mode(混合模式)
混合模式结合了烘焙和实时光照的优点。
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
| using UnityEngine;
public class MixedLightModeController : MonoBehaviour { [Header("Mixed Light Configuration")] public Light[] mixedLights; public MixedLightingMode mixedMode = MixedLightingMode.BakedIndirect; [Header("Mixed Parameters")] public float indirectIntensity = 1.0f; public bool useShadowmask = false; void ConfigureMixedLights() { foreach(var light in mixedLights) { if(light != null) { light.lightmapBakeType = LightmapBakeType.Mixed; light.mixedLightingMode = mixedMode; light.bounceIntensity = indirectIntensity; if(mixedMode == MixedLightingMode.Shadowmask || useShadowmask) { light.lightShadowCasterMode = LightShadowCasterMode.Everything; } } } } public void SetMixedMode(MixedLightingMode newMode) { mixedMode = newMode; ConfigureMixedLights(); } public string GetMixedLightStats() { int bakedIndirect = 0; int subtractive = 0; int shadowmask = 0; foreach(var light in mixedLights) { if(light != null) { switch(light.mixedLightingMode) { case MixedLightingMode.BakedIndirect: bakedIndirect++; break; case MixedLightingMode.Subtractive: subtractive++; break; case MixedLightingMode.Shadowmask: shadowmask++; break; } } } return $"Mixed Light Statistics:\n" + $"Baked Indirect: {bakedIndirect}\n" + $"Subtractive: {subtractive}\n" + $"Shadowmask: {shadowmask}"; } }
|
Reflection Probes在URP中的使用
Reflection Probes(反射探针)用于捕获场景中的环境反射信息,在URP中它们的工作方式与传统管线类似,但需要考虑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 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 ReflectionProbesController : MonoBehaviour { [Header("Reflection Probes Configuration")] public ReflectionProbe[] reflectionProbes; public Renderer[] reflectiveObjects; [Header("Probe Settings")] public float refreshMode = 0f; public int frameUpdateFrequency = 10; public float intensity = 1.0f; void Start() { SetupReflectionProbes(); } void SetupReflectionProbes() { foreach(var probe in reflectionProbes) { if(probe != null) { probe.mode = UnityEngine.Rendering.ReflectionProbeMode.Realtime; probe.refreshMode = UnityEngine.Rendering.ReflectionProbeRefreshMode.ViaScripting; probe.timeSlicingMode = UnityEngine.Rendering.ReflectionProbeTimeSlicingMode.AllFacesAtOnce; probe.intensity = intensity; probe.size = Vector3.one * 10f; probe.center = Vector3.zero; } } foreach(var renderer in reflectiveObjects) { if(renderer != null) { var materials = renderer.sharedMaterials; foreach(var material in materials) { if(material.HasProperty("_SpecularHighlights")) { material.EnableKeyword("_SPECGLOSSMAP"); } } } } } public void UpdateReflectionProbes() { foreach(var probe in reflectionProbes) { if(probe != null) { probe.RenderProbe(); } } } void Update() { if(Time.frameCount % frameUpdateFrequency == 0) { UpdateReflectionProbes(); } } public string GetReflectionProbesInfo() { string info = "Reflection Probes Information:\n"; info += $"Probe Count: {reflectionProbes.Length}\n"; info += $"Reflective Objects: {reflectiveObjects.Length}\n"; info += $"Update Frequency: Every {frameUpdateFrequency} frames\n"; info += $"Intensity: {intensity:F2}\n"; return info; } }
|
Light Cookies与IES配置文件
Light Cookies(光照贴图)和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 63 64 65 66 67 68
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class LightCookiesController : MonoBehaviour { [Header("Light Cookies Configuration")] public Light[] cookieLights; public Texture2D[] cookieTextures; public Texture2D[] iesProfiles; [Header("Cookie Settings")] public float cookieSize = 10f; public float cookieOffset = 0f; void Start() { SetupLightCookies(); } void SetupLightCookies() { int cookieIndex = 0; foreach(var light in cookieLights) { if(light != null && cookieIndex < cookieTextures.Length) { light.cookie = cookieTextures[cookieIndex]; light.cookieSize = cookieSize; cookieIndex++; } } } public void SwitchCookieTexture(int lightIndex, int textureIndex) { if(lightIndex < cookieLights.Length && textureIndex < cookieTextures.Length) { cookieLights[lightIndex].cookie = cookieTextures[textureIndex]; } } public void ApplyIESProfile(int lightIndex, int iesIndex) { if(lightIndex < cookieLights.Length && iesIndex < iesProfiles.Length) { Debug.Log($"Applying IES profile {iesProfiles[iesIndex].name} to light {cookieLights[lightIndex].name}"); } } public string GetCookiesInfo() { string info = "Light Cookies Information:\n"; info += $"Cookie Lights: {cookieLights.Length}\n"; info += $"Available Textures: {cookieTextures.Length}\n"; info += $"IES Profiles: {iesProfiles.Length}\n"; info += $"Cookie Size: {cookieSize:F2}\n"; return info; } }
|
体积光与光照优化策略
体积光(Volumetric Lighting)在URP中可以通过自定义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 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
| using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class VolumetricLightingController : MonoBehaviour { [Header("Volumetric Lighting Configuration")] public Light[] volumetricLights; public float scatteringCoefficient = 0.1f; public float absorptionCoefficient = 0.05f; public Color lightColor = Color.white; [Header("Optimization Settings")] public int volumeResolution = 64; public bool useTemporalReprojection = true; public float temporalReprojectionStrength = 0.9f; private Material volumetricMaterial; private RenderTexture volumeTexture; void Start() { SetupVolumetricLighting(); } void SetupVolumetricLighting() { Shader volumetricShader = Shader.Find("Universal Render Pipeline/Custom/VolumetricLighting"); if(volumetricShader != null) { volumetricMaterial = new Material(volumetricShader); volumetricMaterial.SetFloat("_ScatteringCoeff", scatteringCoefficient); volumetricMaterial.SetFloat("_AbsorptionCoeff", absorptionCoefficient); volumetricMaterial.SetColor("_LightColor", lightColor); } foreach(var light in volumetricLights) { if(light != null) { light.type = LightType.Directional; light.cookie = null; } } } public void ApplyLightingOptimizations() { CullDistantLights(); MergeSimilarLights(); ManageDynamicLights(); } void CullDistantLights() { Camera mainCamera = Camera.main; if(mainCamera == null) return; Vector3 cameraPos = mainCamera.transform.position; float maxLightDistance = 50f; foreach(var light in volumetricLights) { if(light != null) { float distance = Vector3.Distance(light.transform.position, cameraPos); if(distance > maxLightDistance) { light.enabled = false; } else { light.enabled = true; float intensityFactor = Mathf.Clamp01(1.0f - (distance / maxLightDistance)); light.intensity = intensityFactor; } } } } void MergeSimilarLights() { for(int i = 0; i < volumetricLights.Length; i++) { for(int j = i + 1; j < volumetricLights.Length; j++) { if(volumetricLights[i] != null && volumetricLights[j] != null) { float distance = Vector3.Distance( volumetricLights[i].transform.position, volumetricLights[j].transform.position ); if(distance < 2.0f) { Debug.Log($"Lights {i} and {j} are close, consider merging"); } } } } } void ManageDynamicLights() { int maxActiveLights = 4; int activeCount = 0; foreach(var light in volumetricLights) { if(light != null) { if(activeCount < maxActiveLights) { light.enabled = true; activeCount++; } else { light.enabled = false; } } } } public string GetVolumetricLightingInfo() { string info = "Volumetric Lighting Configuration:\n"; info += $"Volumetric Lights: {volumetricLights.Length}\n"; info += $"Scattering Coeff: {scatteringCoefficient:F3}\n"; info += $"Absorption Coeff: {absorptionCoefficient:F3}\n"; info += $"Volume Resolution: {volumeResolution}\n"; info += $"Temporal Reprojection: {useTemporalReprojection}\n"; return info; } void OnDestroy() { if(volumetricMaterial != null) { DestroyImmediate(volumetricMaterial); } if(volumeTexture != null) { RenderTexture.ReleaseTemporary(volumeTexture); } } }
|
代码示例
完整的光照管理系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
| using UnityEngine; using UnityEngine.Rendering.Universal; using System.Collections.Generic;
public class AdvancedLightingSystem : MonoBehaviour { [System.Serializable] public class LightConfiguration { public string configName; public LightType lightType = LightType.Directional; public LightmapBakeType bakeType = LightmapBakeType.Realtime; public MixedLightingMode mixedMode = MixedLightingMode.BakedIndirect; public float intensity = 1.0f; public Color color = Color.white; public bool enableShadows = true; public LightShadows shadowType = LightShadows.Soft; } [Header("Light Management")] public List<LightConfiguration> lightConfigs = new List<LightConfiguration>(); public Light[] managedLights; [Header("Environment Settings")] public float ambientIntensity = 0.2f; public Color ambientColor = Color.gray; public float reflectionIntensity = 1.0f; [Header("Performance Settings")] public int maxRealtimeLights = 8; public float lightCullDistance = 100f; public bool enableLightProbes = true; private Dictionary<string, LightConfiguration> configMap = new Dictionary<string, LightConfiguration>(); private int activeLightCount = 0; void Start() { InitializeLightingSystem(); } void InitializeLightingSystem() { foreach(var config in lightConfigs) { if(!configMap.ContainsKey(config.configName)) { configMap[config.configName] = config; } } ConfigureManagedLights(); SetupEnvironment(); } void ConfigureManagedLights() { activeLightCount = 0; foreach(var light in managedLights) { if(light != null && activeLightCount < maxRealtimeLights) { ApplyLightConfiguration(light, GetDefaultConfiguration()); activeLightCount++; } } } void SetupEnvironment() { RenderSettings.ambientIntensity = ambientIntensity; RenderSettings.ambientLight = ambientColor; RenderSettings.reflectionIntensity = reflectionIntensity; RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight; } LightConfiguration GetDefaultConfiguration() { if(lightConfigs.Count > 0) { return lightConfigs[0]; } return new LightConfiguration { configName = "Default", lightType = LightType.Directional, bakeType = LightmapBakeType.Realtime, intensity = 1.0f, color = Color.white, enableShadows = true }; } public void ApplyLightConfiguration(Light light, LightConfiguration config) { if(light == null || config == null) return; light.type = config.lightType; light.lightmapBakeType = config.bakeType; if(config.bakeType == LightmapBakeType.Mixed) { light.mixedLightingMode = config.mixedMode; } light.intensity = config.intensity; light.color = config.color; if(config.enableShadows) { light.shadows = config.shadowType; } else { light.shadows = LightShadows.None; } switch(config.lightType) { case LightType.Spot: light.spotAngle = 30f; light.range = 10f; break; case LightType.Point: light.range = 10f; break; case LightType.Directional: break; } } public void ApplyConfigurationByName(Light light, string configName) { if(configMap.ContainsKey(configName)) { ApplyLightConfiguration(light, configMap[configName]); } } public Light CreateDynamicLight(LightConfiguration config, Vector3 position, Quaternion rotation) { if(activeLightCount >= maxRealtimeLights) { Debug.LogWarning("Maximum realtime lights reached!"); return null; } GameObject lightObj = new GameObject($"DynamicLight_{config.configName}"); lightObj.transform.position = position; lightObj.transform.rotation = rotation; Light light = lightObj.AddComponent<Light>(); ApplyLightConfiguration(light, config); System.Array.Resize(ref managedLights, managedLights.Length + 1); managedLights[managedLights.Length - 1] = light; activeLightCount++; return light; } public void OptimizeLights(Camera camera) { if(camera == null) return; Vector3 cameraPos = camera.transform.position; foreach(var light in managedLights) { if(light != null) { float distance = Vector3.Distance(light.transform.position, cameraPos); if(distance > lightCullDistance) { if(light.shadows != LightShadows.None) { light.shadows = LightShadows.Soft; light.shadowResolution = LightShadowResolution.Low; } } else { if(light.shadows == LightShadows.Soft) { light.shadowResolution = LightShadowResolution.Medium; } } } } } public string GetLightingSystemInfo() { int realtimeLights = 0; int bakedLights = 0; int mixedLights = 0; foreach(var light in managedLights) { if(light != null) { switch(light.lightmapBakeType) { case LightmapBakeType.Realtime: realtimeLights++; break; case LightmapBakeType.Baked: bakedLights++; break; case LightmapBakeType.Mixed: mixedLights++; break; } } } return $"Lighting System Statistics:\n" + $"Total Managed Lights: {managedLights.Length}\n" + $"Active Realtime: {realtimeLights}\n" + $"Baked: {bakedLights}\n" + $"Mixed: {mixedLights}\n" + $"Max Realtime Limit: {maxRealtimeLights}\n" + $"Ambient Intensity: {ambientIntensity:F2}\n" + $"Reflection Intensity: {reflectionIntensity:F2}"; } public void SwitchLightingConfiguration(string configName) { if(!configMap.ContainsKey(configName)) { Debug.LogError($"Configuration '{configName}' not found!"); return; } var config = configMap[configName]; foreach(var light in managedLights) { if(light != null) { ApplyLightConfiguration(light, config); } } Debug.Log($"Switched to lighting configuration: {configName}"); } public void AddLightConfiguration(LightConfiguration config) { if(!configMap.ContainsKey(config.configName)) { lightConfigs.Add(config); configMap[config.configName] = config; } } void Update() { if(Camera.main != null) { OptimizeLights(Camera.main); } } }
|
实践练习
练习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 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
| using UnityEngine; using UnityEngine.Rendering.Universal; using System.Collections;
public class DynamicLightingSwitcher : MonoBehaviour { [System.Serializable] public class TimeBasedLightingConfig { public string configName; public float timeOfDay = 12.0f; public LightConfiguration lightConfig; public float transitionDuration = 2.0f; public AnimationCurve transitionCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); } [Header("Dynamic Lighting Configuration")] public TimeBasedLightingConfig[] timeBasedConfigs; public Light[] targetLights; [Header("Time Control")] public bool useRealTime = false; public float timeScale = 1.0f; private float currentTime = 8.0f; [Header("Current State")] private TimeBasedLightingConfig currentConfig; private TimeBasedLightingConfig targetConfig; private float transitionProgress = 0f; private bool isTransitioning = false; void Start() { InitializeDynamicLighting(); } void InitializeDynamicLighting() { if(timeBasedConfigs.Length > 0) { currentConfig = timeBasedConfigs[0]; ApplyConfiguration(currentConfig.lightConfig); } } void Update() { if(useRealTime) { currentTime += Time.deltaTime * timeScale / 3600f; if(currentTime >= 24f) currentTime -= 24f; } else { currentTime += Time.deltaTime * 0.1f; if(currentTime >= 24f) currentTime -= 24f; } CheckAndApplyTimeBasedChanges(); if(isTransitioning) { UpdateTransition(); } } void CheckAndApplyTimeBasedChanges() { TimeBasedLightingConfig nextConfig = GetNextConfig(); if(nextConfig != null && nextConfig != targetConfig) { StartConfigurationTransition(nextConfig); } } TimeBasedLightingConfig GetNextConfig() { TimeBasedLightingConfig nextConfig = null; float minDistance = float.MaxValue; foreach(var config in timeBasedConfigs) { float distance = CalculateTimeDistance(currentTime, config.timeOfDay); if(distance < minDistance && distance < 1.0f) { minDistance = distance; nextConfig = config; } } return nextConfig; } float CalculateTimeDistance(float from, float to) { float distance = Mathf.Abs(to - from); if(distance > 12f) { distance = 24f - distance; } return distance; } void StartConfigurationTransition(TimeBasedLightingConfig newConfig) { if(newConfig == null) return; targetConfig = newConfig; isTransitioning = true; transitionProgress = 0f; } void UpdateTransition() { transitionProgress += Time.deltaTime / targetConfig.transitionDuration; if(transitionProgress >= 1.0f) { transitionProgress = 1.0f; isTransitioning = false; currentConfig = targetConfig; } InterpolateBetweenConfigs(currentConfig, targetConfig, targetConfig.transitionCurve.Evaluate(transitionProgress)); } void InterpolateBetweenConfigs(TimeBasedLightingConfig from, TimeBasedLightingConfig to, float t) { foreach(var light in targetLights) { if(light != null) { float intensity = Mathf.Lerp(from.lightConfig.intensity, to.lightConfig.intensity, t); light.intensity = intensity; Color color = Color.Lerp(from.lightConfig.color, to.lightConfig.color, t); light.color = color; } } } void ApplyConfiguration(LightConfiguration config) { foreach(var light in targetLights) { if(light != null) { light.intensity = config.intensity; light.color = config.color; if(config.enableShadows) { light.shadows = config.shadowType; } else { light.shadows = LightShadows.None; } } } } public void SwitchToConfig(string configName) { foreach(var config in timeBasedConfigs) { if(config.configName == configName) { StartConfigurationTransition(config); return; } } } public float GetCurrentTime() { return currentTime; } public string GetLightingInfo() { return $"Current Time: {currentTime:F2}h\n" + $"Current Config: {currentConfig?.configName ?? "None"}\n" + $"Target Config: {targetConfig?.configName ?? "None"}\n" + $"Transition Progress: {transitionProgress * 100:F1}%\n" + $"Lights Count: {targetLights.Length}"; } }
|
练习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 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
| using UnityEngine; using UnityEngine.Rendering.Universal; using System.Collections.Generic; using System.Linq;
public class LightingPerformanceAnalyzer : MonoBehaviour { [Header("Analysis Settings")] public bool autoAnalyze = true; public float analysisInterval = 5.0f; private float lastAnalysisTime = 0f; [Header("Performance Thresholds")] public int maxRecommendedLights = 8; public float maxLightRange = 50f; public float maxShadowDistance = 30f; public LightShadows maxShadowQuality = LightShadows.Soft; [Header("Results")] public bool hasPerformanceIssues = false; public List<Light> problematicLights = new List<Light>(); private Dictionary<Light, LightPerformanceData> lightPerformanceData = new Dictionary<Light, LightPerformanceData>(); [System.Serializable] public class LightPerformanceData { public float lastFrameTime; public int shadowResolution; public float range; public bool hasIssues; public List<string> issues = new List<string>(); } void Update() { if(autoAnalyze && Time.time - lastAnalysisTime >= analysisInterval) { AnalyzeLightingPerformance(); lastAnalysisTime = Time.time; } } public void AnalyzeLightingPerformance() { Light[] allLights = FindObjectsOfType<Light>(); problematicLights.Clear(); lightPerformanceData.Clear(); hasPerformanceIssues = false; foreach(var light in allLights) { if(light == null) continue; var data = new LightPerformanceData(); data.range = light.type == LightType.Directional ? 0 : light.range; data.shadowResolution = (int)light.shadowResolution; CheckLightPerformanceIssues(light, data); lightPerformanceData[light] = data; if(data.hasIssues) { problematicLights.Add(light); hasPerformanceIssues = true; } } DebugAnalysisResults(); } void CheckLightPerformanceIssues(Light light, LightPerformanceData data) { Light[] allLights = FindObjectsOfType<Light>(); if(allLights.Length > maxRecommendedLights) { data.issues.Add($"Too many lights in scene ({allLights.Length} > {maxRecommendedLights})"); data.hasIssues = true; } if(light.type != LightType.Directional && light.range > maxLightRange) { data.issues.Add($"Light range too large ({light.range:F1} > {maxLightRange:F1})"); data.hasIssues = true; } var urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null && urpAsset.shadowDistance > maxShadowDistance) { data.issues.Add($"Shadow distance too large ({urpAsset.shadowDistance:F1} > {maxShadowDistance:F1})"); data.hasIssues = true; } if(light.shadows > maxShadowQuality) { data.issues.Add($"Shadow quality too high ({light.shadows} > {maxShadowQuality})"); data.hasIssues = true; } var lights = FindObjectsOfType<Light>(); var spotLights = lights.Where(l => l.type == LightType.Spot).ToList(); var pointLights = lights.Where(l => l.type == LightType.Point).ToList(); if(spotLights.Count > allLights.Length * 0.7f) { data.issues.Add("Too many spot lights, consider using fewer, higher-quality lights"); data.hasIssues = true; } } void DebugAnalysisResults() { if(!hasPerformanceIssues) { Debug.Log("✅ Lighting performance looks good!"); return; } Debug.LogWarning($"⚠️ Found {problematicLights.Count} lights with performance issues:"); foreach(var light in problematicLights) { if(lightPerformanceData.ContainsKey(light)) { var data = lightPerformanceData[light]; Debug.LogWarning($"Light '{light.name}' issues:"); foreach(var issue in data.issues) { Debug.LogWarning($" - {issue}"); } } } } public string GetOptimizationSuggestions() { if(!hasPerformanceIssues) { return "No performance issues detected. Lighting performance is optimal."; } var suggestions = new List<string>(); Light[] allLights = FindObjectsOfType<Light>(); if(allLights.Length > maxRecommendedLights) { suggestions.Add($"Reduce light count from {allLights.Length} to {maxRecommendedLights} or fewer"); } var shadowLights = allLights.Where(l => l.shadows != LightShadows.None).ToList(); if(shadowLights.Count > 0) { suggestions.Add($"Consider reducing shadow resolution or disabling shadows for distant lights"); } var highRangeLights = allLights.Where(l => l.type != LightType.Directional && l.range > maxLightRange * 0.8f).ToList(); if(highRangeLights.Count > 0) { suggestions.Add($"Reduce range of {highRangeLights.Count} lights to improve performance"); } return "Performance Optimization Suggestions:\n" + string.Join("\n", suggestions.Select(s => "• " + s)); } public void ApplyPerformanceOptimizations() { Light[] allLights = FindObjectsOfType<Light>(); foreach(var light in allLights) { if(light.shadows == LightShadows.Soft && light.range > maxLightRange * 0.7f) { light.shadows = LightShadows.Hard; Debug.Log($"Reduced shadow quality for light: {light.name}"); } } var lightsByImportance = allLights .OrderByDescending(l => l.intensity * (l.type == LightType.Directional ? 2 : 1)) .ToList(); for(int i = maxRecommendedLights; i < lightsByImportance.Count; i++) { lightsByImportance[i].enabled = false; Debug.Log($"Disabled light for performance: {lightsByImportance[i].name}"); } Debug.Log("Applied performance optimizations"); } public string GetDetailedAnalysisReport() { var report = new System.Text.StringBuilder(); Light[] allLights = FindObjectsOfType<Light>(); report.AppendLine("=== Lighting Performance Analysis Report ==="); report.AppendLine($"Total Lights: {allLights.Length}"); report.AppendLine($"Problematic Lights: {problematicLights.Count}"); report.AppendLine(); int directionalCount = allLights.Count(l => l.type == LightType.Directional); int pointCount = allLights.Count(l => l.type == LightType.Point); int spotCount = allLights.Count(l => l.type == LightType.Spot); report.AppendLine("Light Type Distribution:"); report.AppendLine($" Directional: {directionalCount}"); report.AppendLine($" Point: {pointCount}"); report.AppendLine($" Spot: {spotCount}"); report.AppendLine(); float totalIntensity = allLights.Sum(l => l.intensity); float avgIntensity = allLights.Length > 0 ? totalIntensity / allLights.Length : 0; report.AppendLine("Intensity Statistics:"); report.AppendLine($" Total Intensity: {totalIntensity:F2}"); report.AppendLine($" Average Intensity: {avgIntensity:F2}"); report.AppendLine(); int shadowLightsCount = allLights.Count(l => l.shadows != LightShadows.None); report.AppendLine($"Lights with Shadows: {shadowLightsCount}"); return report.ToString(); } }
|
总结
本章详细介绍了URP光照系统的各个方面,包括实时光照与烘焙光照的混合使用、Light Layers与Rendering Layers、三种光照模式(Baked、Realtime、Mixed)的特点和应用、Reflection Probes的使用、Light Cookies与IES配置文件,以及体积光和光照优化策略。
通过理论讲解、代码示例和实践练习,我们学习了:
光照模式:理解了实时光照、烘焙光照和混合光照的工作原理和适用场景,掌握了如何在不同模式间切换和配置。
Light Layers:学会了如何使用Light Layers来控制光源对特定物体的影响,实现更精细的光照控制。
反射探针:掌握了Reflection Probes在URP中的配置和使用方法,用于实现高质量的环境反射效果。
光照优化:学习了多种光照性能优化策略,包括光源剔除、动态管理、质量分级等。
实践应用:通过创建动态光照切换系统和性能分析工具,将理论知识转化为实际可用的解决方案。
URP光照系统提供了灵活而强大的光照解决方案,正确配置和优化光照系统对于创建高质量的视觉效果和保持良好性能至关重要。在下一章中,我们将深入探讨URP阴影系统,了解阴影的生成原理和优化方法。