自定义渲染管线

Cocos Creator 3.8 中正式开放了新的 自定义渲染管线

自定义渲染管线 的接口位于 cocos/core/pipeline/custom/pipeline.ts

介绍

对于游戏引擎来说,渲染是最重要的功能之一。而引擎的渲染效果,由 渲染管线(RenderPipeline)决定,取决于不同的艺术风格、运行平台、硬件设备、渲染技术等等。

如何在引擎中实现各种各样的画面表现,是一个非常复杂的问题。

这需要引擎提供足够的灵活性,让用户可以自由定制渲染管线,以实现各种各样的效果。

Cocos Creator 自定义渲染管线 能够在不同的平台、不同的硬件设备上,编写最优的渲染管线,以达到最佳的画面表现。

也能够在不同的平台、不同的硬件设备上,编写最通用的渲染管线,以达到最佳的性能表现以及跨平台性。

启用自定义管线

勾选 自定义渲染管线

自定义渲染管线(实验性质) - 图1

通过填写 自定义管线 的名字,选择注册好的 自定义渲染管线

  • 目前支持 前向渲染管线(名字为 Custom 或 Forward)和 后向渲染管线(名字为 Deferred)两种。

自定义渲染管线(实验性质) - 图2

编写自定义渲染管线

新建 Typescript 文件,编写一个类,例如 MyPipeline,让该类实现 rendering.PipelineBuilder 接口,通过 rendering.setCustomPipeline 方法把该 pipeline 注册到系统中。

概念

自定义渲染管线以数据流(Dataflow)的形式概括了渲染的整个流程,用渲染图(RenderGraph)描述。

渲染图由不同的渲染节点组成,比如渲染通道(RenderPass),计算通道(ComputePass)等。每个渲染通道有数据输入与输出,比如渲染目标(RenderTarget)、深度模板(DepthStencil)、贴图(Texture)等。这些输入与输出会在计算节点间构成链接关系,形成数据流。

渲染通道计算通道节点下,可以有渲染队列(RenderQueue)子节点,用于控制渲染的顺序。渲染队列节点下,可以有渲染内容(RenderContent)子节点,用于控制渲染的内容。

渲染内容 可以是 场景、屏幕 矩形,也可以是计算任务的 分发(Dispatch),不同的通道支持不同的渲染内容。

自定义渲染管线 的【渲染通道渲染队列渲染内容】构成一个森林:

自定义渲染管线(实验性质) - 图3

自定义渲染管线的【渲染通道渲染资源】构成一个有向无圈图(DAG):

自定义渲染管线(实验性质) - 图4

我们可以层叠(Stack)以上两张图,得到 渲染流程图(RenderGraph)。渲染流程图 描述了 自定义渲染管线 的全部流程,引擎会按照用户定制的流程图进行资源分配、流程优化、渲染执行。

渲染管线类型

引擎的渲染管线根据硬件能力,分为了两种

  • 基础渲染管线
  • 标准渲染管线

基础渲染管线(BasicPipeline)

基础渲染管线提供跨平台的基础渲染功能,适用一切平台。

接口主要分为两类

  • 注册资源。在管线中注册的资源,可以在渲染通道中使用,资源读写状态由管线托管。

    1. interface BasicPipeline {
    2. addRenderWindow (name: string, format: Format, width: number, height: number,
    3. renderWindow: RenderWindow): number;
    4. addRenderTarget (name: string, format: Format, width: number, height: number,
    5. residency?: ResourceResidency): number;
    6. addDepthStencil (name: string, format: Format, width: number, height: number,
    7. residency?: ResourceResidency): number;
    8. }
  • 添加渲染通道

    1. interface BasicPipeline {
    2. addRenderPass (width: number, height: number, passName?: string): BasicRenderPassBuilder;
    3. }

    渲染通道 代表了一次光栅化过程,将物件渲染至渲染目标以及深度模板缓冲

可以通过BasicRenderPassBuilder构建。

  1. interface BasicRenderPassBuilder extends Setter {
  2. addRenderTarget (name: string, loadOp?: LoadOp, storeOp?: StoreOp, color?: Color): void;
  3. addDepthStencil (name: string, loadOp?: LoadOp, storeOp?: StoreOp,
  4. depth?: number, stencil?: number, clearFlags?: ClearFlagBit): void;
  5. addTexture (name: string, slotName: string, sampler?: Sampler | null, plane?: number): void;
  6. addQueue (hint?: QueueHint, phaseName?: string): RenderQueueBuilder;
  7. }

标准渲染管线(Pipeline)

标准渲染具备更丰富的管线功能,目前支持GLES3、Vulkan、Metal三个后端。

标准渲染管线在基础渲染管线的基础上,提供了计算通道(ComputePass),渲染子通道(RenderSubpass)等功能。 并且可以使用 StorageBufferStorageTexture 等资源。

  1. export interface Pipeline extends BasicPipeline {
  2. addStorageBuffer (name: string, format: Format, size: number,
  3. residency?: ResourceResidency): number;
  4. addStorageTexture (name: string, format: Format, width: number, height: number,
  5. residency?: ResourceResidency): number;
  6. addRenderPass (width: number, height: number, passName: string): RenderPassBuilder;
  7. addComputePass (passName: string): ComputePassBuilder;
  8. addUploadPass (uploadPairs: UploadPair[]): void;
  9. addMovePass (movePairs: MovePair[]): void;
  10. }
  11. export interface RenderPassBuilder extends BasicRenderPassBuilder {
  12. addStorageBuffer (name: string, accessType: AccessType, slotName: string): void;
  13. addStorageImage (name: string, accessType: AccessType, slotName: string): void;
  14. addRenderSubpass (subpassName: string): RenderSubpassBuilder;
  15. }
  16. export interface ComputePassBuilder extends Setter {
  17. addTexture (name: string, slotName: string, sampler?: Sampler | null, plane?: number): void;
  18. addStorageBuffer (name: string, accessType: AccessType, slotName: string): void;
  19. addStorageImage (name: string, accessType: AccessType, slotName: string): void;
  20. addQueue (phaseName?: string): ComputeQueueBuilder;
  21. }

根据不同的平台,用户可以针对性构建不同的渲染管线。

比如在移动平台上,用户可以通过渲染子通道(RenderSubpass)利用芯片上的高速缓存,减少内存读写来降低发热。

在桌面平台上,用户则可以使用计算通道(ComputePass)编写复杂的图形算法。充分利用平台特性。

渲染子通道 RenderSubpass (实验性质)

渲染子通道表示渲染的一个阶段,该阶段读取和写入渲染通道中的一部分附件(Attachment)。

渲染命令(Render commands)被记录到渲染通道实例的特定子通道中。

  1. export interface RenderSubpassBuilder extends Setter {
  2. addRenderTarget (
  3. name: string,
  4. accessType: AccessType,
  5. slotName?: string,
  6. loadOp?: LoadOp,
  7. storeOp?: StoreOp,
  8. color?: Color): void;
  9. addDepthStencil (
  10. name: string,
  11. accessType: AccessType,
  12. depthSlotName?: string,
  13. stencilSlotName?: string,
  14. loadOp?: LoadOp,
  15. storeOp?: StoreOp,
  16. depth?: number,
  17. stencil?: number,
  18. clearFlags?: ClearFlagBit): void;
  19. addTexture (name: string, slotName: string, sampler?: Sampler | null, plane?: number): void;
  20. addStorageBuffer (name: string, accessType: AccessType, slotName: string): void;
  21. addStorageImage (name: string, accessType: AccessType, slotName: string): void;
  22. addQueue (hint?: QueueHint, phaseName?: string): RenderQueueBuilder;
  23. }

渲染子通道支持输入附件(Input Attachment),可以通过 slotNamedepthSlotNamestencilSlotName 指定,这个名字需要与 effect 中的注册的输入附件名字一致。

  1. // .effect
  2. #pragma subpass
  3. #pragma subpassColor in albedoMap
  4. #pragma subpassColor in normalMap
  5. #pragma subpassColor in emissiveMap
  6. #pragma subpassDepth in depthBuffer
  7. #pragma isubpassStencil in stencilBuffer // isubpass 的 i 表示输入附件类型为 int
  8. void main () {
  9. vec4 albedo = subpassLoad(albedoMap);
  10. vec4 normal = subpassLoad(normalMap);
  11. vec4 emissive = subpassLoad(emissiveMap);
  12. float depth = subpassLoad(depthBuffer).x;
  13. int stencil = subpassLoad(stencilBuffer).x;
  14. ...
  15. }

计算通道(ComputePass)

计算通道是对一次计算任务的抽象,可以调用Compute Shader,执行计算任务。

  1. export interface ComputePassBuilder extends Setter {
  2. addTexture (name: string, slotName: string, sampler?: Sampler | null, plane?: number): void;
  3. addStorageBuffer (name: string, accessType: AccessType, slotName: string): void;
  4. addStorageImage (name: string, accessType: AccessType, slotName: string): void;
  5. addQueue (phaseName?: string): ComputeQueueBuilder;
  6. }
  7. export interface ComputeQueueBuilder extends Setter {
  8. addDispatch (
  9. threadGroupCountX: number,
  10. threadGroupCountY: number,
  11. threadGroupCountZ: number,
  12. material?: Material,
  13. passID?: number): void;
  14. }

更多内容见计算着色器

渲染队列(RenderQueue)

渲染队列渲染通道/子通道 的子节点,有严格的渲染先后顺序。只有一个 渲染队列 的内容完全绘制后,才会绘制下一个 渲染队列 的内容。

可以通过 RenderQueueBuilder 添加绘制内容,渲染队列 内对象的渲染顺序是未定义的,可能是任何顺序。

  1. export interface RenderQueueBuilder extends Setter {
  2. addScene (camera: Camera, sceneFlags: SceneFlags): void;
  3. addFullscreenQuad (material: Material, passID: number, sceneFlags?: SceneFlags): void;
  4. }

可以通过 SceneFlags 标记场景渲染特性,比如渲染不透明物件、渲染投影物件等。

  1. export enum SceneFlags {
  2. NONE = 0,
  3. OPAQUE = 0x1,
  4. MASK = 0x2,
  5. BLEND = 0x4,
  6. SHADOW_CASTER = 0x8,
  7. UI = 0x10,
  8. }

渲染数据设置

我们可以通过 Setter 设置 Shader 里用到的数据和只读资源,名字是 Shader 里的变量名。

注意这里的 Shader 是管线相关的,而不是普通表面材质的 Shader。

  1. export interface Setter extends RenderNode {
  2. setMat4 (name: string, mat: Mat4): void;
  3. setQuaternion (name: string, quat: Quat): void;
  4. setColor (name: string, color: Color): void;
  5. setVec4 (name: string, vec: Vec4): void;
  6. setVec2 (name: string, vec: Vec2): void;
  7. setFloat (name: string, v: number): void;
  8. setArrayBuffer (name: string, arrayBuffer: ArrayBuffer): void;
  9. setBuffer (name: string, buffer: Buffer): void;
  10. setTexture (name: string, texture: Texture): void;
  11. setSampler (name: string, sampler: Sampler): void;
  12. }

这里用到的数据和资源是管线相关的。材质相关的,需要设置到材质上,不应重复设置。

资源必须是只读的。如果需要读写数据,需要注册到管线中,由管线进行管理。

数据更新频率

Effect中,不同的变量有不同的更新频率。由低到高大致分为:

  • pass
  • phase
  • batch
  • instance

effect中需要在变量声明前加上#pragma rate指定更新频率。

  • batch为缺省值
  • instance暂不支持自定义

例子:

  1. // copy-pass.effect
  2. precision highp float;
  3. in vec2 v_uv;
  4. #pragma rate outputResultMap pass
  5. uniform sampler2D outputResultMap;
  6. layout(location = 0) out vec4 fragColor;
  7. void main () {
  8. fragColor = texture(outputResultMap, v_uv);
  9. }

RenderGraph中的每个节点描述符集的更新频率,由节点的类型决定。

节点类型更新频率
渲染通道pass
渲染子通道pass
计算通道pass
渲染队列phase
计算队列phase