第22章 移动平台优化
理论讲解
22.1 移动端URP配置策略
移动平台与桌面平台在硬件性能、内存容量、功耗限制等方面存在显著差异,因此需要针对移动端特性进行专门的URP配置优化。
移动端硬件特点:
- GPU性能相对较弱
- 内存容量有限
- 电池续航考虑
- 发热控制需求
- 不同设备性能差异大
URP移动端优化策略:
- 渲染质量调整:降低阴影分辨率、减少后处理效果
- 光照简化:使用烘焙光照替代部分实时光照
- 纹理压缩:使用适合移动端的纹理格式
- LOD系统:根据距离和性能动态调整细节级别
URP Asset移动端配置:
- 渲染质量:降低阴影、抗锯齿、后处理质量
- 光照设置:优先使用烘焙光照,限制实时光源数量
- 阴影设置:降低阴影分辨率和级联数量
- 后处理:禁用或简化复杂后处理效果
22.2 LOD与距离剔除
LOD(Level of Detail)系统是移动端性能优化的关键技术,通过根据对象与摄像机的距离动态调整模型和材质的细节级别来节省性能。
LOD系统原理:
- 根据距离切换不同细节级别的模型
- 动态调整材质复杂度
- 优化纹理分辨率
- 调整光照计算精度
距离剔除机制:
- 超出距离阈值的对象不进行渲染
- 减少远距离对象的计算开销
- 优化渲染批次管理
LOD实现策略:
- 几何LOD:使用不同复杂度的网格
- 材质LOD:简化材质属性和着色器
- 光照LOD:调整光照计算精度
- 纹理LOD:使用Mipmap和压缩格式
22.3 Shader变体管理
Shader变体是影响移动端性能的重要因素,过多的变体会导致内存占用增加和加载时间延长。
Shader变体生成机制:
- 条件编译指令生成不同变体
- 多个关键字组合产生指数级变体
- 运行时根据材质属性选择变体
变体管理策略:
- 减少关键字数量:合并相似功能
- 使用LOD关键字:根据平台性能选择变体
- 预加载必要变体:避免运行时编译
- 剔除无用变体:移除不使用的变体组合
移动端Shader优化:
- 简化计算复杂度
- 减少纹理采样次数
- 优化分支逻辑
- 使用适合移动端的指令
22.4 纹理压缩格式选择
纹理是移动端内存占用的主要来源之一,选择合适的压缩格式对性能优化至关重要。
移动端常用纹理格式:
- ASTC:适用于iOS和Android,质量高,压缩率可变
- ETC2:Android标准格式,兼容性好
- PVRTC:iOS专用格式,压缩效率高
- DXT/BC:桌面平台格式,移动端不适用
纹理优化策略:
- 根据平台选择合适格式
- 调整压缩质量参数
- 使用适当的纹理尺寸
- 合理设置Mipmap
纹理内存管理:
- 异步加载纹理
- 按需加载和卸载
- 使用纹理图集减少Draw Call
- 优化纹理采样器使用
22.5 光照与阴影简化
移动端的光照和阴影计算需要特别优化,以平衡视觉效果和性能开销。
光照优化策略:
- 烘焙光照优先:使用Lightmap替代实时光照
- 限制光源数量:减少每像素光照计算
- 简化光照模型:使用简化的BRDF计算
- 光照烘焙优化:优化UV展开和光照贴图分辨率
阴影优化策略:
- 降低阴影分辨率:减少阴影贴图大小
- 限制阴影距离:只对近距离对象投射阴影
- 简化阴影计算:使用简化的阴影算法
- 阴影级联优化:调整级联数量和分布
移动端光照技术:
- Light Probes:用于动态对象的间接光照
- Reflection Probes:简化反射计算
- Light Layers:优化光照分配
- 混合光照:结合烘焙和实时光照
22.6 性能分析工具使用
移动端性能分析是优化工作的基础,需要使用专业工具监控关键性能指标。
Unity性能分析工具:
- Profiler:CPU/GPU性能分析
- Frame Debugger:渲染过程调试
- GPU Profiler:GPU性能监控
- Memory Profiler:内存使用分析
关键性能指标:
- FPS:帧率稳定性
- Draw Calls:绘制调用数量
- Batching:批处理效果
- Memory Usage:内存占用
- GPU Utilization:GPU利用率
性能优化流程:
- 基线测量:建立性能基准
- 瓶颈识别:找出性能瓶颈
- 优化实施:应用优化策略
- 效果验证:验证优化效果
- 持续监控:保持性能稳定
代码示例
22.7 移动端优化管理器
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 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using System;
public class MobileOptimizationManager : MonoBehaviour { [Header("Performance Settings")] public bool enableMobileOptimizations = true; public bool autoAdjustQuality = true; public float qualityAdjustInterval = 1.0f; [Header("LOD Settings")] public int maxLODLevel = 2; public float lodBias = 1.0f; public float distanceScale = 1.0f; [Header("Culling Settings")] public float cullingDistance = 100f; public float cullingUpdateInterval = 0.5f; [Header("Shader Optimization")] public bool enableShaderVariantReduction = true; public bool useMobileShaders = true; public string[] mobileShaderKeywords = new string[0]; [Header("Texture Optimization")] public bool enableTextureStreaming = true; public int textureQuality = 1; public FilterMode textureFilterMode = FilterMode.Bilinear; [Header("Lighting Optimization")] public bool preferBakedLighting = true; public int maxRealtimeLights = 2; public int shadowResolution = 512; public float shadowDistance = 50f; [Header("Memory Management")] public float memoryCheckInterval = 2.0f; public long maxMemoryUsage = 500000000; public bool enableMemoryOptimization = true; private float m_LastQualityAdjustTime = 0f; private float m_LastCullingUpdateTime = 0f; private float m_LastMemoryCheckTime = 0f; private int m_CurrentLODLevel = 0; private List<Renderer> m_CulledRenderers = new List<Renderer>(); private List<Light> m_RealtimeLights = new List<Light>(); [System.Serializable] public class MobilePerformanceData { public float averageFPS; public int drawCalls; public int batches; public long memoryUsage; public float gpuUtilization; public int realtimeLightsCount; public float shadowResolutionScale; public bool isOptimal; public System.DateTime lastUpdate; } [System.Serializable] public class MobileOptimizationSettings { public int lodLevel; public float shadowDistance; public int maxRealtimeLights; public bool useBakedLighting; public FilterMode textureFilterMode; public bool enablePostProcessing; public bool enableShadows; public bool enableMSAA; public System.DateTime lastUpdate; } public MobilePerformanceData performanceData = new MobilePerformanceData(); public MobileOptimizationSettings optimizationSettings = new MobileOptimizationSettings(); void Start() { if (enableMobileOptimizations) { InitializeMobileOptimizations(); } } void Update() { if (enableMobileOptimizations) { if (autoAdjustQuality && Time.time - m_LastQualityAdjustTime >= qualityAdjustInterval) { AdjustQualitySettings(); m_LastQualityAdjustTime = Time.time; } if (Time.time - m_LastCullingUpdateTime >= cullingUpdateInterval) { UpdateCulling(); m_LastCullingUpdateTime = Time.time; } if (enableMemoryOptimization && Time.time - m_LastMemoryCheckTime >= memoryCheckInterval) { CheckMemoryUsage(); m_LastMemoryCheckTime = Time.time; } } } private void InitializeMobileOptimizations() { QualitySettings.maximumLODLevel = maxLODLevel; QualitySettings.lodBias = lodBias; UpdateRealtimeLights(); ApplyTextureOptimizations(); ApplyLightingOptimizations(); Debug.Log("[MobileOptimizationManager] Initialized mobile optimizations"); } private void UpdateRealtimeLights() { m_RealtimeLights.Clear(); var lights = FindObjectsOfType<Light>(); foreach (var light in lights) { if (light != null && light.enabled && light.type != LightType.Baked) { m_RealtimeLights.Add(light); } } if (m_RealtimeLights.Count > maxRealtimeLights) { for (int i = maxRealtimeLights; i < m_RealtimeLights.Count; i++) { m_RealtimeLights[i].enabled = false; } } } private void ApplyTextureOptimizations() { QualitySettings.masterTextureLimit = Mathf.Clamp(3 - textureQuality, 0, 2); var renderers = FindObjectsOfType<Renderer>(); foreach (var renderer in renderers) { if (renderer != null && renderer.sharedMaterials != null) { foreach (var material in renderer.sharedMaterials) { if (material != null) { var textures = GetMaterialTextures(material); foreach (var texture in textures) { if (texture != null) { } } } } } } } private void ApplyLightingOptimizations() { var urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset; if (urpAsset != null) { } if (QualitySettings.shadowDistance != shadowDistance) { QualitySettings.shadowDistance = shadowDistance; } QualitySettings.shadowResolution = (ShadowResolution)Mathf.Clamp(shadowResolution / 512, 0, 3); } private List<Texture> GetMaterialTextures(Material material) { var textures = new List<Texture>(); var propertyCount = ShaderUtil.GetPropertyCount(material.shader); for (int i = 0; i < propertyCount; i++) { if (ShaderUtil.GetPropertyType(material.shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) { var texture = material.GetTexture(ShaderUtil.GetPropertyName(material.shader, i)); if (texture != null) { textures.Add(texture); } } } return textures; } private void AdjustQualitySettings() { var currentFPS = 1.0f / Time.unscaledDeltaTime; var drawCalls = UnityEngine.Rendering.Profiling.Profiler.GetStat(UnityEngine.Rendering.Profiling.CustomSamplerId.DrawCalls).value; if (currentFPS < 30f) { AdjustToLowQuality(); } else if (currentFPS > 55f) { AdjustToHighQuality(); } UpdatePerformanceData(currentFPS, drawCalls); } private void AdjustToLowQuality() { if (QualitySettings.maximumLODLevel < maxLODLevel) { QualitySettings.maximumLODLevel++; } if (QualitySettings.shadowDistance > 20f) { QualitySettings.shadowDistance = Mathf.Max(20f, QualitySettings.shadowDistance * 0.8f); } if (m_RealtimeLights.Count > 1) { for (int i = 1; i < m_RealtimeLights.Count; i++) { if (m_RealtimeLights[i] != null) { m_RealtimeLights[i].enabled = false; } } } } private void AdjustToHighQuality() { if (QualitySettings.maximumLODLevel > 0) { QualitySettings.maximumLODLevel--; } if (QualitySettings.shadowDistance < shadowDistance) { QualitySettings.shadowDistance = Mathf.Min(shadowDistance, QualitySettings.shadowDistance * 1.1f); } if (m_RealtimeLights.Count > 1) { for (int i = 1; i < Mathf.Min(maxRealtimeLights, m_RealtimeLights.Count); i++) { if (m_RealtimeLights[i] != null) { m_RealtimeLights[i].enabled = true; } } } } private void UpdateCulling() { var allRenderers = FindObjectsOfType<Renderer>(); m_CulledRenderers.Clear(); foreach (var renderer in allRenderers) { if (renderer != null && renderer.enabled) { var distance = Vector3.Distance(renderer.transform.position, Camera.main.transform.position); if (distance > cullingDistance) { renderer.enabled = false; m_CulledRenderers.Add(renderer); } else { if (m_CulledRenderers.Contains(renderer)) { renderer.enabled = true; m_CulledRenderers.Remove(renderer); } } } } } private void CheckMemoryUsage() { var memoryUsage = System.GC.GetTotalMemory(false); if (memoryUsage > maxMemoryUsage) { TriggerMemoryOptimization(); } } private void TriggerMemoryOptimization() { System.GC.Collect(); Resources.UnloadUnusedAssets(); if (QualitySettings.masterTextureLimit < 2) { QualitySettings.masterTextureLimit++; } if (QualitySettings.maximumLODLevel < maxLODLevel) { QualitySettings.maximumLODLevel++; } } private void UpdatePerformanceData(float currentFPS, int currentDrawCalls) { performanceData.averageFPS = currentFPS; performanceData.drawCalls = currentDrawCalls; performanceData.batches = UnityEngine.Rendering.Profiling.Profiler.GetStat(UnityEngine.Rendering.Profiling.CustomSamplerId.Batches).value; performanceData.memoryUsage = System.GC.GetTotalMemory(false); performanceData.gpuUtilization = GetGPUUtilization(); performanceData.realtimeLightsCount = m_RealtimeLights.Count; performanceData.shadowResolutionScale = (float)QualitySettings.shadowResolution / 3f; performanceData.isOptimal = currentFPS >= 30f && currentDrawCalls < 1000; performanceData.lastUpdate = System.DateTime.Now; } private float GetGPUUtilization() { return -1f; } public MobileOptimizationSettings GetCurrentSettings() { optimizationSettings.lodLevel = QualitySettings.maximumLODLevel; optimizationSettings.shadowDistance = QualitySettings.shadowDistance; optimizationSettings.maxRealtimeLights = maxRealtimeLights; optimizationSettings.useBakedLighting = preferBakedLighting; optimizationSettings.textureFilterMode = textureFilterMode; optimizationSettings.enablePostProcessing = true; optimizationSettings.enableShadows = QualitySettings.shadows != ShadowQuality.Disable; optimizationSettings.enableMSAA = QualitySettings.antiAliasing > 0; optimizationSettings.lastUpdate = System.DateTime.Now; return optimizationSettings; } public MobilePerformanceData GetPerformanceData() { return performanceData; } public void ApplyOptimizations() { ApplyTextureOptimizations(); ApplyLightingOptimizations(); UpdateRealtimeLights(); } public void SetLODLevel(int level) { QualitySettings.maximumLODLevel = Mathf.Clamp(level, 0, maxLODLevel); } public void SetShadowDistance(float distance) { QualitySettings.shadowDistance = Mathf.Clamp(distance, 10f, 200f); } public void SetMaxRealtimeLights(int count) { maxRealtimeLights = Mathf.Clamp(count, 0, 8); UpdateRealtimeLights(); } public string GetOptimizationRecommendations() { var recommendations = new System.Text.StringBuilder(); if (performanceData.averageFPS < 30f) { recommendations.AppendLine("• 帧率过低,建议降低渲染质量"); } if (performanceData.drawCalls > 1000) { recommendations.AppendLine("• Draw Calls过多,建议优化批处理"); } if (performanceData.memoryUsage > maxMemoryUsage * 0.8f) { recommendations.AppendLine("• 内存使用过高,建议优化资源"); } if (m_RealtimeLights.Count > maxRealtimeLights) { recommendations.AppendLine("• 实时光源过多,建议使用烘焙光照"); } return recommendations.ToString(); } public string GetPerformanceSummary() { return $"FPS: {performanceData.averageFPS:F1}, " + $"Draw Calls: {performanceData.drawCalls}, " + $"Memory: {performanceData.memoryUsage / 1024 / 1024:F1}MB, " + $"Realtime Lights: {performanceData.realtimeLightsCount}"; } void OnDestroy() { m_CulledRenderers.Clear(); m_RealtimeLights.Clear(); } }
|
22.8 移动端纹理优化系统
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 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
| using System.Collections.Generic; using UnityEngine; using System;
public class MobileTextureOptimizer : MonoBehaviour { [Header("Texture Streaming Settings")] public bool enableTextureStreaming = true; public int streamingTextureCount = 50; public float textureStreamingUpdateInterval = 0.1f; [Header("Compression Settings")] public TextureFormat targetFormat = TextureFormat.ASTC_6x6; public int compressionQuality = 80; public bool useMipmap = true; public FilterMode filterMode = FilterMode.Bilinear; [Header("Resolution Settings")] public int maxTextureResolution = 1024; public int textureQualityLevel = 1; [Header("Memory Management")] public long maxTextureMemory = 100000000; public float memoryCheckInterval = 1.0f; private float m_LastStreamingUpdateTime = 0f; private float m_LastMemoryCheckTime = 0f; private Dictionary<Texture, TextureOptimizationData> m_TextureData = new Dictionary<Texture, TextureOptimizationData>(); private List<Texture> m_TexturePool = new List<Texture>(); [System.Serializable] public class TextureOptimizationData { public string name; public int originalSize; public int optimizedSize; public TextureFormat originalFormat; public TextureFormat optimizedFormat; public int resolution; public bool useMipmap; public FilterMode filterMode; public long memoryUsage; public bool isOptimized; public System.DateTime lastUpdate; } [System.Serializable] public class TextureOptimizationMetrics { public int totalTextures; public int optimizedTextures; public long totalMemoryUsage; public long optimizedMemoryUsage; public float memoryReductionPercentage; public int texturesExceedingLimit; public bool isWithinMemoryLimit; public System.DateTime lastUpdate; } public TextureOptimizationMetrics metrics = new TextureOptimizationMetrics(); void Start() { InitializeTextureOptimization(); } void Update() { if (enableTextureStreaming && Time.time - m_LastStreamingUpdateTime >= textureStreamingUpdateInterval) { UpdateTextureStreaming(); m_LastStreamingUpdateTime = Time.time; } if (Time.time - m_LastMemoryCheckTime >= memoryCheckInterval) { CheckTextureMemoryUsage(); m_LastMemoryCheckTime = Time.time; } } private void InitializeTextureOptimization() { CollectSceneTextures(); InitializeTextureData(); Debug.Log("[MobileTextureOptimizer] Initialized texture optimization system"); } private void CollectSceneTextures() { m_TexturePool.Clear(); var renderers = FindObjectsOfType<Renderer>(); foreach (var renderer in renderers) { if (renderer != null && renderer.sharedMaterials != null) { foreach (var material in renderer.sharedMaterials) { if (material != null) { var textures = GetMaterialTextures(material); foreach (var texture in textures) { if (texture != null && !m_TexturePool.Contains(texture)) { m_TexturePool.Add(texture); } } } } } } var canvases = FindObjectsOfType<Canvas>(); foreach (var canvas in canvases) { var images = canvas.GetComponentsInChildren<UnityEngine.UI.Image>(); foreach (var image in images) { if (image != null && image.sprite != null && image.sprite.texture != null) { var texture = image.sprite.texture; if (!m_TexturePool.Contains(texture)) { m_TexturePool.Add(texture); } } } } } private List<Texture> GetMaterialTextures(Material material) { var textures = new List<Texture>(); var propertyCount = ShaderUtil.GetPropertyCount(material.shader); for (int i = 0; i < propertyCount; i++) { if (ShaderUtil.GetPropertyType(material.shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) { var texture = material.GetTexture(ShaderUtil.GetPropertyName(material.shader, i)); if (texture != null) { textures.Add(texture); } } } return textures; } private void InitializeTextureData() { m_TextureData.Clear(); foreach (var texture in m_TexturePool) { if (texture != null) { var data = new TextureOptimizationData { name = texture.name, originalSize = CalculateTextureSize(texture), optimizedSize = CalculateTextureSize(texture), originalFormat = GetTextureFormat(texture), optimizedFormat = GetTextureFormat(texture), resolution = Mathf.Max(texture.width, texture.height), useMipmap = texture.mipmapCount > 1, filterMode = texture.filterMode, memoryUsage = CalculateTextureMemory(texture), isOptimized = false, lastUpdate = System.DateTime.Now }; m_TextureData[texture] = data; } } UpdateMetrics(); } private int CalculateTextureSize(Texture texture) { if (texture == null) return 0; return texture.width * texture.height * GetFormatBytesPerPixel(GetTextureFormat(texture)); } private TextureFormat GetTextureFormat(Texture texture) { if (texture is Texture2D tex2D) { return tex2D.format; } else if (texture is RenderTexture rt) { return GetRenderTextureFormat(rt); } return TextureFormat.RGBA32; } private TextureFormat GetRenderTextureFormat(RenderTexture rt) { switch (rt.format) { case RenderTextureFormat.ARGB32: return TextureFormat.RGBA32; case RenderTextureFormat.ARGBHalf: return TextureFormat.RGBAHalf; case RenderTextureFormat.ARGBFloat: return TextureFormat.RGBAFloat; default: return TextureFormat.RGBA32; } } private int GetFormatBytesPerPixel(TextureFormat format) { switch (format) { case TextureFormat.RGBA32: case TextureFormat.ARGB32: return 4; case TextureFormat.RGB24: return 3; case TextureFormat.Alpha8: return 1; case TextureFormat.RGBAHalf: return 8; case TextureFormat.RGBAFloat: return 16; default: return 4; } } private long CalculateTextureMemory(Texture texture) { if (texture == null) return 0; long size = texture.width * texture.height; size *= GetFormatBytesPerPixel(GetTextureFormat(texture)); if (texture.mipmapCount > 1) { int mipWidth = texture.width; int mipHeight = texture.height; while (mipWidth > 1 || mipHeight > 1) { mipWidth = Mathf.Max(1, mipWidth / 2); mipHeight = Mathf.Max(1, mipHeight / 2); size += mipWidth * mipHeight * GetFormatBytesPerPixel(GetTextureFormat(texture)); } } return size; } private void UpdateTextureStreaming() { foreach (var texture in m_TexturePool) { if (texture != null) { AdjustTextureStreaming(texture); } } } private void AdjustTextureStreaming(Texture texture) { if (m_TextureData.ContainsKey(texture)) { var data = m_TextureData[texture]; int targetResolution = maxTextureResolution; switch (textureQualityLevel) { case 0: targetResolution = Mathf.Min(512, maxTextureResolution); break; case 1: targetResolution = Mathf.Min(1024, maxTextureResolution); break; case 2: targetResolution = maxTextureResolution; break; } if (data.resolution > targetResolution) { } } } private void CheckTextureMemoryUsage() { long totalMemory = 0; foreach (var kvp in m_TextureData) { totalMemory += kvp.Value.memoryUsage; } metrics.totalMemoryUsage = totalMemory; metrics.isWithinMemoryLimit = totalMemory <= maxTextureMemory; if (totalMemory > maxTextureMemory) { OptimizeTextureMemory(); } UpdateMetrics(); } private void OptimizeTextureMemory() { var sortedTextures = new List<KeyValuePair<Texture, TextureOptimizationData>>(m_TextureData); sortedTextures.Sort((a, b) => b.Value.memoryUsage.CompareTo(a.Value.memoryUsage)); long memoryToFree = metrics.totalMemoryUsage - maxTextureMemory; long freedMemory = 0; foreach (var kvp in sortedTextures) { if (freedMemory >= memoryToFree) break; var texture = kvp.Key; var data = kvp.Value; if (data.resolution > 256) { freedMemory += data.memoryUsage / 2; } } } private void UpdateMetrics() { metrics.totalTextures = m_TexturePool.Count; metrics.optimizedTextures = 0; metrics.totalMemoryUsage = 0; metrics.optimizedMemoryUsage = 0; metrics.texturesExceedingLimit = 0; foreach (var kvp in m_TextureData) { var data = kvp.Value; metrics.totalMemoryUsage += data.memoryUsage; if (data.isOptimized) { metrics.optimizedTextures++; metrics.optimizedMemoryUsage += data.memoryUsage; } if (data.memoryUsage > maxTextureMemory / 10) { metrics.texturesExceedingLimit++; } } metrics.memoryReductionPercentage = metrics.totalMemoryUsage > 0 ? (float)(metrics.totalMemoryUsage - metrics.optimizedMemoryUsage) / metrics.totalMemoryUsage : 0f; metrics.lastUpdate = System.DateTime.Now; } public TextureOptimizationData GetTextureData(Texture texture) { if (m_TextureData.ContainsKey(texture)) { return m_TextureData[texture]; } return null; } public Dictionary<Texture, TextureOptimizationData> GetAllTextureData() { return new Dictionary<Texture, TextureOptimizationData>(m_TextureData); } public TextureOptimizationMetrics GetMetrics() { return metrics; } public void OptimizeTexture(Texture texture) { if (m_TextureData.ContainsKey(texture)) { var data = m_TextureData[texture]; data.optimizedFormat = targetFormat; data.useMipmap = useMipmap; data.filterMode = filterMode; data.optimizedSize = CalculateOptimizedSize(data); data.isOptimized = true; data.lastUpdate = System.DateTime.Now; UpdateMetrics(); } } private int CalculateOptimizedSize(TextureOptimizationData data) { int width = Mathf.Min(data.resolution, maxTextureResolution); int height = Mathf.Min(data.resolution, maxTextureResolution); return width * height * GetFormatBytesPerPixel(targetFormat); } public void OptimizeAllTextures() { foreach (var texture in m_TexturePool) { if (texture != null) { OptimizeTexture(texture); } } } public string GetTextureOptimizationSuggestions() { var suggestions = new System.Text.StringBuilder(); if (metrics.totalMemoryUsage > maxTextureMemory) { suggestions.AppendLine("• 纹理内存使用超出限制,建议压缩纹理"); } if (metrics.texturesExceedingLimit > 0) { suggestions.AppendLine($"• 有 {metrics.texturesExceedingLimit} 个大纹理,建议降低分辨率"); } if (textureQualityLevel > 1) { suggestions.AppendLine("• 纹理质量设置过高,可考虑降低以节省内存"); } return suggestions.ToString(); } public string GetOptimizationReport() { var report = new System.Text.StringBuilder(); report.AppendLine("=== Mobile Texture Optimization Report ==="); report.AppendLine($"Total Textures: {metrics.totalTextures}"); report.AppendLine($"Optimized Textures: {metrics.optimizedTextures}"); report.AppendLine($"Total Memory: {metrics.totalMemoryUsage / 1024 / 1024:F2} MB"); report.AppendLine($"Optimized Memory: {metrics.optimizedMemoryUsage / 1024 / 1024:F2} MB"); report.AppendLine($"Memory Reduction: {metrics.memoryReductionPercentage:F2}%"); report.AppendLine($"Large Textures: {metrics.texturesExceedingLimit}"); report.AppendLine($"Within Limit: {metrics.isWithinMemoryLimit}"); report.AppendLine($"Last Update: {metrics.lastUpdate}"); return report.ToString(); } public void ResetOptimizationData() { foreach (var kvp in m_TextureData) { kvp.Value.isOptimized = false; kvp.Value.optimizedFormat = kvp.Value.originalFormat; kvp.Value.optimizedSize = kvp.Value.originalSize; } UpdateMetrics(); } }
|
22.9 移动端光照优化系统
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 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using System;
public class MobileLightingOptimizer : MonoBehaviour { [Header("Lighting Optimization Settings")] public bool enableLightOptimization = true; public bool preferBakedLighting = true; public int maxRealtimeLights = 2; public float realtimeLightDistance = 10f; public bool enableLightCulling = true; public float lightCullingInterval = 0.5f; [Header("Shadow Optimization")] public bool enableShadowOptimization = true; public int shadowResolution = 512; public float shadowDistance = 20f; public float shadowBias = 0.2f; public float shadowNormalBias = 0.5f; [Header("LOD Settings")] public bool enableLightLOD = true; public float lodTransitionDistance = 30f; public int lodLevelCount = 3; [Header("Performance Settings")] public float performanceCheckInterval = 1.0f; public float targetFPS = 30f; public int maxDrawCalls = 1000; private float m_LastLightUpdate = 0f; private float m_LastPerformanceCheck = 0f; private List<Light> m_AllLights = new List<Light>(); private List<Light> m_RealtimeLights = new List<Light>(); private List<Light> m_BakedLights = new List<Light>(); private List<Light> m_DisabledLights = new List<Light>(); [System.Serializable] public class LightingOptimizationData { public int totalLights; public int realtimeLights; public int bakedLights; public int disabledLights; public int maxAllowedRealtimeLights; public float averageFPS; public int drawCalls; public bool isOptimal; public System.DateTime lastUpdate; } [System.Serializable] public class LightOptimizationSettings { public int maxRealtimeLights; public float realtimeLightDistance; public int shadowResolution; public float shadowDistance; public bool useBakedLighting; public bool enableShadows; public System.DateTime lastUpdate; } public LightingOptimizationData optimizationData = new LightingOptimizationData(); public LightOptimizationSettings optimizationSettings = new LightOptimizationSettings(); void Start() { if (enableLightOptimization) { InitializeLightOptimization(); } } void Update() { if (enableLightOptimization) { if (Time.time - m_LastLightUpdate >= lightCullingInterval) { UpdateLightCulling(); m_LastLightUpdate = Time.time; } if (Time.time - m_LastPerformanceCheck >= performanceCheckInterval) { CheckPerformanceAndAdjust(); m_LastPerformanceCheck = Time.time; } } } private void InitializeLightOptimization() { CollectAllLights(); ApplyLightOptimizations(); Debug.Log("[MobileLightingOptimizer] Initialized lighting optimization system"); } private void CollectAllLights() { m_AllLights.Clear(); m_RealtimeLights.Clear(); m_BakedLights.Clear(); m_DisabledLights.Clear(); var lights = FindObjectsOfType<Light>(); foreach (var light in lights) { if (light != null && light.enabled) { m_AllLights.Add(light); if (light.lightmapBakeType == LightmapBakeType.Baked || light.lightmapBakeType == LightmapBakeType.Mixed) { m_BakedLights.Add(light); } else { m_RealtimeLights.Add(light); } } } } private void ApplyLightOptimizations() { if (enableShadowOptimization) { QualitySettings.shadowResolution = (ShadowResolution)Mathf.Clamp(shadowResolution / 512, 0, 3); QualitySettings.shadowDistance = shadowDistance; } LimitRealtimeLights(); UpdateOptimizationSettings(); } private void LimitRealtimeLights() { if (m_RealtimeLights.Count <= maxRealtimeLights) { foreach (var light in m_RealtimeLights) { if (light != null && !light.enabled) { light.enabled = true; } } } else { for (int i = maxRealtimeLights; i < m_RealtimeLights.Count; i++) { if (m_RealtimeLights[i] != null && m_RealtimeLights[i].enabled) { m_RealtimeLights[i].enabled = false; if (!m_DisabledLights.Contains(m_RealtimeLights[i])) { m_DisabledLights.Add(m_RealtimeLights[i]); } } } for (int i = 0; i < maxRealtimeLights && i < m_RealtimeLights.Count; i++) { if (m_RealtimeLights[i] != null && !m_RealtimeLights[i].enabled) { m_RealtimeLights[i].enabled = true; m_DisabledLights.Remove(m_RealtimeLights[i]); } } } } private void UpdateLightCulling() { if (!enableLightCulling || Camera.main == null) return; var cameraPos = Camera.main.transform.position; foreach (var light in m_RealtimeLights) { if (light != null && light.enabled) { var distance = Vector3.Distance(light.transform.position, cameraPos); if (distance > realtimeLightDistance) { if (light.enabled) { light.enabled = false; if (!m_DisabledLights.Contains(light)) { m_DisabledLights.Add(light); } } } else { if (!light.enabled && m_DisabledLights.Contains(light)) { light.enabled = true; m_DisabledLights.Remove(light); } } } } } private void CheckPerformanceAndAdjust() { var currentFPS = 1.0f / Time.unscaledDeltaTime; var drawCalls = UnityEngine.Rendering.Profiling.Profiler.GetStat(UnityEngine.Rendering.Profiling.CustomSamplerId.DrawCalls).value; if (currentFPS < targetFPS * 0.8f) { ReduceLightingQuality(currentFPS); } else if (currentFPS > targetFPS * 1.1f) { IncreaseLightingQuality(currentFPS); } UpdateOptimizationData(currentFPS, drawCalls); } private void ReduceLightingQuality(float currentFPS) { bool changed = false; if (maxRealtimeLights > 0) { maxRealtimeLights = Mathf.Max(0, maxRealtimeLights - 1); changed = true; } if (QualitySettings.shadowDistance > 10f) { QualitySettings.shadowDistance = Mathf.Max(10f, QualitySettings.shadowDistance * 0.8f); changed = true; } if (QualitySettings.shadowResolution > 0) { QualitySettings.shadowResolution = (ShadowResolution)Mathf.Max(0, (int)QualitySettings.shadowResolution - 1); changed = true; } if (changed) { LimitRealtimeLights(); UpdateOptimizationSettings(); } } private void IncreaseLightingQuality(float currentFPS) { bool changed = false; if (maxRealtimeLights < 4) { maxRealtimeLights = Mathf.Min(4, maxRealtimeLights + 1); changed = true; } if (QualitySettings.shadowDistance < shadowDistance) { QualitySettings.shadowDistance = Mathf.Min(shadowDistance, QualitySettings.shadowDistance * 1.1f); changed = true; } if (changed) { LimitRealtimeLights(); UpdateOptimizationSettings(); } } private void UpdateOptimizationData(float currentFPS, int drawCalls) { optimizationData.totalLights = m_AllLights.Count; optimizationData.realtimeLights = m_RealtimeLights.Count; optimizationData.bakedLights = m_BakedLights.Count; optimizationData.disabledLights = m_DisabledLights.Count; optimizationData.maxAllowedRealtimeLights = maxRealtimeLights; optimizationData.averageFPS = currentFPS; optimizationData.drawCalls = drawCalls; optimizationData.isOptimal = currentFPS >= targetFPS && drawCalls <= maxDrawCalls; optimizationData.lastUpdate = System.DateTime.Now; } private void UpdateOptimizationSettings() { optimizationSettings.maxRealtimeLights = maxRealtimeLights; optimizationSettings.realtimeLightDistance = realtimeLightDistance; optimizationSettings.shadowResolution = shadowResolution; optimizationSettings.shadowDistance = shadowDistance; optimizationSettings.useBakedLighting = preferBakedLighting; optimizationSettings.enableShadows = QualitySettings.shadows != ShadowQuality.Disable; optimizationSettings.lastUpdate = System.DateTime.Now; } public LightingOptimizationData GetOptimizationData() { return optimizationData; } public LightOptimizationSettings GetOptimizationSettings() { return optimizationSettings; } public void ForceRefreshLights() { CollectAllLights(); ApplyLightOptimizations(); } public void SetMaxRealtimeLights(int count) { maxRealtimeLights = Mathf.Clamp(count, 0, 8); LimitRealtimeLights(); UpdateOptimizationSettings(); } public void SetRealtimeLightDistance(float distance) { realtimeLightDistance = Mathf.Max(5f, distance); } public void SetShadowDistance(float distance) { shadowDistance = Mathf.Clamp(distance, 10f, 100f); QualitySettings.shadowDistance = shadowDistance; } public List<Light> GetAllLights() { return new List<Light>(m_AllLights); } public List<Light> GetRealtimeLights() { return new List<Light>(m_RealtimeLights); } public List<Light> GetBakedLights() { return new List<Light>(m_BakedLights); } public List<Light> GetDisabledLights() { return new List<Light>(m_DisabledLights); } public void EnableLight(Light light) { if (light != null && m_RealtimeLights.Contains(light)) { int enabledRealtimeCount = 0; foreach (var rl in m_RealtimeLights) { if (rl != null && rl.enabled) { enabledRealtimeCount++; } } if (enabledRealtimeCount < maxRealtimeLights) { light.enabled = true; m_DisabledLights.Remove(light); } } } public void DisableLight(Light light) { if (light != null && m_RealtimeLights.Contains(light) && light.enabled) { light.enabled = false; if (!m_DisabledLights.Contains(light)) { m_DisabledLights.Add(light); } } } public string GetOptimizationSuggestions() { var suggestions = new System.Text.StringBuilder(); if (optimizationData.averageFPS < targetFPS) { suggestions.AppendLine("• 帧率过低,建议减少实时灯光数量"); } if (optimizationData.realtimeLights > maxRealtimeLights) { suggestions.AppendLine("• 实时光源过多,建议使用烘焙光照"); } if (optimizationData.drawCalls > maxDrawCalls) { suggestions.AppendLine("• 绘制调用过多,可能与光照相关"); } if (QualitySettings.shadowDistance > 30f) { suggestions.AppendLine("• 阴影距离过远,建议适当降低"); } return suggestions.ToString(); } public string GetPerformanceSummary() { return $"FPS: {optimizationData.averageFPS:F1}, " + $"Lights: {optimizationData.realtimeLights}/{optimizationData.totalLights}, " + $"Disabled: {optimizationData.disabledLights}, " + $"Draw Calls: {optimizationData.drawCalls}"; } public string GetOptimizationReport() { var report = new System.Text.StringBuilder(); report.AppendLine("=== Mobile Lighting Optimization Report ==="); report.AppendLine($"Total Lights: {optimizationData.totalLights}"); report.AppendLine($"Realtime Lights: {optimizationData.realtimeLights}"); report.AppendLine($"Baked Lights: {optimizationData.bakedLights}"); report.AppendLine($"Disabled Lights: {optimizationData.disabledLights}"); report.AppendLine($"Max Realtime Allowed: {optimizationData.maxAllowedRealtimeLights}"); report.AppendLine($"Current FPS: {optimizationData.averageFPS:F1}"); report.AppendLine($"Draw Calls: {optimizationData.drawCalls}"); report.AppendLine($"Is Optimal: {optimizationData.isOptimal}"); report.AppendLine($"Last Update: {optimizationData.lastUpdate}"); return report.ToString(); } public void ResetOptimizations() { maxRealtimeLights = 2; realtimeLightDistance = 10f; shadowDistance = 20f; ApplyLightOptimizations(); } void OnDestroy() { m_AllLights.Clear(); m_RealtimeLights.Clear(); m_BakedLights.Clear(); m_DisabledLights.Clear(); } }
|
实践练习
22.10 练习1:移动端性能分析
目标:创建移动端性能分析工具
步骤:
- 实现性能数据收集
- 创建性能监控界面
- 分析性能瓶颈
- 生成优化建议
- 验证优化效果
分析要点:
- FPS稳定性
- 内存使用情况
- 绘制调用数量
- GPU利用率
22.11 练习2:LOD系统实现
目标:实现移动端LOD系统
步骤:
- 创建LOD组管理器
- 实现距离计算逻辑
- 设计LOD切换机制
- 优化切换平滑度
- 测试性能提升
实现要素:
22.12 练习3:纹理优化系统
目标:创建纹理优化系统
步骤:
- 实现纹理格式检测
- 创建压缩优化逻辑
- 设计内存管理机制
- 实现流式传输
- 验证内存节省
优化方面:
22.13 练习4:光照优化策略
目标:实现光照优化策略
步骤:
- 分析场景光照需求
- 实现烘焙光照优先
- 创建灯光管理器
- 优化阴影计算
- 验证视觉效果
优化策略:
22.14 练习5:综合优化方案
目标:创建移动端综合优化方案
步骤:
- 集成各项优化技术
- 实现自动调整机制
- 创建性能监控面板
- 测试多种设备
- 验证整体效果
方案特性:
总结
第22章详细介绍了移动端URP优化的各个方面。移动端平台由于硬件限制和功耗考虑,需要特别的优化策略来确保良好的用户体验。
关键要点总结:
- 配置策略:根据移动端特性调整URP设置
- LOD系统:使用距离和性能动态调整细节级别
- 变体管理:控制Shader变体数量以节省内存
- 纹理优化:选择合适的压缩格式和分辨率
- 光照简化:平衡烘焙和实时光照
- 性能分析:使用工具监控和优化性能
移动端优化是一个持续的过程,需要在视觉效果和性能之间找到最佳平衡点。通过系统化的优化策略,可以显著提升移动端应用的性能表现。