GameTech/URP 渲染实战
源码 / features/Cookbook.Ch23.ContactShadowFeature.js
features/Cookbook.Ch23.ContactShadowFeature.js
// 案例 23.9:接触阴影(Contact Shadows)的网页版(对应 Ch23/ContactShadowFeature.cs)。
// 在深度预渲染之后,用一个全屏 Pass 从每个像素出发、沿主光方向在屏幕空间里步进几步,
// 检查有没有被深度缓冲里更靠前的表面挡住,结果写进一张 R8 的全屏纹理 _ContactShadowTexture。
// 接收物体的 Shader 再用屏幕 UV 读它,与阴影贴图的结果取最小值。
//
// 网页引擎没有 URP 那样“声明 ConfigureInput(Depth) 就自动安排 DepthPrepass”的机制,
// 所以这里自己先画一遍 DepthOnly,得到此时的 _CameraDepthTexture。
import { Material } from '../engine/shader.js';
import { RenderPassEvent } from '../engine/features.js';

export default class ContactShadowFeature {
  constructor(engine, data, scene) {
    this.engine = engine; this.scene = scene;
    const s = (data && data.settings) || {};
    this.settings = { length: s.length ?? 0.35, steps: s.steps ?? 16, thickness: s.thickness ?? 0.15, bias: s.bias ?? 0.01, intensity: s.intensity ?? 1 };
  }

  async init() {
    this.material = new Material(await this.engine.shaders.get('Cookbook/Ch23/ContactShadowPass'));
  }

  addPasses(frame, enqueue) {
    const L = frame.mainLight;
    if (!L) return;
    // 方向光的 forward 是光线前进方向,取反得到“指向光源”的 L
    const dir = [-L.forward[0], -L.forward[1], -L.forward[2]];
    // 与 URP 的屏幕空间阴影相同的时机:深度预渲染之后、不透明物体之前
    enqueue(RenderPassEvent.AfterRenderingPrePasses + 1, (fr) => this.execute(fr, dir));
  }

  execute(fr, L) {
    const gl = fr.gl, s = this.settings, W = fr.width, H = fr.height;
    // 1) 深度预渲染(URP 的 DepthPrepass)
    const depthRT = fr.getTemporaryRT('ContactDepthPrepass', W, H, { color: 'R8', depth: 'DEPTH24' });
    this.engine.bindTarget(depthRT);
    gl.depthMask(true); gl.colorMask(true, true, true, true);
    gl.clearColor(1, 1, 1, 1); gl.clearDepth(1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    fr.drawRenderers({ queue: 'opaque', lightModes: ['DepthOnly'] });
    fr.setGlobal('_CameraDepthTexture', depthRT.depthTexture);

    // 2) 全屏步进,写 _ContactShadowTexture
    const target = fr.getTemporaryRT('_ContactShadowTexture', W, H, { color: 'R8' });
    this.material.setVector('_ContactParams', [s.length, s.steps, s.thickness, s.bias]);
    this.material.setVector('_ContactLightDir', [L[0], L[1], L[2], s.intensity]);
    fr.blit(null, target, this.material, 0);
    fr.setGlobal('_ContactShadowTexture', target.texture);
    fr.bindCamera();
  }
}