第6章 URP阴影系统
理论讲解
级联阴影贴图(Cascade Shadow Maps)
级联阴影贴图(Cascaded Shadow Maps, CSM)是现代渲染管线中处理大范围定向光源阴影的重要技术。在URP中,CSM被用于主光源(通常是太阳光)的阴影渲染,以在不同距离上提供高质量的阴影效果。
级联阴影的工作原理
级联阴影贴图将相机的视锥体分割成多个区域(通常是2个或4个),每个区域使用不同分辨率的阴影贴图。距离相机较近的区域使用高分辨率阴影贴图,而距离较远的区域使用低分辨率阴影贴图。这种方法在保持视觉质量的同时,优化了性能和内存使用。
阴影贴图的级联结构:
- 近级联:高分辨率,覆盖距离相机最近的区域
- 中级联:中等分辨率,覆盖中等距离的区域
- 远级联:低分辨率,覆盖最远距离的区域
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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class CascadeShadowMapController : MonoBehaviour { [Header("Cascade Shadow Configuration")] public Light mainLight; public int cascadeCount = 4; [Range(0.01f, 1.0f)] public float cascadeSplitLambda = 0.5f; [Range(0.0f, 1.0f)] public float shadowDistance = 50.0f; [Header("Cascade Visualization")] public bool visualizeCascades = false; public Material cascadeVisualizationMaterial; private UniversalRenderPipelineAsset urpAsset; private float[] cascadeSplits = new float[4]; void Start() { SetupCascadeShadows(); } void SetupCascadeShadows() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null) { urpAsset.shadowDistance = shadowDistance; urpAsset.shadowCascadeOption = cascadeCount == 2 ? ShadowCascadeOption.TwoCascades : ShadowCascadeOption.FourCascades; urpAsset.shadowCascadeSplit = cascadeSplitLambda; } if(mainLight != null) { mainLight.shadows = LightShadows.Soft; } } public void CalculateCascadeSplits(Camera camera) { if(camera == null) return; float cameraNear = camera.nearClipPlane; float cameraFar = Mathf.Min(camera.farClipPlane, shadowDistance); float t0 = cameraNear; float t1 = cameraFar; for(int i = 0; i < cascadeCount - 1; i++) { float fraction = (float)(i + 1) / cascadeCount; float logSplit = Mathf.Lerp(t0, t1, Mathf.Pow(fraction, cascadeSplitLambda)); float uniformSplit = Mathf.Lerp(t0, t1, fraction); cascadeSplits[i] = Mathf.Lerp(logSplit, uniformSplit, 0.5f); } cascadeSplits[cascadeCount - 1] = cameraFar; } public string GetCascadeInfo() { string info = "Cascade Shadow Information:\n"; info += $"Cascade Count: {cascadeCount}\n"; info += $"Split Lambda: {cascadeSplitLambda:F3}\n"; info += $"Shadow Distance: {shadowDistance:F1}\n"; for(int i = 0; i < cascadeCount - 1; i++) { info += $"Cascade {i + 1} Split: {cascadeSplits[i]:F1}\n"; } return info; } public void AdjustCascadeParameters(int newCascadeCount, float newSplitLambda) { cascadeCount = Mathf.Clamp(newCascadeCount, 2, 4); cascadeSplitLambda = Mathf.Clamp01(newSplitLambda); if(urpAsset != null) { urpAsset.shadowCascadeOption = cascadeCount == 2 ? ShadowCascadeOption.TwoCascades : ShadowCascadeOption.FourCascades; urpAsset.shadowCascadeSplit = cascadeSplitLambda; } } }
|
阴影距离与分辨率配置
阴影系统中的距离和分辨率配置直接影响性能和视觉质量。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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class ShadowDistanceController : MonoBehaviour { [Header("Shadow Distance Configuration")] public float shadowDistance = 50.0f; public float shadowFadeStartDistance = 40.0f; [Range(0.0f, 1.0f)] public float shadowFadeStrength = 1.0f; [Header("Performance Settings")] public int shadowResolution = 2048; public int shadowAtlasResolution = 2048; private UniversalRenderPipelineAsset urpAsset; void Start() { ConfigureShadowDistance(); } void ConfigureShadowDistance() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null) { urpAsset.shadowDistance = shadowDistance; } } public void SetShadowDistance(float distance) { shadowDistance = Mathf.Max(1.0f, distance); if(urpAsset != null) { urpAsset.shadowDistance = shadowDistance; } } public string GetShadowDistanceInfo() { return $"Shadow Distance: {shadowDistance:F1}\n" + $"Fade Start Distance: {shadowFadeStartDistance:F1}\n" + $"Fade Strength: {shadowFadeStrength:F2}\n" + $"Shadow Resolution: {shadowResolution}\n" + $"Atlas Resolution: {shadowAtlasResolution}"; } public void OptimizeShadowDistance(Camera camera) { if(camera != null) { float optimizedDistance = Mathf.Min(camera.farClipPlane * 0.8f, 100.0f); SetShadowDistance(optimizedDistance); shadowFadeStartDistance = shadowDistance * 0.8f; } } }
|
阴影分辨率配置
阴影分辨率影响阴影的清晰度和性能。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 89 90 91 92 93 94 95 96 97
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class ShadowResolutionController : MonoBehaviour { [Header("Shadow Resolution Settings")] public LightShadowResolution resolution = LightShadowResolution.Medium; public int customResolution = 1024; public bool useCustomResolution = false; [Header("Atlas Configuration")] public int atlasWidth = 1024; public int atlasHeight = 1024; public float atlasPadding = 2.0f; private UniversalRenderPipelineAsset urpAsset; void Start() { ConfigureShadowResolution(); } void ConfigureShadowResolution() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null) { switch(resolution) { case LightShadowResolution.Low: urpAsset.shadowAtlasResolution = 256; break; case LightShadowResolution.Medium: urpAsset.shadowAtlasResolution = 512; break; case LightShadowResolution.High: urpAsset.shadowAtlasResolution = 1024; break; case LightShadowResolution.VeryHigh: urpAsset.shadowAtlasResolution = 2048; break; } if(useCustomResolution) { urpAsset.shadowAtlasResolution = customResolution; } } } public void SetShadowResolution(LightShadowResolution res) { resolution = res; ConfigureShadowResolution(); } public void SetCustomResolution(int res) { customResolution = Mathf.ClosestPowerOfTwo(Mathf.Clamp(res, 16, 4096)); useCustomResolution = true; ConfigureShadowResolution(); } public string GetResolutionInfo() { return $"Resolution Setting: {resolution}\n" + $"Custom Resolution: {(useCustomResolution ? customResolution.ToString() : "None")}\n" + $"Atlas Size: {atlasWidth}x{atlasHeight}\n" + $"Padding: {atlasPadding}"; } public void AutoSelectResolution(float performanceBudget = 1.0f) { if(performanceBudget > 0.8f) { SetShadowResolution(LightShadowResolution.VeryHigh); } else if(performanceBudget > 0.5f) { SetShadowResolution(LightShadowResolution.High); } else if(performanceBudget > 0.3f) { SetShadowResolution(LightShadowResolution.Medium); } else { SetShadowResolution(LightShadowResolution.Low); } } }
|
Shadow Bias参数调优
Shadow Bias(阴影偏移)是解决阴影渲染中常见问题(如阴影痤疮和彼得潘效应)的重要参数。
阴影痤疮(Shadow Acne)
阴影痤疮是由于深度比较精度问题导致的伪影,表现为物体表面出现不自然的条纹或斑点。
彼得潘效应(Peter Panning)
彼得潘效应是由于过大的阴影偏移导致阴影与物体分离的现象。
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; using UnityEngine.Rendering.Universal;
public class ShadowBiasController : MonoBehaviour { [Header("Shadow Bias Configuration")] [Range(0.0f, 2.0f)] public float shadowDepthBias = 1.0f; [Range(0.0f, 2.0f)] public float shadowNormalBias = 1.0f; [Range(0.0f, 1.0f)] public float shadowNearPlaneOffset = 0.2f; [Header("Bias Adjustment")] public float biasMultiplier = 1.0f; public float normalBiasMultiplier = 1.0f; private UniversalRenderPipelineAsset urpAsset; void Start() { ConfigureShadowBias(); } void ConfigureShadowBias() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null) { urpAsset.shadowDepthBias = shadowDepthBias * biasMultiplier; urpAsset.shadowNormalBias = shadowNormalBias * normalBiasMultiplier; urpAsset.shadowNearPlane = shadowNearPlaneOffset; } } public void AdjustBiasParameters(float depthBias, float normalBias) { shadowDepthBias = Mathf.Clamp(depthBias, 0.0f, 2.0f); shadowNormalBias = Mathf.Clamp(normalBias, 0.0f, 2.0f); ConfigureShadowBias(); } public void OptimizeBiasForScene(float sceneScale = 1.0f) { float scaleAdjustment = Mathf.Log(sceneScale, 10.0f) + 1.0f; shadowDepthBias = Mathf.Clamp(1.0f * scaleAdjustment, 0.1f, 2.0f); shadowNormalBias = Mathf.Clamp(0.5f * scaleAdjustment, 0.1f, 2.0f); ConfigureShadowBias(); } public string GetBiasInfo() { return $"Depth Bias: {shadowDepthBias:F3}\n" + $"Normal Bias: {shadowNormalBias:F3}\n" + $"Near Plane Offset: {shadowNearPlaneOffset:F3}\n" + $"Bias Multiplier: {biasMultiplier:F2}\n" + $"Normal Bias Multiplier: {normalBiasMultiplier:F2}"; } public void TestShadowQuality() { Debug.Log("Testing shadow quality with current bias settings..."); CheckForShadowAcne(); CheckForPeterPanning(); } void CheckForShadowAcne() { Debug.Log("Checking for shadow acne..."); } void CheckForPeterPanning() { Debug.Log("Checking for Peter Panning effect..."); } }
|
软阴影实现原理
软阴影通过模拟真实世界中光源具有一定尺寸的效果,使阴影边缘呈现柔和的过渡效果。
PCF(Percentage Closer Filtering)
PCF是最常用的软阴影技术之一,通过对阴影贴图进行多次采样并计算平均值来实现软阴影效果。
VSM(Variance Shadow Maps)
VSM使用统计方法来计算软阴影,可以产生更自然的软阴影效果。
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; using UnityEngine.Rendering.Universal;
public class SoftShadowController : MonoBehaviour { [Header("Soft Shadow Configuration")] public Light lightWithSoftShadows; public float softShadowQuality = 1.0f; public float shadowSoftness = 0.5f; public int sampleCount = 4; [Header("Advanced Soft Shadow Settings")] public float shadowRadius = 1.0f; public float lightSize = 0.5f; public float distanceScale = 1.0f; void Start() { ConfigureSoftShadows(); } void ConfigureSoftShadows() { if(lightWithSoftShadows != null) { lightWithSoftShadows.shadows = LightShadows.Soft; lightWithSoftShadows.shadowRadius = shadowRadius; lightWithSoftShadows.shadowAngle = lightSize; } } public void AdjustSoftShadowParameters(float softness, int samples) { shadowSoftness = Mathf.Clamp01(softness); sampleCount = Mathf.Clamp(samples, 1, 16); UpdateSoftShadowSettings(); } void UpdateSoftShadowSettings() { if(lightWithSoftShadows != null) { lightWithSoftShadows.shadowRadius = shadowRadius * shadowSoftness; } } public string GetSoftShadowInfo() { return $"Soft Shadow Quality: {softShadowQuality:F2}\n" + $"Shadow Softness: {shadowSoftness:F2}\n" + $"Sample Count: {sampleCount}\n" + $"Shadow Radius: {shadowRadius:F2}\n" + $"Light Size: {lightSize:F2}"; } public void OptimizeSoftShadowsForPerformance() { if(PerformanceCheck.IsLowEndDevice()) { shadowSoftness = 0.3f; sampleCount = 2; } else if(PerformanceCheck.IsMidRangeDevice()) { shadowSoftness = 0.6f; sampleCount = 4; } else { shadowSoftness = 1.0f; sampleCount = 8; } UpdateSoftShadowSettings(); } }
public static class PerformanceCheck { public static bool IsLowEndDevice() { return SystemInfo.systemMemorySize < 4000 || SystemInfo.graphicsMemorySize < 1000; } public static bool IsMidRangeDevice() { return SystemInfo.systemMemorySize >= 4000 && SystemInfo.systemMemorySize < 8000 && SystemInfo.graphicsMemorySize >= 1000 && SystemInfo.graphicsMemorySize < 4000; } public static bool IsHighEndDevice() { return SystemInfo.systemMemorySize >= 8000 && SystemInfo.graphicsMemorySize >= 4000; } }
|
附加光源阴影处理
除了主光源的级联阴影外,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 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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class AdditionalLightsShadowController : MonoBehaviour { [Header("Additional Lights Configuration")] public Light[] additionalLights; public int maxAdditionalLights = 4; public float additionalShadowDistance = 20.0f; public LightShadows additionalShadowType = LightShadows.Soft; [Header("Shadow Resolution for Additional Lights")] public LightShadowResolution additionalShadowResolution = LightShadowResolution.Medium; [Header("Performance Settings")] public bool enableAdditionalLightShadows = true; public float shadowFadeDistance = 15.0f; private UniversalRenderPipelineAsset urpAsset; void Start() { ConfigureAdditionalLightShadows(); } void ConfigureAdditionalLightShadows() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null) { urpAsset.maxAdditionalLightsCount = maxAdditionalLights; } foreach(var light in additionalLights) { if(light != null) { ConfigureAdditionalLight(light); } } } void ConfigureAdditionalLight(Light light) { if(!enableAdditionalLightShadows) { light.shadows = LightShadows.None; return; } light.shadows = additionalShadowType; light.shadowResolution = additionalShadowResolution; if(light.type == LightType.Point) { light.shadowStrength = 1.0f; } } public void ManageAdditionalLights(Camera mainCamera) { if(mainCamera == null) return; Vector3 cameraPos = mainCamera.transform.position; int activeLightCount = 0; foreach(var light in additionalLights) { if(light != null && activeLightCount < maxAdditionalLights) { float distance = Vector3.Distance(light.transform.position, cameraPos); if(distance <= additionalShadowDistance) { light.enabled = true; if(distance > shadowFadeDistance) { light.shadowResolution = LightShadowResolution.Low; light.shadowStrength = Mathf.InverseLerp(additionalShadowDistance, shadowFadeDistance, distance); } else { light.shadowResolution = additionalShadowResolution; light.shadowStrength = 1.0f; } activeLightCount++; } else { light.enabled = false; } } else if(light != null) { light.enabled = false; } } } public string GetAdditionalLightsInfo() { int enabledLights = 0; int shadowLights = 0; foreach(var light in additionalLights) { if(light != null && light.enabled) { enabledLights++; if(light.shadows != LightShadows.None) { shadowLights++; } } } return $"Additional Lights Configuration:\n" + $"Max Lights: {maxAdditionalLights}\n" + $"Enabled Lights: {enabledLights}\n" + $"Lights with Shadows: {shadowLights}\n" + $"Shadow Distance: {additionalShadowDistance:F1}\n" + $"Shadow Resolution: {additionalShadowResolution}"; } public void OptimizeAdditionalLightsPerformance() { if(PerformanceCheck.IsLowEndDevice()) { maxAdditionalLights = 2; additionalShadowType = LightShadows.Hard; additionalShadowResolution = LightShadowResolution.Low; } else if(PerformanceCheck.IsMidRangeDevice()) { maxAdditionalLights = 4; additionalShadowType = LightShadows.Soft; additionalShadowResolution = LightShadowResolution.Medium; } else { maxAdditionalLights = 8; additionalShadowType = LightShadows.Soft; additionalShadowResolution = LightShadowResolution.High; } ConfigureAdditionalLightShadows(); } }
|
移动平台阴影优化
移动平台由于硬件限制,需要特别的阴影优化策略。
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
| using UnityEngine; using UnityEngine.Rendering.Universal;
public class MobileShadowOptimization : MonoBehaviour { [Header("Mobile Shadow Settings")] public bool optimizeForMobile = true; [Range(1, 4)] public int mobileCascadeCount = 2; public LightShadowResolution mobileShadowResolution = LightShadowResolution.Low; public float mobileShadowDistance = 25.0f; [Header("Mobile-Specific Optimizations")] public bool useShadowMask = false; public bool reduceShadowQualityWithDistance = true; public float distanceQualityReductionFactor = 0.5f; private UniversalRenderPipelineAsset urpAsset; private bool isMobilePlatform = false; void Start() { DetectPlatformAndOptimize(); } void DetectPlatformAndOptimize() { isMobilePlatform = Application.isMobilePlatform; if(optimizeForMobile && isMobilePlatform) { ApplyMobileOptimizations(); } } void ApplyMobileOptimizations() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if(urpAsset != null) { urpAsset.shadowCascadeOption = mobileCascadeCount == 2 ? ShadowCascadeOption.TwoCascades : ShadowCascadeOption.FourCascades; urpAsset.shadowDistance = mobileShadowDistance; int resolutionValue = 0; switch(mobileShadowResolution) { case LightShadowResolution.Low: resolutionValue = 256; break; case LightShadowResolution.Medium: resolutionValue = 512; break; case LightShadowResolution.High: resolutionValue = 1024; break; case LightShadowResolution.VeryHigh: resolutionValue = 2048; break; } urpAsset.shadowAtlasResolution = resolutionValue; urpAsset.shadowDepthBias = Mathf.Max(urpAsset.shadowDepthBias, 1.5f); urpAsset.shadowNormalBias = Mathf.Max(urpAsset.shadowNormalBias, 1.0f); } Debug.Log("Applied mobile shadow optimizations"); } public void AdjustForDevicePerformance() { if(!isMobilePlatform) return; if(SystemInfo.systemMemorySize < 3000) { mobileShadowDistance = 15.0f; mobileShadowResolution = LightShadowResolution.Low; mobileCascadeCount = 2; } else if(SystemInfo.systemMemorySize < 6000) { mobileShadowDistance = 25.0f; mobileShadowResolution = LightShadowResolution.Medium; mobileCascadeCount = 2; } else { mobileShadowDistance = 35.0f; mobileShadowResolution = LightShadowResolution.High; mobileCascadeCount = 4; } ApplyMobileOptimizations(); } public string GetMobileOptimizationInfo() { return $"Mobile Optimization Status:\n" + $"Is Mobile: {isMobilePlatform}\n" + $"Cascade Count: {mobileCascadeCount}\n" + $"Shadow Resolution: {mobileShadowResolution}\n" + $"Shadow Distance: {mobileShadowDistance:F1}\n" + $"Distance Quality Reduction: {reduceShadowQualityWithDistance}"; } public void ToggleMobileOptimization(bool enable) { optimizeForMobile = enable; DetectPlatformAndOptimize(); } }
|
代码示例
完整的阴影管理系统
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
| using UnityEngine; using UnityEngine.Rendering.Universal; using System.Collections.Generic;
public class AdvancedShadowSystem : MonoBehaviour { [System.Serializable] public class ShadowQualitySettings { public string qualityName; public int cascadeCount = 4; public LightShadowResolution resolution = LightShadowResolution.Medium; public float shadowDistance = 50.0f; public float depthBias = 1.0f; public float normalBias = 0.5f; public LightShadows shadowType = LightShadows.Soft; public int maxAdditionalLights = 4; } [Header("Shadow System Configuration")] public List<ShadowQualitySettings> qualityPresets = new List<ShadowQualitySettings>(); public Light mainDirectionalLight; public Light[] additionalLights; [Header("Performance Monitoring")] public bool enablePerformanceMonitoring = true; public float performanceCheckInterval = 1.0f; private float lastPerformanceCheck = 0f; [Header("Current Settings")] public int currentQualityLevel = 0; private UniversalRenderPipelineAsset urpAsset; private Dictionary<string, ShadowQualitySettings> qualityMap = new Dictionary<string, ShadowQualitySettings>(); void Start() { InitializeShadowSystem(); } void Update() { if(enablePerformanceMonitoring && Time.time - lastPerformanceCheck >= performanceCheckInterval) { MonitorPerformance(); lastPerformanceCheck = Time.time; } } void InitializeShadowSystem() { foreach(var preset in qualityPresets) { if(!qualityMap.ContainsKey(preset.qualityName)) { qualityMap[preset.qualityName] = preset; } } urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; ApplyQualitySettings(currentQualityLevel); } public void ApplyQualitySettings(int level) { if(level < 0 || level >= qualityPresets.Count) { Debug.LogError($"Invalid quality level: {level}"); return; } currentQualityLevel = level; var settings = qualityPresets[level]; if(urpAsset != null) { urpAsset.shadowCascadeOption = settings.cascadeCount == 2 ? ShadowCascadeOption.TwoCascades : ShadowCascadeOption.FourCascades; urpAsset.shadowDistance = settings.shadowDistance; urpAsset.shadowDepthBias = settings.depthBias; urpAsset.shadowNormalBias = settings.normalBias; int resolutionValue = GetResolutionValue(settings.resolution); urpAsset.shadowAtlasResolution = resolutionValue; urpAsset.maxAdditionalLightsCount = settings.maxAdditionalLights; } if(mainDirectionalLight != null) { mainDirectionalLight.shadows = settings.shadowType; } ConfigureAdditionalLights(settings); Debug.Log($"Applied shadow quality: {settings.qualityName}"); } int GetResolutionValue(LightShadowResolution resolution) { switch(resolution) { case LightShadowResolution.Low: return 256; case LightShadowResolution.Medium: return 512; case LightShadowResolution.High: return 1024; case LightShadowResolution.VeryHigh: return 2048; default: return 512; } } void ConfigureAdditionalLights(ShadowQualitySettings settings) { foreach(var light in additionalLights) { if(light != null) { light.shadows = settings.shadowType; light.shadowResolution = settings.resolution; } } } public void ApplyQualityByName(string qualityName) { if(qualityMap.ContainsKey(qualityName)) { int index = qualityPresets.IndexOf(qualityMap[qualityName]); if(index >= 0) { ApplyQualitySettings(index); } } } void MonitorPerformance() { float currentFPS = 1.0f / Time.unscaledDeltaTime; if(currentFPS < 30f && currentQualityLevel > 0) { int newQuality = Mathf.Max(0, currentQualityLevel - 1); ApplyQualitySettings(newQuality); Debug.LogWarning($"Performance warning! Lowered shadow quality to maintain FPS. Current FPS: {currentFPS:F1}"); } else if(currentFPS > 50f && currentQualityLevel < qualityPresets.Count - 1) { int newQuality = Mathf.Min(qualityPresets.Count - 1, currentQualityLevel + 1); ApplyQualitySettings(newQuality); Debug.Log($"Performance good! Increased shadow quality. Current FPS: {currentFPS:F1}"); } } public string GetShadowSystemInfo() { if(urpAsset != null && qualityPresets.Count > currentQualityLevel) { var currentSettings = qualityPresets[currentQualityLevel]; float currentFPS = 1.0f / Time.unscaledDeltaTime; return $"Shadow System Information:\n" + $"Current Quality: {currentSettings.qualityName}\n" + $"Cascade Count: {currentSettings.cascadeCount}\n" + $"Shadow Distance: {currentSettings.shadowDistance:F1}\n" + $"Shadow Resolution: {currentSettings.resolution}\n" + $"Max Additional Lights: {currentSettings.maxAdditionalLights}\n" + $"Current FPS: {currentFPS:F1}\n" + $"Main Light Shadows: {(mainDirectionalLight != null && mainDirectionalLight.shadows != LightShadows.None ? "Enabled" : "Disabled")}"; } return "Shadow system not properly initialized"; } public void AddQualityPreset(ShadowQualitySettings preset) { if(!qualityMap.ContainsKey(preset.qualityName)) { qualityPresets.Add(preset); qualityMap[preset.qualityName] = preset; } } public void NextQualityLevel() { int nextLevel = (currentQualityLevel + 1) % qualityPresets.Count; ApplyQualitySettings(nextLevel); } public void PreviousQualityLevel() { int prevLevel = (currentQualityLevel - 1 + qualityPresets.Count) % qualityPresets.Count; ApplyQualitySettings(prevLevel); } public void AutoSelectQuality() { if(PerformanceCheck.IsLowEndDevice()) { ApplyQualityByName("Low"); } else if(PerformanceCheck.IsMidRangeDevice()) { ApplyQualityByName("Medium"); } else { ApplyQualityByName("High"); } } }
|
实践练习
练习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 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
| using UnityEngine; using UnityEngine.Rendering.Universal; using System.Collections;
public class DynamicShadowQualityController : MonoBehaviour { [System.Serializable] public class PerformanceBasedSettings { public string levelName; public float minFPS = 30f; public float maxFPS = 60f; public int cascadeCount = 2; public LightShadowResolution resolution = LightShadowResolution.Medium; public float shadowDistance = 30f; public int maxAdditionalLights = 4; } [Header("Dynamic Quality Configuration")] public PerformanceBasedSettings[] performanceLevels; public float qualityAdjustmentDelay = 2.0f; public float performanceCheckInterval = 0.5f; [Header("Current State")] public int currentLevelIndex = 0; private float averageFPS = 0f; private float[] fpsSamples = new float[10]; private int sampleIndex = 0; private bool isAdjustingQuality = false; private UniversalRenderPipelineAsset urpAsset; private Coroutine qualityAdjustmentCoroutine; void Start() { InitializeSystem(); } void InitializeSystem() { urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; for(int i = 0; i < fpsSamples.Length; i++) { fpsSamples[i] = 60f; } ApplyCurrentSettings(); } void Update() { UpdateFPSSampling(); if(!isAdjustingQuality) { CheckAndAdjustQuality(); } } void UpdateFPSSampling() { float currentFPS = 1.0f / Time.unscaledDeltaTime; fpsSamples[sampleIndex] = currentFPS; sampleIndex = (sampleIndex + 1) % fpsSamples.Length; float sum = 0f; for(int i = 0; i < fpsSamples.Length; i++) { sum += fpsSamples[i]; } averageFPS = sum / fpsSamples.Length; } void CheckAndAdjustQuality() { if(performanceLevels.Length == 0) return; int targetLevel = FindBestPerformanceLevel(); if(targetLevel != currentLevelIndex) { StartCoroutine(AdjustQualityWithDelay(targetLevel)); } } int FindBestPerformanceLevel() { for(int i = performanceLevels.Length - 1; i >= 0; i--) { var level = performanceLevels[i]; if(averageFPS >= level.minFPS) { return i; } } return 0; } IEnumerator AdjustQualityWithDelay(int targetLevel) { isAdjustingQuality = true; yield return new WaitForSeconds(qualityAdjustmentDelay); UpdateFPSSampling(); int finalTargetLevel = FindBestPerformanceLevel(); if(finalTargetLevel != currentLevelIndex) { ApplyPerformanceLevel(finalTargetLevel); Debug.Log($"Adjusted shadow quality from {performanceLevels[currentLevelIndex].levelName} " + $"to {performanceLevels[finalTargetLevel].levelName}. Avg FPS: {averageFPS:F1}"); } isAdjustingQuality = false; } void ApplyPerformanceLevel(int levelIndex) { if(levelIndex < 0 || levelIndex >= performanceLevels.Length) return; var level = performanceLevels[levelIndex]; currentLevelIndex = levelIndex; if(urpAsset != null) { urpAsset.shadowCascadeOption = level.cascadeCount == 2 ? ShadowCascadeOption.TwoCascades : ShadowCascadeOption.FourCascades; urpAsset.shadowDistance = level.shadowDistance; urpAsset.maxAdditionalLightsCount = level.maxAdditionalLights; int resolutionValue = GetResolutionValue(level.resolution); urpAsset.shadowAtlasResolution = resolutionValue; } } int GetResolutionValue(LightShadowResolution resolution) { switch(resolution) { case LightShadowResolution.Low: return 256; case LightShadowResolution.Medium: return 512; case LightShadowResolution.High: return 1024; case LightShadowResolution.VeryHigh: return 2048; default: return 512; } } void ApplyCurrentSettings() { if(currentLevelIndex >= 0 && currentLevelIndex < performanceLevels.Length) { ApplyPerformanceLevel(currentLevelIndex); } } public void SetPerformanceLevel(int levelIndex) { if(levelIndex >= 0 && levelIndex < performanceLevels.Length) { StopAdjustmentCoroutine(); ApplyPerformanceLevel(levelIndex); } } void StopAdjustmentCoroutine() { if(qualityAdjustmentCoroutine != null) { StopCoroutine(qualityAdjustmentCoroutine); } } public string GetSystemInfo() { if(currentLevelIndex >= 0 && currentLevelIndex < performanceLevels.Length) { var currentLevel = performanceLevels[currentLevelIndex]; return $"Dynamic Shadow Quality System:\n" + $"Current Level: {currentLevel.levelName}\n" + $"Average FPS: {averageFPS:F1}\n" + $"Target FPS Range: {currentLevel.minFPS:F0} - {currentLevel.maxFPS:F0}\n" + $"Cascade Count: {currentLevel.cascadeCount}\n" + $"Shadow Distance: {currentLevel.shadowDistance:F1}\n" + $"Resolution: {currentLevel.resolution}\n" + $"Adjusting Quality: {isAdjustingQuality}"; } return "System not initialized properly"; } public void ForcePerformanceReevaluation() { StopAdjustmentCoroutine(); CheckAndAdjustQuality(); } public void AddPerformanceLevel(PerformanceBasedSettings level) { System.Array.Resize(ref performanceLevels, performanceLevels.Length + 1); performanceLevels[performanceLevels.Length - 1] = level; } }
|
练习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 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
| using UnityEngine; using UnityEngine.Rendering.Universal; using System.Collections.Generic;
public class ShadowDiagnosticTool : MonoBehaviour { [Header("Diagnostic Settings")] public bool enableRealTimeDiagnostics = true; public float diagnosticInterval = 2.0f; private float lastDiagnosticTime = 0f; [Header("Shadow Problem Detection")] public float acneDetectionThreshold = 0.1f; public float panningDetectionThreshold = 0.5f; public float resolutionDetectionThreshold = 0.05f; [Header("Test Objects")] public GameObject[] testObjects; public Light testLight; public Camera diagnosticCamera; [Header("Results")] public List<string> detectedIssues = new List<string>(); public bool hasShadowProblems = false; private UniversalRenderPipelineAsset urpAsset; private Dictionary<string, ShadowProblemData> problemData = new Dictionary<string, ShadowProblemData>(); [System.Serializable] public class ShadowProblemData { public string problemType; public string description; public float severity; public float lastDetected; public bool isFixed; public List<string> suggestedSolutions = new List<string>(); } void Update() { if(enableRealTimeDiagnostics && Time.time - lastDiagnosticTime >= diagnosticInterval) { RunShadowDiagnostics(); lastDiagnosticTime = Time.time; } } public void RunShadowDiagnostics() { detectedIssues.Clear(); problemData.Clear(); hasShadowProblems = false; CheckForShadowAcne(); CheckForPeterPanning(); CheckShadowResolutionIssues(); CheckShadowDistanceIssues(); CheckShadowPerformanceIssues(); if(detectedIssues.Count > 0) { hasShadowProblems = true; Debug.LogWarning($"Shadow Diagnostic: Found {detectedIssues.Count} issues"); foreach(var issue in detectedIssues) { Debug.LogWarning($" - {issue}"); } } else { Debug.Log("Shadow Diagnostic: No issues detected"); } } void CheckForShadowAcne() { if(urpAsset != null) { if(urpAsset.shadowDepthBias < 0.5f) { var problem = new ShadowProblemData { problemType = "Shadow Acne", description = "Shadow acne detected due to low depth bias", severity = 0.7f, lastDetected = Time.time, suggestedSolutions = { "Increase shadow depth bias", "Adjust normal bias", "Check material properties" } }; problemData["ShadowAcne"] = problem; detectedIssues.Add("Shadow Acne: Depth bias too low"); } } } void CheckForPeterPanning() { if(urpAsset != null) { if(urpAsset.shadowDepthBias > 2.0f) { var problem = new ShadowProblemData { problemType = "Peter Panning", description = "Peter Panning effect detected due to high depth bias", severity = 0.6f, lastDetected = Time.time, suggestedSolutions = { "Decrease shadow depth bias", "Adjust normal bias", "Check light distance" } }; problemData["PeterPanning"] = problem; detectedIssues.Add("Peter Panning: Depth bias too high"); } } } void CheckShadowResolutionIssues() { if(urpAsset != null) { if(urpAsset.shadowAtlasResolution < 512) { var problem = new ShadowProblemData { problemType = "Low Resolution", description = "Shadow resolution too low causing pixelated shadows", severity = 0.4f, lastDetected = Time.time, suggestedSolutions = { "Increase shadow atlas resolution", "Balance quality with performance", "Consider target platform capabilities" } }; problemData["LowResolution"] = problem; detectedIssues.Add("Low Resolution: Shadow atlas resolution too low"); } } } void CheckShadowDistanceIssues() { if(urpAsset != null && Camera.main != null) { if(urpAsset.shadowDistance > Camera.main.farClipPlane * 1.5f) { var problem = new ShadowProblemData { problemType = "Shadow Distance", description = "Shadow distance set too high relative to camera range", severity = 0.3f, lastDetected = Time.time, suggestedSolutions = { "Reduce shadow distance", "Match to camera far clip plane", "Consider performance impact" } }; problemData["ShadowDistance"] = problem; detectedIssues.Add("Shadow Distance: Set too high"); } } } void CheckShadowPerformanceIssues() { if(urpAsset != null) { int cascadeCount = urpAsset.shadowCascadeOption == ShadowCascadeOption.FourCascades ? 4 : 2; if(cascadeCount == 4 && SystemInfo.graphicsMemorySize < 2000) { var problem = new ShadowProblemData { problemType = "Performance", description = "4-cascade shadows on low-end hardware", severity = 0.5f, lastDetected = Time.time, suggestedSolutions = { "Reduce to 2-cascade shadows", "Lower shadow resolution", "Optimize for target hardware" } }; problemData["Performance"] = problem; detectedIssues.Add("Performance: 4-cascade shadows on low-end hardware"); } } } public string GetDiagnosticReport() { var report = new System.Text.StringBuilder(); report.AppendLine("=== Shadow Diagnostic Report ==="); report.AppendLine($"Total Issues Found: {detectedIssues.Count}"); report.AppendLine($"Last Run: {System.DateTime.Now:HH:mm:ss}"); report.AppendLine(); if(detectedIssues.Count > 0) { report.AppendLine("Detected Issues:"); foreach(var issue in detectedIssues) { report.AppendLine($" • {issue}"); } report.AppendLine(); } if(problemData.Count > 0) { report.AppendLine("Detailed Problem Analysis:"); foreach(var kvp in problemData) { var problem = kvp.Value; report.AppendLine($"Problem: {problem.problemType}"); report.AppendLine($" Description: {problem.description}"); report.AppendLine($" Severity: {problem.severity * 100:F0}%"); report.AppendLine($" Solutions:"); foreach(var solution in problem.suggestedSolutions) { report.AppendLine($" - {solution}"); } report.AppendLine(); } } else { report.AppendLine("No shadow problems detected. System appears to be configured correctly."); } return report.ToString(); } public void ApplyRecommendedFixes() { int fixesApplied = 0; foreach(var kvp in problemData) { var problem = kvp.Value; if(!problem.isFixed) { switch(problem.problemType) { case "Shadow Acne": if(urpAsset != null) { urpAsset.shadowDepthBias = Mathf.Clamp(urpAsset.shadowDepthBias + 0.2f, 0.5f, 2.0f); problem.isFixed = true; fixesApplied++; } break; case "Peter Panning": if(urpAsset != null) { urpAsset.shadowDepthBias = Mathf.Clamp(urpAsset.shadowDepthBias - 0.2f, 0.1f, 1.5f); problem.isFixed = true; fixesApplied++; } break; case "Low Resolution": if(urpAsset != null) { urpAsset.shadowAtlasResolution = Mathf.Min(urpAsset.shadowAtlasResolution * 2, 2048); problem.isFixed = true; fixesApplied++; } break; } } } if(fixesApplied > 0) { Debug.Log($"Applied {fixesApplied} recommended fixes"); } else { Debug.Log("No fixes were applied"); } } public void ResetDiagnostics() { detectedIssues.Clear(); problemData.Clear(); hasShadowProblems = false; } public void ManualRunDiagnostics() { RunShadowDiagnostics(); } public string GetPerformanceRecommendations() { var recommendations = new List<string>(); if(SystemInfo.graphicsMemorySize < 2000) { recommendations.Add("• Use lower shadow resolution (256-512)"); recommendations.Add("• Limit to 2-cascade shadows"); recommendations.Add("• Reduce shadow distance"); } if(SystemInfo.systemMemorySize < 4000) { recommendations.Add("• Reduce max additional lights count"); recommendations.Add("• Use simpler shadow receiver materials"); } if(recommendations.Count == 0) { recommendations.Add("• Current settings appear appropriate for hardware"); } return "Performance Recommendations:\n" + string.Join("\n", recommendations); } }
|
总结
本章详细介绍了URP阴影系统的各个方面,包括级联阴影贴图的工作原理、阴影距离与分辨率配置、Shadow Bias参数调优、软阴影实现原理、附加光源阴影处理,以及移动平台阴影优化策略。
通过理论讲解、代码示例和实践练习,我们学习了:
级联阴影贴图:理解了CSM技术如何在不同距离上提供高质量阴影,以及如何配置级联参数以平衡性能和视觉质量。
阴影配置:掌握了阴影距离、分辨率等关键参数的设置方法,以及如何根据场景需求进行优化。
Shadow Bias调优:学会了如何正确设置深度偏移和法线偏移,以避免阴影痤疮和彼得潘效应等常见问题。
软阴影技术:了解了软阴影的实现原理和配置方法,以及如何在质量和性能之间找到平衡。
附加光源阴影:掌握了如何配置和优化附加光源的阴影效果。
移动平台优化:学习了针对移动设备的特殊阴影优化策略。
实践应用:通过创建动态阴影质量调整系统和阴影问题诊断工具,将理论知识转化为实际可用的解决方案。
阴影系统是渲染管线中的重要组成部分,正确配置和优化阴影系统对于创建高质量的视觉效果和保持良好性能至关重要。在下一章中,我们将深入探讨URP后处理系统,了解各种后处理效果的实现原理和使用方法。