帧通道
在 PlayCanvas 中,帧图的每个逻辑通道都是一个帧通道:它是引擎对象,在帧渲染时记录该通道的工作。引擎自身也用这种方式渲染:阴影贴图、各相机场景和后处理都是帧通道。你也可以添加自定义帧通道,向纹理渲染、处理相机渲染结果,或运行计算着色器。
每帧,引擎按顺序将帧通道收集到帧图中,再依次执行,开始和结束它们所需的物理通道。无需自行开始或结束物理通道。
通道类
每个帧通道都是 FramePass 或其派生类的实例。两种派生类对应两种物理通道:用于渲染的 RenderPass,以及用于计算着色器的 ComputePass:
| 类 | 用途 |
|---|---|
FramePass | 基类。用于不需要物理通道的工作,或组织其他帧通道。 |
RenderPass | 渲染到渲染目标或后备缓冲区。 |
ComputePass | 在帧中指定位置派发计算着色器。 |
RenderPass 是逻辑渲染通道,将绘制调用记录到对应目标的物理渲染通道中。通常引擎为每个 RenderPass 开始独立物理通道,并在其结束后关闭。当连续的 RenderPass 对象渲染到同一目标时,会共享单个物理通道,参见帧图。类似地,引擎将 ComputePass 的派发记录到物理计算通道。
引擎提供 RenderPass 的派生类来完成常见绘制,例如对渲染目标的每个像素运行着色器,或渲染场景。参见渲染通道。
帧通道的生命周期
帧通道在参与的每帧中经历两个阶段。构建帧时,在任何渲染发生前,引擎更新通道,确定本帧要渲染哪些通道。随后按顺序渲染它们。
子类通过覆盖以下方法完成工作:
frameUpdate:在任何渲染之前更新本帧通道,例如调整渲染目标大小、设置读取的纹理,或增减前置和后置通道。before和after:围绕通道工作执行。execute:记录工作。对于RenderPass,它在对应目标的物理渲染通道内部执行。
通过 onBefore 和 onAfter 回调,无需编写子类即可接入通道:在执行前设置参数或清空写入缓冲区,在执行后立即回读结果:
pass.onBefore = () => {
buffer.clear();
};
pass.onAfter = () => {
buffer.read().then((data) => {
// the results arrive a few frames later
});
};
enabled 与 executeEnabled
两个属性可在生命周期的不同阶段关闭通道:
- 将
enabled设为 false,会在构建帧时跳过该通道及其前置和后置通道,也不调用frameUpdate。必须在构建帧之前更改;通道不能在自己的frameUpdate中禁用自身。 - 将
executeEnabled设为 false,仅跳过execute。通道仍更新,前置和后置通道仍渲染,before、after、onBefore和onAfter仍调用。最迟应在frameUpdate中设置。
前置和后置通道
每个通道都有 beforePasses 和 afterPasses,在该通道每次参与渲染时,分别紧邻其前后执行:
由此可以嵌套通道,普通 FramePass 也能容纳一组共同启用和禁用的通道:
// a group of two blur passes: disabling the group skips both
const blur = new FramePass(app.graphicsDevice);
blur.name = 'Blur';
blur.beforePasses.push(horizontalBlurPass, verticalBlurPass);
blur.enabled = false;
向相机添加通道
相机先执行前置通道,再执行自身渲染(可用自定义帧通道替换),最后执行后置通道。参见相机通道。
独立渲染通道
调用 render 也可在帧外渲染通道,例如向纹理仅渲染一次。它会执行 before、onBefore、execute、after 和 onAfter,但不经过帧准备过程:不调用 frameUpdate,也不渲染前置和后置通道。
pass.render();
通道所有权
没有其他对象会替你销毁通道。相机不销毁其列表中的通道,通道也不销毁前置和后置通道。创建通道的代码应先将其从所有已加入的列表移除,再调用 destroy 销毁。销毁后不得再使用。通道也不会销毁传给它的渲染目标或着色器等对象。
通过 name 设置的通道名称会在 GPU 性能分析和图形调试器中标识它。请显式设置,因为默认的类名在压缩构建中会缩短。