diff --git a/docs/plan/SRP_UniversalRendererData配置层计划_2026-04-20.md b/docs/plan/SRP_UniversalRendererData配置层计划_2026-04-20.md new file mode 100644 index 00000000..02d0b0d9 --- /dev/null +++ b/docs/plan/SRP_UniversalRendererData配置层计划_2026-04-20.md @@ -0,0 +1,109 @@ +# SRP UniversalRendererData 配置层计划 2026-04-20 + +## 1. 阶段目标 + +把 `UniversalRendererData` 从“直接暴露裸 `defaultScenePasses` 数组”的状态,收成更接近 Unity URP 的 renderer settings 形态。 + +这一阶段完成后,要达到: + +1. `UniversalRendererData` 对外暴露的是高层配置,而不是原始 pass 序列 +2. 项目侧不需要自己拼 `BeforeOpaque / AfterOpaque` 这类内建 injection pass +3. `UniversalRenderer` 负责执行固定的内建主场景顺序 +4. renderer data 负责描述 opaque / skybox / transparent 这些默认段的开关和绘制设置 + +## 2. 当前问题 + +上一阶段虽然把默认主场景 pass 从 renderer 字段堆里抽成了 data 驱动,但当前形态仍然不够像 Unity: + +1. `UniversalRendererData` 直接暴露 `defaultScenePasses` +2. 项目脚本要自己 author injection pass 和 builtin draw pass 组合 +3. `UniversalScenePassData` 暴露的是运行时拓扑细节,不是 renderer asset 配置语义 + +这会导致: + +1. renderer data API 过低层 +2. 用户会在错误层级上改内建 topology +3. 未来做 renderer preset / renderer variant 时,外层 authoring 会继续失控 + +## 3. 目标结构 + +本阶段改成: + +1. `UniversalRendererData` + - `mainScene` + - `rendererFeatures` +2. `UniversalMainSceneData` + - `renderOpaque` + - `opaquePassEvent` + - `opaqueRendererListDesc` + - `opaqueDrawingSettings` + - `renderSkybox` + - `skyboxPassEvent` + - `renderTransparent` + - `transparentPassEvent` + - `transparentRendererListDesc` + - `transparentDrawingSettings` +3. `UniversalRenderer` + - 固定拥有 builtin opaque / skybox / transparent pass 组 + - 每帧根据 `mainScene` 配置这些 builtin pass + +## 4. 本阶段范围 + +包括: + +1. 新增 `UniversalMainSceneData` +2. 移除 `UniversalRendererData.defaultScenePasses` +3. 收掉 `UniversalScenePassData` 这层对外 authoring 面 +4. 更新项目 probe 和脚本侧调用 +5. 重编译 `XCEditor` +6. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒 + +不包括: + +1. renderer inspector/editor UI +2. deferred renderer +3. shadow/post-process 迁移到更完整的 renderer preset +4. C# 端用户自定义 `ScriptableRenderer` 体系扩展 + +## 5. 设计原则 + +这一步明确回到 Unity 风格: + +1. 内建 topology 由 renderer 负责 +2. renderer data 负责设置 +3. feature 负责扩展 +4. 用户不直接 author 内建 injection pass 序列 + +## 6. 实施步骤 + +### Step 1:新增高层 main-scene 配置对象 + +提供默认、仅 opaque、空主场景等工厂方法,降低项目侧 authoring 成本。 + +### Step 2:收掉裸 pass 数组接口 + +让 `UniversalRendererData` 改为暴露 `mainScene`,不再让上层直接构造内建主场景 pass 列表。 + +### Step 3:让 UniversalRenderer 回到 builtin pass 执行器角色 + +renderer 固定维护内建 pass 实例,但从 data 读取每段的设置来配置它们。 + +### Step 4:编译与冒烟 + +固定流程: + +1. `cmake --build . --config Debug --target XCEditor` +2. 启动 `editor/bin/Debug/XCEngine.exe` +3. 冒烟至少 10 秒 +4. 检查 `editor/bin/Debug/editor.log` + +## 7. 验收标准 + +本阶段收口后必须满足: + +1. `UniversalRendererData` 不再暴露裸 `defaultScenePasses` +2. `UniversalScenePassData` 不再是项目 authoring 入口 +3. `UniversalRenderer` 以内建 opaque / skybox / transparent 段来组织默认主场景 +4. 项目侧可以通过高层 `mainScene` 配置实现“仅 opaque”或“无默认主场景” +5. `XCEditor` 编译通过 +6. 旧 editor 冒烟通过 diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 7cebec6c..5b0853c5 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -234,7 +234,7 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs - ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalScenePassData.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index dc8d7581..b0b4e479 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -2537,24 +2537,9 @@ namespace Gameplay { new UniversalRendererData { - defaultScenePasses = - new UniversalScenePassData[] - { - UniversalScenePassData - .CreateInjection( - RenderPassEvent.BeforeRenderingOpaques, - SceneRenderInjectionPoint.BeforeOpaque), - UniversalScenePassData - .CreateDrawRenderers( - RenderPassEvent.RenderOpaques, - SceneRenderPhase.Opaque, - RendererListDesc.CreateDefault( - RendererListType.Opaque)), - UniversalScenePassData - .CreateInjection( - RenderPassEvent.AfterRenderingOpaques, - SceneRenderInjectionPoint.AfterOpaque) - } + mainScene = + UniversalMainSceneData + .CreateOpaqueOnly() } } }; diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneData.cs new file mode 100644 index 00000000..15fc1a4c --- /dev/null +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneData.cs @@ -0,0 +1,54 @@ +using XCEngine; +using XCEngine.Rendering; + +namespace XCEngine.Rendering.Universal +{ + public sealed class UniversalMainSceneData + { + public bool renderOpaque = true; + public RenderPassEvent opaquePassEvent = + RenderPassEvent.RenderOpaques; + public RendererListDesc opaqueRendererListDesc = + RendererListDesc.CreateDefault( + RendererListType.Opaque); + public DrawingSettings opaqueDrawingSettings = + DrawingSettings.CreateDefault(); + + public bool renderSkybox = true; + public RenderPassEvent skyboxPassEvent = + RenderPassEvent.RenderSkybox; + + public bool renderTransparent = true; + public RenderPassEvent transparentPassEvent = + RenderPassEvent.RenderTransparents; + public RendererListDesc transparentRendererListDesc = + RendererListDesc.CreateDefault( + RendererListType.Transparent); + public DrawingSettings transparentDrawingSettings = + DrawingSettings.CreateDefault(); + + public static UniversalMainSceneData CreateDefault() + { + return new UniversalMainSceneData(); + } + + public static UniversalMainSceneData CreateOpaqueOnly() + { + return new UniversalMainSceneData + { + renderSkybox = false, + renderTransparent = false + }; + } + + public static UniversalMainSceneData CreateEmpty() + { + return new UniversalMainSceneData + { + renderOpaque = false, + renderSkybox = false, + renderTransparent = false + }; + } + } +} diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs index be3ee594..c1ba61d9 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs @@ -1,4 +1,3 @@ -using System; using XCEngine; using XCEngine.Rendering; @@ -6,20 +5,11 @@ namespace XCEngine.Rendering.Universal { internal sealed class UniversalSceneInjectionPass : ScriptableRenderPass { - private SceneRenderInjectionPoint m_injectionPoint; + private readonly SceneRenderInjectionPoint m_injectionPoint; public UniversalSceneInjectionPass( RenderPassEvent passEvent, SceneRenderInjectionPoint injectionPoint) - { - Configure( - passEvent, - injectionPoint); - } - - public void Configure( - RenderPassEvent passEvent, - SceneRenderInjectionPoint injectionPoint) { renderPassEvent = passEvent; m_injectionPoint = injectionPoint; @@ -40,8 +30,44 @@ namespace XCEngine.Rendering.Universal public sealed class UniversalRenderer : ScriptableRenderer { private readonly UniversalRendererData m_rendererData; - private ScriptableRenderPass[] m_defaultScenePasses = - Array.Empty(); + private readonly UniversalSceneInjectionPass m_beforeOpaquePass = + new UniversalSceneInjectionPass( + RenderPassEvent.BeforeRenderingOpaques, + SceneRenderInjectionPoint.BeforeOpaque); + private readonly DrawObjectsPass m_drawOpaqueObjectsPass = + new DrawObjectsPass( + RenderPassEvent.RenderOpaques, + SceneRenderPhase.Opaque, + RendererListDesc.CreateDefault( + RendererListType.Opaque)); + private readonly UniversalSceneInjectionPass m_afterOpaquePass = + new UniversalSceneInjectionPass( + RenderPassEvent.AfterRenderingOpaques, + SceneRenderInjectionPoint.AfterOpaque); + private readonly UniversalSceneInjectionPass m_beforeSkyboxPass = + new UniversalSceneInjectionPass( + RenderPassEvent.BeforeRenderingSkybox, + SceneRenderInjectionPoint.BeforeSkybox); + private readonly DrawSkyboxPass m_drawSkyboxPass = + new DrawSkyboxPass(); + private readonly UniversalSceneInjectionPass m_afterSkyboxPass = + new UniversalSceneInjectionPass( + RenderPassEvent.AfterRenderingSkybox, + SceneRenderInjectionPoint.AfterSkybox); + private readonly UniversalSceneInjectionPass m_beforeTransparentPass = + new UniversalSceneInjectionPass( + RenderPassEvent.BeforeRenderingTransparents, + SceneRenderInjectionPoint.BeforeTransparent); + private readonly DrawObjectsPass m_drawTransparentObjectsPass = + new DrawObjectsPass( + RenderPassEvent.RenderTransparents, + SceneRenderPhase.Transparent, + RendererListDesc.CreateDefault( + RendererListType.Transparent)); + private readonly UniversalSceneInjectionPass m_afterTransparentPass = + new UniversalSceneInjectionPass( + RenderPassEvent.AfterRenderingTransparents, + SceneRenderInjectionPoint.AfterTransparent); public UniversalRenderer( UniversalRendererData rendererData) @@ -59,130 +85,59 @@ namespace XCEngine.Rendering.Universal return; } - UniversalScenePassData[] scenePasses = - m_rendererData - .GetDefaultScenePassesInstance(); - EnsureDefaultScenePassCache(scenePasses); + UniversalMainSceneData mainScene = + m_rendererData.GetMainSceneInstance(); - for (int i = 0; i < scenePasses.Length; ++i) + if (mainScene.renderOpaque) { - UniversalScenePassData scenePass = - scenePasses[i]; - if (scenePass == null || - !scenePass.isEnabled) - { - continue; - } + EnqueueOpaquePasses(mainScene); + } - ScriptableRenderPass renderPass = - ConfigureDefaultScenePass( - i, - scenePass); - if (renderPass != null) - { - EnqueuePass(renderPass); - } + if (mainScene.renderSkybox) + { + EnqueueSkyboxPasses(mainScene); + } + + if (mainScene.renderTransparent) + { + EnqueueTransparentPasses(mainScene); } } - private void EnsureDefaultScenePassCache( - UniversalScenePassData[] scenePasses) + private void EnqueueOpaquePasses( + UniversalMainSceneData mainScene) { - int passCount = - scenePasses != null - ? scenePasses.Length - : 0; - if (m_defaultScenePasses.Length == passCount) - { - return; - } - - ScriptableRenderPass[] resizedPasses = - new ScriptableRenderPass[passCount]; - int copyCount = - Math.Min( - m_defaultScenePasses.Length, - passCount); - for (int i = 0; i < copyCount; ++i) - { - resizedPasses[i] = - m_defaultScenePasses[i]; - } - - m_defaultScenePasses = resizedPasses; + m_drawOpaqueObjectsPass.Configure( + mainScene.opaquePassEvent, + SceneRenderPhase.Opaque, + mainScene.opaqueRendererListDesc, + mainScene.opaqueDrawingSettings); + EnqueuePass(m_beforeOpaquePass); + EnqueuePass(m_drawOpaqueObjectsPass); + EnqueuePass(m_afterOpaquePass); } - private ScriptableRenderPass ConfigureDefaultScenePass( - int passIndex, - UniversalScenePassData scenePass) + private void EnqueueSkyboxPasses( + UniversalMainSceneData mainScene) { - if (scenePass == null || - passIndex < 0 || - passIndex >= m_defaultScenePasses.Length) - { - return null; - } + m_drawSkyboxPass.Configure( + mainScene.skyboxPassEvent); + EnqueuePass(m_beforeSkyboxPass); + EnqueuePass(m_drawSkyboxPass); + EnqueuePass(m_afterSkyboxPass); + } - ScriptableRenderPass renderPass = - m_defaultScenePasses[passIndex]; - switch (scenePass.passType) - { - case UniversalScenePassType.Injection: - UniversalSceneInjectionPass injectionPass = - renderPass as UniversalSceneInjectionPass; - if (injectionPass == null) - { - injectionPass = - new UniversalSceneInjectionPass( - scenePass.passEvent, - scenePass.injectionPoint); - m_defaultScenePasses[passIndex] = - injectionPass; - } - - injectionPass.Configure( - scenePass.passEvent, - scenePass.injectionPoint); - return injectionPass; - case UniversalScenePassType.DrawRenderers: - DrawObjectsPass drawObjectsPass = - renderPass as DrawObjectsPass; - if (drawObjectsPass == null) - { - drawObjectsPass = - new DrawObjectsPass( - scenePass.passEvent, - scenePass.scenePhase, - scenePass.rendererListDesc, - scenePass.drawingSettings); - m_defaultScenePasses[passIndex] = - drawObjectsPass; - } - - drawObjectsPass.Configure( - scenePass.passEvent, - scenePass.scenePhase, - scenePass.rendererListDesc, - scenePass.drawingSettings); - return drawObjectsPass; - case UniversalScenePassType.DrawSkybox: - DrawSkyboxPass drawSkyboxPass = - renderPass as DrawSkyboxPass; - if (drawSkyboxPass == null) - { - drawSkyboxPass = - new DrawSkyboxPass( - scenePass.passEvent); - m_defaultScenePasses[passIndex] = - drawSkyboxPass; - } - - drawSkyboxPass.Configure( - scenePass.passEvent); - return drawSkyboxPass; - default: - return null; - } + private void EnqueueTransparentPasses( + UniversalMainSceneData mainScene) + { + m_drawTransparentObjectsPass.Configure( + mainScene.transparentPassEvent, + SceneRenderPhase.Transparent, + mainScene.transparentRendererListDesc, + mainScene.transparentDrawingSettings); + EnqueuePass(m_beforeTransparentPass); + EnqueuePass(m_drawTransparentObjectsPass); + EnqueuePass(m_afterTransparentPass); } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 1ea94424..d4773167 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -6,12 +6,12 @@ namespace XCEngine.Rendering.Universal { public class UniversalRendererData : ScriptableRendererData { - public UniversalScenePassData[] defaultScenePasses; + public UniversalMainSceneData mainScene; public ScriptableRendererFeature[] rendererFeatures; public UniversalRendererData() { - defaultScenePasses = CreateDefaultScenePasses(); + mainScene = UniversalMainSceneData.CreateDefault(); rendererFeatures = Array.Empty(); } @@ -32,50 +32,15 @@ namespace XCEngine.Rendering.Universal return "BuiltinForward"; } - internal UniversalScenePassData[] GetDefaultScenePassesInstance() + internal UniversalMainSceneData GetMainSceneInstance() { - if (defaultScenePasses == null) + if (mainScene == null) { - defaultScenePasses = CreateDefaultScenePasses(); + mainScene = + UniversalMainSceneData.CreateDefault(); } - return defaultScenePasses; - } - - public static UniversalScenePassData[] CreateDefaultScenePasses() - { - return new[] - { - UniversalScenePassData.CreateInjection( - RenderPassEvent.BeforeRenderingOpaques, - SceneRenderInjectionPoint.BeforeOpaque), - UniversalScenePassData.CreateDrawRenderers( - RenderPassEvent.RenderOpaques, - SceneRenderPhase.Opaque, - RendererListDesc.CreateDefault( - RendererListType.Opaque)), - UniversalScenePassData.CreateInjection( - RenderPassEvent.AfterRenderingOpaques, - SceneRenderInjectionPoint.AfterOpaque), - UniversalScenePassData.CreateInjection( - RenderPassEvent.BeforeRenderingSkybox, - SceneRenderInjectionPoint.BeforeSkybox), - UniversalScenePassData.CreateDrawSkybox(), - UniversalScenePassData.CreateInjection( - RenderPassEvent.AfterRenderingSkybox, - SceneRenderInjectionPoint.AfterSkybox), - UniversalScenePassData.CreateInjection( - RenderPassEvent.BeforeRenderingTransparents, - SceneRenderInjectionPoint.BeforeTransparent), - UniversalScenePassData.CreateDrawRenderers( - RenderPassEvent.RenderTransparents, - SceneRenderPhase.Transparent, - RendererListDesc.CreateDefault( - RendererListType.Transparent)), - UniversalScenePassData.CreateInjection( - RenderPassEvent.AfterRenderingTransparents, - SceneRenderInjectionPoint.AfterTransparent) - }; + return mainScene; } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalScenePassData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalScenePassData.cs deleted file mode 100644 index 1a3161cb..00000000 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalScenePassData.cs +++ /dev/null @@ -1,81 +0,0 @@ -using XCEngine; -using XCEngine.Rendering; - -namespace XCEngine.Rendering.Universal -{ - public enum UniversalScenePassType - { - Injection, - DrawRenderers, - DrawSkybox - } - - public sealed class UniversalScenePassData - { - public bool isEnabled = true; - public UniversalScenePassType passType = - UniversalScenePassType.Injection; - public RenderPassEvent passEvent = - RenderPassEvent.BeforeRenderingOpaques; - public SceneRenderInjectionPoint injectionPoint = - SceneRenderInjectionPoint.BeforeOpaque; - public SceneRenderPhase scenePhase = - SceneRenderPhase.Opaque; - public RendererListDesc rendererListDesc = - RendererListDesc.CreateDefault( - RendererListType.Opaque); - public DrawingSettings drawingSettings = - DrawingSettings.CreateDefault(); - - public static UniversalScenePassData CreateInjection( - RenderPassEvent passEvent, - SceneRenderInjectionPoint injectionPoint) - { - return new UniversalScenePassData - { - passType = UniversalScenePassType.Injection, - passEvent = passEvent, - injectionPoint = injectionPoint - }; - } - - public static UniversalScenePassData CreateDrawRenderers( - RenderPassEvent passEvent, - SceneRenderPhase scenePhase, - RendererListDesc rendererListDesc) - { - return CreateDrawRenderers( - passEvent, - scenePhase, - rendererListDesc, - DrawingSettings.CreateDefault()); - } - - public static UniversalScenePassData CreateDrawRenderers( - RenderPassEvent passEvent, - SceneRenderPhase scenePhase, - RendererListDesc rendererListDesc, - DrawingSettings drawingSettings) - { - return new UniversalScenePassData - { - passType = UniversalScenePassType.DrawRenderers, - passEvent = passEvent, - scenePhase = scenePhase, - rendererListDesc = rendererListDesc, - drawingSettings = drawingSettings - }; - } - - public static UniversalScenePassData CreateDrawSkybox( - RenderPassEvent passEvent = - RenderPassEvent.RenderSkybox) - { - return new UniversalScenePassData - { - passType = UniversalScenePassType.DrawSkybox, - passEvent = passEvent - }; - } - } -} diff --git a/project/Assets/Scripts/ProjectRenderPipelineProbe.cs b/project/Assets/Scripts/ProjectRenderPipelineProbe.cs index 8f2657c7..0b1b1b74 100644 --- a/project/Assets/Scripts/ProjectRenderPipelineProbe.cs +++ b/project/Assets/Scripts/ProjectRenderPipelineProbe.cs @@ -1,4 +1,3 @@ -using System; using System.Reflection; using XCEngine; using XCEngine.Rendering; @@ -129,8 +128,8 @@ namespace ProjectScripts { new UniversalRendererData { - defaultScenePasses = - Array.Empty(), + mainScene = + UniversalMainSceneData.CreateEmpty(), rendererFeatures = new ScriptableRendererFeature[] { new RenderObjectsRendererFeature