GameTech/URP 渲染实战
源码 / features/Cookbook.Ch02.FrameSnapshotFeature.js
features/Cookbook.Ch02.FrameSnapshotFeature.js
// 第 2 章 案例 2.1:帧快照 Renderer Feature(JS 版,对应 Ch02/FrameSnapshotFeature.cs)
// 在 captureEvent 这个时间点,把相机颜色缓冲“拍”一张快照;
// 在后处理之后,再把快照贴回相机颜色缓冲。于是屏幕上看到的,就是这一帧在 captureEvent 时刻的“半成品”。
import { RenderPassEvent } from '../engine/features.js';

export default class FrameSnapshotFeature {
  constructor(engine, data) {
    this.engine = engine;
    this.captureEvent = data.captureEvent ?? RenderPassEvent.AfterRenderingOpaques;   // 在哪个时间点拍快照
    this.clearColorBeforeOpaques = data.clearColorBeforeOpaques ?? true;               // 画不透明物体之前把颜色清成黑色
  }

  addPasses(frame, enqueue) {
    const E = this.engine, gl = E.gl;
    let snapshot = null;

    // ClearColorPass:此时相机颜色/深度缓冲已经绑定,只清颜色
    if (this.clearColorBeforeOpaques) {
      enqueue(RenderPassEvent.BeforeRenderingOpaques, (fr) => {
        fr.bindCamera();
        gl.colorMask(true, true, true, true);
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
      }, 'Cookbook Snapshot Clear');
    }

    // CapturePass:快照格式与相机颜色缓冲一致(HDR 时是浮点),不要深度、不要 MSAA。
    // cameraColorTexture() 会先把多重采样缓冲解析(resolve)出来,读到的才是此刻的画面
    enqueue(this.captureEvent, (fr) => {
      const rt = fr.getTemporaryRT('_FrameSnapshot', fr.width, fr.height, { color: fr.colorFormat });
      fr.blit(fr.cameraColorTexture(), rt, E.blitMat, 'Copy');
      snapshot = rt.texture;
      fr.bindCamera();
    }, 'Cookbook Snapshot Capture');

    // RestorePass:后处理之后、FinalBlit 之前,把快照贴回“当前”的相机颜色(后处理开启时就是后处理的输出)
    enqueue(RenderPassEvent.AfterRenderingPostProcessing, (fr) => {
      if (!snapshot) return;
      if (fr.postDone) fr.postOutput = snapshot;
      else { fr.blit(snapshot, 'camera', E.blitMat, 'Copy'); fr.bindCamera(); }
    }, 'Cookbook Snapshot Restore');
  }
}