docs: expand rendering core docs

This commit is contained in:
2026-04-03 15:30:47 +08:00
parent e8f64407ba
commit 4616e6fd1f
16 changed files with 636 additions and 241 deletions

View File

@@ -1,149 +1,146 @@
# Scene Extraction And Builtin Forward Pipeline
## 先理解这套渲染架构在做什么
## 这条链路现在怎么工作
当前 XCEngine 的渲染链路,不是“场景对象自己直接调用 RHI 去画”,而是先做一次轻量的场景提取,再交给具体渲染管线执行
当前 XCEngine 的主场景渲染链路可以按下面的顺序理解
1. `Scene``GameObject` 维护对象层级与组件
2. `MeshFilterComponent` 提供几何数据
3. `MeshRendererComponent` 提供材质槽和渲染标志
4. `RenderSceneExtractor` 把这些信息拍平为 `RenderSceneData`
5. `SceneRenderer` 把提取结果交给 `BuiltinForwardPipeline`
6. `BuiltinForwardPipeline` 通过 RHI 发出真正 draw call。
1. `SceneRenderer` 负责组织相机请求和执行顺序
2. `CameraRenderer` 选择主渲染管线;默认情况下,它通过 `BuiltinForwardPipelineAsset` 创建 `BuiltinForwardPipeline`
3. `RenderSceneExtractor` 把 scene/game-object/component 结构压平成 `RenderSceneData`
4. `BuiltinForwardPipeline``RenderSceneData` 绘制到 `RenderSurface`
条链路和很多商业引擎的基本思路是一致的。真正成熟的区别,不在“有没有 scene extraction 这个词”,而在于提取阶段做得有多深。
里的关键点不是“有没有前向渲染”,而是场景数据和绘制数据已经被拆成两层:
## 为什么要把 Mesh 和 Renderer 分开
- 提取阶段负责从场景组织里收集相机、光照和可绘制项。
- pipeline 阶段只消费已经整理好的 `RenderSceneData`
`MeshFilterComponent + MeshRendererComponent` 这种拆分,和 Unity 的老思路非常接近。它背后的设计理由很实用
不过这里讲的只是主场景绘制槽位,不是整次相机提交的完整尾段。按 `engine/src/Rendering/CameraRenderer.cpp` 的当前实现,`BuiltinForwardPipeline` 前后还可能包着
- 几何数据和材质配置是两种不同维度的信息。
- 同一个 mesh 可能被不同材质配置复用。
- 渲染系统提取时,可以分别读取 geometry source 和 material state。
- `preScenePasses`
- object-id pass
- `postScenePasses`
- `overlayPasses`
如果把这两层全部混在一个组件里,短期写起来更省事,但长期会让复用、序列化和编辑器展示都更别扭
Scene View 的网格、选中描边和 editor overlay当前就是先在 Editor 层组装成这些 request 级序列,再交给 `CameraRenderer` 执行
## 为什么需要 Scene Extraction
## `RenderSceneData` 现在包含什么
对游戏对象系统来说,最自然的数据组织方式是树状层级和组件组合;但对渲染来说,最自然的数据组织方式通常是
`RenderSceneExtractor` 当前产出的核心数据有三块
- 一台相机
- 一组可见对象
- 每个对象对应的变换、mesh 和材质
- `camera``cameraData`
- `lighting`
- `visibleItems`
所以提取阶段的意义,就是把“适合编辑和运行时逻辑”的数据形状,转换成“适合绘制”的数据形状
其中 `visibleItems` 的元素类型是 `VisibleRenderItem`。头文件里仍保留 `VisibleRenderObject = VisibleRenderItem` 的兼容别名,但当前实现和新文档都应以 `visibleItems` 为准,而不是旧说法 `visibleObjects`
当前 `RenderSceneExtractor` 做的是这条路线中最基础的一版
每个 `VisibleRenderItem` 除了 mesh 和变换,还已经携带
- 选相机
- 算矩阵
- 遍历层级
- 收集 mesh 对象
- `material`
- `materialIndex`
- `sectionIndex`
- `hasSection`
- `renderQueue`
- `cameraDistanceSq`
和商业引擎的真正可见性阶段相比还很早期,但方向没有问题
让后续 pipeline 不需要回到场景层重新做组件查询
## 当前 extractor 做了什么,没做什么
## 为什么还要保留 scene extraction
已经做的:
游戏对象系统天然偏向层级、组件和编辑器语义;渲染系统天然偏向“当前相机要画哪些项”。`RenderSceneExtractor` 的价值就是把这两种数据形状解耦。
- 覆盖相机优先。
- 没有覆盖相机时,优先选择 `IsPrimary()``depth` 更高的相机。
- 只收集激活层级中的对象。
- 只收集同时拥有 `MeshFilterComponent``MeshRendererComponent` 的对象。
当前 extractor 已经做的事情包括:
还没做的:
- 选择相机并生成 `RenderCameraData`
- 提取主方向光数据
- 递归收集满足基础绘制条件的可见项
- 产出 `visibleItems` 并带上 material / section / distance 等渲染侧字段
- 视锥裁剪。
- 遮挡裁剪。
- 透明排序。
- render layer 过滤。
- 阴影 caster / receiver 分类。
- instancing / batching。
它还没有做完整的商业级可见性系统,例如:
所以当前 `visibleObjects` 更准确地说是“当前场景里满足基础绘制条件的对象集合”,而不是严格意义上的最终可见集。
- 视锥裁剪
- 遮挡裁剪
- instancing / batching
- 透明排序主路径
## RenderSurface 为什么重要
## `BuiltinForwardPipeline` 的当前定位
很多小型引擎一开始会把“渲染目标”写死成 swap chain back buffer但这条路很快就会卡住
`BuiltinForwardPipeline` 现在是默认主渲染路径,但它内部已经不是旧的固定绑定模型,而是三层缓存
- 离屏渲染怎么办?
- 编辑器预览窗口怎么办?
- 反射探针、后处理链路怎么办?
- `(shader*, passName)` 缓存 `PassResourceLayout`
- `(shader*, passName, render state)` 缓存 `RHIPipelineState`
- `(passLayout, setIndex, objectId, material)` 缓存动态 descriptor set
`RenderSurface` 的作用,就是把这些目标抽象出来。当前它虽然还很轻量,但已经能描述
此外它已经接入 [RenderPassSequence](../../XCEngine/Rendering/RenderPass/RenderPass.md)
- 构造时注册 `BuiltinForwardOpaquePass`
- `Initialize()` 走 sequence 生命周期
- `Render()` 先构造 `RenderPassContext`,再执行 sequence
- `Shutdown()` 也走 sequence再由 opaque pass 回调资源清理
所以当前 builtin forward 更像“一条默认可运行的 pass-sequence 管线”,而不是过去那种把所有逻辑揉在一个固定 layout 里的实现。
## Shader pass 资源契约
当前 builtin forward 对 shader pass 的资源声明有明确白名单。可识别语义只有:
- `PerObject`:必需,单个 CBV
- `Material`:可选,单个 CBV
- `BaseColorTexture`:可选,单个 `Texture2D``TextureCube`
- `LinearClampSampler`:可选,单个 sampler
如果 shader pass 没有声明 `resources`,则会回退到 legacy builtin forward 绑定:
- `set 1 binding 0` -> `PerObject`
- `set 2 binding 0` -> `Material`
- `set 3 binding 0` -> `BaseColorTexture`
- `set 4 binding 0` -> `LinearClampSampler`
如果声明不合法,例如未知语义、同 set 混用 sampler 和非 sampler、重复 binding 或缺少 `PerObject`pipeline 会拒绝为该 pass 创建布局。
测试 `tests/Rendering/unit/test_builtin_forward_pipeline.cpp` 还明确校验了 builtin shader `ForwardLit` pass 的这组资源契约,以及 `BuildInputLayout()` 现在使用 `POSITION=float3`
## 单个可见项如何被画出来
`BuiltinForwardOpaquePass` 遍历 `RenderSceneData::visibleItems` 时,每个物体大致会经历以下步骤:
1. 根据材质解析真正要用的 shader/pass没有合适 pass 时回退到 builtin forward shader。
2. 取得 mesh 的 GPU 资源缓存。
3. 生成 `PerObjectConstants``PerMaterialConstants`
4. 获取或创建当前 shader pass 的 `PassResourceLayout`
5. 逐 set 绑定 descriptor
-`PerObject / Material / Texture` 的 set 走动态缓存。
- 只含 sampler 的 set 走静态缓存。
6. 按 section 或整 mesh 发出 draw call。
`BaseColorTexture` 如果解析不到,会回退到初始化阶段创建的 1x1 白色纹理 SRV。
## `RenderSurface` 在这里的作用
`RenderSurface` 不是单纯的附件容器而是当前这一帧输出目标的完整描述。builtin forward 会消费它的:
- 尺寸
- 颜色附件
- 深度附件
- 清屏色覆盖
- 渲染前后颜色资源状态
- render area
- clear color override
- 自动状态切换开关
- `colorStateBefore` / `colorStateAfter`
这一步非常关键,因为它让 pipeline 不再只服务于窗口表面
因此这条主渲染路径既能服务于窗口 back buffer也能服务于 editor viewport、离屏 RT 或其它自定义输出目标
## BuiltinForwardPipeline 的定位
## 当前限制
`BuiltinForwardPipeline` 当前的定位,不是“最终渲染架构”,而是“默认可用的第一条内建管线”
它现在主要解决的是:
- 把 mesh 上传成 vertex/index buffer
- 把 texture 上传成 shader resource
- 创建一套最基本的 pipeline state
- 对每个对象写常量、绑纹理、发 draw
这和很多商业引擎早期 builtin pipeline 的定位很像:先确保有一条完整闭环,再逐步扩展能力。
## 为什么要有 RenderResourceCache
场景对象和资源系统不应该直接持有具体 RHI 对象,否则很快会把层级关系搞乱:
- 资源层会被具体后端污染。
- 不同 backend 的资源生命周期会混到游戏对象逻辑里。
`RenderResourceCache` 把“CPU 资源 -> GPU 资源”的转换收口到渲染层,这是对的。
但当前它还只是基础版:
- 以裸指针做 cache key。
- 没有资源变更失效。
- 没有线程安全。
- 格式支持有限。
所以它更像“运行时上传缓存”,还不是完整的 render resource system。
## 当前这套设计和商业引擎相比差什么
如果拿商业级游戏引擎的渲染系统作参照,当前最明显的缺口包括:
1. 没有真正的 visibility / culling pipeline。
2. 没有 render queue、pass 分类和排序系统。
3. 没有 lighting、shadow、post-processing 等完整渲染特性。
4. 没有 render graph 或更强的 target dependency 管理。
5. `RenderPipelineAsset` 还没有真正驱动运行时 pipeline 创建。
但从架构骨架看,当前已经把最核心的几个边界切开了:
- scene data
- render data
- render target
- pipeline implementation
- GPU resource upload
这恰恰是后续能继续演进的前提。
## 当前阶段的推荐用法
- 需要渲染场景时,优先通过 `SceneRenderer` 作为统一入口。
- 几何数据放在 `MeshFilterComponent`,材质槽和渲染标志放在 `MeshRendererComponent`
-`RenderSurface` 当作“这一帧的输出目标描述”,而不是只当附件容器。
- 当前把 `BuiltinForwardPipeline` 看作默认可运行路径,而不是最终渲染架构。
- 主场景默认只有 builtin forward opaque pass
- 透明、阴影、延迟渲染和更复杂的资源依赖管理还不在这条链路里。
- `RenderPassSequence` 目前只提供顺序执行,不是 render graph。
## 相关 API
- [Rendering](../../XCEngine/Rendering/Rendering.md)
- [RenderSceneExtractor](../../XCEngine/Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
- [RenderSurface](../../XCEngine/Rendering/RenderSurface/RenderSurface.md)
- [RenderPass](../../XCEngine/Rendering/RenderPass/RenderPass.md)
- [SceneRenderer](../../XCEngine/Rendering/SceneRenderer/SceneRenderer.md)
- [BuiltinForwardPipeline](../../XCEngine/Rendering/Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md)
- [BuiltinForwardPipelineAsset](../../XCEngine/Rendering/Pipelines/BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md)
- [MeshFilterComponent](../../XCEngine/Components/MeshFilterComponent/MeshFilterComponent.md)
- [MeshRendererComponent](../../XCEngine/Components/MeshRendererComponent/MeshRendererComponent.md)