UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

258 lines (257 loc) 12.2 kB
/** * @import { StorageBuffer } from '../../platform/graphics/storage-buffer.js' * @import { GraphNode } from '../graph-node.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { Layer } from '../layer.js' * @import { GSplatWorkBuffer } from './gsplat-work-buffer.js' * @import { GSplatWorld } from './gsplat-world.js' * @import { GSplatWorldState } from './gsplat-world-state.js' * @import { GSplatRenderViewParams } from './gsplat-renderer.js' */ /** * Renders splats from a pre-projected cache built by the projector compute pass * (see {@link GSplatProjector}) and a globally radix-sorted index array. The * vertex shader is `gsplatHybridVS`; the fragment is the existing `gsplatPS`. * * Supports forward rendering and explicit pick/prepass paths. Shadow rendering is * intentionally not supported because the projection cache is camera-specific. * * @ignore */ export class GSplatHybridRenderer extends GSplatRenderer { /** * @param {GraphicsDevice} device - The graphics device. * @param {GraphNode} node - The graph node. * @param {GraphNode} cameraNode - The camera node. * @param {Layer} layer - The layer to add mesh instances to. * @param {GSplatWorkBuffer} workBuffer - The work buffer (kept for parent compatibility; * the hybrid renderer does not bind work-buffer textures itself). * @param {import('./gsplat-hybrid-renderer-scratch.js').GSplatHybridRendererScratch|null} [scratch] - * Manager-owned shared scratch; forwarded to the interval compaction (shared with the shadow cull). */ constructor(device: GraphicsDevice, node: GraphNode, cameraNode: GraphNode, layer: Layer, workBuffer: GSplatWorkBuffer, scratch?: import("./gsplat-hybrid-renderer-scratch.js").GSplatHybridRendererScratch | null); /** @type {ShaderMaterial} */ _material: ShaderMaterial; /** @type {MeshInstance} */ meshInstance: MeshInstance; /** @type {ShaderMaterial|null} */ _pickMaterial: ShaderMaterial | null; /** @type {MeshInstance|null} */ _pickMeshInstance: MeshInstance | null; /** * Per-camera `clipToViewZ` value for the forward material. Persistent: the GPU * upload happens at draw time, so the buffer must outlive `setHybridSortedRendering`. * * @type {Float32Array} */ _clipToViewZ: Float32Array; /** * Per-camera `clipToViewZ` value for the pick material. Allocated on first * `prepareForPicking` call and reused thereafter. * * @type {Float32Array|null} */ _clipToViewZPick: Float32Array | null; /** @type {number} */ originalBlendType: number; /** @type {Set<string>} */ _internalDefines: Set<string>; /** @type {boolean} */ forceCopyMaterial: boolean; /** @type {string} */ _lastSourceChunksKey: string; /** * The projection cache stride in u32 words: the base layout plus user varying stream words. * * @type {number} */ _cacheStride: number; /** * GPU radix sorter for the projected cache indices. * * @type {ComputeRadixSort|null} */ gpuSorter: ComputeRadixSort | null; /** * Compute projector that builds the per-camera projection cache + sort keys. * * @type {GSplatProjector|null} */ projector: GSplatProjector | null; /** * Interval-based GPU culling + compaction (lazily created on first sort). * * @type {GSplatIntervalCompaction|null} */ intervalCompaction: GSplatIntervalCompaction | null; /** * Manager-owned shared scratch injected at construction; forwarded to the interval compaction so * its compacted index list is shared with the directional-shadow cull. Null when not provided. * * @type {import('./gsplat-hybrid-renderer-scratch.js').GSplatHybridRendererScratch|null} * @private */ private _scratch; /** * Per-frame indirect draw slot index (-1 when unallocated). * * @type {number} */ indirectDrawSlot: number; /** * Per-frame indirect dispatch slot index (projector + radix sort passes). * * @type {number} */ indirectDispatchSlot: number; /** * Total intervals from the last interval-compaction dispatch (index into the prefix sum * for the visible count). * * @type {number} */ lastCompactedNumIntervals: number; configureMaterial(): void; /** * Toggles the XR stereo (GSPLAT_XR) variant of the forward material. The vertex shader then * reads the per-eye stereo projection-cache layout and selects the eye via `view_index`. Only * recompiles when the stereo state changes, so it is cheap to call every frame. Must stay in * sync with the projector's stereo variant (both driven by the same isStereo value). * * @param {boolean} enabled - Whether stereo (2-view) rendering is active. */ setStereo(enabled: boolean): void; update(count: any, textureSize: any): void; /** * Lazily creates the GPU sort pipeline resources on first forward use. Kept out of the * constructor so a hybrid renderer that never renders a forward pass (e.g. one owned by a * shadow-only manager) allocates none of them. * * @private */ private _ensureGpuPipeline; /** * Runs interval cull + compaction, the projector, and the indirect radix sort for a specific * camera/view. Shared by the forward render and the picker. Assumes the work buffer is baked and * render-ready (the manager's version lifecycle marks it before delegating). * * @param {GSplatWorld} world - The world providing the work buffer, bounds and states. * @param {GSplatWorldState} worldState - The world state to sort. * @param {GraphNode} cameraNode - Camera node used for projection and sort keys. * @param {number} viewportWidth - Projection viewport width in pixels. * @param {number} viewportHeight - Projection viewport height in pixels. * @param {number} alphaClip - Projector producer alpha threshold. * @param {boolean} pickMode - Whether the projector writes pcId into the cache. * @param {boolean} isStereo - Whether to project both XR eyes in one pass (forward only). * @param {GSplatRenderViewParams} params - Per-call gsplat params. * @returns {StorageBuffer|null} The sorted cache indices, or null if no work was dispatched. * @private */ private sortAndProjectForCamera; /** * Allocates per-frame indirect draw and dispatch slots and writes the interval-compaction * indirect args. * * @param {number} numIntervals - Total interval count (index into prefix sum for visible count). * @private */ private allocateAndWriteIntervalIndirectArgs; /** * Prepares frustum culling data: updates the GPU transform buffers and computes frustum planes * from the camera. The actual culling test runs inline in the interval compaction compute shader. * * @param {object} world - The {@link GSplatWorld} owning the frustum culler. * @param {object} worldState - The world state whose splats provide transforms. * @param {GraphNode} cameraNode - Camera node to cull against. * @param {object} params - Per-call gsplat params (for fisheye). * @private */ private _runFrustumCulling; /** * Computes the min/max effective distances for the current world state (radial or linear). * * @param {object} worldState - The world state. * @param {GraphNode} cameraNode - Camera node to measure distances from. * @param {boolean} radialSort - Whether radial sorting is enabled. * @returns {{minDist: number, maxDist: number}} The distance range. * @private */ private computeDistanceRange; /** * Configures the renderer to draw from the projector's caches. * * @param {number} drawSlot - The indirect draw slot index. * @param {StorageBuffer} sortedIndices - Globally-sorted indices into projCache. * @param {StorageBuffer} projCache - Per-splat projection cache produced by the projector. * @param {StorageBuffer} numSplatsBuffer - GPU-written visible-splat count. */ setHybridSortedRendering(drawSlot: number, sortedIndices: StorageBuffer, projCache: StorageBuffer, numSplatsBuffer: StorageBuffer): void; /** * Configures and returns a transient pick mesh instance for the picker render pass. * * @param {number} drawSlot - The indirect draw slot index. * @param {StorageBuffer} sortedIndices - Globally-sorted indices into projCache. * @param {StorageBuffer} projCache - Per-splat projection cache produced by the projector. * @param {StorageBuffer} numSplatsBuffer - GPU-written visible-splat count. * @param {number} alphaClip - Fragment alpha threshold for picking. * @param {number} alphaClipForward - Forward alpha floor (must match {@link GSplatRenderer#frameUpdate}). * @param {GraphNode} cameraNode - The picker camera node, used to derive the * `clipToViewZ` reconstruction uniform. * @returns {MeshInstance} The pick mesh instance. */ prepareForPicking(drawSlot: number, sortedIndices: StorageBuffer, projCache: StorageBuffer, numSplatsBuffer: StorageBuffer, alphaClip: number, alphaClipForward: number, cameraNode: GraphNode): MeshInstance; /** * Computes the per-camera `clipToViewZ` value into `dst`. The hybrid VS dot-products * this with the cached `clipPos` to recover linear view depth, used by fog / overdraw * / prepass. * * - Perspective + orthographic: `dst = -inverse(matrix_projection)[row 2]`. The * projector stores `clipPos.w` in slot [3] and the dot-product yields `-view.z`. * - Fisheye: `dst = (0, 0, far - near, near)`. The projector stores depthNdc in * slot [2] and `1.0` in slot [3], so the dot-product reduces to * `depthNdc * (far - near) + near`, which equals linear `-view.z`. * * The destination buffer must be retained by the caller (typically a per-material * instance field) because the GPU upload happens at draw time. * * @param {GraphNode} cameraNode - Camera node to derive the uniform from. * @param {Float32Array} dst - 4-element destination, written in place. * @private */ private _computeClipToViewZ; frameUpdate(params: any): void; _lastNoFog: any; /** * Copies material settings from a source material to the internal material. * Preserves internal defines while copying user defines, parameters, and shader chunks. * This delivers user customizations (e.g. the `gsplatModifyPS` fragment chunk and its * parameters) set on `app.scene.gsplat.material` to the hybrid render material. Note that * the `gsplatModifyVS` chunk is handled by the projector compute instead, and even when * copied here it is not referenced by the hybrid vertex shader. * * @param {ShaderMaterial} sourceMaterial - The source material to copy settings from. * @private */ private copyMaterialSettings; /** * Updates pick ID defines to match the work-buffer format. * * @param {ShaderMaterial} material - Material to update. * @returns {boolean} True if the material defines changed. * @private */ private _updateIdDefines; updateOverdrawMode(params: any): void; createMeshInstance(): MeshInstance; } import { GSplatRenderer } from './gsplat-renderer.js'; import { ShaderMaterial } from '../materials/shader-material.js'; import { MeshInstance } from '../mesh-instance.js'; import { ComputeRadixSort } from '../graphics/radix-sort/compute-radix-sort.js'; import { GSplatProjector } from './gsplat-projector.js'; import { GSplatIntervalCompaction } from './gsplat-interval-compaction.js'; import type { StorageBuffer } from '../../platform/graphics/storage-buffer.js'; import type { GraphNode } from '../graph-node.js'; import type { GraphicsDevice } from '../../platform/graphics/graphics-device.js'; import type { Layer } from '../layer.js'; import type { GSplatWorkBuffer } from './gsplat-work-buffer.js';