第14章 RenderingData与Context
理论讲解
14.1 ScriptableRenderContext作用
ScriptableRenderContext是Unity可编程渲染管线中的核心组件,它充当了渲染命令执行的上下文环境。这个上下文管理着渲染管线中的所有渲染操作,包括几何体绘制、阴影投射、光照计算等。
ScriptableRenderContext的主要职责:
- 命令执行:执行渲染命令缓冲区中的命令
- 资源管理:管理渲染过程中使用的资源
- 状态管理:维护渲染状态(如深度测试、混合等)
- 批处理:优化渲染调用,减少Draw Call
- 同步机制:处理CPU与GPU之间的同步
ScriptableRenderContext的使用场景:
- 在ScriptableRenderPass的Execute方法中作为参数传入
- 执行DrawRenderers命令
- 执行阴影渲染命令
- 提交渲染命令到GPU
14.2 RenderingData数据结构
RenderingData是URP中封装当前帧渲染所需数据的结构体,它包含了渲染管线各个阶段所需的信息。
RenderingData的主要组成部分:
CameraData:包含相机相关的渲染信息
- 相机变换矩阵
- 视锥体参数
- 渲染目标信息
- 相机配置参数
LightData:包含光照相关的渲染信息
- 主光源信息
- 附加光源信息
- 光照模式配置
- 光照烘焙信息
ShadowData:包含阴影相关的渲染信息
CullingResults:包含视锥体裁剪结果
PostProcessData:包含后处理相关的数据
14.3 CameraData、LightData、ShadowData
CameraData详解:
CameraData封装了相机渲染所需的所有信息,是渲染管线中最重要的数据结构之一。
主要属性:
- camera:Unity Camera组件引用
- isSceneViewCamera:是否为场景视图相机
- isDefaultViewport:是否使用默认视口
- isHdrEnabled:是否启用HDR
- maxShadowDistance:最大阴影距离
- renderScale:渲染缩放
- cameraBounds:相机边界
- cullResults:裁剪结果
LightData详解:
LightData管理场景中的光照信息,包括主光源和附加光源的配置。
主要属性:
- mainLightIndex:主光源索引
- additionalLightsCount:附加光源数量
- supportsAdditionalLights:是否支持附加光源
- supportsMixedLighting:是否支持混合光照
- lightData:光照数据数组
ShadowData详解:
ShadowData管理阴影渲染的相关配置和数据。
主要属性:
- supportsMainLightShadows:是否支持主光源阴影
- supportsAdditionalLightShadows:是否支持附加光源阴影
- mainLightShadowmapWidth/Height:主光源阴影贴图尺寸
- additionalLightsShadowmapWidth/Height:附加光源阴影贴图尺寸
- shadowmapDepthBufferBits:阴影贴图深度缓冲位数
14.4 渲染上下文的传递机制
在URP渲染管线中,渲染数据通过特定的机制在各个组件间传递,确保数据的一致性和完整性。
数据传递流程:
- 渲染管线入口:在Render方法中创建初始的RenderingData
- 相机处理:为每个相机创建CameraData
- 光照处理:根据场景光照创建LightData
- 阴影处理:根据光源配置创建ShadowData
- 裁剪处理:执行视锥体裁剪生成CullingResults
- 渲染通道:将完整的RenderingData传递给各个渲染通道
数据传递优化策略:
- 结构体传递:使用ref参数减少数据复制开销
- 数据缓存:缓存计算结果避免重复计算
- 按需计算:只计算当前渲染通道需要的数据
14.5 CommandBuffer的获取与执行
CommandBuffer是Unity中用于记录渲染命令的数据结构,在URP中被广泛使用。
CommandBuffer的使用模式:
获取方式:
- 从CommandBufferPool获取(推荐)
- 直接创建CommandBuffer实例
执行方式:
- 通过ScriptableRenderContext.ExecuteCommandBuffer执行
- 在渲染通道中执行
CommandBuffer最佳实践:
- 使用池化:使用CommandBufferPool.Get()和Release()管理命令缓冲区
- 及时释放:在使用完毕后立即释放到池中
- 作用域管理:使用using语句确保资源正确释放
- 命令优化:批量执行相关命令减少开销
14.6 渲染状态的保存与恢复
在复杂的渲染管线中,正确管理渲染状态对于确保渲染结果的正确性至关重要。
渲染状态类型:
- 深度状态:深度测试、深度写入
- 混合状态:颜色混合模式
- 光栅化状态:剔除模式、多边形模式
- 着色器状态:当前激活的着色器和材质
状态管理策略:
- 状态缓存:缓存当前渲染状态避免不必要的状态切换
- 状态验证:在设置新状态前验证是否需要更改
- 状态恢复:在渲染完成后恢复原始状态
- 作用域管理:使用状态作用域确保状态正确恢复
代码示例
14.7 RenderingData访问与使用
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
| using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class RenderingDataAnalyzer : MonoBehaviour { [Header("Analysis Settings")] public bool enableAnalysis = true; public bool logCameraData = true; public bool logLightData = true; public bool logShadowData = true; [Header("Performance Thresholds")] public int warningVisibleLights = 10; public int warningVisibleRenderers = 50; public float warningMaxShadowDistance = 50f; private RenderingData m_CurrentRenderingData; private CameraData m_CurrentCameraData; private LightData m_CurrentLightData; private ShadowData m_CurrentShadowData; [System.Serializable] public class RenderingStats { public int visibleLights; public int visibleRenderers; public int visibleShadowCasters; public float maxShadowDistance; public bool isHdrEnabled; public bool supportsMainLightShadows; public bool supportsAdditionalLightShadows; public int additionalLightsCount; public float renderScale; } public RenderingStats currentStats = new RenderingStats(); void Start() { if (enableAnalysis) { RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; RenderPipelineManager.endCameraRendering += OnEndCameraRendering; } } void OnDestroy() { if (enableAnalysis) { RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering; RenderPipelineManager.endCameraRendering -= OnEndCameraRendering; } } private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { if (!enableAnalysis) return; AnalyzeCamera(camera); } private void OnEndCameraRendering(ScriptableRenderContext context, Camera camera) { if (!enableAnalysis) return; LogAnalysisResults(); } private void AnalyzeCamera(Camera camera) { var urpData = camera.GetUniversalAdditionalCameraData(); if (urpData != null) { currentStats.isHdrEnabled = urpData.allowHDR; currentStats.renderScale = urpData.renderScale; } var lights = FindObjectsOfType<Light>(); currentStats.visibleLights = lights.Length; currentStats.additionalLightsCount = Mathf.Max(0, lights.Length - 1); var shadowLights = System.Array.FindAll(lights, l => l.shadows != LightShadows.None); currentStats.supportsMainLightShadows = shadowLights.Length > 0; currentStats.supportsAdditionalLightShadows = shadowLights.Length > 1; currentStats.maxShadowDistance = camera.farClipPlane; var renderers = FindObjectsOfType<Renderer>(); currentStats.visibleRenderers = renderers.Length; var shadowCasters = System.Array.FindAll(renderers, r => r.shadowCastingMode != ShadowCastingMode.Off); currentStats.visibleShadowCasters = shadowCasters.Length; CheckPerformanceThresholds(); } private void CheckPerformanceThresholds() { var warnings = new System.Collections.Generic.List<string>(); if (currentStats.visibleLights > warningVisibleLights) { warnings.Add($"Too many visible lights: {currentStats.visibleLights} (threshold: {warningVisibleLights})"); } if (currentStats.visibleRenderers > warningVisibleRenderers) { warnings.Add($"Too many visible renderers: {currentStats.visibleRenderers} (threshold: {warningVisibleRenderers})"); } if (currentStats.maxShadowDistance > warningMaxShadowDistance) { warnings.Add($"Large shadow distance: {currentStats.maxShadowDistance:F1} (threshold: {warningMaxShadowDistance})"); } if (warnings.Count > 0 && logCameraData) { foreach (var warning in warnings) { Debug.LogWarning($"[Rendering Analysis] {warning}"); } } } private void LogAnalysisResults() { if (!logCameraData) return; var log = new System.Text.StringBuilder(); log.AppendLine("=== Rendering Data Analysis ==="); log.AppendLine($"Visible Lights: {currentStats.visibleLights}"); log.AppendLine($"Visible Renderers: {currentStats.visibleRenderers}"); log.AppendLine($"Visible Shadow Casters: {currentStats.visibleShadowCasters}"); log.AppendLine($"Additional Lights Count: {currentStats.additionalLightsCount}"); log.AppendLine($"HDR Enabled: {currentStats.isHdrEnabled}"); log.AppendLine($"Render Scale: {currentStats.renderScale:F2}"); log.AppendLine($"Supports Main Light Shadows: {currentStats.supportsMainLightShadows}"); log.AppendLine($"Supports Additional Light Shadows: {currentStats.supportsAdditionalLightShadows}"); Debug.Log(log.ToString()); } public RenderingStats GetCurrentStats() { return currentStats; } public void ResetStats() { currentStats = new RenderingStats(); } public string GetPerformanceRecommendations() { var recommendations = new System.Collections.Generic.List<string>(); if (currentStats.visibleLights > warningVisibleLights) { recommendations.Add("Reduce number of active lights or use light probes for static objects"); } if (currentStats.visibleRenderers > warningVisibleRenderers) { recommendations.Add("Implement LOD system or occlusion culling to reduce visible geometry"); } if (currentStats.supportsAdditionalLightShadows && currentStats.additionalLightsCount > 4) { recommendations.Add("Consider reducing shadow-casting additional lights for better performance"); } return recommendations.Count > 0 ? string.Join("\n", recommendations) : "No performance recommendations"; } }
|
14.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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class RenderContextManager : MonoBehaviour { [Header("Context Management")] public bool enableContextPooling = true; public bool enableStateTracking = true; public bool logContextOperations = false; [Header("Performance Settings")] public int maxCommandBuffersInPool = 20; public float stateChangeThreshold = 0.1f; private Queue<CommandBuffer> m_CommandBufferPool = new Queue<CommandBuffer>(); private List<CommandBuffer> m_ActiveCommandBuffers = new List<CommandBuffer>(); private Dictionary<string, object> m_RenderStates = new Dictionary<string, object>(); private Dictionary<string, float> m_LastStateChangeTime = new Dictionary<string, float>(); private Stack<RenderContextScope> m_ContextScopes = new Stack<RenderContextScope>(); [System.Serializable] public class RenderContextScope { public string name; public RenderTargetIdentifier currentRenderTarget; public RenderTextureDescriptor currentDescriptor; public int commandCount; public float startTime; public RenderContextScope(string scopeName) { name = scopeName; startTime = Time.realtimeSinceStartup; commandCount = 0; } } void Start() { InitializeContextManager(); } private void InitializeContextManager() { if (enableContextPooling) { for (int i = 0; i < maxCommandBuffersInPool; i++) { var cmdBuffer = new CommandBuffer(); cmdBuffer.name = $"Pooled Command Buffer {i}"; m_CommandBufferPool.Enqueue(cmdBuffer); } } } public CommandBuffer GetCommandBuffer(string name = "Default") { CommandBuffer cmdBuffer; if (enableContextPooling && m_CommandBufferPool.Count > 0) { cmdBuffer = m_CommandBufferPool.Dequeue(); } else { cmdBuffer = new CommandBuffer(); } cmdBuffer.name = name; cmdBuffer.Clear(); m_ActiveCommandBuffers.Add(cmdBuffer); if (logContextOperations) { Debug.Log($"[RenderContextManager] Acquired command buffer: {name}"); } return cmdBuffer; } public void ReleaseCommandBuffer(CommandBuffer cmdBuffer) { if (cmdBuffer == null) return; if (enableContextPooling && m_CommandBufferPool.Count < maxCommandBuffersInPool) { cmdBuffer.Clear(); m_CommandBufferPool.Enqueue(cmdBuffer); } else { cmdBuffer.Dispose(); } m_ActiveCommandBuffers.Remove(cmdBuffer); if (logContextOperations) { Debug.Log($"[RenderContextManager] Released command buffer"); } } public void BeginContextScope(string scopeName) { var scope = new RenderContextScope(scopeName); m_ContextScopes.Push(scope); if (logContextOperations) { Debug.Log($"[RenderContextManager] Begin scope: {scopeName}"); } } public void EndContextScope() { if (m_ContextScopes.Count > 0) { var scope = m_ContextScopes.Pop(); var duration = Time.realtimeSinceStartup - scope.startTime; if (logContextOperations) { Debug.Log($"[RenderContextManager] End scope: {scope.name}, Duration: {duration:F3}s, Commands: {scope.commandCount}"); } } } public void SetRenderState(string stateName, object value) { if (!enableStateTracking) return; if (m_RenderStates.ContainsKey(stateName)) { if (AreEqual(m_RenderStates[stateName], value)) { return; } } float currentTime = Time.realtimeSinceStartup; if (m_LastStateChangeTime.ContainsKey(stateName)) { if (currentTime - m_LastStateChangeTime[stateName] < stateChangeThreshold) { return; } } m_RenderStates[stateName] = value; m_LastStateChangeTime[stateName] = currentTime; if (logContextOperations) { Debug.Log($"[RenderContextManager] Set state: {stateName} = {value}"); } } public T GetRenderState<T>(string stateName, T defaultValue = default(T)) { if (enableStateTracking && m_RenderStates.ContainsKey(stateName)) { try { return (T)m_RenderStates[stateName]; } catch { return defaultValue; } } return defaultValue; } private bool AreEqual(object a, object b) { if (a == null && b == null) return true; if (a == null || b == null) return false; if (a is Vector3 && b is Vector3) { return ((Vector3)a == (Vector3)b); } if (a is Vector4 && b is Vector4) { return ((Vector4)a == (Vector4)b); } if (a is Matrix4x4 && b is Matrix4x4) { return ((Matrix4x4)a == (Matrix4x4)b); } return a.Equals(b); } public void ExecuteCommandBuffer(ScriptableRenderContext context, CommandBuffer cmdBuffer) { if (cmdBuffer == null) return; if (m_ContextScopes.Count > 0) { var currentScope = m_ContextScopes.Peek(); currentScope.commandCount++; } context.ExecuteCommandBuffer(cmdBuffer); cmdBuffer.Clear(); if (logContextOperations) { Debug.Log($"[RenderContextManager] Executed command buffer: {cmdBuffer.name}"); } } public void SubmitRenderContext(ScriptableRenderContext context) { context.Submit(); if (logContextOperations) { Debug.Log("[RenderContextManager] Submitted render context"); } } public string GetContextStats() { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== Render Context Statistics ==="); stats.AppendLine($"Command Buffer Pool Size: {m_CommandBufferPool.Count}"); stats.AppendLine($"Active Command Buffers: {m_ActiveCommandBuffers.Count}"); stats.AppendLine($"Max Pool Size: {maxCommandBuffersInPool}"); stats.AppendLine($"Active Scopes: {m_ContextScopes.Count}"); stats.AppendLine($"Tracked States: {m_RenderStates.Count}"); return stats.ToString(); } public void CleanupAllResources() { foreach (var cmdBuffer in m_ActiveCommandBuffers) { cmdBuffer.Dispose(); } m_ActiveCommandBuffers.Clear(); while (m_CommandBufferPool.Count > 0) { var cmdBuffer = m_CommandBufferPool.Dequeue(); cmdBuffer.Dispose(); } m_RenderStates.Clear(); m_LastStateChangeTime.Clear(); m_ContextScopes.Clear(); if (logContextOperations) { Debug.Log("[RenderContextManager] All resources cleaned up"); } } void OnDestroy() { CleanupAllResources(); } }
|
14.9 RenderingData扩展分析器
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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class ExtendedRenderingDataAnalyzer : MonoBehaviour { [Header("Extended Analysis")] public bool enableExtendedAnalysis = true; public bool analyzePerObjectData = true; public bool analyzePerLightData = true; public bool logDetailedStats = false; [Header("Analysis Settings")] public float analysisUpdateInterval = 0.5f; public int maxObjectsToAnalyze = 1000; private float m_LastAnalysisTime = 0f; private Dictionary<Renderer, RendererAnalysisData> m_RendererAnalysisData = new Dictionary<Renderer, RendererAnalysisData>(); private Dictionary<Light, LightAnalysisData> m_LightAnalysisData = new Dictionary<Light, LightAnalysisData>(); [System.Serializable] public class RendererAnalysisData { public Renderer renderer; public Material[] materials; public int triangleCount; public bool castsShadows; public ShadowCastingMode shadowMode; public bool receivesShadows; public float distanceToCamera; public int renderQueue; public bool isStatic; public string shaderName; } [System.Serializable] public class LightAnalysisData { public Light light; public LightType type; public float range; public float intensity; public Color color; public bool castsShadows; public LightShadows shadowType; public float innerSpotAngle; public float outerSpotAngle; public bool enabled; public float distanceToCamera; } [System.Serializable] public class SceneAnalysisSummary { public int totalRenderers; public int totalLights; public int shadowCastingRenderers; public int shadowReceivingRenderers; public int lightsWithShadows; public float averageRendererDistance; public float averageLightIntensity; public int dynamicBatchableRenderers; public int staticBatchableRenderers; } public SceneAnalysisSummary summary = new SceneAnalysisSummary(); void Update() { if (enableExtendedAnalysis && Time.time - m_LastAnalysisTime >= analysisUpdateInterval) { PerformExtendedAnalysis(); m_LastAnalysisTime = Time.time; } } private void PerformExtendedAnalysis() { if (analyzePerObjectData) { AnalyzeRenderers(); } if (analyzePerLightData) { AnalyzeLights(); } GenerateSummary(); if (logDetailedStats) { LogDetailedAnalysis(); } } private void AnalyzeRenderers() { var renderers = FindObjectsOfType<Renderer>(); m_RendererAnalysisData.Clear(); int analyzedCount = 0; foreach (var renderer in renderers) { if (analyzedCount >= maxObjectsToAnalyze) break; var analysisData = new RendererAnalysisData { renderer = renderer, materials = renderer.sharedMaterials, triangleCount = GetRendererTriangleCount(renderer), castsShadows = renderer.shadowCastingMode != ShadowCastingMode.Off, shadowMode = renderer.shadowCastingMode, receivesShadows = renderer.receiveShadows, distanceToCamera = GetDistanceToMainCamera(renderer), renderQueue = GetRendererRenderQueue(renderer), isStatic = renderer.gameObject.isStatic, shaderName = GetRendererShaderName(renderer) }; m_RendererAnalysisData[renderer] = analysisData; analyzedCount++; } } private void AnalyzeLights() { var lights = FindObjectsOfType<Light>(); m_LightAnalysisData.Clear(); foreach (var light in lights) { var analysisData = new LightAnalysisData { light = light, type = light.type, range = light.range, intensity = light.intensity, color = light.color, castsShadows = light.shadows != LightShadows.None, shadowType = light.shadows, innerSpotAngle = light.innerSpotAngle, outerSpotAngle = light.spotAngle, enabled = light.enabled, distanceToCamera = GetDistanceToMainCamera(light) }; m_LightAnalysisData[light] = analysisData; } } private void GenerateSummary() { summary.totalRenderers = m_RendererAnalysisData.Count; summary.totalLights = m_LightAnalysisData.Count; summary.shadowCastingRenderers = 0; summary.shadowReceivingRenderers = 0; summary.averageRendererDistance = 0f; foreach (var kvp in m_RendererAnalysisData) { var data = kvp.Value; if (data.castsShadows) summary.shadowCastingRenderers++; if (data.receivesShadows) summary.shadowReceivingRenderers++; summary.averageRendererDistance += data.distanceToCamera; } if (summary.totalRenderers > 0) { summary.averageRendererDistance /= summary.totalRenderers; } summary.lightsWithShadows = 0; summary.averageLightIntensity = 0f; foreach (var kvp in m_LightAnalysisData) { var data = kvp.Value; if (data.castsShadows) summary.lightsWithShadows++; summary.averageLightIntensity += data.intensity; } if (summary.totalLights > 0) { summary.averageLightIntensity /= summary.totalLights; } summary.dynamicBatchableRenderers = 0; summary.staticBatchableRenderers = 0; foreach (var kvp in m_RendererAnalysisData) { var data = kvp.Value; if (data.isStatic) { summary.staticBatchableRenderers++; } else { if (data.triangleCount * 3 < 300) { summary.dynamicBatchableRenderers++; } } } } private int GetRendererTriangleCount(Renderer renderer) { var meshFilter = renderer.GetComponent<MeshFilter>(); if (meshFilter != null && meshFilter.sharedMesh != null) { return meshFilter.sharedMesh.triangles.Length / 3; } return 100; } private float GetDistanceToMainCamera(Component component) { var mainCamera = Camera.main; if (mainCamera != null) { return Vector3.Distance(component.transform.position, mainCamera.transform.position); } return 0f; } private int GetRendererRenderQueue(Renderer renderer) { if (renderer.sharedMaterials.Length > 0 && renderer.sharedMaterials[0] != null) { return renderer.sharedMaterials[0].renderQueue; } return 2000; } private string GetRendererShaderName(Renderer renderer) { if (renderer.sharedMaterials.Length > 0 && renderer.sharedMaterials[0] != null) { return renderer.sharedMaterials[0].shader.name; } return "Default"; } private void LogDetailedAnalysis() { var log = new System.Text.StringBuilder(); log.AppendLine("=== Extended Rendering Data Analysis ==="); log.AppendLine($"Total Renderers: {summary.totalRenderers}"); log.AppendLine($"Total Lights: {summary.totalLights}"); log.AppendLine($"Shadow Casting Renderers: {summary.shadowCastingRenderers}"); log.AppendLine($"Shadow Receiving Renderers: {summary.shadowReceivingRenderers}"); log.AppendLine($"Lights with Shadows: {summary.lightsWithShadows}"); log.AppendLine($"Average Renderer Distance: {summary.averageRendererDistance:F2}"); log.AppendLine($"Average Light Intensity: {summary.averageLightIntensity:F2}"); log.AppendLine($"Dynamic Batchable Renderers: {summary.dynamicBatchableRenderers}"); log.AppendLine($"Static Batchable Renderers: {summary.staticBatchableRenderers}"); log.AppendLine("\nTop Renderers by Triangle Count:"); var sortedRenderers = new List<RendererAnalysisData>(m_RendererAnalysisData.Values); sortedRenderers.Sort((a, b) => b.triangleCount.CompareTo(a.triangleCount)); for (int i = 0; i < Mathf.Min(5, sortedRenderers.Count); i++) { var data = sortedRenderers[i]; log.AppendLine($" {data.renderer.name}: {data.triangleCount} triangles, " + $"Shadows: {data.castsShadows}, Distance: {data.distanceToCamera:F1}"); } log.AppendLine("\nLights Analysis:"); foreach (var kvp in m_LightAnalysisData) { var data = kvp.Value; log.AppendLine($" {data.light.name}: {data.type}({data.intensity} intensity), " + $"Shadows: {data.castsShadows}, Distance: {data.distanceToCamera:F1}"); } Debug.Log(log.ToString()); } public RendererAnalysisData GetRendererAnalysisData(Renderer renderer) { if (m_RendererAnalysisData.ContainsKey(renderer)) { return m_RendererAnalysisData[renderer]; } return null; } public LightAnalysisData GetLightAnalysisData(Light light) { if (m_LightAnalysisData.ContainsKey(light)) { return m_LightAnalysisData[light]; } return null; } public List<RendererAnalysisData> GetRendererAnalysisDataList() { return new List<RendererAnalysisData>(m_RendererAnalysisData.Values); } public List<LightAnalysisData> GetLightAnalysisDataList() { return new List<LightAnalysisData>(m_LightAnalysisData.Values); } public SceneAnalysisSummary GetSceneSummary() { return summary; } public List<string> GetPerformanceSuggestions() { var suggestions = new List<string>(); if (summary.shadowCastingRenderers > 50) { suggestions.Add($"High number of shadow casting renderers ({summary.shadowCastingRenderers}). Consider reducing shadow casters for performance."); } if (summary.lightsWithShadows > 8) { suggestions.Add($"High number of shadow casting lights ({summary.lightsWithShadows}). Consider using baked lighting."); } if (summary.dynamicBatchableRenderers > 0 && summary.dynamicBatchableRenderers < summary.totalRenderers * 0.5f) { suggestions.Add("Low dynamic batching efficiency. Check materials and mesh sizes."); } if (summary.averageLightIntensity > 2.0f) { suggestions.Add($"High average light intensity ({summary.averageLightIntensity:F2}). Consider reducing intensities for better performance."); } return suggestions; } }
|
14.10 渲染状态管理器
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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering;
public class RenderStateManager : MonoBehaviour { [Header("State Management")] public bool enableStateValidation = true; public bool logStateChanges = false; public bool enableStateCaching = true; [Header("State Settings")] public float stateChangeDebounceTime = 0.01f; private Dictionary<string, object> m_CurrentStates = new Dictionary<string, object>(); private Dictionary<string, float> m_LastStateChangeTime = new Dictionary<string, float>(); private Dictionary<string, Queue<object>> m_StateHistory = new Dictionary<string, Queue<object>>(); private int m_HistorySize = 10; private Stack<RenderStateScope> m_StateScopes = new Stack<RenderStateScope>(); [System.Serializable] public class RenderStateScope { public string name; public Dictionary<string, object> savedStates = new Dictionary<string, object>(); public float enterTime; public RenderStateScope(string scopeName) { name = scopeName; enterTime = Time.realtimeSinceStartup; } } [System.Serializable] public class StateChange { public string stateName; public object oldValue; public object newValue; public float time; public StateChange(string name, object oldVal, object newVal) { stateName = name; oldValue = oldVal; newValue = newVal; time = Time.realtimeSinceStartup; } } private List<StateChange> m_StateChanges = new List<StateChange>(); void Start() { InitializeStateManager(); } private void InitializeStateManager() { SetState("DepthTest", CompareFunction.Less); SetState("DepthWrite", true); SetState("ColorWrite", true); SetState("CullMode", CullMode.Back); } public bool SetState(string stateName, object value) { if (enableStateValidation && !ValidateStateChange(stateName, value)) { if (logStateChanges) { Debug.LogWarning($"[RenderStateManager] Invalid state change: {stateName} = {value}"); } return false; } float currentTime = Time.realtimeSinceStartup; if (m_LastStateChangeTime.ContainsKey(stateName)) { if (currentTime - m_LastStateChangeTime[stateName] < stateChangeDebounceTime) { return false; } } object oldValue = null; bool stateExists = m_CurrentStates.ContainsKey(stateName); if (stateExists) { oldValue = m_CurrentStates[stateName]; if (AreEqual(oldValue, value)) { return false; } } m_CurrentStates[stateName] = value; m_LastStateChangeTime[stateName] = currentTime; if (!m_StateHistory.ContainsKey(stateName)) { m_StateHistory[stateName] = new Queue<object>(); } var history = m_StateHistory[stateName]; history.Enqueue(value); if (history.Count > m_HistorySize) { history.Dequeue(); } m_StateChanges.Add(new StateChange(stateName, oldValue, value)); if (m_StateChanges.Count > 100) { m_StateChanges.RemoveAt(0); } if (logStateChanges) { Debug.Log($"[RenderStateManager] State changed: {stateName} = {value} (was: {oldValue})"); } return true; } public T GetState<T>(string stateName, T defaultValue = default(T)) { if (m_CurrentStates.ContainsKey(stateName)) { try { return (T)m_CurrentStates[stateName]; } catch { if (logStateChanges) { Debug.LogWarning($"[RenderStateManager] Type mismatch for state: {stateName}"); } return defaultValue; } } return defaultValue; } public void BeginStateScope(string scopeName) { var scope = new RenderStateScope(scopeName); foreach (var kvp in m_CurrentStates) { scope.savedStates[kvp.Key] = kvp.Value; } m_StateScopes.Push(scope); if (logStateChanges) { Debug.Log($"[RenderStateManager] Begin state scope: {scopeName}"); } } public void EndStateScope() { if (m_StateScopes.Count > 0) { var scope = m_StateScopes.Pop(); var statesToRestore = new Dictionary<string, object>(scope.savedStates); var statesToRemove = new List<string>(); foreach (var kvp in m_CurrentStates) { if (!scope.savedStates.ContainsKey(kvp.Key)) { statesToRemove.Add(kvp.Key); } } foreach (var state in statesToRemove) { m_CurrentStates.Remove(state); if (m_LastStateChangeTime.ContainsKey(state)) { m_LastStateChangeTime.Remove(state); } if (m_StateHistory.ContainsKey(state)) { m_StateHistory.Remove(state); } } foreach (var kvp in scope.savedStates) { m_CurrentStates[kvp.Key] = kvp.Value; } if (logStateChanges) { Debug.Log($"[RenderStateManager] End state scope: {scope.name}"); } } } private bool ValidateStateChange(string stateName, object value) { switch (stateName) { case "DepthTest": return value is CompareFunction; case "DepthWrite": return value is bool; case "ColorWrite": return value is bool || value is ColorWriteMask; case "CullMode": return value is CullMode; case "BlendSrc": case "BlendDst": return value is BlendMode; case "ZTest": return value is CompareFunction; default: return true; } } private bool AreEqual(object a, object b) { if (a == null && b == null) return true; if (a == null || b == null) return false; if (a is Vector3 && b is Vector3) { return ((Vector3)a - (Vector3)b).magnitude < 0.001f; } if (a is Vector4 && b is Vector4) { return ((Vector4)a - (Vector4)b).magnitude < 0.001f; } if (a is Matrix4x4 && b is Matrix4x4) { return ((Matrix4x4)a == (Matrix4x4)b); } return a.Equals(b); } public object[] GetStateHistory(string stateName) { if (m_StateHistory.ContainsKey(stateName)) { return m_StateHistory[stateName].ToArray(); } return new object[0]; } public StateChange[] GetRecentStateChanges(int count = 10) { int startIndex = Mathf.Max(0, m_StateChanges.Count - count); var changes = new StateChange[m_StateChanges.Count - startIndex]; for (int i = startIndex; i < m_StateChanges.Count; i++) { changes[i - startIndex] = m_StateChanges[i]; } return changes; } public void ResetAllStates() { m_CurrentStates.Clear(); m_LastStateChangeTime.Clear(); m_StateHistory.Clear(); m_StateChanges.Clear(); InitializeStateManager(); if (logStateChanges) { Debug.Log("[RenderStateManager] All states reset"); } } public string GetStateStats() { var stats = new System.Text.StringBuilder(); stats.AppendLine("=== Render State Statistics ==="); stats.AppendLine($"Current States: {m_CurrentStates.Count}"); stats.AppendLine($"State Scopes: {m_StateScopes.Count}"); stats.AppendLine($"Recent State Changes: {m_StateChanges.Count}"); stats.AppendLine($"History Size Limit: {m_HistorySize}"); if (m_CurrentStates.Count > 0) { stats.AppendLine("\nCurrent States:"); foreach (var kvp in m_CurrentStates) { stats.AppendLine($" {kvp.Key}: {kvp.Value}"); } } return stats.ToString(); } public void ApplyStatesToCommandBuffer(CommandBuffer cmd) { if (m_CurrentStates.ContainsKey("DepthTest")) { var depthTest = GetState<CompareFunction>("DepthTest", CompareFunction.Less); cmd.SetGlobalInt("_ZTest", (int)depthTest); } if (m_CurrentStates.ContainsKey("DepthWrite")) { var depthWrite = GetState<bool>("DepthWrite", true); cmd.SetGlobalInt("_ZWrite", depthWrite ? 1 : 0); } if (m_CurrentStates.ContainsKey("ColorWrite")) { if (m_CurrentStates["ColorWrite"] is bool) { var colorWrite = GetState<bool>("ColorWrite", true); cmd.SetGlobalInt("_ColorWrite", colorWrite ? 0xF : 0x0); } else if (m_CurrentStates["ColorWrite"] is ColorWriteMask) { var colorWriteMask = GetState<ColorWriteMask>("ColorWrite", ColorWriteMask.All); cmd.SetGlobalInt("_ColorWrite", (int)colorWriteMask); } } if (m_CurrentStates.ContainsKey("CullMode")) { var cullMode = GetState<CullMode>("CullMode", CullMode.Back); cmd.SetGlobalInt("_CullMode", (int)cullMode); } } }
|
实践练习
14.11 练习1:RenderingData访问分析
目标:创建一个工具来分析RenderingData中的各种数据
步骤:
- 创建RenderingDataAnalyzer类
- 实现相机数据访问
- 实现光照数据分析
- 实现阴影数据统计
- 测试分析工具的功能
实现要点:
- 使用RenderPipelineManager事件
- 分析各种渲染数据
- 生成性能报告
14.12 练习2:渲染上下文管理
目标:实现渲染上下文的管理和优化
步骤:
- 创建RenderContextManager类
- 实现命令缓冲区池化
- 实现上下文作用域管理
- 实现状态跟踪功能
- 测试性能优化效果
关键技术:
- CommandBuffer池化
- 作用域管理
- 状态跟踪
14.13 练习3:扩展数据分析师
目标:创建详细的场景数据分析师
步骤:
- 实现渲染器数据分析
- 实现光源数据分析
- 生成场景摘要
- 提供性能建议
- 优化分析性能
分析维度:
14.14 练习4:渲染状态管理
目标:实现渲染状态的有效管理
步骤:
- 创建RenderStateManager
- 实现状态设置和获取
- 实现状态作用域
- 实现状态验证
- 测试状态管理效果
管理功能:
14.15 练习5:性能监控与优化
目标:实现渲染性能的全面监控
步骤:
- 集成所有分析工具
- 实现实时性能监控
- 生成性能报告
- 提供优化建议
- 验证优化效果
监控指标:
总结
第14章深入探讨了RenderingData与Context的概念,这是URP渲染管线中数据传递和上下文管理的核心机制。RenderingData封装了渲染所需的所有数据,而ScriptableRenderContext提供了执行渲染命令的环境。
关键要点总结:
- 数据结构:RenderingData包含CameraData、LightData、ShadowData等组件
- 上下文管理:ScriptableRenderContext管理渲染命令的执行
- 数据传递:通过ref参数高效传递渲染数据
- 状态管理:正确管理渲染状态确保渲染正确性
- 资源管理:CommandBuffer池化和作用域管理
理解RenderingData与Context的机制对于开发高效的渲染功能至关重要,它确保了渲染管线中数据的一致性和执行的正确性。通过合理的状态管理和资源管理,可以显著提升渲染性能。
下一章将分析光照与阴影的源码实现,深入了解URP中光照系统的内部工作原理。