import { Camera, ColorRepresentation, RenderTarget, Scene } from "three"; import { WebGPURenderer } from "three/webgpu"; //#region src/lights/OcclusionPass.d.ts /** * Optional construction knobs for {@link OcclusionPass}. */ interface OcclusionPassOptions { /** * Resolution multiplier relative to the main viewport (0 < x <= 1). * The SDF is derived from this RT, so lower values trade shadow fidelity * for shadow cost. Default: 0.5 (half resolution) — quarters fill cost * across every RT-sized pass while staying visually indistinguishable * from full-res on typical viewports (the separable blur masks the * coarser seed; NearestFilter keeps edges crisp). Set `1` for very * small / pixel-art viewports where the half-pixel silhouette * quantization reads as blocky, or drop to `0.25` on low-end mobile * where the shadow cost dominates and a blockier silhouette is acceptable. */ resolutionScale?: number; /** Clear color for the RT. Default: transparent black. */ clearColor?: ColorRepresentation; /** Clear alpha. Default: 0. */ clearAlpha?: number; /** * Use LinearFilter on the RT texture instead of the NearestFilter default. * Linear sampling smears alpha across silhouette edges (a texel just * outside the sprite interpolates to a fractional alpha), which inflates * the SDF seed by ~½ pixel in every direction and produces a faint halo * around every caster. NearestFilter keeps the alpha binary and pixel- * perfect. Flip to `true` only if a custom consumer explicitly wants the * anti-aliased silhouette. */ linearFilter?: boolean; } /** * Offscreen pre-pass that produces an occluder-silhouette render target for * the SDF shadow pipeline. * * Owns: * - A {@link RenderTarget} sized to `resolutionScale * viewport`. * - No material — renders the host scene with the scene's own sprite * materials. The SDF JFA consumes the RT's alpha channel only, so sprite * color output is discarded downstream. This keeps per-sprite opt-out * (eventually via `castShadow`) bindable through the existing material * path without requiring a scene-wide override material that loses * per-object texture bindings in TSL. * * **Limitation (deliberate):** every rendered mesh currently contributes * its alpha to the SDF seed. A follow-up commit propagates the Object3D * `castShadow` flag through the batched sprite attribute buffers so * non-casters write alpha = 0 from inside the sprite material. Tracked in * `planning/experiments/SDF-Shadow-Plumbing.md` as the T2 follow-up. * * @internal */ declare class OcclusionPass { private _resolutionScale; private _clearColor; private _clearAlpha; private _rt; private _width; private _height; /** * Per-source-texture occlusion material cache. Each SpriteBatch that feeds * the pass has its own source texture (sprite atlas); we mint an * occlusion material once per texture and reuse it across frames. */ private _occlusionMaterials; /** * Reusable arrays for the per-frame material-swap dance. Never reallocated * — only `length = 0` + push — so the render path stays zero-alloc past * warmup, matching the perf conventions in Sprite2D / transformSyncSystem. */ private _swappedMeshes; private _swappedOriginals; /** * Meshes hidden for the duration of the occlusion pass because their * geometry lacks the `instanceSystem` interleaved attribute the * occlusion shader reads for the per-instance castsShadow bit. In * practice this is only ever a custom mesh that hijacked the * material class without going through SpriteBatch / TileLayer. */ private _hiddenMeshes; /** * Re-entrancy guard. OcclusionPass.render() calls renderer.render(scene) * on the host scene, which triggers updateMatrixWorld → SpriteGroup runs * the ECS schedule → shadowPipelineSystem → OcclusionPass.render() again. * Without this guard the inner call clears _swappedMeshes, destroying the * outer call's material-restore data. */ private _rendering; constructor(options?: OcclusionPassOptions); /** The render target whose `texture.a` is the occluder silhouette. */ get renderTarget(): RenderTarget; /** * Read-only — set at construction only. Treated as a static config * value so the shadow pipeline doesn't need teardown/rebuild logic * for runtime scale changes. Viewport resizes take the cheap * `resize()` path (RTs set new dimensions, JFA pass count * recomputed) without touching material / node graphs. */ get resolutionScale(): number; get width(): number; get height(): number; /** * Resize the RT to match `viewportWidth × viewportHeight * resolutionScale`. * Cheap when the size hasn't changed. Clamped to a 1×1 minimum so * instantiation-before-first-render never hits a zero-size GPU resource. */ resize(viewportWidth: number, viewportHeight: number): void; /** * Render `scene` with `camera` into the occlusion RT. Every mesh whose * material is a {@link Sprite2DMaterial} has its material temporarily * swapped to a per-texture occlusion variant that samples the sprite's * alpha and masks it by the per-instance `castsShadow` bit in * `instanceSystem.z`. Non-casters contribute alpha = 0 to the SDF seed; * casters contribute their silhouette alpha unchanged. * * Non-sprite meshes render with their own materials. That's usually * harmless (background meshes don't emit alpha) but callers who mix in * custom materials can park them on a dedicated layer that the occlusion * camera excludes. * * Saves and restores renderer render target and scene.background so the * caller's subsequent main-scene render sees no side effects. */ render(renderer: WebGPURenderer, scene: Scene, camera: Camera): void; /** * Traverse callback bound once so `scene.traverse` doesn't re-box it * every frame. Reads `this._swappedMeshes` / `_swappedOriginals` / * `_occlusionMaterials` via arrow-function closure. */ private _collectAndSwap; private _getOrCreateOcclusionMaterial; dispose(): void; } //#endregion export { OcclusionPass, OcclusionPassOptions }; //# sourceMappingURL=OcclusionPass.d.ts.map