557 lines
13 KiB
Markdown
557 lines
13 KiB
Markdown
# Renderer模块设计与实现
|
||
|
||
## 1. 背景
|
||
|
||
XCEngine 当前已经完成了较为可用的 RHI 抽象层,且已经具备:
|
||
|
||
- `Scene + GameObject + Component` 基础场景模型
|
||
- `CameraComponent` / `LightComponent` 等基础组件
|
||
- `Mesh` / `Material` / `Texture` / `Shader` 等资源类型
|
||
- D3D12 / OpenGL 双后端 RHI 抽象与测试体系
|
||
|
||
下一阶段不应该继续封闭式打磨 RHI,而应该在 RHI 之上正式建立 **Renderer 模块**。
|
||
|
||
这里的 Renderer 模块不是“最终形态的 SRP”,而是:
|
||
|
||
- 先建立一层 **原生渲染运行时**
|
||
- 先让场景对象能够以正式渲染链路被绘制
|
||
- 同时在设计上预留未来 **C# Scriptable Render Pipeline(SRP)** 的接入点
|
||
|
||
也就是说,当前阶段的正确目标不是直接实现 Unity 的 URP/HDRP,而是先建立一套 **与 Unity 渲染架构方向一致的原生基础层**,后续让 C# SRP 驱动它。
|
||
|
||
---
|
||
|
||
## 2. 设计目标
|
||
|
||
Renderer 模块的目标分为两层:
|
||
|
||
### 2.1 当前阶段目标
|
||
|
||
先完成一套最小但完整的原生渲染链路:
|
||
|
||
- 从 `Scene` 中提取可渲染对象
|
||
- 通过 `Camera` 构建视图与投影数据
|
||
- 通过 `Material` / `Mesh` / `Texture` 构建 GPU 绘制数据
|
||
- 在 RHI 之上完成正式的 frame 渲染
|
||
- 支持 swapchain 输出与离屏输出
|
||
- 建立独立于 editor 的渲染宿主模型
|
||
|
||
### 2.2 面向未来 C# SRP 的目标
|
||
|
||
当前阶段的实现必须为后续演进预留稳定边界:
|
||
|
||
- 未来允许用 C# 定义 `RenderPipelineAsset` / `RenderPipeline`
|
||
- 未来允许用 C# 组织 render pass
|
||
- 未来允许 editor `SceneView` / `GameView` 通过同一套 renderer 输出
|
||
- 未来允许 C# 脚本控制 camera 渲染、pass 排序、目标输出与 command buffer
|
||
|
||
因此,当前阶段的原生 Renderer 不能做成一个写死的“大一统内建渲染函数”,而应该一开始就具备“可被上层 pipeline 驱动”的结构。
|
||
|
||
---
|
||
|
||
## 3. 与 Unity 渲染架构的对应关系
|
||
|
||
当前建议的路线与 Unity 的总体方向是对齐的,但要注意分层位置。
|
||
|
||
### 3.1 推荐分层
|
||
|
||
```text
|
||
Scene / Components / Resources
|
||
↓
|
||
Renderer 模块(原生渲染运行时)
|
||
↓
|
||
未来 C# SRP 层(脚本化渲染管线)
|
||
↓
|
||
RHI 抽象层
|
||
↓
|
||
D3D12 / OpenGL / Vulkan 后端
|
||
```
|
||
|
||
### 3.2 各层职责
|
||
|
||
#### Scene / Components / Resources
|
||
|
||
负责描述“要渲染什么”,例如:
|
||
|
||
- 场景对象
|
||
- 相机
|
||
- 灯光
|
||
- 网格
|
||
- 材质
|
||
- 贴图
|
||
|
||
这一层不应该直接持有后端 API 对象。
|
||
|
||
#### Renderer 模块(本阶段核心)
|
||
|
||
负责描述“如何从场景变成 draw call”,例如:
|
||
|
||
- 渲染对象抽取
|
||
- 可见性裁剪
|
||
- GPU 资源缓存
|
||
- camera frame 数据组织
|
||
- render target / depth target 管理
|
||
- render pass 调度
|
||
- 内建前向管线
|
||
|
||
这一层是未来 C# SRP 的原生支撑层。
|
||
|
||
#### 未来 C# SRP 层
|
||
|
||
负责描述“以脚本方式控制渲染流程”,例如:
|
||
|
||
- `RenderPipelineAsset`
|
||
- `RenderPipeline`
|
||
- `ScriptableRenderContext`
|
||
- `CommandBuffer`
|
||
- `RenderPassEvent`
|
||
- pass 注入与重排
|
||
|
||
这一层不应该直接绕过 Renderer 模块去操作后端 API。
|
||
|
||
#### RHI 抽象层
|
||
|
||
负责统一 GPU 接口与资源对象,是渲染系统的执行后端,而不是场景渲染逻辑本身。
|
||
|
||
### 3.3 与 Unity 的概念映射
|
||
|
||
| Unity 概念 | XCEngine 当前/规划对应 |
|
||
|---|---|
|
||
| `Camera` | `CameraComponent` |
|
||
| `Light` | `LightComponent` |
|
||
| `MeshFilter` | 计划新增 `MeshFilterComponent` |
|
||
| `MeshRenderer` | 计划新增 `MeshRendererComponent` |
|
||
| `RenderPipelineAsset` | 未来 Renderer 模块上的 pipeline asset 抽象 |
|
||
| `RenderPipeline` | 未来 Renderer 模块上的 pipeline 实例抽象 |
|
||
| `ScriptableRenderContext` | 未来 Renderer 模块对脚本暴露的原生 render context |
|
||
| `CommandBuffer` | 未来 Renderer 模块对脚本暴露的命令缓冲抽象 |
|
||
| `GraphicsDevice` / native render backend | 当前 RHI + 后端实现 |
|
||
|
||
结论是:
|
||
|
||
- **方向上符合 Unity 渲染架构**
|
||
- **当前阶段实现的应是 Unity 渲染体系中的原生底座**
|
||
- **而不是直接跳到最终的脚本化 SRP**
|
||
|
||
---
|
||
|
||
## 4. 核心设计原则
|
||
|
||
### 4.1 先建立原生渲染运行时,再开放脚本化管线
|
||
|
||
如果现在直接做 C# SRP,而原生 Renderer 边界还不存在,后续会出现:
|
||
|
||
- C# API 直接耦合 RHI
|
||
- editor viewport 与 runtime camera 逻辑混杂
|
||
- 资源对象与 GPU 对象生命周期混乱
|
||
|
||
因此必须先收敛原生 Renderer 模块。
|
||
|
||
### 4.2 Scene 层只描述逻辑对象,不持有后端对象
|
||
|
||
`GameObject`、`Component`、`Mesh`、`Material` 等对象只能描述逻辑与资源,不应该直接持有 D3D12/OpenGL 私有对象。
|
||
|
||
GPU 对象应由 Renderer 内部缓存层负责创建和复用。
|
||
|
||
### 4.3 editor 只是渲染宿主,不是渲染逻辑本体
|
||
|
||
`GameView` / `SceneView` 最终只是 Renderer 的输出宿主。
|
||
|
||
Renderer 本身必须先支持:
|
||
|
||
- 输出到 swapchain
|
||
- 输出到离屏纹理
|
||
|
||
然后 editor 再把离屏纹理接进 ImGui 面板。
|
||
|
||
### 4.4 为未来 SRP 预留 pipeline 抽象
|
||
|
||
即使第一阶段先做内建前向渲染,也不应该把逻辑写死成单一 `SceneRenderer::DrawEverything()`。
|
||
|
||
应该从一开始就保留:
|
||
|
||
- `RenderPipeline`
|
||
- `RenderPipelineAsset`
|
||
- `RenderContext`
|
||
- camera 列表驱动
|
||
- pass 分阶段执行
|
||
|
||
这样未来 C# 只是在这个原生结构上做绑定,而不是重做一遍架构。
|
||
|
||
### 4.5 测试体系与渲染层分离
|
||
|
||
`tests/RHI/` 继续只验证 RHI。
|
||
|
||
Renderer 模块应建立自己的测试体系:
|
||
|
||
- `tests/Rendering/unit/`
|
||
- `tests/Rendering/integration/`
|
||
|
||
这样职责边界才清晰。
|
||
|
||
---
|
||
|
||
## 5. 模块划分建议
|
||
|
||
建议新增 `Rendering` 模块,作为场景与 RHI 之间的正式中间层。
|
||
|
||
### 5.1 推荐目录结构
|
||
|
||
```text
|
||
engine/
|
||
├── include/XCEngine/Rendering/
|
||
│ ├── RenderSurface.h
|
||
│ ├── RenderContext.h
|
||
│ ├── RenderPipeline.h
|
||
│ ├── RenderPipelineAsset.h
|
||
│ ├── SceneRenderer.h
|
||
│ ├── RenderSceneExtractor.h
|
||
│ ├── RenderCameraData.h
|
||
│ ├── VisibleRenderObject.h
|
||
│ ├── RenderResourceCache.h
|
||
│ └── Pipelines/
|
||
│ └── BuiltinForwardPipeline.h
|
||
└── src/Rendering/
|
||
├── RenderSurface.cpp
|
||
├── SceneRenderer.cpp
|
||
├── RenderSceneExtractor.cpp
|
||
├── RenderResourceCache.cpp
|
||
└── Pipelines/
|
||
└── BuiltinForwardPipeline.cpp
|
||
```
|
||
|
||
### 5.2 组件层建议
|
||
|
||
为了尽可能对齐 Unity,而不是做一个临时过渡方案,建议直接采用:
|
||
|
||
- `MeshFilterComponent`
|
||
- `MeshRendererComponent`
|
||
|
||
其中:
|
||
|
||
#### `MeshFilterComponent`
|
||
|
||
负责“这个对象使用哪一个 mesh”:
|
||
|
||
- `ResourceHandle<Mesh>`
|
||
|
||
#### `MeshRendererComponent`
|
||
|
||
负责“这个对象如何被渲染”:
|
||
|
||
- 材质数组
|
||
- cast shadow / receive shadow
|
||
- render queue / layer / enable 状态
|
||
- 未来可扩展 light probe / motion vector / static batching 标记
|
||
|
||
这样做的好处是:
|
||
|
||
- 更贴近 Unity 的对象模型
|
||
- 更容易映射未来 C# API
|
||
- 更容易在 editor Inspector 中呈现
|
||
- 更容易为 `SkinnedMeshRenderer`、`SpriteRenderer` 等后续组件扩展留位置
|
||
|
||
### 5.3 Renderer 内部运行时对象
|
||
|
||
#### `RenderSurface`
|
||
|
||
统一表示渲染输出目标:
|
||
|
||
- 交换链输出
|
||
- 离屏 color/depth 输出
|
||
- editor viewport 输出
|
||
|
||
#### `RenderSceneExtractor`
|
||
|
||
负责从 `Scene` 中提取本帧可渲染对象:
|
||
|
||
- mesh
|
||
- material
|
||
- transform
|
||
- bounds
|
||
- render state
|
||
|
||
#### `RenderResourceCache`
|
||
|
||
负责把资源模块对象转成 GPU 可用对象:
|
||
|
||
- mesh -> vertex/index buffer
|
||
- texture -> RHI texture / resource view
|
||
- material -> descriptor set / uniform buffer / pipeline key
|
||
- shader pass -> pipeline state
|
||
|
||
#### `RenderContext`
|
||
|
||
作为原生渲染执行上下文,未来用于承接脚本化 pipeline 的调度。
|
||
|
||
它应封装:
|
||
|
||
- 当前 frame 的 command list
|
||
- render target 设置
|
||
- clear / draw / submit
|
||
- camera 相关渲染上下文
|
||
|
||
#### `RenderPipeline`
|
||
|
||
用于抽象具体渲染流程。
|
||
|
||
第一阶段只有一个原生内建实现:
|
||
|
||
- `BuiltinForwardPipeline`
|
||
|
||
未来再开放:
|
||
|
||
- native 可切换 pipeline
|
||
- C# 绑定的 scriptable pipeline
|
||
|
||
---
|
||
|
||
## 6. 第一阶段实现边界
|
||
|
||
第一阶段只做最小可用链路,不做“大而全”。
|
||
|
||
### 6.1 第一阶段要做
|
||
|
||
- `Rendering` 模块骨架
|
||
- `MeshFilterComponent` / `MeshRendererComponent`
|
||
- `RenderSurface`
|
||
- `RenderSceneExtractor`
|
||
- `RenderResourceCache`
|
||
- `SceneRenderer`
|
||
- `BuiltinForwardPipeline`
|
||
- 单 camera
|
||
- 单方向的 opaque forward 渲染
|
||
- 深度测试与深度写入
|
||
- 交换链输出
|
||
- 离屏输出
|
||
|
||
### 6.2 第一阶段先不做
|
||
|
||
- 阴影
|
||
- 后处理
|
||
- 延迟渲染
|
||
- 完整 PBR
|
||
- render graph
|
||
- C# SRP 真正落地
|
||
- editor viewport 完整交互
|
||
- 多 camera 叠加
|
||
|
||
### 6.3 第一阶段材质能力建议
|
||
|
||
建议先支持两档:
|
||
|
||
1. `UnlitTexture`
|
||
2. `SimpleLit`
|
||
|
||
其中:
|
||
|
||
- `UnlitTexture` 用于先打通最小链路
|
||
- `SimpleLit` 用于验证灯光、法线与材质基础通路
|
||
|
||
---
|
||
|
||
## 7. 面向未来 C# SRP 的预留设计
|
||
|
||
虽然第一阶段先做原生内建渲染,但必须提前约束下面这些方向。
|
||
|
||
### 7.1 先定义 pipeline 边界,再定义内建实现
|
||
|
||
正确顺序应当是:
|
||
|
||
1. 先定义 `RenderPipeline` 抽象
|
||
2. 再实现 `BuiltinForwardPipeline`
|
||
3. 后续 C# SRP 只是在这个边界上做脚本绑定
|
||
|
||
而不是:
|
||
|
||
1. 先写死一个 `SceneRenderer`
|
||
2. 以后再强行拆成 pipeline
|
||
|
||
第二种方式后续返工会很大。
|
||
|
||
### 7.2 Renderer 模块应向未来脚本层暴露的概念
|
||
|
||
当前阶段不一定全部实现,但结构上要留位置:
|
||
|
||
- `RenderPipelineAsset`
|
||
- `RenderPipeline`
|
||
- `RenderContext`
|
||
- `CullingResults`
|
||
- `DrawingSettings`
|
||
- `FilteringSettings`
|
||
- `ShaderTag`
|
||
- `CommandBuffer`
|
||
- `RendererList`
|
||
|
||
这些概念不一定要立刻与 Unity 一字不差,但应该在职责上能对应上。
|
||
|
||
### 7.3 材质与 Shader 资产模型不能停留在“单 shader 文件 + 属性包”
|
||
|
||
未来做 SRP 时,shader pass 选择、render queue、tag、render state 都是必要能力。
|
||
|
||
因此当前阶段即使先不全量实现,也不能把资产模型彻底锁死在过于简单的结构上。
|
||
|
||
这一点单独列为 issue。
|
||
|
||
---
|
||
|
||
## 8. 分阶段推进建议
|
||
|
||
### 阶段 A:Renderer v0 骨架
|
||
|
||
目标:
|
||
|
||
- 建立 `Rendering` 模块
|
||
- 建立 `MeshFilterComponent` / `MeshRendererComponent`
|
||
- 建立 `RenderSurface`
|
||
- 建立 `BuiltinForwardPipeline`
|
||
|
||
验收:
|
||
|
||
- 可以通过 Renderer 正式绘制一个 textured quad 场景
|
||
- 输出到 swapchain
|
||
- 输出到离屏 RT
|
||
|
||
### 阶段 B:真实资源场景接入
|
||
|
||
目标:
|
||
|
||
- 接入 mesh / texture / material 资源模块
|
||
- 跑通 `backpack` 这样的真实模型场景
|
||
|
||
验收:
|
||
|
||
- 真实 obj 资源经资源模块导入后可通过 Renderer 正式绘制
|
||
- D3D12 / OpenGL 双后端结果一致
|
||
|
||
### 阶段 C:基础光照
|
||
|
||
目标:
|
||
|
||
- 接入 `LightComponent`
|
||
- 跑通最基础的单方向光前向渲染
|
||
|
||
验收:
|
||
|
||
- `sphere` / `backpack` 存在正确基础明暗
|
||
- 材质参数与法线链路可验证
|
||
|
||
### 阶段 D:pipeline 抽象显式化
|
||
|
||
目标:
|
||
|
||
- 把内建前向渲染切到 `RenderPipeline` 抽象之下
|
||
- 支持 camera 列表驱动
|
||
- 为未来 C# SRP 绑定准备原生接口
|
||
|
||
验收:
|
||
|
||
- 原生内建 pipeline 通过统一接口驱动
|
||
- renderer 不再依赖单一路径写死执行
|
||
|
||
### 阶段 E:editor viewport 接入
|
||
|
||
目标:
|
||
|
||
- `SceneView` / `GameView` 使用 Renderer 的离屏输出
|
||
|
||
验收:
|
||
|
||
- editor 面板只是渲染宿主
|
||
- 不额外复制一套渲染逻辑
|
||
|
||
### 阶段 F:C# SRP 桥接
|
||
|
||
目标:
|
||
|
||
- 在既有 Renderer 模块基础上绑定脚本化 pipeline
|
||
|
||
验收:
|
||
|
||
- C# 可以控制 camera 渲染流程
|
||
- 原生 Renderer 继续负责底层资源、上下文与执行
|
||
|
||
---
|
||
|
||
## 9. 测试体系建议
|
||
|
||
Renderer 模块需要独立测试体系。
|
||
|
||
### 9.1 单元测试
|
||
|
||
建议放在:
|
||
|
||
- `tests/Rendering/unit/`
|
||
|
||
测试内容:
|
||
|
||
- render object 抽取
|
||
- material 参数打包
|
||
- pipeline key 构建
|
||
- GPU cache 命中与失效
|
||
- render surface 创建与 resize
|
||
|
||
### 9.2 集成测试
|
||
|
||
建议放在:
|
||
|
||
- `tests/Rendering/integration/`
|
||
|
||
建议场景:
|
||
|
||
1. `textured_quad_scene`
|
||
2. `backpack_scene`
|
||
3. `lit_sphere_scene`
|
||
|
||
仍然维持当前 RHI 抽象测试的好习惯:
|
||
|
||
- 一场景一张 `GT.ppm`
|
||
- D3D12 / OpenGL 都与同一张 GT 比对
|
||
|
||
### 9.3 与 RHI 测试的关系
|
||
|
||
`tests/RHI/` 继续用于验证:
|
||
|
||
- API 抽象正确性
|
||
- 后端行为一致性
|
||
- 资源 / 命令 /格式映射等底层问题
|
||
|
||
`tests/Rendering/` 则验证:
|
||
|
||
- 场景渲染链路
|
||
- 组件与资源到渲染结果的闭环
|
||
|
||
---
|
||
|
||
## 10. 当前已识别的不适配问题
|
||
|
||
以下问题不适合直接塞进本设计文档正文实现里,而应该独立跟踪:
|
||
|
||
1. `Scene / Components` 层还没有 `MeshFilter / MeshRenderer` 抽象
|
||
2. `Editor` 还没有 viewport 的离屏渲染宿主接入层
|
||
3. `Material / Shader` 资产模型还不足以支撑未来 SRP 的 pass/tag 语义
|
||
|
||
对应 issue:
|
||
|
||
- `docs/issues/Renderer模块_Scene层缺少MeshFilter与MeshRenderer抽象.md`
|
||
- `docs/issues/Renderer模块_EditorViewport缺少RenderSurface接入层.md`
|
||
- `docs/issues/Renderer模块_Material与Shader资产模型暂不满足SRP演进需求.md`
|
||
|
||
---
|
||
|
||
## 11. 结论
|
||
|
||
当前 Renderer 阶段的正确方向是:
|
||
|
||
- 在 RHI 之上建立 **原生渲染运行时**
|
||
- 用它先承接基础前向渲染
|
||
- 同时提前为未来 **C# SRP** 留出清晰接口
|
||
|
||
因此,下一阶段的 Renderer 规划如果按本文执行,是与 Unity 渲染架构方向相容的,而且比“先做一个临时内建 renderer,后面再拆”更稳。
|
||
|
||
一句话概括:
|
||
|
||
- **现在做的是 Unity 式渲染体系的原生底座**
|
||
- **以后在这个底座之上接 C# SRP**
|
||
|