GameTech/URP 渲染实战
源码 / features/Cookbook.Ch28.VolumetricLightFeature.js
features/Cookbook.Ch28.VolumetricLightFeature.js
// 案例 28.5:体积光 Renderer Feature(对应 Ch28/VolumetricLightFeature.cs)—— 半分辨率光线步进 → 模糊 → 合成。
import { featureMaterial, RenderPassEvent } from '../engine/features.js';
import { ensureDepth } from './Cookbook.Ch28.ScreenSpaceUtil.js';

const Output = { Composite: 0, ScatterOnly: 1 };

export default class VolumetricLightFeature {
  constructor(engine, data, scene) {
    this.engine = engine;
    this.scene = scene;
    this.settings = { material: null, downsample: 2, blur: true, output: Output.Composite, renderPassEvent: RenderPassEvent.BeforeRenderingTransparents, ...((data && data.settings) || {}) };
  }

  async init() { this.material = featureMaterial(this.scene, this.settings.material); }

  addPasses(frame, enqueue) {
    const s = this.settings, mat = this.material;
    if (!mat) return;
    // 没有主光阴影就没有“光束”,直接跳过
    if (frame.scene.asset.m_MainLightShadowsSupported === false) return;
    enqueue(s.renderPassEvent, (fr) => {
      ensureDepth(fr);   // ConfigureInput(Depth)
      const ds = Math.max(1, s.downsample | 0);
      const w = Math.max(1, Math.floor(fr.width / ds)), h = Math.max(1, Math.floor(fr.height / ds));
      // 散射值可能 >1,用半精度浮点
      const vol = fr.getTemporaryRT('_Volumetric', w, h, { color: 'RGBA16F' });
      const volTemp = fr.getTemporaryRT('_VolumetricTemp', w, h, { color: 'RGBA16F' });
      mat.setVector('_VolTexelSize', [1 / w, 1 / h, w, h]);
      fr.blit(null, vol, mat, 0);                       // 光线步进
      if (s.blur) {
        fr.blit(vol.texture, volTemp, mat, 1);          // 横向模糊
        fr.blit(volTemp.texture, vol, mat, 2);          // 纵向模糊
      }
      fr.setGlobal('_VolumetricResult', vol.texture);
      const copy = fr.getTemporaryRT('_VolumetricColorCopy', fr.width, fr.height, { filter: 'Point' });
      fr.blit(fr.cameraColorTexture(), copy);
      fr.blit(copy.texture, 'camera', mat, s.output === Output.Composite ? 3 : 4);
      fr.bindCamera();
    }, 'VolumetricLight');
  }
}