渲染管线
每个 Engine 内部独占一个 EffectComposer、一个支持 Multi-View 的默认 main 和一个 OutputPass。业务不接触这些 identity,只通过 Pass Definition 借入自己创建的原生 Three.js Pass。
两个阶段
Pipeline 只暴露:
| 阶段 | 用途 |
|---|---|
after-render | 默认 main 之后、颜色输出之前的 working-space 后处理 |
after-output | OutputPass 之后的 sRGB 后处理或直接 Canvas 绘制 |
stage 默认是 after-render。同一 stage 和执行 lane 内按 Definition 的 numeric order 升序,再按加入顺序稳定排序。
默认 auto 模式固定执行:
DefaultMain → after-render → OutputPass 上屏 → Canvas Passmain 和 OutputPass 都不能由业务替换。auto 模式不会执行 after-output 的后处理链 contribution,但仍会执行显式的 Canvas contribution。
创建后处理
import { createPass } from '@tripo3d/engine';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
const rgb = new ShaderPass(RGBShiftShader);
const ColorShift = createPass('color-shift', ({
onDestroy,
onResize,
usePass,
}) => {
usePass(rgb);
onResize(({ width, height, pixelRatio }) => {
console.log('logical size', width, height, pixelRatio);
});
onDestroy(() => rgb.dispose());
return rgb;
}, {
enabled: false,
order: 20,
stage: 'after-render',
});
engine.pipeline.addPass(ColorShift);
engine.pipeline.setPassEnabled(ColorShift, true);usePass 只能在同步 setup 中调用,可按调用顺序借入多个 Pass。Pipeline 会把 physical size 传给每个原生 Pass 的 setSize,并共同读取 Definition enabled 与原生 pass.enabled。
Pipeline 不 dispose borrowed Pass;资源由创建它的业务 owner 释放。
Manual 输出与 FXAA
需要在 OutputPass 后继续读取 sRGB buffer 时,切换为 manual 模式,并让最终 contribution 显式声明 presentation:
const Fxaa = createPass('fxaa', ({ onDestroy, onResize, usePass }) => {
const pass = new ShaderPass(FXAAShader);
onResize(({ width, height, pixelRatio }) => {
pass.uniforms.resolution!.value.set(
1 / (width * pixelRatio),
1 / (height * pixelRatio),
);
});
usePass(pass, { present: true });
onDestroy(() => pass.dispose());
}, {
enabled: false,
stage: 'after-output',
});
engine.pipeline.addPass(Fxaa);
// 同步切换 FXAA owner 与其所需的 manual presentation 模式。
function setFxaaEnabled(enabled: boolean) {
engine.pipeline.setPassEnabled(Fxaa, enabled);
engine.pipeline.setOutputMode(enabled ? 'manual' : 'auto');
}manual 模式固定执行:
DefaultMain
→ after-render
→ OutputPass 写入 sRGB buffer
→ after-output 后处理链
→ 最终 present Pass 上屏
→ Canvas Pass如果存在有效颜色工作,但最终 effective 后处理 Pass 没有声明 present: true,Composer 结果不会自动上屏。Engine 不提供 fallback,也不会推断 presenter;它会在每个连续的缺失 presenter 异常周期输出一次 console.warn,提示业务修复组合。Canvas contribution 不会替代该 presenter。
直接绘制 Canvas
Gizmo、HUD 等不需要读取或改写整张画面的内容,可以复用同一个 usePass:
const Gizmo = createPass('gizmo', ({ onDestroy, usePass }) => {
const pass = new GizmoPass();
usePass(pass, { target: 'canvas' });
onDestroy(() => pass.dispose());
}, {
order: 100,
stage: 'after-output',
});Canvas Pass 是只写的 direct-draw lane:它在默认 framebuffer baseline 上以 renderToScreen = true 直接执行,不进入后处理链、不 swap,也不需要通过 CopyShader 复制整张画面。
为兼容 Three.js Pass.render() 方法签名,Pipeline 仍会传入 Composer 当前的 writeBuffer / readBuffer,但 Canvas lane 不为它们的画面内容或颜色空间提供任何稳定契约。尤其在切换 output mode 或 Composer ping-pong 状态变化后,它们可能分别指向输出变换前的 working-space 内容、输出变换后的 output-encoded 内容或旧内容;Canvas Pass 不得采样或依赖这两个参数。
需要读取整张画面的 Pass 必须使用 Composer target:读取输出变换前的 working-space 内容时放在 after-render;读取输出变换后的 output-encoded 内容时放在 after-output,并将 Pipeline 设为 manual 模式。
target 决定物理执行 lane:后处理链先完整执行,Canvas lane 随后执行。order 只比较同 stage、同 lane 的 contribution。Engine 不分析、拒绝或修正跨 lane 顺序;业务必须保证最终 presenter 是最后一个有效后处理 Pass,并只把确实需要在 presentation 后绘制的 Pass 声明为 Canvas target。
每帧准备
const Dynamic = createPass('dynamic', ({ onPrepareFrame, usePass }) => {
const pass = new MyPass();
usePass(pass);
onPrepareFrame(({ elapsed }) => {
pass.strength = computeStrength(elapsed);
});
});prepare 按 stage/order/sequence 执行。全部 prepare 完成后才冻结当帧计划,因此 prepare 可以通过当前 Definition 的 setEnabled 改变本帧是否执行;planning/render 期间不能再改 Pipeline topology、enabled 或 output mode。
Render target 能力
const engine = createEngine({
renderer: { antialias: false },
pipeline: {
samples: 0,
stencil: false,
},
});默认 Canvas antialias 为 false,默认 Pipeline samples 为 0,因此没有 Canvas MSAA、离屏 MSAA 或 resolve blit。两项配置相互独立;业务仍可分别显式开启。samples 必须是非负整数,stencil 默认为 false。
性能模型
- stage bucket、排序和 View snapshot 在 add/remove/order mutation 冷路径重建;
- frame context、后处理链、Canvas chain 与 plan result 跨帧复用;
- stable frame 不 sort/filter,不创建业务 owner 数组;
- 默认无 FXAA 路径只执行 main、OutputPass 和可选的直接 Canvas Pass;
- 业务应避免每帧 add/remove Definition,动态开关使用 enabled。