前言
旧管线(ForwardPipeline、DefferedPipeline)的一帧渲染数据流是 Pipeline → Flow → Stage → Queue。
这一篇讲新管线(Custom Pipeline),它用RenderGraph替换了旧管线的固定层次,让开发者以声明式的方式构建渲染流程。
注:本文基于cocos引擎 4.0.0之前版本。
新旧管线核心差异:
| 维度 |
旧管线 |
新管线 |
| 架构 |
固定流程:Pipeline → Flow → Stage → Queue |
可编程渲染图:RenderGraph + PipelineBuilder |
| 灵活性 |
低,需继承并重写 Flow/Stage |
高,可自由组合 Pass、Subpass、Compute |
| 资源管理 |
手动创建 Framebuffer,管线内部缓存 |
资源注册到 ResourceGraph,引擎自动管理状态转换和 Barrier |
| 渲染流程组织 |
以对象类型分类(不透明、透明、阴影等) |
以 Pass 为节点,Queue 为子节点,Scene 为叶子节点 |
| 扩展方式 |
继承 RenderPipeline 并组装 Flow/Stage |
实现 PipelineBuilder,在 setup() 中声明式构建 |
新管线的核心是 RenderGraph——一张有向无环图,节点是渲染 Pass,边是资源依赖。一帧渲染分三步:
1 2 3 4 5
| PipelineBuilder.setup() → 声明式构建 RenderGraph ↓ Pipeline.compile() → 编译 RenderGraph,分析依赖 ↓ Executor.execute() → 深度优先遍历 RenderGraph,执行渲染
|
一、类层次结构
新管线有三层接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| interface PipelineRuntime { activate(swapchain: Swapchain): boolean; render(cameras: Camera[]): void; destroy(): boolean; }
interface BasicPipeline extends PipelineRuntime { addRenderPass(width, height, layoutName?): BasicRenderPassBuilder; addQueue(hint?, phaseName?, passName?): RenderQueueBuilder; addScene(camera, sceneFlags, light?, scene?): SceneBuilder; beginFrame(): void; endFrame(): void; }
interface Pipeline extends BasicPipeline { addComputePass(passName?): ComputePassBuilder; addRenderSubpass(subpassName): RenderSubpassBuilder; addStorageBuffer(name, accessType, slotName): void; addUploadPass(uploadPairs): void; }
|
Web 平台实现 BasicPipeline,原生平台(C++)实现 Pipeline:
classDiagram
PipelineRuntime <|-- BasicPipeline
BasicPipeline <|-- Pipeline
BasicPipeline <|.. WebPipeline
Pipeline <|.. NativePipeline
二、从 Root 到 Pipeline
新管线的入口和旧管线完全一样——Root._frameMoveEnd() 调用 pipeline.render(cameraList)。但 render() 的实现不同:
1 2 3 4 5 6 7
| render(cameras: Camera[]): void { this._applySize(cameras); this.beginFrame(); this.execute(); this.endFrame(); }
|
**beginFrame()**:触发 director.buildRenderPipeline(),让 PipelineBuilder 重新构建 RenderGraph。
execute() (web-pipeline.ts:1662-1677):
1 2 3 4 5 6
| execute(): void { this.compile(); this._executor.execute(this._renderGraph); }
|
**endFrame()**:清空 RenderGraph,为下一帧做准备。
三、声明式构建 RenderGraph
PipelineBuilder 是开发者定义渲染流程的入口。以默认管线为例,setup() 中按顺序声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export const setup: PipelineBuilder = { setup(cameras, pipeline) { pipeline.addStorageBuffer('LightBuffer', ...);
for (const camera of cameras) { if (camera.scene?.mainLight) { pipeline.addRenderPass(width, height, 'shadow-map') .addQueue(QueueHint.RENDER_OPAQUE) .addScene(camera, SceneFlags.OPAQUE | SceneFlags.SHADOW_CASTER); } }
for (const camera of cameras) { pipeline.addRenderPass(width, height, 'default') .addQueue(QueueHint.RENDER_OPAQUE, 'default') .addScene(camera, SceneFlags.OPAQUE, light) .addQueue(QueueHint.RENDER_TRANSPARENT, 'default') .addScene(camera, SceneFlags.TRANSPARENT, light); }
pipeline.addRenderPass(width, height, 'post-process') .addQueue(QueueHint.RENDER_TRANSPARENT) .addScene(camera, SceneFlags.BLEND, null, scene); } };
|
每个 API 调用都在 RenderGraph 中添加一个节点:
1 2 3
| addRenderPass() → RasterPass 节点(渲染目标、Viewport) └── addQueue() → Queue 节点(不透明/透明、排序规则) └── addScene() → Scene 节点(场景渲染、剔除参数)
|
四、资源管理 — ResourceGraph
新管线的一个重要特性是 ResourceGraph——和 RenderGraph 平行的资源描述图。开发者在 PipelineBuilder 中声明资源(纹理、Buffer),引擎自动管理:
1 2 3 4
| pipeline.addRenderTarget('GBufferAlbedo', Format.RGBA8, width, height); pipeline.addRenderTarget('GBufferNormal', Format.RGBA16F, width, height); pipeline.addDepthStencil('DepthStencil', Format.DEPTH_STENCIL, width, height);
|
引擎根据 ResourceGraph 自动处理:
- 资源创建与复用:同一名字的资源只创建一次,跨帧复用
- 状态转换(Barrier):自动插入 Vulkan/Metal 的 Image Layout Transition
- 资源生命周期:不再使用的资源自动回收
五、场景剔除 — SceneCulling
新管线的剔除逻辑封装在 cocos/rendering/custom/scene-culling.ts 的 SceneCulling 类中:
1 2 3 4 5
| class SceneCulling { frustumCullings: Map<RenderScene, FrustumCulling>; lightBoundsCullings: Map<RenderScene, LightBoundsCulling>; renderQueues: Array<RenderQueue>; }
|
剔除流程和旧管线类似,但做了以下优化:
- 缓存剔除结果:同一场景 + 同一相机 + 同一光源的剔除结果会被缓存,避免重复计算
- 按需剔除:只在 SceneData 变化时重新剔除,静态场景零开销
- 内置光源剔除:
lightBoundsCulling() 根据光源的包围盒筛选受影响的模型,避免无效光照计算
核心剔除函数 sceneCulling() 的源码(scene-culling.ts:164),判断条件依次为:LOD 级别 → 图层匹配 → 视锥体相交,和旧管线一致。
六、Executor 执行
Executor.execute() 是 RenderGraph 的真正执行者(executor.ts):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| execute(rg: RenderGraph): void { context.renderGraph = rg; context.reset();
culling.buildRenderQueues(rg, context.layoutGraph, context.pipelineSceneData);
context.lightResource.buildLights(culling, ...);
this._removeDeviceResource();
cmdBuff.begin();
context.lightResource.buildLightBuffer(cmdBuff);
context.lightResource.tryUpdateRenderSceneLocalDescriptorSet(context.culling);
culling.uploadInstancing(cmdBuff);
depthFirstSearch(this._visitor.graphView, this._visitor, this._visitor.colorMap);
cmdBuff.end(); context.device.queue.submit([cmdBuff]); }
|
七、RenderGraph 深度优先遍历
执行的核心是 DFS 遍历。每遇到一个节点,根据类型调用对应的回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| DFS 遍历 RenderGraph │ ├── RasterPass 节点 │ ├── applyID(id) → 记录当前 Pass ID │ ├── preRecord() → 准备 Pass 描述符集 │ ├── 递归遍历子节点 (Queue) │ │ ├── Queue 节点 │ │ │ ├── preRecord() → 清空场景列表 │ │ │ ├── 递归遍历子节点 (Scene) │ │ │ │ └── Scene 节点 │ │ │ │ ├── preRecord() → 初始化 Blit 描述符 │ │ │ │ └── record() → 根据 BlitType 分发 │ │ │ │ ├── DRAW_3D → 遍历 Blit.models,逐个绘制 │ │ │ │ ├── DRAW_2D → 遍历 UI Batches,逐个绘制 │ │ │ │ └── FULLSCREEN_QUAD → 全屏四边形绘制 │ │ │ └── record() → 收集所有 Scene 的绘制命令 │ │ │ └── postRecord() │ │ └── record() → 收集所有 Queue 的绘制命令 │ └── postRecord() │ ├── ComputePass 节点 │ ├── preRecord() │ ├── 递归遍历子节点 (ComputeQueue) │ │ └── ComputeQueue.record() → 执行 Compute Dispatch │ └── postRecord() │ └── CopyPass 节点 └── 执行 Upload/Copy 操作
|
八、DeviceRenderScene 的 record 方法
DFS 遍历到 Scene 节点时,调用 DeviceRenderScene.record()(executor.ts)。根据 BlitType 分发:
| BlitType |
方法 |
含义 |
FULLSCREEN_QUAD |
_recordBlit() |
全屏四边形,用于后处理 |
DRAW_2D |
_recordUI() |
遍历 UI Batches,逐个绘制 |
DRAW_3D |
_record3D() |
遍历 Blit.models 的 SubModel/Pass,逐个录制 DrawCall |
DRAW_PROFILE |
_showProfiler() |
绘制 Profiler 面板 |
九、重要文件汇总
| 文件 |
含义 |
cocos/rendering/custom/pipeline.ts |
PipelineRuntime、BasicPipeline、Pipeline 接口和 PipelineBuilder |
cocos/rendering/custom/web-pipeline.ts |
WebPipeline 实现,RenderGraph 构建、编译、执行 |
cocos/rendering/custom/executor.ts |
Executor 执行器,DFS 遍历 RenderGraph |
cocos/rendering/custom/scene-culling.ts |
SceneCulling,剔除逻辑 + 渲染队列构建 |
cocos/rendering/custom/render-graph.ts |
RenderGraph 和 ResourceGraph 数据结构 |
cocos/rendering/custom/web-pipeline-types.ts |
PipelineBuilder.setup() 类型定义 |
editor/assets/default_renderpipeline/builtin-pipeline.ts |
默认管线的 PipelineBuilder 实现 |
十、总结
Cocos 新管线用 RenderGraph 替代了旧管线的 Pipeline → Flow → Stage → Queue 固定架构。一帧渲染流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Root.frameMove() → pipeline.render(cameraList) → beginFrame() ← 清空上一帧 → execute() → compile() ← 编译 RenderGraph → executor.execute(rg) → buildRenderQueues() ← 构建队列 + 剔除 → buildLights() ← 构建光源数据 → depthFirstSearch() ← DFS 遍历 RenderGraph → Pass.preRecord() ← 准备 Pass → Queue.record() ← 收集场景绘制命令 → Scene.record() ← 执行 DrawCall → Pass.postRecord() → submit() ← 提交到 GPU → endFrame() ← 清理
|
新管线的核心优势是声明式编程:开发者用 addRenderPass() → addQueue() → addScene() 的语义描述”我要画什么”,引擎自动处理资源管理、状态转换和命令录制。