# 内建管线

除了内建节点,小游戏框架还提供了内置管线来满足不同需求,默认有两条管线。

# 前向渲染管线

前向渲染管线ForwardBaseRG是小游戏框架内置的标准管线,其集成了Forward BaseForward Add,同时将UI、Gizmo、Skybox全部纳入其中。

作为内置管线,为了和其他部分结合,其是基于相机Camera构建的,每个场景中正在活跃的相机都拥有一个自己的子渲染图。所以这个内建管线的基本流程是:

onCamerasChange -> 遍历cameras -> 为每个camera生成子图 -> 把这些子图首尾相连

图本身针对子图进行了缓存,避免每次修改都重构整张图。而除了构建图,另一个重要的部分是全局 Uniform 的创建和更新,这个直接影响到了每个渲染节点的更新。除此之外,一般而言对默认降级效果的注册也是在这里面进行的。

由于涉及到方方面面,下面就直接将整个整个内建管线的代码带注释放出:

export default class ForwardBaseRG extends RenderGraph {
  // 是否已经注册过降级的阴影Effect
  protected _shadowFallbackRegistered: boolean = false;
  // 全局Uniform
  protected _globalUniforms!: {
    // ForwardBase阶段的全局Uniform
    fbUniforms: engine.UniformBlock;
    // ForwardAdd阶段的全局Uniform
    faUniforms: engine.UniformBlock;
    // UI阶段的全局Uniform
    uiUniforms?: engine.UniformBlock;
    // 阴影阶段的全局Uniform
    scUniforms?: engine.UniformBlock;
    // #if defined(EDITOR)
    rcUniforms?: engine.UniformBlock;
    // #endif
  };
  protected _subGraphs: {[nodeId: number]: BaseCamera} = {};

  // 用于默认清屏的View
  protected _defaultClearView?: engine.View;

  public onActive(context: RenderSystem) {
    this._globalUniforms = {
      fbUniforms: new engine.UniformBlock(forwardBaseUD),
      faUniforms: new engine.UniformBlock(forwardAddUD),
      uiUniforms: new engine.UniformBlock(forwardBaseUD),
      scUniforms: new engine.UniformBlock(shadowCaterUD),
      // #if defined(EDITOR)
      rcUniforms: new engine.UniformBlock(forwardBaseUD)
      // #endif
    }

    this._defaultClearView = new engine.View({
      passAction: {
        clearColor: [0, 0, 0, 1],
        colorAction: engine.ELoadAction.CLEAR
      },
      viewport: {x: 0, y: 0, w: 1, h: 1},
      scissor: {x: 0, y: 0, w: 1, h: 1}
    });

    this._rebuildRG(context.cameras, context.changedCameras);
  }

  public onCamerasChange(cameras: BaseCamera[], changeCameras: BaseCamera[]) {
    this._rebuildRG(cameras, changeCameras);
  }

  // 通过`cameras`创建整张图,相机列表已经排好序
  protected _rebuildRG(cameras: BaseCamera[], changeCameras: BaseCamera[]) {
    // 首先清空整张图,但保留 Camera 没有变过的子图
    const cachedCameras: {[cameraId: number]: TRGNodeAny} = {};
    this._clear((node: TRGNodeAny) => {
      const camera = this._subGraphs[node.id];
      if (camera && changeCameras.indexOf(camera) < 0) {
        cachedCameras[camera.id] = node;
        return true;
      }

      delete this._subGraphs[node.id];
      return false;
    });
    let lastNode: TRGNodeAny;

    // 如果只有一个相机并且是 UI 相机,则强制清屏一次,因为主 UI 相机默认不清颜色
    if (cameras[0] instanceof UICamera && (!cameras[0].renderTarget || cameras[0].renderTarget instanceof Screen)) {
      const defaultCameraNode = this.createNode<RGGenViewNode>(`default-view-gen`, RGGenViewNode, {viewObject: {view: this._defaultClearView!}});
      const defaultRtNode = this.createNode<RGGenRenderTargetNode>('rt-screen', RGGenRenderTargetNode, {createRenderTarget: () => this.context.screen});
      const defaultClearNode = this.createNode<RGClearNode>('default-view-clear', RGClearNode, {});
      this.connect(defaultCameraNode, defaultClearNode, 'camera');
      this.connect(defaultRtNode, defaultClearNode, 'renderTarget');
      lastNode = defaultClearNode;
    }

    // 利用缓存,创建所有子图节点
    cameras.forEach(camera => {
      let subGraph = cachedCameras[camera.id] as TForwardBaseSubGraphNode;

      if (!subGraph) {
        subGraph = this.createNode<TForwardBaseSubGraphNode>(`subGraph-${camera.entity.name}`, RGSubGraphNode, {
          // 子图的类
          RGClass: ForwardBaseSubGraph,
          options: {
            ...this._globalUniforms,
            camera
          }
        });
        this._subGraphs[subGraph.id] = camera;
      }

      lastNode && this.connect(lastNode, subGraph);
      lastNode = subGraph;
    });
  }

  // 每帧渲染开始前,设置全局Uniform
  public onExecuteBegin(context: RenderSystem) {
    const dirL = context.lights.mainDirectionalLight;

    const uniforms = this._globalUniforms.fbUniforms!;
    const settings = this.game.activeScene.settings;

    uniforms.setUniform("u_ambientLight", settings.ambientLight._raw);
    tempArr4[0] = settings.fogMode;
    tempArr4[1] = settings.fogStart;
    tempArr4[2] = settings.fogStart + settings.fogRange;
    tempArr4[3] = settings.fogDensity;
    uniforms.setUniform("u_fogInfos", tempArr4);
    uniforms.setUniform("u_fogColor", settings.fogColor._raw);
    uniforms.setUniform("u_lightDir", dirL ? dirL.direction._raw : [0, 0, 1]);
    uniforms.setUniform("u_lightColor", dirL ? dirL.color.scale(dirL.intensity, tempVec3)._raw : [0, 0, 0]);
    tempArr1[0] = this.game.gameTime;
    uniforms.setUniform("u_gameTime", tempArr1);
    tempArr3[0] = settings.subtractiveShadowColor.r / 255.0;
    tempArr3[1] = settings.subtractiveShadowColor.g / 255.0;
    tempArr3[2] = settings.subtractiveShadowColor.b / 255.0;
    uniforms.setUniform("u_shadowColor", tempArr3);
    tempArr1[0] = 1 - (dirL ? dirL.shadowStrength : 1);
    uniforms.setUniform("u_shadowStrength", tempArr1);
    uniforms.setUniform("u_shadowMapTex", BuildInTextures.white._getengineTexture()!);
    uniforms.setUniform("u_lightMap", BuildInTextures.black._getengineTexture()!);
    uniforms.setUniform("u_speCube", settings.mainEnvironmentMap._getengineTexture()!);
    uniforms.setUniform("u_environmentMap", settings.mainPanoramaMap._getengineTexture()!);
    uniforms.setUniform('u_csmFarBounds', [0, 0, 0, 0]);
    uniforms.setUniform('u_SH', settings.shCoefficients);

    this._globalUniforms.uiUniforms?.copy(uniforms);

    const faUniforms = this._globalUniforms.faUniforms!;
    tempArr4[0] = settings.fogMode;
    tempArr4[1] = settings.fogStart;
    tempArr4[2] = settings.fogStart + settings.fogRange;
    tempArr4[3] = settings.fogDensity;
    faUniforms.setUniform("u_fogInfos", tempArr4);
    faUniforms.setUniform("u_fogColor", settings.fogColor._raw);
    tempArr1[0] = this.game.gameTime;
    faUniforms.setUniform("u_gameTime", tempArr1);

    // 注册默认阴影Effect
    if (!this._shadowFallbackRegistered) {
      const scEffect = engine.loader.getAsset<Effect>('System::Effect::ShadowCasterFallBack');
      if (scEffect) {
        renderEnv.registerFallbackEffect('ShadowCaster', scEffect);
        this._shadowFallbackRegistered = true;
      }
    }
  }

  // 在所有相机的处理结束后,清除当前 game 内部的 UICanvas 中脏列表的,顶点脏位以及材质脏位,以及脏列表本身
  public onExecuteDone(context: RenderSystem) {
    const uiCanvas = this.game!.rootUICanvas;
    if (uiCanvas._vertexDirtyList.length > 0) {
      for (let i = 0; i < uiCanvas._vertexDirtyList.length; i++) {
        const dirtyRenderable2d = uiCanvas._vertexDirtyList.data[i];
        if (dirtyRenderable2d._vertexDirtyUpdateFlag) {
          // 清理顶点脏位
          dirtyRenderable2d._vertexDirtyUpdateFlag = false;
        }
      }
      uiCanvas._vertexDirtyList.reset();
    }
    if (uiCanvas._materialDirtyList.length > 0) {
      for (let i = 0; i < uiCanvas._materialDirtyList.length; i++) {
        const dirtyRenderable2d = uiCanvas._materialDirtyList.data[i];
        if (dirtyRenderable2d._materialDirtyUpdateFlag) {
          // 清理材质脏位
          dirtyRenderable2d._materialDirtyUpdateFlag = false;
        }
      }
      uiCanvas._materialDirtyList.reset();
    }
    if (uiCanvas._designResolutionDirty) {
      uiCanvas._designResolutionDirty = false;
    }
  }

  public onDisable(context: RenderSystem) {
    // 取消注册默认阴影Effect
    this._shadowFallbackRegistered && renderEnv.unregisterFallbackEffect('ShadowCaster');
  }
}

而子图的实现为:

export interface IForwardBaseSubGraphOptions {
  // 相机
  camera: BaseCamera;
  // ForwardBase阶段的全局Uniform
  fbUniforms: engine.UniformBlock;
  // ForwardAdd阶段的全局Uniform
  faUniforms: engine.UniformBlock;
  // UI阶段的全局Uniform
  uiUniforms?: engine.UniformBlock;
  // 阴影阶段的全局Uniform
  scUniforms?: engine.UniformBlock;
  // #if defined(EDITOR)
  rcUniforms?: engine.UniformBlock;
  // #endif
}

/**
 * 默认的内置前向渲染管线的子图,针对每个相机生成。
 * 实际上包含了Forward Add部分。
 */
export default class ForwardBaseSubGraph extends RenderGraph<IForwardBaseSubGraphOptions> {
  // 用于默认清屏的View
  protected _defaultClearView?: engine.View;

  public onActive(context: RenderSystem, options: IForwardBaseSubGraphOptions) {
    this._rebuildRG(options);
  }

  // 通过`cameras`创建整张图,相机列表已经排好序
  protected _rebuildRG(options: IForwardBaseSubGraphOptions) {
    // 首先清空整张图
    this._clear();
    const {camera} = options;

    if (camera instanceof UICamera) {
      // 如果是 UI 相机,则创建 UI 相机的子图
      const cameraNode = this.createNode<RGCameraNode>(`camera-${camera.entity.name}`, RGCameraNode, {camera});
      const clearNode = this.createNode<RGClearNode>('ui-clear', RGClearNode, {});
      const uiNode = this.createNode<RGUIPrepareNode>('ui-prepare', RGUIPrepareNode, {camera});
      const uiRTNode = this.createNode<RGGenRenderTargetNode>('render-target', RGGenRenderTargetNode, {createRenderTarget: () => this._createRT(camera)});
      const uiRenderNode = this.createNode<RGRenderNode>('ui-render', RGRenderNode, {
        lightMode: 'ForwardBase',
        createUniformBlocks: () => [options.uiUniforms!]
      });
      this.connect(cameraNode, uiRenderNode, 'camera');
      this.connect(cameraNode, clearNode, 'camera');
      this.connect(uiRTNode, clearNode, 'renderTarget');
      this.connect(uiRTNode, uiRenderNode, 'renderTarget');
      this.connect(clearNode, uiRenderNode);
      this.connect(uiNode, uiRenderNode, 'meshList');

      return;
    }

    this._buildNodesForCamera(camera as Camera, options)
  }

  // 将会返回`lastNode`,用于连接各个子图,保证顺序
  protected _buildNodesForCamera(camera: Camera, options: IForwardBaseSubGraphOptions) {
    // 以下是 ForwardBase 节点创建部分
    let rtNode!: RGGenRenderTargetNode;
    if (camera.postProcess) {
      rtNode = this.createNode<RGGenRenderTargetNode>('render-target', RGGenRenderTargetNode, {createRenderTarget: () => this._createPostProcessRT(camera, camera.postProcess?.hdr || false)});
    } else {
      rtNode = this.createNode<RGGenRenderTargetNode>('render-target', RGGenRenderTargetNode, {createRenderTarget: () => this._createRT(camera)});
    }

    const cameraNode = this.createNode<RGCameraNode>(`camera-${camera.entity.name}`, RGCameraNode, {camera});
    const fwCullNode = this.createNode<RGCullNode>('fb-cull', RGCullNode, {lightMode: 'ForwardBase'});
    const fwClearNode = this.createNode<RGClearNode>('fb-clear', RGClearNode, {});
    const fwRenderNode = this.createNode<RGRenderNode<{shadowMap: 'RenderTarget'}>>('fb-render', RGRenderNode, {
      lightMode: 'ForwardBase',
      createUniformBlocks: () => [options.fbUniforms!],
      inputTypes: {'shadowMap': 'RenderTarget'},
      uniformsMap: {
        u_shadowMapTex: {
          inputKey: 'shadowMap',
          name: 'color'
        }
      }
    });

    // 以下是 Skybox 节点创建连接部分
    if (camera.drawSkybox) {
      const skyBoxNode = this.createNode<RGSkyBoxNode>('skybox', RGSkyBoxNode, {});
      const skyBoxRenderNode = this.createNode<RGLightNode>('skybox-render', RGRenderNode, {
        lightMode: 'Skybox',
        createUniformBlocks: () => [options.fbUniforms!]
      });
      this.connect(cameraNode, skyBoxRenderNode, 'camera');
      this.connect(skyBoxNode, skyBoxRenderNode, 'meshList');
      this.connect(rtNode, skyBoxRenderNode, 'renderTarget');
      this.connect(skyBoxRenderNode, fwRenderNode);
    }

    // 以下是阴影渲染节点创建连接部分
    if (camera.shadowMode !== engine.EShadowMode.None) {
      const scRTNode = this.createNode<RGGenRenderTargetNode>('sc-render-target', RGGenRenderTargetNode, {createRenderTarget: () => this._createRT(camera, 'ShadowCaster')});
      const scRenderNode = this.createNode<RGLightNode>('shadow-caster', RGLightNode, {
        lightMode: 'ShadowCaster',
        createUniformBlocks: () => [options.scUniforms!]
      });
      this.connect(cameraNode, scRenderNode, 'camera');
      this.connect(scRTNode, scRenderNode, 'renderTarget');
      this.connect(fwCullNode, scRenderNode, 'meshList');
      this.connect(scRenderNode, fwRenderNode, 'shadowMap');
    }

    // 以下是 ForwardBase 节点连接部分
    this.connect(cameraNode, fwCullNode, 'camera');
    this.connect(cameraNode, fwRenderNode, 'camera');
    this.connect(rtNode, fwClearNode, 'renderTarget');
    this.connect(cameraNode, fwClearNode, 'camera');
    this.connect(rtNode, fwRenderNode, 'renderTarget');
    this.connect(fwClearNode, fwRenderNode);
    this.connect(fwCullNode, fwRenderNode, 'meshList');

    // 以下是 ForwardAdd 节点创建连接部分
    const faCullLightNode = this.createNode<RGCullLightFANode>('fa-cull-light', RGCullLightFANode, {});
    const faCullMeshNode = this.createNode<RGCullMeshFANode>('fa-cull-mesh', RGCullMeshFANode, {});
    const faRenderNode = this.createNode<RGFARenderNode>('fa-render', RGFARenderNode, {
      lightMode: 'ForwardAdd',
      createUniformBlocks: () => [options.faUniforms!],
    });
    this.connect(fwCullNode, faCullLightNode, 'meshList');
    this.connect(fwCullNode, faCullMeshNode, 'meshList');
    this.connect(faCullLightNode, faCullMeshNode);

    this.connect(fwRenderNode, faRenderNode);
    this.connect(cameraNode, faRenderNode, 'camera');
    this.connect(rtNode, faRenderNode, 'renderTarget');
    this.connect(faCullMeshNode, faRenderNode, 'meshList');
    this.connect(faCullLightNode, faRenderNode, 'lightList');

    // 以下是后处理节点创建连接部分
    let finalBlitNode!: RGBlitPostProcessNode;
    if (camera.postProcess) {
      let preNode: RGNode<any, any, any> = rtNode;
      const {hdr, steps} = camera.postProcess.getData();
      steps.forEach((step, index) => {
        let currentNode: RGNode<any, any, any>;

        switch (step.type) {
          case EBuiltinPostProcess.BLIT:
            currentNode = this.createNode('post-process-blit', RGBlitPostProcessNode, {hdr, ...step.data});
            break;
          case EBuiltinPostProcess.BLOOM:
            currentNode = this.createNode('post-process-bloom', RGBloomPostProcessNode, {hdr, ...step.data});
            break;
          case EBuiltinPostProcess.FXAA:
            currentNode = this.createNode('post-process-fxaa', RGFXAAPostProcessNode, {hdr, ...step.data});
            break;
          case EBuiltinPostProcess.HDR:
            currentNode = this.createNode('post-process-tone', RGHDRPostProcessNode, {hdr, ...step.data});
            break;
        }

        this.connect(preNode, currentNode, 'sourceTex');
        index === 0 && this.connect(faRenderNode, currentNode);

        preNode = currentNode;
      });

      finalBlitNode = this.createNode('final-blit', RGBlitPostProcessNode, {dstTex: this._createRT(camera)});
      this.connect(preNode, finalBlitNode, 'sourceTex');
    }

    // 以下是 Gizmo 节点创建连接部分
    if (camera.drawGizmo) {
      const gizmoNode = this.createNode<RGGizmosNode>('gizmo', RGGizmosNode, {});
      const gizmoRenderNode = this.createNode<RGRenderNode>('gizmo-render', RGRenderNode, {
        lightMode: 'ForwardBase',
        createUniformBlocks: () => [options.fbUniforms!],
      });

      const rect = camera.viewport;
      const x = rect ? rect.xMin : 0;
      const y = rect ? rect.yMin : 0;
      const width = rect ? rect.width : 1;
      const height = rect ? rect.height : 1;

      const viewPortRect: engine.IRect = {x, y, w: width, h: height};
      const scissorRect: engine.IRect = {x, y, w: width, h: height};
      const gizmoView = new engine.View({
        passAction: {
          depthAction: engine.ELoadAction.CLEAR,
          clearDepth: 1,
          colorAction: engine.ELoadAction.LOAD,
          stencilAction: engine.ELoadAction.LOAD,
        },
        viewport: viewPortRect,
        scissor: scissorRect
      })
      // 在画 gizmo 之前,将深度清除,保证 gizmo 画在最前面
      const gizmoClearDepthNode = this.createNode<RGClearNode>('gizmo-clear-depth', RGClearNode, {})
      const gizmoViewNode = this.createNode<RGGenViewNode>('gizmo-view', RGGenViewNode, { viewObject: { view: gizmoView } })

      this.connect(rtNode, gizmoClearDepthNode, 'renderTarget');
      this.connect(finalBlitNode || faRenderNode, gizmoViewNode);
      this.connect(gizmoViewNode, gizmoClearDepthNode, 'camera')
      this.connect(gizmoClearDepthNode, gizmoRenderNode);

      this.connect(cameraNode, gizmoNode, 'camera');
      this.connect(cameraNode, gizmoRenderNode, 'camera');
      this.connect(rtNode, gizmoRenderNode, 'renderTarget');
      this.connect(gizmoNode, gizmoRenderNode, 'meshList');
    }
  }

  // 通过相机和 lightMode 创建RenderTarget
  protected _createRT(camera: BaseCamera, lightMode: string = 'ForwardBase') {
    let fbRT: RenderTexture | Screen | null = null;

    if (camera.renderTarget) {
      fbRT = camera.renderTarget;
    } else {
      // 模拟器跟 ide 都需要这个逻辑,所以不加define editor
      if (camera.editorRenderTarget) {
        fbRT = camera.editorRenderTarget
      } else {
        fbRT = this.context.screen;
      }
    }

    if (lightMode === 'ShadowCaster') {
      // 如果是阴影流程,则创建一张RenderTexture
      return new RenderTexture({width: 2048, height: 2048});
    }

    return fbRT;
  }

  protected _createPostProcessRT(camera: BaseCamera, hdr: boolean): RenderTexture {
    const origRT = camera.renderTarget || this.context.screen;
    const {width, height} = origRT;
    
    return new RenderTexture({width, height, colors: [{
      pixelFormat: hdr ? engine.ETextureFormat.RGBA16F : engine.ETextureFormat.RGBA8
    }]});
  }
}

# 调试管线

调试管线DebugRG专门用于调试当前渲染环境是否正常,要使用它非常简单:

// 初始化参数中的`texture`可以改,这里默认给了个绿色
const debugRG = new engine.DebugRG({texture: engine.BuildInTextures.green});

game.renderSystem.useRenderGraph(debugRG);

如果屏幕上显示了四分之一个绿色的色块,则表明渲染系统运作正常。