GameTech/URP 渲染实战
源码 / features/Cookbook.Ch27.MotionBlurPreviewFeature.js
features/Cookbook.Ch27.MotionBlurPreviewFeature.js
// 27.17 节配图用的 MotionBlurPreviewFeature(对应 Ch27/MotionBlurPreviewFeature.cs)。
// 直接调用 URP 自带的 Hidden/Universal Render Pipeline/CameraMotionBlur(网页版见 shaders/URP/CameraMotionBlur.glsl),
// “上一帧的 VP 矩阵”由外部指定(导出数据里的 previousViewProj)。
import { RenderPassEvent } from '../engine/features.js';
import { cameraMotionBlur } from '../engine/post/extra.js';

export default class MotionBlurPreviewFeature {
  constructor(engine, data) {
    this.engine = engine;
    this.data = { intensity: 0.5, clamp: 0.05, quality: 2, ...(data || {}) };
    // C# 里是 GL.GetGPUProjectionMatrix(P, true) * V:渲染到贴图时 y 被翻转(D3D/Metal 约定)。
    // WebGL 不翻转,所以把第 2 行取反还原;z 行不影响屏幕速度,可以不管。
    const e = this.data.previousViewProj;
    const m = new Float32Array(16);
    if (e && e.e00 !== undefined) {
      for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) m[c * 4 + r] = e[`e${r}${c}`];
      for (let c = 0; c < 4; c++) m[c * 4 + 1] = -m[c * 4 + 1];
    } else m.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
    this.prevViewProj = m;
  }

  addPasses(frame, enqueue) {
    const s = this.data;
    if (!(s.intensity > 0)) return;
    // 与 URP 一样:在后处理(色调映射)之前,作用于 HDR 颜色
    enqueue(RenderPassEvent.BeforeRenderingPostProcessing, (fr) => {
      const temp = cameraMotionBlur(this.engine, fr, fr.cameraColorTexture(), {
        viewProj: fr.viewProj, prevViewProj: this.prevViewProj, intensity: s.intensity, clamp: s.clamp, quality: s.quality, depth: fr.cameraDepthTexture(),
      });
      fr.blit(temp.texture, 'camera');
      fr.bindCamera();
    }, 'MotionBlurPreview');
  }
}