15 KiB
Renderer 结构收口与代码正式化计划
日期:2026-04-05
1. 阶段定位
当前 Rendering 主线在功能上已经完成了相当多闭环:
- 三后端统一的 runtime renderer 主链已经建立
- directional shadow、multi-light、object-id、editor overlay 等能力都已接入
- SceneView / GameView 基本共用了同一条 runtime 渲染路径
但从代码结构和职责边界上看,这一阶段还没有真正收口。现在的问题已经不再是“某个功能没接上”,而是:
renderer 的核心模块里仍然混有明显的阶段性写法、特殊分支、职责堆叠和 editor/runtime 边界不清的问题。
如果此时直接继续往上叠 skybox、环境、后处理、更多 renderer feature,后面会越来越难拆,最终重新把已经相对稳定的 renderer 主链拖回到“能跑但很难维护”的状态。
因此,本阶段的唯一目标不是加新功能,而是:
把当前 renderer 这一阶段真正做成可持续演进的正式结构,为后续 Skybox / Environment / PostProcess / 更正式的 SRP 承接清掉结构债。
2. 为什么现在必须先做结构收口
这不是“目录看着乱一点”的表面问题,而是已经影响后续演进的实质性架构问题。
2.1 CameraRenderer 仍然存在特殊通道
当前 CameraRenderer 虽然已经具备请求规划与多阶段执行能力,但 object-id 仍然是单独的一套特殊路径,而不是正式 frame composition 里的统一 pass 节点。
这带来的问题:
- 相机级执行顺序不是单一模型,而是“主链 + 特判”
- 后续 skybox / post-process / capture / debug target 更难正规接入
- 单元测试里被迫维护特殊 mock pass 类型,而不是统一的 pass contract
关键文件:
engine/include/XCEngine/Rendering/ObjectIdPass.hengine/include/XCEngine/Rendering/CameraRenderer.hengine/include/XCEngine/Rendering/CameraRenderRequest.hengine/src/Rendering/CameraRenderer.cpp
2.2 BuiltinForwardPipeline.cpp 已经是典型 god file
这个文件里当前同时混着:
- pass wrapper
- shader pass resolve
- graphics pipeline 创建
- descriptor set layout 规划
- descriptor set 资源写入
- lighting 常量打包
- material fallback
- draw submission
这已经不是“文件有点长”,而是职责拆分失败。后续任何修改都会把高层语义、资源绑定、RHI 细节、draw 级逻辑一起牵动,测试也只能做大颗粒回归,无法精准保护。
关键文件:
engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp
2.3 RenderMaterialUtility.h 混合了契约、兼容层和运行时解析
当前这个头里至少混了五类职责:
- builtin pass contract 定义
- shader/property/binding 查询
- descriptor layout 规划
- legacy/material fallback 兼容
- runtime material resolve 与绑定辅助
这会导致:
- “正式 contract” 与 “过渡兼容逻辑” 难以分开演进
- 很多 renderer 代码只能依赖一个超大工具头
- 头文件膨胀,职责不可读,接口边界不清
关键文件:
engine/include/XCEngine/Rendering/RenderMaterialUtility.h
2.4 editor / debug pass 仍然混在 runtime renderer 核心层里
grid、outline 这些能力现在已经走到了比较正式的 runtime host path,但它们在 engine 里的组织方式仍然更接近“把 editor 需求塞进 renderer 核心”。
风险在于:
- runtime 核心会继续被 editor 语义污染
- 后续 scene/game/editor 三条宿主路径边界会再次变模糊
- 玩家运行时和编辑器专用渲染能力的依赖关系难以长期维护
关键文件:
engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.hengine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h
2.5 文件拆分层次仍然不干净
例如 BuiltinDepthStylePassBase.cpp 的底部还直接放着 BuiltinDepthOnlyPass、BuiltinShadowCasterPass 具体实现,这说明“抽象基类”和“具体 pass”仍然没有彻底分层。
关键文件:
engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp
2.6 少量稳定性问题仍然暴露出“临时写法”
比如 scene extractor 里 visible item 的稳定排序仍然用 raw pointer 作为 tie-breaker,而 additional light 已经升级成了稳定的 GameObject::ID 语义。
这类问题虽然不大,但非常能说明当前代码里仍混有阶段性临时写法,必须顺手清理干净。
关键文件:
engine/src/Rendering/RenderSceneExtractor.cpp
3. 本阶段的核心设计原则
本阶段继续严格遵循当前工程既定设计理念,并与 RHI模块总览 中的核心原则保持一致:
RHI只负责 GPU 抽象,不感知 object-id、grid、outline、skybox、post-process 等高层语义。Renderer负责 scene extraction、frame composition、material/shader contract、runtime pass orchestration。Editor只作为 renderer 的宿主和扩展使用方,不把 editor 语义反向污染 RHI。- 兼容层和正式 contract 必须拆开,不能继续把“临时兜底”混在正式主链里。
- 不引入
render graph。本阶段先把现有 renderer 结构做正式化,不跳级优化。 - 不做“修修补补式”文件搬家,必须同时修职责边界、执行路径和测试结构。
4. 阶段总目标
本阶段收口完成后,应达到以下状态:
CameraRenderer形成单一、明确、可测试的 frame composition 模型。- runtime pass 与 object-id / editor-debug pass 的边界清晰,接入点正式化。
BuiltinForwardPipeline不再由一个 god file 承担所有责任。RenderMaterialUtility被拆分为“正式 contract 层”和“兼容/运行时辅助层”。- renderer 文件结构与代码结构一致,抽象基类、具体 pass、绑定辅助、compat helper 各归其位。
- 当前所有 rendering / editor 相关测试继续通过,不破坏已闭环功能。
5. 非目标
本阶段明确不做:
render graph- deferred / clustered / tiled lighting
- 新一轮 editor 视觉特效堆叠
- 大规模 shader authoring 体系重写
- point / spot shadow
- 重新设计 RHI
6. 分阶段执行方案
6.1 Phase A:Camera Frame Composition 正式化
目标
消灭 CameraRenderer 里 object-id 的特殊执行通道,把相机级执行模型统一成正式 frame composition。
要解决的根因
- 现在相机渲染顺序不是单一 contract
ObjectIdPass是旁路抽象,不利于后续继续扩展 composition- 测试里存在针对 object-id 的特殊 mock pass 体系
具体工作
- 重新审视
CameraRenderRequest的阶段描述,明确:- pre-scene
- shadow/depth
- scene pipeline
- auxiliary offscreen passes
- post-scene
- overlay
- 去掉
ObjectIdPass作为并行特例抽象的地位。 - 把 object-id 统一纳入正式 pass 执行序列,必要时通过 pass category / target intent 标识语义,而不是再保留独立虚函数族。
- 简化
CameraRenderer执行逻辑,让失败传播、目标准备、pass 顺序只走一套主链。 - 同步收敛相关单元测试,让测试验证“阶段顺序”和“失败传播”,而不是验证某个特判分支。
验收标准
CameraRenderer不再对 object-id 走特判主逻辑test_camera_scene_renderer等单测仍覆盖 object-id 顺序与失败传播- editor viewport object-id picking 不回退
计划提交点
这一阶段完成后立即提交推送一次。
6.2 Phase B:BuiltinForwardPipeline 职责拆分
目标
把 BuiltinForwardPipeline.cpp 从 god file 拆成正式的职责层次,但不改变现有 forward runtime 的对外行为。
要解决的根因
- pipeline resolve、resource layout、descriptor write、material resolve、lighting packing、draw submission 全部耦合
- 任何小修改都会波及整个文件
- 难以为 skybox / post-process / future pipeline 承接建立稳定接口
具体工作
- 先按职责切出独立模块,优先拆成以下几层:
- shader/pass resolve
- pipeline cache/build
- resource binding layout / descriptor planning
- frame-scoped lighting / pass constants upload
- draw item submission
- 让
BuiltinForwardPipeline保留 orchestration 职责,而不是继续承载全部细节。 - 清理与
RenderMaterialUtility的交叉依赖,为下一阶段拆 contract 做准备。 - 保证 unlit / lit / object-id / depth-only / shadow-caster 的绑定逻辑不被混淆。
验收标准
BuiltinForwardPipeline.cpp明显缩小,核心职责清晰- 新拆出的模块命名与职责稳定,不是单纯“工具类化”
- forward 相关单测、集成测试全部不回退
计划提交点
这一阶段完成后立即提交推送一次。
6.3 Phase C:RenderMaterialUtility 正式拆层
目标
把 shader/material/pass 的正式 contract 与 legacy/compat/runtime helper 拆开。
要解决的根因
- 正式接口和过渡逻辑混在一起
- 任何依赖
RenderMaterialUtility.h的代码都会被迫包含大量不相干能力 - 后续 shader/material 演进会被兼容逻辑长期绑死
具体工作
- 明确拆成三层语义:
contract:builtin pass 名称、标准 binding 名称、正式解析规则runtime resolve:材质/着色器运行时查询、pass 选择、绑定规划compat:legacy property 名称、历史 fallback、过渡适配
- 避免再把大段实现继续塞在头文件里,能下沉到
.cpp的尽量下沉。 - 对外只暴露最小且稳定的正式接口。
- 给 compat 层加清晰边界,避免以后继续被当作默认主路径使用。
验收标准
RenderMaterialUtility.h体量显著下降,职责单一- renderer 主链依赖的是正式 contract / runtime resolve,而不是 compat 大杂烩
- 现有 shader/material 行为与测试结果保持一致
计划提交点
这一阶段完成后立即提交推送一次。
6.4 Phase D:Runtime Pass 与 Editor/Debug Pass 边界重整
目标
明确 engine runtime rendering core 与 editor/debug-oriented rendering extension 的边界。
要解决的根因
- grid、outline 等语义虽然已经可用,但组织上仍偏临时
- engine 核心层里混有 editor 专用概念
- 后续 camera frame composition 扩展容易再次被 editor 需求污染
具体工作
- 明确哪些是 runtime 正式能力,哪些是 editor/debug extension。
- 把 editor/debug pass 的注册与宿主接入方式整理成正式 extension seam。
- 保持 SceneView / GameView 继续复用 runtime renderer 主链,但 editor overlay / outline / grid 不侵入 runtime scene composition。
- 补足必要文档,说明 engine、renderer、editor 三者的责任边界。
验收标准
- editor/debug pass 不再作为 runtime renderer 核心概念扩散
- SceneView / GameView 显示、grid、outline、gizmo 宿主路径不回退
- 新增代码结构能自然承接后续 icon/light gizmo/camera gizmo 等扩展
计划提交点
这一阶段完成后立即提交推送一次。
6.5 Phase E:稳定性清扫、文件收口与文档归档
目标
清掉这一阶段剩余的临时写法,让实现、测试、文档口径再次一致。
具体工作
- 修正
RenderSceneExtractor里仍然使用 raw pointer 的稳定排序 tie-breaker。 - 把
BuiltinDepthStylePassBase.cpp中具体 pass 实现拆出到独立文件。 - 全面复查 renderer 相关文件命名、目录结构、头源分布是否仍有明显反模式。
- 更新
tests/TEST_SPEC.md与相关 renderer / editor guide。 - 阶段完成后,把已过期 plan 归档到
docs/used。
验收标准
- renderer 核心目录结构与职责边界基本一致
- 没有明显残留的阶段性临时代码入口
- 文档、测试矩阵、实现状态三者一致
计划提交点
这一阶段完成后立即提交推送一次。
7. 测试策略
本阶段的测试必须是“每阶段落地即验证”,不能到最后一次性回归。
7.1 Unit
重点保护:
CameraRenderer阶段顺序与失败传播RenderSceneExtractor的稳定输出BuiltinForwardPipeline的绑定与材质解析- material/shader contract 拆层后的接口行为
优先关注:
tests/Rendering/unit/test_camera_scene_renderer.cpptests/Rendering/unit/test_builtin_forward_pipeline.cpp- 与 material utility / scene extractor 相关的 unit tests
7.2 Editor / Runtime Integration
重点回归:
- object-id picking
- SceneView / GameView runtime 渲染链
- overlay / outline / grid
- backpack / shadow / multi-light / camera stack / offscreen 等现有场景
至少覆盖:
tests/editor/test_viewport_render_flow_utils.cpptests/editor/test_scene_viewport_overlay_renderer.cpptests/editor/test_viewport_object_id_picker.cpp- 现有 rendering integration matrix 中与 lighting、object-id、camera flow 相关的场景
7.3 编译与宿主验证
每一阶段至少执行:
- 相关 test target 编译
- 相关 unit / integration tests
- 必要时编译
XCEditor - 对 editor 中 SceneView / GameView 做 smoke 验证
8. 风险与控制
风险 1:把“结构重构”做成单纯的文件搬家
后果:
- 文件名变了,职责没变
- 代码仍然继续跨层互相依赖
控制策略:
- 每次拆分都要同时调整接口边界和测试保护点
风险 2:为了图省事继续保留 object-id 特判
后果:
- Camera frame composition 永远无法正式化
- 后续 skybox / post-process 会继续引入更多特判
控制策略:
- 第一阶段必须先砍掉这类特殊旁路
风险 3:compat 逻辑继续侵入正式 contract
后果:
- shader/material 体系长期混乱
- 之后 Unity 风格 shader authoring 很难落地
控制策略:
- compat 层必须显式命名、显式隔离、显式限定使用场景
风险 4:editor/debug pass 重整时破坏现有 editor 体验
后果:
- 影响当前 SceneView 主线
- 把结构收口又变成功能回退
控制策略:
- 每一阶段都要做 editor smoke 和既有测试回归
9. 阶段完成判定
满足以下条件时,本阶段才算真正收口:
CameraRenderer已统一成正式 frame composition 执行模型。BuiltinForwardPipeline与RenderMaterialUtility已完成职责拆分,核心 god file 问题消除。- runtime pass 与 editor/debug pass 边界清晰,不再混成一团。
- 现有 rendering / editor tests 继续稳定通过。
docs/plan与docs/used的 plan 入口重新清晰,不再保留已过期的执行入口。
10. 与当前主线的关系
这份计划不是替代“Skybox 环境与 Frame Composition 正式化”方向,而是它的前置收口。
顺序必须是:
- 先做 renderer 结构收口与代码正式化
- 再做 skybox / environment / post-process 的正式接入
- 最后才考虑更高阶的 renderer feature 与未来 SRP 承接
否则就是在结构债未清的情况下继续加层,后面只会越收越难。