Files
XCEngine/docs/plan/Renderer模块设计与实现.md
2026-03-29 01:36:53 +08:00

13 KiB
Raw Blame History

Renderer模块设计与实现

1. 背景

XCEngine 当前已经完成了较为可用的 RHI 抽象层,且已经具备:

  • Scene + GameObject + Component 基础场景模型
  • CameraComponent / LightComponent 等基础组件
  • Mesh / Material / Texture / Shader 等资源类型
  • D3D12 / OpenGL 双后端 RHI 抽象与测试体系

下一阶段不应该继续封闭式打磨 RHI而应该在 RHI 之上正式建立 Renderer 模块

这里的 Renderer 模块不是“最终形态的 SRP”而是

  • 先建立一层 原生渲染运行时
  • 先让场景对象能够以正式渲染链路被绘制
  • 同时在设计上预留未来 C# Scriptable Render PipelineSRP 的接入点

也就是说,当前阶段的正确目标不是直接实现 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 推荐分层

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 层只描述逻辑对象,不持有后端对象

GameObjectComponentMeshMaterial 等对象只能描述逻辑与资源,不应该直接持有 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 推荐目录结构

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 中呈现
  • 更容易为 SkinnedMeshRendererSpriteRenderer 等后续组件扩展留位置

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. 分阶段推进建议

阶段 ARenderer v0 骨架

目标:

  • 建立 Rendering 模块
  • 建立 MeshFilterComponent / MeshRendererComponent
  • 建立 RenderSurface
  • 建立 BuiltinForwardPipeline

验收:

  • 可以通过 Renderer 正式绘制一个 textured quad 场景
  • 输出到 swapchain
  • 输出到离屏 RT

阶段 B真实资源场景接入

目标:

  • 接入 mesh / texture / material 资源模块
  • 跑通 backpack 这样的真实模型场景

验收:

  • 真实 obj 资源经资源模块导入后可通过 Renderer 正式绘制
  • D3D12 / OpenGL 双后端结果一致

阶段 C基础光照

目标:

  • 接入 LightComponent
  • 跑通最基础的单方向光前向渲染

验收:

  • sphere / backpack 存在正确基础明暗
  • 材质参数与法线链路可验证

阶段 Dpipeline 抽象显式化

目标:

  • 把内建前向渲染切到 RenderPipeline 抽象之下
  • 支持 camera 列表驱动
  • 为未来 C# SRP 绑定准备原生接口

验收:

  • 原生内建 pipeline 通过统一接口驱动
  • renderer 不再依赖单一路径写死执行

阶段 Eeditor viewport 接入

目标:

  • SceneView / GameView 使用 Renderer 的离屏输出

验收:

  • editor 面板只是渲染宿主
  • 不额外复制一套渲染逻辑

阶段 FC# 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