第11章 URP渲染管线架构
理论讲解
11.1 UniversalRenderPipeline类解析
UniversalRenderPipeline (URP) 是Unity的可编程渲染管线实现,专为跨平台性能优化而设计。它是Scriptable Render Pipeline (SRP) 框架的一部分,提供了灵活的渲染管线定制能力。
UniversalRenderPipeline类的主要职责:
- 渲染管线管理:作为渲染管线的入口点,管理整个渲染流程
- 资源管理:管理渲染过程中使用的临时资源和缓存
- 相机处理:处理场景中的所有相机及其渲染需求
- 渲染状态管理:维护渲染管线的状态和配置
URP核心架构组件
RenderPipelineAsset:渲染管线的配置数据,定义管线的基本设置
- 包含渲染管线的全局设置
- 定义默认的Renderer配置
- 存储渲染管线的元数据
RenderPipeline:渲染管线的运行时实例
- 实现IRenderPipeline接口
- 处理实际的渲染调用
- 管理渲染资源的生命周期
ScriptableRenderer:可编程渲染器,定义渲染逻辑
- 处理相机的渲染请求
- 管理渲染特性(Renderer Features)
- 执行渲染通道(Render Passes)
11.2 RenderPipelineAsset与RenderPipeline关系
RenderPipelineAsset和RenderPipeline是URP架构中的两个核心概念,它们之间存在紧密的关系:
RenderPipelineAsset
- 配置容器:存储渲染管线的配置数据
- 持久化:保存在项目中,可序列化到磁盘
- 编辑器集成:在Unity编辑器中可编辑和配置
- 实例化:运行时用于创建RenderPipeline实例
RenderPipeline
- 运行时实例:基于Asset创建的运行时对象
- 渲染执行:实际执行渲染逻辑
- 资源管理:管理渲染过程中的资源
- 生命周期:与场景生命周期相关
11.3 ScriptableRenderer架构设计
ScriptableRenderer是URP中负责实际渲染逻辑的核心组件,它提供了完全可定制的渲染流程。
ScriptableRenderer主要功能
- 渲染通道管理:管理一系列渲染通道的执行顺序
- 相机数据处理:处理相机的渲染数据和配置
- 渲染特性集成:集成各种渲染特性(如后处理、阴影等)
- 资源分配:管理渲染过程中使用的临时资源
ScriptableRenderer生命周期
- 创建:从Renderer Asset实例化
- 配置:根据相机数据配置渲染器
- 执行:执行渲染通道
- 清理:释放渲染资源
11.4 RenderPass系统设计模式
RenderPass是URP中最小的渲染单元,每个RenderPass负责特定的渲染任务。
RenderPass类型
- ScriptableRenderPass:基础渲染通道基类
- ScriptableRenderer:管理多个RenderPass
- 自定义RenderPass:开发者可创建的特定渲染任务
RenderPass执行流程
- 配置阶段:设置渲染目标和渲染状态
- 执行阶段:执行实际的渲染命令
- 清理阶段:释放渲染资源
11.5 CommandBuffer与渲染命令
CommandBuffer是Unity中用于记录渲染命令的数据结构,在URP中广泛使用。
CommandBuffer在URP中的应用
- 渲染命令记录:记录GPU渲染命令
- 多Pass协调:在不同渲染通道间传递数据
- 性能优化:批量执行渲染命令
CommandBuffer管理
- 创建与销毁:正确管理CommandBuffer生命周期
- 命令记录:记录渲染命令到缓冲区
- 执行与同步:执行命令并处理同步问题
11.6 渲染流程整体概览
URP的渲染流程遵循以下主要步骤:
渲染流程步骤
- 初始化阶段:创建渲染管线实例
- 相机排序:根据深度和设置排序相机
- 渲染设置:配置渲染参数和资源
- 通道执行:按顺序执行各个渲染通道
- 后处理:应用后处理效果
- 输出阶段:将结果输出到屏幕
渲染管线执行顺序
- 深度预通道:渲染深度信息
- 不透明通道:渲染不透明物体
- 透明通道:渲染透明物体
- 后期处理通道:应用后处理效果
代码示例
11.7 自定义RenderPipeline实现
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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class CustomRenderPipelineAsset : RenderPipelineAsset { [SerializeField] private bool m_RenderSinglePassStereo = false; [SerializeField] private bool m_RequireDepthTexture = false; [SerializeField] private bool m_RequireOpaqueTexture = false; protected override RenderPipeline CreatePipeline() { return new CustomRenderPipeline( m_RenderSinglePassStereo, m_RequireDepthTexture, m_RequireOpaqueTexture ); } protected override void OnValidate() { base.OnValidate(); if (m_RequireDepthTexture && m_RequireOpaqueTexture) { Debug.LogWarning("同时启用深度纹理和不透明纹理可能会增加内存使用"); } } }
public class CustomRenderPipeline : RenderPipeline { private bool m_RenderSinglePassStereo; private bool m_RequireDepthTexture; private bool m_RequireOpaqueTexture; private List<ShaderTagId> m_ShaderTags; public CustomRenderPipeline( bool renderSinglePassStereo, bool requireDepthTexture, bool requireOpaqueTexture) { m_RenderSinglePassStereo = renderSinglePassStereo; m_RequireDepthTexture = requireDepthTexture; m_RequireOpaqueTexture = requireOpaqueTexture; m_ShaderTags = new List<ShaderTagId>(); m_ShaderTags.Add(new ShaderTagId("SRPDefaultUnlit")); m_ShaderTags.Add(new ShaderTagId("UniversalForward")); m_ShaderTags.Add(new ShaderTagId("LightweightForward")); } public override void Render(ScriptableRenderContext renderContext, Camera[] cameras) { SortCameras(cameras); foreach (Camera camera in cameras) { RenderCamera(renderContext, camera); } } private void SortCameras(Camera[] cameras) { System.Array.Sort(cameras, (a, b) => a.depth.CompareTo(b.depth)); } private void RenderCamera(ScriptableRenderContext renderContext, Camera camera) { var cameraData = CreateCameraData(camera); renderContext.SetupCameraProperties(camera); CommandBuffer cmd = CommandBufferPool.Get("Custom Render Camera"); try { ClearRenderTarget(cmd, camera); ExecuteRenderPasses(renderContext, cmd, cameraData); renderContext.ExecuteCommandBuffer(cmd); } finally { cmd.Clear(); CommandBufferPool.Release(cmd); } renderContext.Submit(); } private void ClearRenderTarget(CommandBuffer cmd, Camera camera) { var clearColor = camera.backgroundColor; var clearFlags = camera.clearFlags; cmd.ClearRenderTarget( (ClearFlag)clearFlags, clearColor, camera.farClipPlane ); } private void ExecuteRenderPasses( ScriptableRenderContext renderContext, CommandBuffer cmd, CameraData cameraData) { RenderOpaqueObjects(renderContext, cmd, cameraData); RenderTransparentObjects(renderContext, cmd, cameraData); } private void RenderOpaqueObjects( ScriptableRenderContext renderContext, CommandBuffer cmd, CameraData cameraData) { var drawingSettings = CreateDrawingSettings(); var filteringSettings = new FilteringSettings(RenderQueueRange.opaque); renderContext.DrawRenderers( cameraData.cullResults, ref drawingSettings, ref filteringSettings ); } private void RenderTransparentObjects( ScriptableRenderContext renderContext, CommandBuffer cmd, CameraData cameraData) { var drawingSettings = CreateDrawingSettings(); var filteringSettings = new FilteringSettings(RenderQueueRange.transparent); renderContext.DrawRenderers( cameraData.cullResults, ref drawingSettings, ref filteringSettings ); } private DrawingSettings CreateDrawingSettings() { var drawingSettings = new DrawingSettings(); for (int i = 0; i < m_ShaderTags.Count; ++i) { drawingSettings.SetShaderPassName(i, m_ShaderTags[i]); } drawingSettings.sortingSettings = new SortingSettings(Camera.main) { criteria = SortingCriteria.CommonOpaque }; return drawingSettings; } private CameraData CreateCameraData(Camera camera) { var cameraData = new CameraData(); cameraData.camera = camera; cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView; cameraData.isDefaultViewport = camera.rect == new Rect(0, 0, 1, 1); ScriptableCullingParameters cullingParams; if (!camera.TryGetCullingParameters(out cullingParams)) return cameraData; cameraData.cullResults = renderContext.Cull(ref cullingParams); return cameraData; } }
public struct CameraData { public Camera camera; public CullingResults cullResults; public bool isSceneViewCamera; public bool isDefaultViewport; public bool isHdrEnabled; }
|
11.8 自定义ScriptableRenderer实现
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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class CustomScriptableRenderer : ScriptableRenderer { private List<ScriptableRenderPass> m_ActiveRenderPassQueue = new List<ScriptableRenderPass>(); private List<RendererFeature> m_RendererFeatures = new List<RendererFeature>(); private bool m_CreateDepthTexture; private bool m_CreateOpaqueColorTexture; private bool m_SupportsDynamicBatching; private bool m_SupportsMixedLighting; public CustomScriptableRenderer( bool createDepthTexture, bool createOpaqueColorTexture, bool supportsDynamicBatching, bool supportsMixedLighting) { m_CreateDepthTexture = createDepthTexture; m_CreateOpaqueColorTexture = createOpaqueColorTexture; m_SupportsDynamicBatching = supportsDynamicBatching; m_SupportsMixedLighting = supportsMixedLighting; } public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData) { m_ActiveRenderPassQueue.Clear(); ConfigureRenderPasses(ref renderingData); AddRendererFeatures(ref renderingData); } private void ConfigureRenderPasses(ref RenderingData renderingData) { if (m_CreateDepthTexture) { var depthPrepass = new DepthOnlyPass(RenderPassEvent.BeforeRenderingOpaques); m_ActiveRenderPassQueue.Add(depthPrepass); } var opaquePass = new CustomOpaquePass(RenderPassEvent.BeforeRenderingOpaques); m_ActiveRenderPassQueue.Add(opaquePass); var transparentPass = new CustomTransparentPass(RenderPassEvent.BeforeRenderingTransparents); m_ActiveRenderPassQueue.Add(transparentPass); if (renderingData.postProcessingData.IsEnabled()) { var postProcessPass = new CustomPostProcessPass(RenderPassEvent.AfterRenderingPostProcessing); m_ActiveRenderPassQueue.Add(postProcessPass); } } private void AddRendererFeatures(ref RenderingData renderingData) { foreach (var feature in m_RendererFeatures) { if (feature?.isActive != false) { feature.AddRenderPasses(this, ref renderingData); } } } public override void EnqueuePass(ScriptableRenderPass pass) { if (pass != null) { m_ActiveRenderPassQueue.Add(pass); } } public override void ConfigureCameraTarget(RenderTargetIdentifier cameraColorTarget, RenderTargetIdentifier cameraDepthTarget) { base.ConfigureCameraTarget(cameraColorTarget, cameraDepthTarget); } public override void SetupLights(ScriptableRenderContext context, ref RenderingData renderingData) { base.SetupLights(context, ref renderingData); } public override void SetupCullingParameters(ref ScriptableCullingParameters cullingParameters, ref CameraData cameraData) { base.SetupCullingParameters(ref cullingParameters, ref cameraData); } public override void Render(ScriptableRenderContext renderContext, ref RenderingData renderingData) { for (int i = 0; i < m_ActiveRenderPassQueue.Count; i++) { var pass = m_ActiveRenderPassQueue[i]; pass.Execute(renderContext, ref renderingData); } for (int i = 0; i < m_ActiveRenderPassQueue.Count; i++) { var pass = m_ActiveRenderPassQueue[i]; pass.FrameCleanup(ref renderingData); } } public void AddRendererFeature(RendererFeature feature) { if (feature != null) { m_RendererFeatures.Add(feature); } } public void RemoveRendererFeature(RendererFeature feature) { if (feature != null) { m_RendererFeatures.Remove(feature); } } public void ClearRendererFeatures() { m_RendererFeatures.Clear(); } }
public class CustomOpaquePass : ScriptableRenderPass { private FilteringSettings m_FilteringSettings; private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>(); public CustomOpaquePass(RenderPassEvent renderPassEvent) { this.renderPassEvent = renderPassEvent; m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit")); m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward")); m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward")); m_FilteringSettings = new FilteringSettings(RenderQueueRange.opaque); } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { ConfigureTarget(cameraTextureDescriptor.colorFormat, cameraTextureDescriptor.depthBufferFormat); ConfigureClear(ClearFlag.All, Color.black); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonOpaque); var filterSettings = m_FilteringSettings; context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filterSettings); } public override void FrameCleanup(ref RenderingData renderingData) { } }
public class CustomTransparentPass : ScriptableRenderPass { private FilteringSettings m_FilteringSettings; private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>(); public CustomTransparentPass(RenderPassEvent renderPassEvent) { this.renderPassEvent = renderPassEvent; m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit")); m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward")); m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward")); m_FilteringSettings = new FilteringSettings(RenderQueueRange.transparent); } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { ConfigureTarget(cameraTextureDescriptor.colorFormat, cameraTextureDescriptor.depthBufferFormat); ConfigureClear(ClearFlag.None, Color.black); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonTransparent); var filterSettings = m_FilteringSettings; context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filterSettings); } public override void FrameCleanup(ref RenderingData renderingData) { } }
|
11.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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering;
public class RenderPipelineResourceManager : MonoBehaviour { [System.Serializable] public class ResourcePoolConfig { public int initialPoolSize = 10; public int maxPoolSize = 100; public bool autoExpand = true; } [Header("Resource Pool Configuration")] public ResourcePoolConfig commandBufferConfig = new ResourcePoolConfig(); public ResourcePoolConfig renderTextureConfig = new ResourcePoolConfig(); private Queue<CommandBuffer> m_CommandBufferPool = new Queue<CommandBuffer>(); private Queue<RenderTexture> m_RenderTexturePool = new Queue<RenderTexture>(); private List<RenderTexture> m_ActiveRenderTextures = new List<RenderTexture>(); private List<CommandBuffer> m_ActiveCommandBuffers = new List<CommandBuffer>(); void Start() { InitializeResourcePools(); } private void InitializeResourcePools() { for (int i = 0; i < commandBufferConfig.initialPoolSize; i++) { var cmdBuffer = new CommandBuffer(); cmdBuffer.name = $"Pooled Command Buffer {i}"; m_CommandBufferPool.Enqueue(cmdBuffer); } for (int i = 0; i < renderTextureConfig.initialPoolSize; i++) { var rt = new RenderTexture(256, 256, 24); rt.name = $"Pooled Render Texture {i}"; m_RenderTexturePool.Enqueue(rt); } } public CommandBuffer GetCommandBuffer(string name = "Default") { CommandBuffer cmdBuffer; if (m_CommandBufferPool.Count > 0) { cmdBuffer = m_CommandBufferPool.Dequeue(); } else if (commandBufferConfig.autoExpand && m_ActiveCommandBuffers.Count < commandBufferConfig.maxPoolSize) { cmdBuffer = new CommandBuffer(); } else { Debug.LogWarning("Command buffer pool exhausted!"); return null; } cmdBuffer.name = name; cmdBuffer.Clear(); m_ActiveCommandBuffers.Add(cmdBuffer); return cmdBuffer; } public void ReleaseCommandBuffer(CommandBuffer cmdBuffer) { if (cmdBuffer == null) return; if (m_CommandBufferPool.Count < commandBufferConfig.maxPoolSize) { cmdBuffer.Clear(); m_CommandBufferPool.Enqueue(cmdBuffer); } else { cmdBuffer.Dispose(); } m_ActiveCommandBuffers.Remove(cmdBuffer); } public RenderTexture GetRenderTexture(int width, int height, int depthBuffer = 24) { RenderTexture rt = null; var tempPool = new Queue<RenderTexture>(); while (m_RenderTexturePool.Count > 0) { var candidate = m_RenderTexturePool.Dequeue(); if (candidate.width == width && candidate.height == height && candidate.depth == depthBuffer) { rt = candidate; break; } else { tempPool.Enqueue(candidate); } } while (tempPool.Count > 0) { m_RenderTexturePool.Enqueue(tempPool.Dequeue()); } if (rt == null) { if (renderTextureConfig.autoExpand && m_ActiveRenderTextures.Count < renderTextureConfig.maxPoolSize) { rt = new RenderTexture(width, height, depthBuffer); } else { Debug.LogWarning("Render texture pool exhausted!"); return null; } } m_ActiveRenderTextures.Add(rt); return rt; } public void ReleaseRenderTexture(RenderTexture rt) { if (rt == null) return; if (m_RenderTexturePool.Count < renderTextureConfig.maxPoolSize) { rt.Release(); m_RenderTexturePool.Enqueue(rt); } else { rt.Release(); DestroyImmediate(rt); } m_ActiveRenderTextures.Remove(rt); } public string GetResourceStats() { return $"Command Buffers - Pool: {m_CommandBufferPool.Count}, Active: {m_ActiveCommandBuffers.Count}\n" + $"Render Textures - Pool: {m_RenderTexturePool.Count}, Active: {m_ActiveRenderTextures.Count}"; } 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(); } foreach (var rt in m_ActiveRenderTextures) { rt.Release(); DestroyImmediate(rt); } m_ActiveRenderTextures.Clear(); while (m_RenderTexturePool.Count > 0) { var rt = m_RenderTexturePool.Dequeue(); rt.Release(); DestroyImmediate(rt); } } void OnDestroy() { CleanupAllResources(); } }
|
11.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
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;
public class RenderPipelineDebugger : MonoBehaviour { [Header("Debug Settings")] public bool enableRenderingDebug = true; public bool showRenderPassInfo = true; public bool logPerformanceData = false; [Header("Performance Thresholds")] public float warningFrameTime = 16.6f; public int warningDrawCallCount = 100; public int warningBatchCount = 100; private List<float> frameTimes = new List<float>(); private int maxFrameTimes = 60; [System.Serializable] public class RenderPassInfo { public string name; public RenderPassEvent passEvent; public float executionTime; public int drawCallCount; public string description; } private List<RenderPassInfo> renderPassInfos = new List<RenderPassInfo>(); private Dictionary<string, float> performanceTimers = new Dictionary<string, float>(); void OnEnable() { if (enableRenderingDebug) { RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; RenderPipelineManager.endCameraRendering += OnEndCameraRendering; } } void OnDisable() { RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering; RenderPipelineManager.endCameraRendering -= OnEndCameraRendering; } private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { if (!enableRenderingDebug) return; string timerKey = $"Camera_{camera.GetInstanceID()}"; performanceTimers[timerKey] = Time.realtimeSinceStartup; if (showRenderPassInfo) { Debug.Log($"[Render Debug] Starting render for camera: {camera.name}"); } } private void OnEndCameraRendering(ScriptableRenderContext context, Camera camera) { if (!enableRenderingDebug) return; string timerKey = $"Camera_{camera.GetInstanceID()}"; if (performanceTimers.ContainsKey(timerKey)) { float renderTime = (Time.realtimeSinceStartup - performanceTimers[timerKey]) * 1000f; if (logPerformanceData) { LogPerformanceData(camera, renderTime); } performanceTimers.Remove(timerKey); } } private void LogPerformanceData(Camera camera, float renderTime) { int drawCalls = UnityEngine.Rendering.UnityRenderPipeline.activeRenderPassCount; int batches = UnityEngine.Rendering.UnityRenderPipeline.batchCount; string performanceInfo = $"[Performance] Camera: {camera.name} | " + $"Render Time: {renderTime:F2}ms | " + $"Draw Calls: {drawCalls} | " + $"Batches: {batches}"; if (renderTime > warningFrameTime) { Debug.LogWarning($"{performanceInfo} - Frame time exceeds threshold!"); } else if (drawCalls > warningDrawCallCount) { Debug.LogWarning($"{performanceInfo} - High draw call count!"); } else if (batches > warningBatchCount) { Debug.LogWarning($"{performanceInfo} - High batch count!"); } else { Debug.Log(performanceInfo); } } public void AddRenderPassInfo(string name, RenderPassEvent passEvent, float executionTime, int drawCallCount, string description = "") { var info = new RenderPassInfo { name = name, passEvent = passEvent, executionTime = executionTime, drawCallCount = drawCallCount, description = description }; renderPassInfos.Add(info); } public string GetRenderPassStats() { if (renderPassInfos.Count == 0) return "No render pass data available"; var stats = new System.Text.StringBuilder(); stats.AppendLine("Render Pass Statistics:"); foreach (var info in renderPassInfos) { stats.AppendLine($" {info.name} ({info.passEvent}): " + $"Time={info.executionTime:F2}ms, " + $"Draws={info.drawCallCount}"); } return stats.ToString(); } public string GetPipelineArchitectureInfo() { var info = new System.Text.StringBuilder(); info.AppendLine("=== URP Pipeline Architecture ==="); info.AppendLine($"Render Pipeline Type: {RenderPipelineManager.currentPipeline.GetType().Name}"); info.AppendLine($"Active Render Pipeline: {(RenderPipelineManager.currentPipeline != null ? "Yes" : "No")}"); info.AppendLine($"SRP Batcher: {GraphicsSettings.useScriptableRenderPipelineBatching}"); info.AppendLine($"LOD CrossFade: {QualitySettings.lodBias}"); var cameras = FindObjectsOfType<Camera>(); info.AppendLine($"Active Cameras: {cameras.Length}"); foreach (var cam in cameras) { var urpData = cam.GetUniversalAdditionalCameraData(); if (urpData != null) { info.AppendLine($" Camera '{cam.name}': " + $"Renderer={urpData.rendererIndex}, " + $"Render Type={urpData.renderType}, " + $"Volume={urpData.volumeTrigger != null}"); } } return info.ToString(); } public void AnalyzePerformance() { float avgFrameTime = 0f; if (frameTimes.Count > 0) { foreach (float time in frameTimes) { avgFrameTime += time; } avgFrameTime /= frameTimes.Count; } Debug.Log($"[Pipeline Analysis] Average Frame Time: {avgFrameTime:F2}ms, " + $"Frame Times Recorded: {frameTimes.Count}"); } void Update() { float frameTime = Time.unscaledDeltaTime * 1000f; frameTimes.Add(frameTime); if (frameTimes.Count > maxFrameTimes) { frameTimes.RemoveAt(0); } } public void ClearDebugData() { renderPassInfos.Clear(); frameTimes.Clear(); performanceTimers.Clear(); } }
|
实践练习
11.11 练习1:创建基础渲染管线
目标:实现一个最简单的自定义渲染管线
步骤:
- 创建自定义RenderPipelineAsset
- 实现基础的RenderPipeline
- 配置基本的渲染流程
- 测试渲染管线是否正常工作
- 验证基本渲染功能
实现要点:
- 继承RenderPipelineAsset类
- 重写CreatePipeline方法
- 实现Render方法
11.12 练习2:实现自定义渲染器
目标:创建自定义ScriptableRenderer
步骤:
- 创建CustomScriptableRenderer类
- 实现Setup和Render方法
- 添加基本的渲染通道
- 集成到渲染管线中
- 测试渲染器功能
关键技术:
- ScriptableRenderer基类继承
- RenderPass管理
- 资源配置
11.13 练习3:渲染通道调试
目标:实现渲染通道的调试功能
步骤:
- 创建渲染通道信息收集器
- 记录每个通道的执行时间
- 显示通道执行顺序
- 分析性能瓶颈
- 优化通道执行
调试工具:
11.14 练习4:资源管理优化
目标:实现渲染资源的池化管理
步骤:
- 创建CommandBuffer池
- 实现RenderTexture池
- 添加资源生命周期管理
- 测试内存使用情况
- 优化池大小配置
优化策略:
11.15 练习5:渲染管线性能分析
目标:实现渲染管线性能监控
步骤:
- 集成Unity的渲染事件
- 记录渲染性能数据
- 分析性能瓶颈
- 生成性能报告
- 实现性能警告系统
监控指标:
- 帧时间
- Draw Call数量
- 批处理数量
- 内存使用
总结
第11章深入探讨了URP渲染管线的架构设计,这是理解URP工作原理的基础。UniversalRenderPipeline作为Unity可编程渲染管线的核心实现,提供了灵活的渲染定制能力。
关键要点总结:
- 架构组件:RenderPipelineAsset、RenderPipeline、ScriptableRenderer的职责分工
- 渲染流程:从初始化到输出的完整渲染流程
- 资源管理:渲染资源的生命周期和池化管理
- 扩展性:通过ScriptableRenderPass和RendererFeature实现功能扩展
- 性能优化:渲染管线的性能监控和优化策略
URP的架构设计体现了可编程渲染管线的灵活性和扩展性,通过合理的架构分层,开发者可以根据项目需求定制渲染管线。理解这些底层架构对于优化渲染性能和实现特殊渲染效果至关重要。
下一章将深入分析Forward Renderer的具体实现,了解其内部工作原理和源码结构。