[学习记录]URP流程解析(2)--初始化阶段

发布于:2025-07-30 ⋅ 阅读:(16) ⋅ 点赞:(0)

 本篇主要提及了URP管线流程中的阶段二 初始化阶段 (Initialization)

涉及关键类间联系和部分URP源码注释解析。(个人经验总结简要总结,并不面面俱到,欢迎指正)

一.阶段描述
1.阶段目的

初始化渲染数据RenderingData、相机参数 (UniversalCameraData)、光照数据 (UniversalLightData),阴影数据(UniversialShadowData)等数据,确定渲染模式。

2.触发时机

在 UniversalRenderPipeline.Render()中,为每个相机调用RenderSingleCamera()。

3.阶段输出

RenderingData:包含 cameraData(相机参数)、lightData(光照数据)、shadowData(阴影设置)、postProcessingData(后处理配置)、supportsDynamicBatching 等

子结构体:UniversalCameraData:渲染目标描述符、相机类型、后处理设置。UniversalLightData:主光源、附加光源、光照设置。UniversalShadowData:阴影贴图分辨率、软阴影设置。UniversalPostProcessingData:后处理效果配置。UniversalRenderingData:命令缓冲区、动态批处理设置(cullResults 在剔除阶段生成)。

4.简要总结

(1)UniversalRenderPipeline → ContextContainer

UniversalRenderPipeline.Render() 中调用 RenderSingleCamera(),传递 ScriptableRenderContext 和 UniversalCameraData。

【ContextContainer(frameData)由 UniversalRenderer 提供,动态存储 UniversalRenderingData 等子结构体。】

(2)ContextContainer → UniversalRenderingData

frameData.Create<UniversalRenderingData>()创建 UniversalRenderingData,存储剔除结果(cullResults)。

CreateUniversalResourceData填充 UniversalRenderingData 的字段(如 commandBuffer)。

(3)ContextContainer → UniversalLightData / UniversalShadowData / UniversalPostProcessingData

通过 CreateLightData()、CreateShadowData() 和 CreatePostProcessingData()创建并存储这些子结构体。

(4)ContextContainer → RenderingData

CreateRenderingData从 frameData 提取 UniversalRenderingData、UniversalLightData 等,整合到 RenderingData。legacyRenderingData从 frameData 创建,兼容传统路径。

(5)UniversalRenderer → RenderingData:UniversalRenderer 使用 RenderingData(或 legacyRenderingData)配置和执行渲染通道。

二.关键源码解析

1.UniversalRenderPipeline类

1.关键函数

1.Render()
1.作用

相当核心,实现 URP 的主渲染循环,处理所有相机的渲染。
支持相机堆栈、XR 渲染、后处理和调试功能。

2.意义

作为渲染管线的核心执行函数,协调所有相机的渲染流程,确保正确性和性能。作为“URP 每帧渲染循环的入口点”。Unity 引擎会为每一帧画面调用Render方法,传入当前的渲染上下文和需要渲染的相机列表。

3.参数

(1)ScriptableRenderContext  renderContext

类型:ScriptableRenderContext作用:Unity 的渲染上下文,负责提交渲染命令(如 Cull、DrawRenderers、ExecuteCommandBuffer)到 GPU。

来源:由 Unity 渲染系统提供,URP 通过它与底层渲染 API 交互。

(2)Camera[] cameras 或 List<Camera> cameras(根据 Unity 版本):

类型:相机数组或列表,包含当前帧需要渲染的所有相机(Game Camera、Scene View Camera、Reflection Camera 等)。

作用:定义渲染目标,URP 遍历这些相机执行渲染。

#if UNITY_2021_1_OR_NEWER  
        /// <inheritdoc/>  
        protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)  
        {            Render(renderContext, new List<Camera>(cameras));  
        }  
#endif  
  
#if UNITY_2021_1_OR_NEWER  
        /// <inheritdoc/>  
        protected override void Render(ScriptableRenderContext renderContext, List<Camera> cameras)  
#else  
        /// <inheritdoc/>  
        protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)  
#endif  
        {  
        // 1. 设置 HDR 状态 
        // 根据相机配置调整 HDR 渲染状态
            SetHDRState(cameras);  
            
		// 2. 获取相机数量 (根据 Unity 版本使用不同的集合类型)
#if UNITY_2021_1_OR_NEWER  
            int cameraCount = cameras.Count;  
#else  
            int cameraCount = cameras.Length;  
#endif  
            // 3. 调整 UI Overlay 的所有权 // 处理 UI Overlay (如 Canvas Overlay) 的渲染所有权,特别是在 XR、HDR 或无相机(例如仅渲染到RT)的情况下  
            AdjustUIOverlayOwnership(cameraCount);  
  
            // 4. 设置屏幕 MSAA 状态 (Render Graph 相关优化) 
            // 根据相机数量设置屏幕 MSAA 采样状态,这可能与 Render Graph 的带宽优化有关
        SetupScreenMSAASamplesState(cameraCount);  
  
            // 5. GPU Resident Drawer 初始化检查 
            // 如果启用了 GPU Resident Drawer (BRG),检查是否需要重新初始化GPUResidentDrawer.ReinitializeIfNeeded();  
  
            // 6. 性能分析 Scope // 用于在 Unity Profiler 中标记整个 URP 渲染循环的开始和结束,便于性能分析          using var profScope = new ProfilingScope(ProfilingSampler.Get(URPProfileId.UniversalRenderTotal));  
            
			// 7. 渲染上下文作用域 
			// 确保在渲染完成后,ScriptableRenderContext 被正确提交 (Submit)
            using (new ContextRenderingScope(renderContext, cameras))  
            {        
            // 8. 设置全局图形设置 // 根据当前颜色空间(Linear或Gamma)设置灯光强度是否使用线性模式        GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);  
            // 强制灯光使用色温GraphicsSettings.lightsUseColorTemperature = true;

			// 9. 设置每帧 Shader 常量 // 更新一些在每帧开始时都需要设置的全局 Shader 常量 (例如时间、相机数据等)
            SetupPerFrameShaderConstants();  
                
            // 10. 更新 XR 系统的显示 MSAA 采样数 (可能在构造函数中已设置,此处再次确保)XRSystem.SetDisplayMSAASamples((MSAASamples)asset.msaaSampleCount);  
  
#if DEVELOPMENT_BUILD || UNITY_EDITOR  
                if (DebugManager.instance.isAnyDebugUIActive)  
                    UniversalRenderPipelineDebugDisplaySettings.Instance.UpdateDisplayStats();  
  
                // This is for texture streaming  
                UniversalRenderPipelineDebugDisplaySettings.Instance.UpdateMaterials();  
#endif  
                // URP uses the camera's allowDynamicResolution flag to decide if useDynamicScale should be enabled for camera render targets.  
		                // However, the RTHandle system has an additional setting that controls if useDynamicScale will be set for render targets allocated via RTHandles.                // In order to avoid issues at runtime, we must make the RTHandle system setting consistent with URP's logic. URP already synchronizes the setting                // during initialization, but unfortunately it's possible for external code to overwrite the setting due to RTHandle state being global.                // The best we can do to avoid errors in this situation is to ensure the state is set to the correct value every time we perform rendering.                
		// 12. 确保 RTHandle 系统的动态分辨率状态与 URP 逻辑一致 
		// 由于 RTHandle 是全局状态,可能被外部代码修改,因此在每帧渲染前再次确保其动态分辨率状态正确RTHandles.SetHardwareDynamicResolutionState(true);  

		// 13. 排序相机列表 
		// 按照一定的规则(如渲染顺序、堆栈关系等)对相机进行排序,以确保正确的渲染顺序
        SortCameras(cameras);  
        
        // 获取最后一个基础相机(Base Camera)的索引,这对于处理相机堆栈和UI叠加非常重要
        int lastBaseCameraIndex = GetLastBaseCameraIndex(cameras); 

		 // 14. 遍历并渲染每个相机
        for (int i = 0; i < cameraCount; ++i)  
        {                    
	        var camera = cameras[i];  
            bool isLastBaseCamera = i == lastBaseCameraIndex;  
            // 14.1. 渲染游戏相机 (Base Camera 或 Overlay Camera)
	        if (IsGameCamera(camera))  
            {        
            // 渲染相机堆栈 (如果相机是 Base Camera,会处理其 Overlay Cameras)                
            RenderCameraStack(renderContext, camera, isLastBaseCamera);  
            }
            // 14.2. 渲染非游戏相机 (如 Scene View Camera, Preview Camera)
            else{  
            
            using (new CameraRenderingScope(renderContext, camera))  
            {#if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER 
            // 准备 VFX (视觉特效图) 材质,在剔除前调用       VFX.VFXManager.PrepareCamera(camera);  
			 #endif  
            // 更新体积框架 (用于后处理等)   
            UpdateVolumeFramework(camera, null);  
	        // 渲染单个非游戏相机         
            RenderSingleCameraInternal(renderContext, camera, isLastBaseCamera);  
	            }  
	        }  
	    }
    // 15. Render Graph 结束帧
    // 提交 Render Graph 的执行,清理帧相关的资源
    s_RenderGraph.EndFrame();  
    
    // 16. 清理未使用的 RTHandle 资源
    // 释放 RTHandle 池中不再使用的渲染纹理资源,回收内存s_RTHandlePool.PurgeUnusedResources(Time.frameCount);  
    }  
#if ENABLE_SHADER_DEBUG_PRINT  
         // 17. 结束 Shader 调试打印帧 (如果启用)   ShaderDebugPrintManager.instance.EndFrame();  
#endif  
}

关于最上方的条件编译指令,简单来说是指:

Unity 在不同版本中可能会对 API 进行调整,代码中的条件编译(#if UNITY_2021_1_OR_NEWER)是为了兼容这些变化:

Unity 2021.1 或更高版本:Render 方法接受 List<Camera> 类型的参数,这是 Unity 在较新版本中对 SRP API 的改进,允许更灵活的相机列表管理。为了向后兼容,代码还定义了一个重载方法,将 Camera[] 转换为 List<Camera>,然后调用主 Render 方法。

早于 Unity 2021.1 的版本:Render 方法直接接受 Camera[] 数组,这是早期 Unity 版本中 SRP 的标准参数类型。

其中最核心的部分要数相机堆栈的渲染,将分别渲染游戏相机和非游戏相机。

4.功能

初始化全局状态:设置 HDR、MSAA、Shader 常量等,确保渲染环境正确。

相机排序和分类:对相机排序,区分 Base Camera 和 Overlay Camera,处理渲染顺序。

渲染相机堆栈:为游戏相机调用 RenderCameraStack,为非游戏相机调用 RenderSingleCameraInternal。

资源管理和优化:使用 Render Graph 和 RTHandle 系统管理渲染纹理和资源。

调试和扩展支持:支持性能分析、VFX、Shader 调试等。


 2.CreateCameraData
1.作用

        CreateCameraData 方法的主要作用是为给定的相机(Camera)创建并初始化 UniversalCameraData 结构体,封装相机相关的渲染参数(如渲染目标、MSAA 设置、HDR 配置等)。它是 URP 渲染管线中相机数据初始化的关键步骤,为后续的渲染过程(例如在 RenderSingleCameraInternal 和 RenderSingleCamera 中)提供必要的数据。

2.意义

(1)数据封装:将相机的渲染配置(如分辨率、MSAA、HDR、渲染目标描述符)封装到一个 UniversalCameraData 结构体中,便于渲染管线访问和使用。

(2)渲染准备:为渲染器(如 UniversalRenderer)提供完整的相机数据,确保渲染过程(如剔除、光照、后处理)能够正确执行。

(3)支持多种渲染场景:适配不同类型的相机(游戏相机、编辑器相机、XR 相机),并处理复杂场景(如多重采样抗锯齿、HDR、动态分辨率)。

3.参数
static UniversalCameraData CreateCameraData(ContextContainer frameData, Camera camera, UniversalAdditionalCameraData additionalCameraData, bool resolveFinalTarget)

(1)frameData (ContextContainer):帧数据容器,包含渲染管线的全局状态和上下文信息。URP 使用 ContextContainer 来管理渲染过程中的共享数据。在此方法中,frameData 用于创建 UniversalCameraData 实例(通过 frameData.Create<UniversalCameraData>())。

(2)camera (Camera):Unity 的相机对象,表示要渲染的相机(可以是游戏相机、非游戏相机如 Scene View Camera 或 Preview Camera)。提供相机的基本属性(如视锥、渲染目标、分辨率)。

(3)additionalCameraData (UniversalAdditionalCameraData):URP 特定的相机附加数据组件,包含渲染类型(Base/Overlay)、后处理设置、相机堆栈等信息。如果相机没有此组件(例如非游戏相机),则为 null。

(4)resolveFinalTarget (bool):指示是否解析最终渲染目标。通常为 true,表示渲染结果需要输出到最终目标(如屏幕或 RenderTexture)。在某些特殊场景(如中间渲染到临时 RenderTexture)可能为 false。

static UniversalCameraData CreateCameraData(ContextContainer frameData, Camera camera, UniversalAdditionalCameraData additionalCameraData, bool resolveFinalTarget)
{
    // 使用性能分析工具标记此方法的执行时间,便于在 Unity Profiler 中分析相机数据初始化的性能开销
    using var profScope = new ProfilingScope(Profiling.Pipeline.initializeCameraData);

    // 获取与相机关联的渲染器(如 UniversalRenderer),用于访问渲染特性(如是否支持 MSAA)
    var renderer = GetRenderer(camera, additionalCameraData);

    // 从 frameData 中创建 UniversalCameraData 实例,frameData 是渲染管线的上下文容器
    UniversalCameraData cameraData = frameData.Create<UniversalCameraData>();

    // 初始化相机堆栈数据(如果 camera 是 Base 相机,可能设置其 Overlay 相机相关信息)
    InitializeStackedCameraData(camera, additionalCameraData, cameraData);

    // 设置 cameraData 的相机引用,关联到传入的 Camera 对象
    cameraData.camera = camera;

    // 如果 additionalCameraData 存在,将其 historyManager 赋值给 cameraData
    // historyManager 用于管理渲染历史数据(如 TAA 或其他需要帧间数据的渲染通道)
    cameraData.historyManager = additionalCameraData?.historyManager;

    ///////////////////////////////////////////////////////////////////
    // Descriptor settings                                            /
    ///////////////////////////////////////////////////////////////////

    // 检查渲染器是否支持 MSAA(多重采样抗锯齿),取决于渲染器支持的特性
    bool rendererSupportsMSAA = renderer != null && renderer.supportedRenderingFeatures.msaa;

    // 默认 MSAA 采样数为 1(无抗锯齿)
    int msaaSamples = 1;

    // 如果相机允许 MSAA、URP 资产配置了 MSAA 采样数且渲染器支持 MSAA,则设置采样数
    // 如果相机有 targetTexture,则使用其 antiAliasing 设置;否则使用 URP 资产的 msaaSampleCount
    if (camera.allowMSAA && asset.msaaSampleCount > 1 && rendererSupportsMSAA)
        msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : asset.msaaSampleCount;

    // 处理 XR 渲染情况:如果相机是 XR 相机且支持 MSAA,且没有自定义 targetTexture
    // 使用 XR 系统配置的 MSAA 采样数(XR 显示的 MSAA 是全局设置,多个相机共享)
    if (cameraData.xrRendering && rendererSupportsMSAA && camera.targetTexture == null)
        msaaSamples = (int)XRSystem.GetDisplayMSAASamples();

    // 检查是否需要 Alpha 通道(例如用于后期效果或 UI 渲染)
    bool needsAlphaChannel = Graphics.preserveFramebufferAlpha;

    // 设置 HDR 颜色缓冲区精度(默认从 URP 资产获取,通常为 32 位)
    cameraData.hdrColorBufferPrecision = asset ? asset.hdrColorBufferPrecision : HDRColorBufferPrecision._32Bits;

    // 创建渲染目标描述符,包含分辨率、HDR 设置、MSAA 采样数、Alpha 通道等信息
    cameraData.cameraTargetDescriptor = CreateRenderTextureDescriptor(camera, cameraData,
        cameraData.isHdrEnabled, cameraData.hdrColorBufferPrecision, msaaSamples, needsAlphaChannel, cameraData.requiresOpaqueTexture);

    // 检查渲染目标描述符的图形格式是否支持 Alpha 通道
    uint count = GraphicsFormatUtility.GetAlphaComponentCount(cameraData.cameraTargetDescriptor.graphicsFormat);
    cameraData.isAlphaOutputEnabled = GraphicsFormatUtility.HasAlphaChannel(cameraData.cameraTargetDescriptor.graphicsFormat);

    // 如果相机是 Scene View Camera 且启用了场景过滤(编辑器功能),强制启用 Alpha 输出
    if (cameraData.camera.cameraType == CameraType.SceneView && CoreUtils.IsSceneFilteringEnabled())
        cameraData.isAlphaOutputEnabled = true;

    // 返回初始化的 UniversalCameraData 结构体
    return cameraData;
}
4.功能

(1)创建 UniversalCameraData 实例:从 frameData 中分配并初始化一个 UniversalCameraData 结构体。

(2)初始化相机堆栈数据:根据 additionalCameraData 设置相机堆栈相关信息(如 Base/Overlay 关系)。

(3)配置相机属性:设置相机的基本属性(如 camera 引用、历史管理器)。

(4)设置渲染目标描述符:根据相机配置(如分辨率、MSAA、HDR、Alpha 通道)生成渲染目标描述符(RenderTextureDescriptor)。


3.RenderCameraStack()
1.作用

RenderCameraStack 是 URP 中用于渲染一个相机堆栈的方法。一个相机堆栈通常包括一个 Base 相机(渲染主要场景内容)和多个 Overlay 相机(渲染附加内容,如 UI、特效等)。此方法协调 Base 相机和 Overlay 相机的渲染顺序,确保它们正确组合到最终的渲染目标。

2.意义

(1)支持相机堆栈:URP 的相机堆栈功能允许开发者将多个相机的渲染结果组合到一个渲染目标上,适合复杂场景(如 UI 与 3D 场景叠加)。

(2)优化渲染流程:通过统一管理 Base 和 Overlay 相机,减少重复的渲染设置,提高性能。

编辑器支持:在编辑器中,RenderCameraStack 可以处理非游戏相机(如 Scene View Camera),如果它们被配置为 Base 相机。

3.参数
internal static void RenderCameraStack(ScriptableRenderContext context, Camera baseCamera)

(1)context:ScriptableRenderContext,渲染管线的上下文,用于提交渲染命令。

(2)baseCamera:Camera,堆栈中的 Base 相机,负责渲染主要场景内容。

4.功能

(1)获取相机堆栈:检查 Base 相机的 UniversalAdditionalCameraData 组件,获取其 cameraStack(Overlay 相机列表)。如果没有 Overlay 相机,则只渲染 Base 相机。

(2)验证和排序:验证 Base 相机和 Overlay 相机的有效性(如渲染目标尺寸、渲染类型)。按照优先级或渲染顺序对 Overlay 相机进行排序(由 UniversalAdditionalCameraData 的 renderingOrder 决定)。

(3)渲染 Base 相机:调用 RenderSingleCameraInternal 渲染 Base 相机,将其结果输出到渲染目标(如屏幕或 RenderTexture)。

(4)渲染 Overlay 相机:遍历 Overlay 相机列表,依次调用 RenderSingleCameraInternal 或类似方法渲染每个 Overlay 相机。Overlay 相机的渲染结果会叠加到 Base 相机的渲染目标上,可能使用特定的混合模式。

(5)后处理与清理:如果 Base 相机启用了后处理(如抗锯齿、颜色分级),在渲染完所有相机后应用后处理效果。如果是最后一个 Base 相机(通过 isLastBaseCamera),可能执行清理操作(如释放临时渲染目标)。


4.RenderSingleCameraInternal()
1.作用

渲染单个Base相机,处理剔除、渲染器设置和渲染通道执行。
验证相机有效性(例如目标纹理尺寸)并初始化相机数据。

2.意义

设计用于渲染单个 Base 相机,可能是由渲染管线手动调用,用于特定的渲染任务。

处理单个相机或者说是那些不参与相机堆栈渲染的独立相机的完整渲染流程。,支持相机堆栈和 XR 渲染,是渲染管线的核心子流程。

3.使用场景

是用来渲染单个相机的方法。它处理的是非游戏相机,例如:
(1)Scene View Camera(场景视图相机):在 Unity 编辑器中看到的 3D 场景视图就是由一个内部的Scene View Camera渲染的。
(2)Preview Camera(预览相机:在 Inspector 窗口中预览模型、材质等资产时,会有一个 Preview Camera` 来渲染这些对象的缩略图。
(3)Reflection Probe Camera(反射探头相机):用于生成反射探头立方体贴图的内部相机。

源码中定义了两个重载的 RenderSingleCameraInternal 方法,用于处理单个相机的渲染任务。

internal static void RenderSingleCameraInternal(ScriptableRenderContext context, Camera camera, bool isLastBaseCamera = true)
internal static void RenderSingleCameraInternal(ScriptableRenderContext context, Camera camera, ref UniversalAdditionalCameraData additionalCameraData, bool isLastBaseCamera = true)
   internal static void RenderSingleCameraInternal(ScriptableRenderContext context, Camera camera, bool isLastBaseCamera = true)
{
   UniversalAdditionalCameraData additionalCameraData = null;
   if (IsGameCamera(camera))
       camera.gameObject.TryGetComponent(out additionalCameraData);

   RenderSingleCameraInternal(context, camera, ref additionalCameraData, isLastBaseCamera);
}

internal static void RenderSingleCameraInternal(ScriptableRenderContext context, Camera camera, ref UniversalAdditionalCameraData additionalCameraData, bool isLastBaseCamera = true)
{

//1.验证相机渲染类型    
//检查相机是否具有 UniversalAdditionalCameraData 组件,并且其 renderType 是否为 //CameraRenderType.Base。        
if (additionalCameraData != null && additionalCameraData.renderType != CameraRenderType.Base)
{
      Debug.LogWarning("Only Base cameras can be rendered with standalone RenderSingleCamera. Camera will be skipped.");
                return;
}

//2.验证相机渲染目标尺寸
 if (camera.targetTexture.width == 0 || camera.targetTexture.height == 0 || camera.pixelWidth == 0 || camera.pixelHeight == 0)
{
       Debug.LogWarning($"Camera '{camera.name}' has an invalid render target size (width: {camera.targetTexture.width}, height: {camera.targetTexture.height}) or pixel dimensions (width: {camera.pixelWidth}, height: {camera.pixelHeight}). Camera will be skipped.");
return;
}

//3.获取和初始化相机数据
var frameData = GetRenderer(camera, additionalCameraData).frameData;
var cameraData = CreateCameraData(frameData, camera, additionalCameraData, true);
InitializeAdditionalCameraData(camera, additionalCameraData, true, isLastBaseCamera, cameraData);

//4.适配性能优化(可选)
#if ADAPTIVE_PERFORMANCE_2_0_0_OR_NEWER
    if (asset.useAdaptivePerformance)
       ApplyAdaptivePerformance(cameraData);
#endif

//5.执行渲染
RenderSingleCamera(context, cameraData);
}
4.参数

(1)context:ScriptableRenderContext,URP 渲染管线的上下文,用于提交渲染命令

(2)camera:要渲染的 Camera 对象,定义了渲染的视角和目标。

(3)isLastBaseCamera:布尔值,默认为 true,表示是否是最后一个基础(Base)相机,可能影响渲染管线的某些行为(如清理渲染目标)。

(4)additionalCameraData:UniversalAdditionalCameraData(引用传递),URP 相机的附加数据,包含渲染类型(Base/Overlay)、后处理设置等。如果为 null,表示相机没有附加数据(常见于非游戏相机)。

5.功能

这是一个入口方法,检查相机是否为游戏相机(通过 IsGameCamera),并尝试获取附加的相机数据组件 UniversalAdditionalCameraData。如果相机是游戏相机,则通过 TryGetComponent 获取 UniversalAdditionalCameraData 组件。调用另一个重载方法,传递附加相机数据。


5.RenderSingleCamera()
1.作用

RenderSingleCamera 是 URP 中渲染单个相机的核心方法,

(1)初始化渲染环境:为相机准备渲染数据(如 RenderingData、UniversalRenderingData)和资源(如命令缓冲区、渲染纹理)。

(2)执行场景剔除:生成 cullResults,确定可见对象、光源和探头。

(3)设置渲染参数:包括 Shader 常量、自适应探头体积(APV)、时间抗锯齿(TAA)等。

(4)驱动渲染器:通过 ScriptableRenderer 执行渲染 Pass(如不透明、透明、阴影、后处理)。

(5)支持多种渲染路径:兼容 Render Graph(现代优化路径)和传统渲染路径。

(6)资源管理和调试:提交命令缓冲区、释放资源,并支持性能分析和调试功能。

2.意义

核心渲染执行:RenderSingleCamera( )是 URP 渲染管线的底层执行层,负责将相机数据转换为渲染命令,生成最终图像。

性能优化:使用性能分析(ProfilingScope)、命令缓冲区管理和 Render Graph 等技术,优化渲染性能。

3.参数

(1)ScriptableRenderContext context

类型:ScriptableRenderContext

作用:Unity 的渲染上下文,负责提交渲染命令(如 Cull、DrawRenderers)到 GPU。

来源:由上层 Render 方法(如 UniversalRenderPipeline.Render)传递。

(2)UniversalCameraData cameraData

类型:UniversalCameraData

作用:包含相机参数(如 camera、cameraTargetDescriptor、postProcessEnabled)和渲染器引用(renderer)。

来源:由 RenderCameraStack 或 RenderSingleCameraInternal 创建并传递。

// 静态方法,用于渲染单个相机。
    // cameraData: 包含相机及其相关 URP 数据的结构体。
    static void RenderSingleCamera(ScriptableRenderContext context, UniversalCameraData cameraData)
    {
        Camera camera = cameraData.camera;
        ScriptableRenderer renderer = cameraData.renderer; // 获取与当前相机关联的渲染器实例

        // 1. 渲染器有效性检查
        if (renderer == null)
        {
            Debug.LogWarning(string.Format("Trying to render {0} with an invalid renderer. Camera rendering will be skipped.", camera.name));
            return; // 如果渲染器无效,则跳过渲染
        }

        // 2. 帧数据容器 (ContextContainer)
        // Note: We are disposing frameData once this variable goes out of scope.
        using ContextContainer frameData = renderer.frameData; // 获取渲染器的帧数据容器,它会在作用域结束时自动释放

        // 3. 尝试获取剔除参数
        if (!TryGetCullingParameters(cameraData, out var cullingParameters))
            return; // 如果无法获取剔除参数,则跳过渲染

        // 4. 设置当前活跃的渲染器
        ScriptableRenderer.current = renderer; // 设置静态变量 current,以便其他部分可以访问当前渲染器

        // 5. Render Graph 本地渲染 Pass 编译器设置 (根据 Unity 版本和渲染器支持)
#if RENDER_GRAPH_OLD_COMPILER // 旧的 Render Graph 编译器宏
        s_RenderGraph.nativeRenderPassesEnabled = false;
        Debug.LogWarning("The native render pass compiler is disabled. Use this for debugging only. Mobile performance may be sub-optimal.");
#else // 新版本 Render Graph 编译器
        s_RenderGraph.nativeRenderPassesEnabled = renderer.supportsNativeRenderPassRendergraphCompiler; // 根据渲染器是否支持本地 Pass 编译器来启用/禁用
#endif
        bool isSceneViewCamera = cameraData.isSceneViewCamera; // 判断是否为场景视图相机

        // 6. 命令缓冲区和性能分析 Scope 初始化
        // NOTE: Do NOT mix ProfilingScope with named CommandBuffers i.e. CommandBufferPool.Get("name"). ...
        CommandBuffer cmd = CommandBufferPool.Get(); // 从池中获取一个命令缓冲区

        // TODO: move skybox code from C++ to URP in order to remove the call to context.Submit() inside DrawSkyboxPass
        // Until then, we can't use nested profiling scopes with XR multipass
        CommandBuffer cmdScope = cameraData.xr.enabled ? null : cmd; // 如果 XR 启用,则不使用嵌套 ProfilingScope,避免问题

        var cameraMetadata = CameraMetadataCache.GetCached(camera); // 获取相机的元数据,包含其 ProfilingSampler
        using (new ProfilingScope(cmdScope, cameraMetadata.sampler)) // 开始一个性能分析 Scope,会将 BeginSample 命令添加到 cmd (如果 cmdScope 不为 null)
        {
            // 7. 清理渲染器
            renderer.Clear(cameraData.renderType); // 清理渲染器内部状态,例如重置一些纹理引用

            // 8. 剔除参数设置和预剔除渲染 Pass
            using (new ProfilingScope(Profiling.Pipeline.Renderer.setupCullingParameters)) // 性能分析 Scope:剔除参数设置
            {
                var legacyCameraData = new CameraData(frameData); // 创建旧版 CameraData 结构体(兼容性原因)

                renderer.OnPreCullRenderPasses(in legacyCameraData); // 渲染器执行预剔除渲染 Pass(在实际剔除之前执行一些操作)
                renderer.SetupCullingParameters(ref cullingParameters, ref legacyCameraData); // 渲染器设置剔除参数
            }

            // 9. 执行命令缓冲区 (发送之前的命令到 GPU)
            context.ExecuteCommandBuffer(cmd); // 将 cmd 中目前为止记录的所有命令发送给 ScriptableRenderContext
            cmd.Clear(); // 清空命令缓冲区,为后续命令做准备

            // 10. 设置每相机 Shader 常量
            SetupPerCameraShaderConstants(cmd); // 设置与当前相机相关的全局 Shader 常量

            // 11. 自适应探头体积 (APV) 相关设置和更新
            ProbeVolumesOptions apvOptions = null;
            if (camera.TryGetComponent<UniversalAdditionalCameraData>(out var additionalCameraData))
                apvOptions = additionalCameraData.volumeStack?.GetComponent<ProbeVolumesOptions>(); // 获取相机附加数据中的 APV 选项

            bool supportProbeVolume = asset != null && asset.lightProbeSystem == LightProbeSystem.ProbeVolumes; // 检查是否支持 APV
            ProbeReferenceVolume.instance.SetEnableStateFromSRP(supportProbeVolume); // 设置 APV 的启用状态
            ProbeReferenceVolume.instance.SetVertexSamplingEnabled(asset.shEvalMode == ShEvalMode.PerVertex || asset.shEvalMode == ShEvalMode.Mixed); // 设置 APV 顶点采样模式
            if (supportProbeVolume && ProbeReferenceVolume.instance.isInitialized) // 如果支持且已初始化
            {
                ProbeReferenceVolume.instance.PerformPendingOperations(); // 执行 APV 任何待处理操作(如加载资产)
                if (camera.cameraType != CameraType.Reflection &&
                    camera.cameraType != CameraType.Preview)
                {
                    // TODO: Move this to one call for all cameras
                    ProbeReferenceVolume.instance.UpdateCellStreaming(cmd, camera, apvOptions); // 更新 APV 单元格流送(非反射/预览相机)
                }
            }

            // 12. 发射场景/游戏视图 UI 几何体
            // 对于反射相机或预览相机,直接发出相机几何体
            if (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview)
                ScriptableRenderContext.EmitGeometryForCamera(camera);
#if UNITY_EDITOR // 仅在编辑器中
            else if (isSceneViewCamera)
                ScriptableRenderContext.EmitWorldGeometryForSceneView(camera); // 对于场景视图相机,发出世界几何体
#endif

            // 13. 绑定 APV 运行时资源
            if (supportProbeVolume)
                ProbeReferenceVolume.instance.BindAPVRuntimeResources(cmd, true); // 如果支持 APV,绑定运行时资源到命令缓冲区

            // 14. 渲染 APV 调试信息
            // 必须在剔除前调用,因为它通过 Graphics.DrawInstanced 发出中间渲染器
            ProbeReferenceVolume.instance.RenderDebug(camera, apvOptions, Texture2D.whiteTexture);

            // 15. 更新相机运动追踪 (TAA 和其他基于历史的渲染)
            // 更新相机的上一帧矩阵,用于运动向量等。只更新一次。
            if (additionalCameraData != null)
                additionalCameraData.motionVectorsPersistentData.Update(cameraData);

            // 16. 更新 TAA (时间抗锯齿) 目标
            // 调整 TAA 历史渲染目标的大小。这是持久数据。
            if (cameraData.taaHistory != null)
                UpdateTemporalAATargets(cameraData);

            // 17. 设置 RTHandles 的参考尺寸
            // 根据相机目标描述符的尺寸设置 RTHandles 系统的参考尺寸,用于渲染纹理的动态分配
            RTHandles.SetReferenceSize(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height);

            // 18. 创建 UniversalRenderingData 并执行剔除
            // Do NOT use cameraData after 'InitializeRenderingData'. CameraData state may diverge otherwise.
            // RenderingData takes a copy of the CameraData.
            // UniversalRenderingData needs to be created here to avoid copying cullResults.
            var data = frameData.Create<UniversalRenderingData>(); // 从帧数据容器创建 UniversalRenderingData 实例
            
            
            //剔除
            data.cullResults = context.Cull(ref cullingParameters); // 执行剔除,获取剔除结果

            // 19. GPU Resident Drawer 后剔除相机渲染开始
            GPUResidentDrawer.PostCullBeginCameraRendering(new RenderRequestBatcherContext { commandBuffer = cmd });

            // 20. 检查是否为 Forward Plus 渲染模式
            var isForwardPlus = cameraData.renderer is UniversalRenderer { renderingModeActual: RenderingMode.ForwardPlus };

            // 21. 初始化渲染所需的所有数据类型 (LightData, ShadowData, PostProcessingData, RenderingData)
            UniversalLightData lightData;
            UniversalShadowData shadowData;
            using (new ProfilingScope(Profiling.Pipeline.initializeRenderingData)) // 性能分析 Scope:初始化渲染数据
            {
                CreateUniversalResourceData(frameData); // 创建通用资源数据
                lightData = CreateLightData(frameData, asset, data.cullResults.visibleLights); // 创建灯光数据
                shadowData = CreateShadowData(frameData, asset, isForwardPlus); // 创建阴影数据
                CreatePostProcessingData(frameData, asset); // 创建后处理数据
                CreateRenderingData(frameData, asset, cmd, isForwardPlus, cameraData.renderer); // 创建核心渲染数据
            }

            // 22. 检查并应用调试设置
            RenderingData legacyRenderingData = new RenderingData(frameData); // 创建旧版 RenderingData(兼容性)
            CheckAndApplyDebugSettings(ref legacyRenderingData); // 检查并应用调试设置

#if ADAPTIVE_PERFORMANCE_2_0_0_OR_NEWER
            // 23. 应用自适应性能 (如果启用)
            if (asset.useAdaptivePerformance)
                ApplyAdaptivePerformance(frameData);
#endif

            // 24. 创建阴影图集并剔除阴影投射器
            CreateShadowAtlasAndCullShadowCasters(lightData, shadowData, cameraData, ref data.cullResults, ref context);

            // 25. 渲染器添加渲染 Pass
            renderer.AddRenderPasses(ref legacyRenderingData); // 渲染器根据渲染数据添加具体的渲染 Pass(如不透明、透明、后处理等)

            // 26. 执行 Render Graph 或旧版渲染路径
            if (useRenderGraph) // 如果启用 Render Graph
            {
                RecordAndExecuteRenderGraph(s_RenderGraph, context, renderer, cmd, cameraData.camera, cameraMetadata.name); // 记录并执行 Render Graph
                renderer.FinishRenderGraphRendering(cmd); // 渲染器完成 Render Graph 渲染
            }
            else // 如果禁用 Render Graph (兼容模式)
            {
                // Disable obsolete warning for internal usage
                #pragma warning disable CS0618
                using (new ProfilingScope(Profiling.Pipeline.Renderer.setup)) // 性能分析 Scope:渲染器设置
                {
                    renderer.Setup(context, ref legacyRenderingData); // 渲染器进行设置
                }

                // Timing scope inside
                renderer.Execute(context, ref legacyRenderingData); // 渲染器执行所有渲染 Pass
                #pragma warning restore CS0618
            }
        } // When ProfilingSample goes out of scope, an "EndSample" command is enqueued into CommandBuffer cmd

        // 27. 提交命令缓冲区 (发送 EndSample 命令)
        context.ExecuteCommandBuffer(cmd); // 将 cmd 中自上次 Clear 后记录的所有命令发送到 ScriptableRenderContext (主要是 EndSample 命令)
        CommandBufferPool.Release(cmd); // 释放命令缓冲区回池

        // 28. 提交渲染上下文
        using (new ProfilingScope(Profiling.Pipeline.Context.submit)) // 性能分析 Scope:上下文提交
        {
            // Render Graph will do the validation by itself, so this is redundant in that case
            if (!useRenderGraph && renderer.useRenderPassEnabled && !context.SubmitForRenderPassValidation())
            {
                renderer.useRenderPassEnabled = false;
                cmd.SetKeyword(ShaderGlobalKeywords.RenderPassEnabled, false);
                Debug.LogWarning("Rendering command not supported inside a native RenderPass found. Falling back to non-RenderPass rendering path");
            }
            context.Submit(); // 实际执行所有之前发送给 ScriptableRenderContext 的渲染命令
        }
        // 29. 清理当前渲染器引用
        ScriptableRenderer.current = null; // 清空当前活跃渲染器的静态引用
    }

提取出在RenderSingleCamera()中的关于URP流程中初始化阶段的核心代码片段如下:(此时已经经过剔除操作)

// Initialize all the data types required for rendering.
UniversalLightData lightData;
UniversalShadowData shadowData;

//创建一个性能分析作用域,标记为 initializeRenderingData,用于在 Unity Profiler 中追踪初始化阶段的性能。
//using 语句确保 ProfilingScope 在作用域结束时释放。
using (new ProfilingScope(Profiling.Pipeline.initializeRenderingData))
{
//在 frameData 中创建或更新 UniversalRenderingData 结构体,填充通用渲染数据。
CreateUniversalResourceData(frameData);

//创建并初始化 UniversalLightData,存储光照数据。
lightData = CreateLightData(frameData, asset, data.cullResults.visibleLights);

//创建并初始化 UniversalShadowData,存储阴影设置。
shadowData = CreateShadowData(frameData, asset, isForwardPlus);

//创建并初始化 UniversalPostProcessingData,存储后处理配置。
CreatePostProcessingData(frameData, asset);

//整合 frameData 中的子结构体,创建 RenderingData 作为渲染器的核心输入。
CreateRenderingData(frameData, asset, cmd, isForwardPlus, cameraData.renderer);
}
//创建 RenderingData 的实例,标记为 legacyRenderingData,用于非 Render Graph 路径的渲染。
RenderingData legacyRenderingData = new RenderingData(frameData);

初始化阶段生成以下输出:

RenderingData(legacyRenderingData):整合相机、光照、阴影、后处理数据,驱动渲染流程。

子结构体(存储在 frameData):

UniversalRenderingData:cullResults、commandBuffer 等。

UniversalCameraData:相机参数。

UniversalLightData:光照数据。

UniversalShadowData:阴影设置。

UniversalPostProcessingData:后处理配置。

这些数据通过 frameData 管理,RenderingData 从 frameData 提取子结构体。


2.RenderingData结构体

        RenderingData 是 URP 中一个核心的结构体,用于封装渲染单个相机所需的全部数据。它在渲染过程中作为数据的“容器”,传递给渲染器(如 UniversalRenderer)以执行渲染任务。UniversalRenderer 执行渲染 Pass(如不透明、透明、后处理)的核心输入。

注意:由于每个相机需要独立的 RenderingData,因为剔除结果、光照数据等与具体相机相关,所以RenderingData 的初始化发生在渲染单个相机时,具体在 RenderSingleCamera()方法中。

1.作用

(1)封装渲染上下文:RenderingData 包含渲染单个相机所需的所有信息,确保渲染器能够访问必要的参数。

(2)模块化渲染:通过将相机数据、光照数据、后处理数据等集中在一个结构体中,URP 可以复用相同的渲染逻辑(例如前向或延迟渲染)来处理不同的相机。

(3)优化数据传递:将所有相关数据打包到一个结构体中,减少函数调用时的参数传递开销,提高性能。

(4)支持多种渲染场景:适用于游戏相机(Base/Overlay)和非游戏相机(如 Scene View Camera、Preview Camera),确保渲染流程一致。

2.关键字段

frameData (ContextContainer):渲染管线的帧数据容器,存储全局渲染状态。

cameraData (CameraData):相机相关设置(如视锥、渲染目标、后处理),由 CreateCameraData 初始化。

lightData (LightData):光照相关设置(如主光源、附加光源、阴影)。

shadowData (ShadowData):阴影相关设置(如阴影贴图分辨率、强度)。

postProcessingData (PostProcessingData):后处理设置(如颜色分级、模糊、抗锯齿)。

cullResults (CullingResults):剔除结果,包含可见对象、光源和探头的句柄,用于绘制渲染对象。

commandBuffer (CommandBuffer):渲染命令缓冲区,仅在非 Render Graph 路径中使用(Render Graph 使用独立的命令缓冲区)。

3.关键函数

1.RenderingData()

初始化:RenderingData 通过构造函数初始化,接收 frameData(ContextContainer)作为输入。


    /// 用于渲染摄像机堆栈的多个渲染设置的结构体。
    /// URP 从多个位置构建渲染数据设置,包括管线资产、摄像机和光源设置。
    /// 设置也可能因不同平台而异,具体取决于是否使用自适应性能。
    public struct RenderingData
    {
        internal ContextContainer frameData;

        internal RenderingData(ContextContainer frameData)
        {
            this.frameData = frameData;
            cameraData = new CameraData(frameData);
            lightData = new LightData(frameData);
            shadowData = new ShadowData(frameData);
            postProcessingData = new PostProcessingData(frameData);
        }

        internal UniversalRenderingData universalRenderingData => frameData.Get<UniversalRenderingData>();

        //仅限非渲染图路径。不要与渲染图一起使用!
        internal ref CommandBuffer commandBuffer
        {
            get
            {
                ref var cmd = ref frameData.Get<UniversalRenderingData>().m_CommandBuffer;
                if (cmd == null)
                    Debug.LogError("RenderingData.commandBuffer is null. RenderGraph does not support this property. Please use the command buffer provided by the RenderGraphContext.");

                return ref cmd;
            }
        }

        /// 返回将手柄公开给可见对象、光源和探针的剔除结果。
        ///你可以使用它来绘制带有ScriptableRenderContext.DrawRenderers的对象
        public ref CullingResults cullResults => ref frameData.Get<UniversalRenderingData>().cullResults;

        ///包含多个与摄像机相关的渲染设置。
        public CameraData cameraData;

        ///包含多个与光源相关的渲染设置。
        public LightData lightData;

        /// 包含多个与阴影相关的渲染设置。
        public ShadowData shadowData;

        ///包含与集成后处理堆栈相关的多个渲染设置和资源。
        public PostProcessingData postProcessingData;

		
        //如果管道支持动态批处理,则为 true。
绘制阴影投射器时,此设置不适用。绘制阴影投射器时,动态批处理始终处于禁用状态。
        public ref bool supportsDynamicBatching => ref frameData.Get<UniversalRenderingData>().supportsDynamicBatching;

        
        /// 保存绘制时请求的每个对象数据
        public ref PerObjectData perObjectData => ref frameData.Get<UniversalRenderingData>().perObjectData;

        /// 如果在渲染摄像机堆栈时启用了后期处理效果,则为 true。
        public ref bool postProcessingEnabled => ref frameData.Get<UniversalPostProcessingData>().isEnabled;
    }

4.注意和UniversalRenderingData区分:

UniversalRenderingData 是ScriptableRenderer类型里的 frameData(ContextContainer 类型)中的一个子结构体(注意不是以字段的形式存在的,而是在RenderSingleCamera()中注册的)。

内容

commandBuffer(CommandBuffer):渲染命令缓冲区,用于记录命令(如绘制对象)。cullResults(CullingResults):剔除结果,在剔除阶段生成。

supportsDynamicBatching(bool):动态批处理开关。

perObjectData(PerObjectData):每对象数据设置。


三.总结

1. 实现流程(简要)

(1) 创建 UniversalRenderingData

通过 frameData.Create<UniversalRenderingData>()创建 UniversalRenderingData,并在 CreateUniversalResourceData中填充字段(如 commandBuffer、cullResults)。

(2) 初始化子结构体:通过 CreateLightData、CreateShadowData 和 CreatePostProcessingData创建并填充 UniversalLightData、UniversalShadowData 和 UniversalPostProcessingData。

(3) 整合 RenderingData:通过 CreateRenderingData从 frameData 提取子结构体(如 UniversalRenderingData.cullResults),生成完整的 RenderingData。

(4) 创建 legacyRenderingData:通过 new RenderingData(frameData)创建旧版 RenderingData,用于兼容传统渲染路径。

2. 类之间的联系

(1)UniversalRenderPipeline → ContextContainer

UniversalRenderPipeline.Render 调用 RenderSingleCamera,传递 ScriptableRenderContext 和 UniversalCameraData。

ContextContainer(frameData)由 UniversalRenderer 提供,动态存储 UniversalRenderingData 等子结构体。

(2)ContextContainer → UniversalRenderingData

frameData.Create<UniversalRenderingData>()创建 UniversalRenderingData,存储剔除结果(cullResults)。

CreateUniversalResourceData填充 UniversalRenderingData 的字段(如 commandBuffer)。

(3)ContextContainer → UniversalLightData / UniversalShadowData / UniversalPostProcessingData

通过 CreateLightData、CreateShadowData 和 CreatePostProcessingData创建并存储这些子结构体。

(4)ContextContainer → RenderingData

CreateRenderingData从 frameData 提取 UniversalRenderingData、UniversalLightData 等,整合到 RenderingData。legacyRenderingData从 frameData 创建,兼容传统路径。

(5)UniversalRenderer → RenderingData:UniversalRenderer 使用 RenderingData(或 legacyRenderingData)配置和执行渲染通道。

3. 涉及类间关系类比

(1)UniversalRenderPipeline

类比:流水线“工厂”

职责:协调渲染流程,调用 RenderSingleCamera 初始化和渲染单个相机。

作用:提供 ScriptableRenderContext 和 UniversalCameraData,触发初始化阶段。

(2)ContextContainer

类比:数据“仓库”

职责:动态存储和管理渲染数据(如UniversalRenderingData、UniversalLightData),通过泛型方法(Create<T>、Get<T>)支持扩展。

作用:存储初始化阶段的子结构体,供 RenderingData 整合。

(3)UniversalRenderingData

类比:通用数据“蓝图”

职责:存储通用渲染数据(如 cullResults、commandBuffer),为渲染提供基础。

作用:通过 frameData.Create<UniversalRenderingData>()和 CreateUniversalResourceData初始化,传递给 RenderingData。

(4)UniversalLightData / UniversalShadowData / UniversalPostProcessingData

类比:特定数据“模块”

职责:分别存储光照、阴影和后处理配置。

作用:通过 CreateLightData、CreateShadowData 和 CreatePostProcessingData初始化,填充到 RenderingData。

(5)RenderingData

类比:渲染数据“总清单”

职责:整合所有子结构体(cameraData、cullResults、lightData 等),驱动渲染器执行。

作用:通过 CreateRenderingData生成,legacyRenderingData支持传统路径。

(6)UniversalRenderer

类比:渲染“工人”

职责:根据 RenderingData 配置和执行渲染通道(如不透明、透明、后处理)。

作用:使用初始化阶段生成的 RenderingData 执行渲染。


 本篇完——


网站公告

今日签到

点亮在社区的每一天
去签到