import type { BufferAttribute, BufferGeometry, Camera, ComputeNode, Matrix4, Mesh, Renderer, Sphere, StorageBufferAttribute, StorageInstancedBufferAttribute, TypedArray, Vector2, Vector3, Vector4 } from 'three/webgpu'; import { ComputeRadixSort } from './ComputeRadixSort.cjs'; import { instanceCulling } from '../TSL/InstanceCullingNode.cjs'; import type { TypedIndirectStorageBufferAttribute, TypedStorageBufferAttribute } from '../internal/three/resources.cjs'; import type { TSLStorageNode, TSLUintNode, TSLUniformNode } from '../types/tsl.cjs'; export type ComputeInstanceCullingStorageAttribute = StorageBufferAttribute | StorageInstancedBufferAttribute; export type ComputeInstanceCullingBufferSource = BufferAttribute | ComputeInstanceCullingStorageAttribute | TypedArray; export type ComputeInstanceCullingIdBuffer = TypedStorageBufferAttribute; export type ComputeInstanceCullingDrawBuffer = TypedIndirectStorageBufferAttribute; /** Local-space bounding sphere accepted by culling options. */ export interface ComputeInstanceCullingBoundingSphere { center: { x: number; y: number; z: number; }; radius: number; } /** Result returned by {@link ComputeInstanceCulling.getBoundingSphereAt}. */ export interface ComputeInstanceCullingBoundingSphereResult { center: { x: number | undefined; y: number | undefined; z: number | undefined; }; radius: number | undefined; } /** Shared options accepted by mesh-bound and standalone cullers. */ export interface ComputeInstanceCullingCommonOptions { count?: number | undefined; indexCount?: number | undefined; instanceMatrixStorage?: ComputeInstanceCullingBufferSource | null | undefined; refPosition?: ComputeInstanceCullingBufferSource | null | undefined; refNormal?: ComputeInstanceCullingBufferSource | null | undefined; enabled?: boolean | undefined; sortObjects?: boolean | undefined; forceSort?: boolean | undefined; frustumPadXY?: number | undefined; frustumPadZNear?: number | undefined; frustumPadZFar?: number | undefined; boundingSphere?: ComputeInstanceCullingBoundingSphere | Sphere | null | undefined; perInstanceBoundingBox?: boolean | undefined; boundingSpheresStorage?: ComputeInstanceCullingBufferSource | null | undefined; } /** Overrides used with the `(mesh, renderer, options)` constructor signature. */ export interface ComputeInstanceCullingMeshOptions extends ComputeInstanceCullingCommonOptions { useInstanceMatrix?: boolean | undefined; } /** Base options used with the standalone constructor signature. */ export interface ComputeInstanceCullingStandaloneOptions extends ComputeInstanceCullingCommonOptions { renderer: Renderer; } /** Standalone construction requires either matrix storage or reference positions. */ export type ComputeInstanceCullingOptions = ComputeInstanceCullingStandaloneOptions & ({ instanceMatrixStorage: ComputeInstanceCullingBufferSource; refPosition?: ComputeInstanceCullingBufferSource | null | undefined; } | { refPosition: ComputeInstanceCullingBufferSource; instanceMatrixStorage?: ComputeInstanceCullingBufferSource | null | undefined; }); /** Stable normalized options shared by both constructor paths. */ export interface ComputeInstanceCullingInternalOptions { renderer: Renderer; count: number; instanceMatrixStorage: ComputeInstanceCullingStorageAttribute | null; refPosition: ComputeInstanceCullingStorageAttribute | null; refNormal: ComputeInstanceCullingStorageAttribute | null; indexCount: number; enabled: boolean | undefined; sortObjects: boolean; forceSort: boolean | undefined; frustumPadXY: number | undefined; frustumPadZNear: number | undefined; frustumPadZFar: number | undefined; boundingSphere: ComputeInstanceCullingBoundingSphere | Sphere | null | undefined; sourceInstanceMatrix: BufferAttribute | null; perInstanceBoundingBox: boolean; boundingSpheresStorage: ComputeInstanceCullingStorageAttribute | null; } /** Minimal GUI controller surface used by supported inspectors. */ export interface ComputeInstanceCullingGUIController { name(label: string): this; } /** Structural GUI folder accepted by {@link ComputeInstanceCulling.attachGUI}. */ export interface ComputeInstanceCullingGUIFolder { add(target: object, property: string, ...parameters: unknown[]): ComputeInstanceCullingGUIController; addFolder?: (label: string) => ComputeInstanceCullingGUIFolder; destroy?: () => void; parent?: { remove(target: unknown): void; } | undefined; paramList?: unknown; } export type ComputeInstanceCullingBoundsData = TypedArray | readonly ComputeInstanceCullingBoundingSphere[]; /** * GPU-driven frustum and LOD culling for massive instanced rendering. * * **Architecture** * - Runs entirely on GPU via compute shaders (TSL) * - Tests each instance against camera frustum and distance * - Compacts visible instances into packed survivor buffer * - Writes indirect draw args (instanceCount) atomically * - Optional depth sorting for transparent objects * * **Culling Modes** * - **Frustum Culling**: Excludes instances outside camera view * - **LOD Sampling**: Continuous distance-based sampling (reduces far instances) * * **LOD System** * - **Purpose**: Reduce draw density smoothly with distance by keeping each instance with probability `pKeep`. * - **Near/Far**: `lodNear` defines where LOD begins. `lodFar` defines where range mode reaches full falloff. * - **Modes** (`lodMode`): * - **Disabled (`LOD_MODE_DISABLED`)**: LOD sampling is off. * - **Range (`LOD_MODE_RANGE`)**: Smoothstep falloff between `lodNear` and `lodFar`. * `pKeep = 1 - smoothstep( lodNear, lodFar, d )` * - **Exp (`LOD_MODE_EXP`)**: Exponential density falloff starting at `lodNear`. * `pKeep = exp( -density^2 * (d - lodNear)^2 )` * Note: `lodNear` shifts the start of the exp curve; increasing it delays the falloff. * - **Density Parameter**: `lodDensity` acts as exp density (smaller = slower decay). * - **Sampling**: Each instance is kept if `hash(instanceId) < pKeep`, giving stable stochastic thinning. * - **Compatibility**: Internally computes `stepF = sqrt(1 / max(pKeep, eps))` for legacy outputs. * * **Performance** * - O(N) GPU parallel culling vs O(N) CPU serial testing * - Zero CPU overhead after setup * - Indirect draw eliminates CPU→GPU instance data sync * - Typical 10-100x performance gain for large instance counts * * ```js * import { ComputeInstanceCulling } from 'three-blocks'; * import * as THREE from 'three/webgpu'; * * // Create instanced mesh * const count = 10000; * const instancedMesh = new THREE.InstancedMesh( * new THREE.BoxGeometry(), * new THREE.MeshBasicNodeMaterial(), * count * ); * * // Initialize instance matrices * const tempMat = new THREE.Matrix4(); * for (let i = 0; i < count; i++) { * tempMat.setPosition( * Math.random() * 100 - 50, * Math.random() * 100 - 50, * Math.random() * 100 - 50 * ); * instancedMesh.setMatrixAt(i, tempMat); * } * * // Enable GPU culling - that's it! * // Automatically patches material.setupPosition() to use culled instances * new ComputeInstanceCulling(instancedMesh, renderer); * * // Render (culling happens automatically) * function animate() { * renderer.render(scene, camera); * } * ``` * * ```js * import { ComputeInstanceCulling } from 'three-blocks'; * import { instanceCullingIndex as index } from 'three-blocks/instance-culling'; * * // Create instanced mesh * const instancedMesh = new THREE.InstancedMesh( * new THREE.BoxGeometry(), * new THREE.MeshBasicNodeMaterial(), * count * ); * * // Create culler * const culler = new ComputeInstanceCulling(instancedMesh, renderer); * * // Use culling index instead of instanceIndex * material.positionNode = rotate(positionLocal, angle.add(hash(index(culler)))) * ``` * * **Example: Custom positionNode with TSL** * * When using `positionNode` with GPU culling, the instanceCulling transform is applied * automatically after your positionNode. Use `instanceCullingIndex` to access per-instance * data like random seeds or animation offsets: * * ```js * import { ComputeInstanceCulling } from 'three-blocks'; * import { instanceCullingIndex } from 'three-blocks/instance-culling'; * import { time, hash, rotate, positionLocal, normalLocal, transformNormalToView } from 'three/tsl'; * * const count = 10000; * const instancedMesh = new THREE.InstancedMesh( * new THREE.BoxGeometry(), * new THREE.MeshNormalNodeMaterial(), * count * ); * * // Setup GPU culling * const culler = new ComputeInstanceCulling(instancedMesh, renderer); * * // Access the culled instance index for per-instance variation * const culledIndex = instanceCullingIndex(culler); * * // Compute per-instance rotation angle based on time and instance hash * const angle = time.mul(0.6).add(hash(culledIndex).mul(Math.PI * 2)); * * // Apply rotation to position (runs BEFORE instance matrix transform) * instancedMesh.material.positionNode = rotate(positionLocal, angle); * * // Transform normals to match the rotation * instancedMesh.material.normalNode = transformNormalToView( * rotate(normalLocal, angle) * ).normalize(); * ``` * * **Example: Advanced culling with custom visibility logic** * * For advanced use cases, you can access the culler's internal buffers directly: * * ```js * import { ComputeInstanceCulling } from 'three-blocks'; * import { storage, instanceIndex, If, uint } from 'three/tsl'; * * const culler = new ComputeInstanceCulling(instancedMesh, renderer, { * enabled: true, * sortObjects: true // Enable depth sorting for transparency * }); * * // Access culling parameters * culler.lodNear.value = 50; // LOD near radius * culler.lodFar.value = 400; // LOD far radius (range mode) * culler.lodMode.value = LOD_MODE_RANGE; * culler.lodDensity.value = 0.00025; // Exp density (exp mode) * * // Read back survivor count for debugging * const args = await culler.readIndirectArgs(); * console.log(`Visible instances: ${args[1]} / ${count}`); * ``` * * * @demo docs/demos/compute-instance-culling.html * @class ComputeInstanceCulling * @short GPU frustum/LOD culling for InstancedMesh that compacts visible IDs and writes indirect args (optional depth sort). * @category Compute * @tags WebGPU * @see {@link instanceCulling} - TSL node for applying culled instance transformations */ export declare class ComputeInstanceCulling { isComputeInstanceCulling: true; renderer: Renderer | null; REF_COUNT: number; activeCount: TSLUniformNode<'uint', number>; refMatSSBO: ComputeInstanceCullingStorageAttribute | null; refMatNode: TSLStorageNode<'mat4'> | null; _sourceInstanceMatrix: BufferAttribute | null; _sourceInstanceMatrixVersion: number; refPosSSBO: ComputeInstanceCullingStorageAttribute | null; refPosNode: TSLStorageNode<'vec3'> | null; refNrmSSBO: ComputeInstanceCullingStorageAttribute | null; refNrmNode: TSLStorageNode<'vec3'> | null; outIdSSBO: ComputeInstanceCullingIdBuffer; outIdNode: TSLStorageNode<'uint'> | null; _culledInstanceIndexNode: TSLUintNode | null; outVisSSBO: ComputeInstanceCullingIdBuffer; outVisNode: TSLStorageNode<'uint'> | null; indirect: ComputeInstanceCullingDrawBuffer; lodNear: TSLUniformNode<'float', number>; nearRadius: TSLUniformNode<'float', number>; maxStep: TSLUniformNode<'float', number>; lodFar: TSLUniformNode<'float', number>; lodMode: TSLUniformNode<'float', number>; lodDensity: TSLUniformNode<'float', number>; falloffExp: TSLUniformNode<'float', number>; camPos: TSLUniformNode<'vec3', Vector3>; enabled: TSLUniformNode<'bool', boolean>; camView: TSLUniformNode<'mat4', Matrix4>; camProj: TSLUniformNode<'mat4', Matrix4>; isOrthographic: TSLUniformNode<'bool', boolean>; orthoScale: TSLUniformNode<'vec3', Vector3>; frustumPadXY: TSLUniformNode<'float', number>; frustumPadZNear: TSLUniformNode<'float', number>; frustumPadZFar: TSLUniformNode<'float', number>; frustumProjectionScale: TSLUniformNode<'vec2', Vector2>; boundingSphereCenter: TSLUniformNode<'vec3', Vector3>; boundingSphereRadius: TSLUniformNode<'float', number>; perInstanceBoundingBox: boolean; boundingSpheresSSBO: ComputeInstanceCullingStorageAttribute | null; boundingSpheresNode: TSLStorageNode<'vec4'> | null; initialized: boolean; SORT_COUNT: number; sortObjects: boolean; forceSort: boolean; sortBackToFront: TSLUniformNode<'bool', boolean>; sortKeysIA: TSLStorageNode<'uint'>; sortValuesIA: TSLStorageNode<'uint'>; _sorter: ComputeRadixSort<'uint'> | null; drawStructNode: TSLStorageNode<'struct'>; initAll: ComputeNode; clearArgs: ComputeNode; clearVis: ComputeNode; selectPack: ComputeNode; capInstanceCount: ComputeNode; fillSortPairs: ComputeNode; applySortedPairs: ComputeNode; meshAttached: Mesh | null; _guiFolder: ComputeInstanceCullingGUIFolder | null | undefined; /** * Create a GPU instance culler. * * @param {THREE.InstancedMesh|THREE.Mesh|Object} meshOrOptions - Instanced mesh to cull or options bag. * @param {THREE.WebGPURenderer} [renderer] - WebGPU renderer (required when first param is a mesh). * @param {Object} [options] - Options object when using the mesh signature. */ constructor(mesh: Mesh, renderer: Renderer, options?: ComputeInstanceCullingMeshOptions); constructor(options: ComputeInstanceCullingOptions); /** * Internal initialization (used by both constructor paths). * @private */ _initInternal(options: ComputeInstanceCullingInternalOptions): void; /** * Update camera uniforms for frustum culling. * Call before `update()` each frame. * * @param {THREE.Camera} camera Active camera for culling tests. */ setCameraUniforms(camera: Camera): void; /** * Keep GPU storage buffer in sync with the mesh's instanceMatrix attribute. * @param {boolean} [force=false] - Force a sync even if version did not change. * @private */ _syncSourceInstanceMatrix(force?: boolean): void; /** * Attach culling controls to a GUI folder. * Compatible with lil-gui, dat.gui, and Three.js Inspector. * * @param {Object} folder A lil-gui instance or folder. * @example * const gui = new GUI( { title: 'Settings' } ); * const cullingFolder = gui.addFolder('GPU Culling'); * culler.attachGUI(cullingFolder); */ attachGUI(folder: object): void; /** * Detach and destroy the GUI folder. */ disposeGUI(): void; buildCompute(): void; /** * Initialize GPU buffers (called automatically on first update). * @private */ init(): void; /** * Execute GPU culling and compaction. * Runs compute shaders to test visibility, compact survivors, and optionally sort. * Must be called every frame before rendering. */ update(): void; /** * Attach geometry to receive indirect draw args. * * @param {THREE.BufferGeometry} geometry Target geometry for indirect rendering. */ attachGeometry(geometry: BufferGeometry): void; /** * Attach mesh and auto-disable sorting for opaque materials. * * @param {THREE.Mesh} mesh Target mesh instance. */ attachMesh(mesh: Mesh): void; isSortNeeded(): boolean; /** * Read back indirect draw arguments from GPU (debug/stats). * * @returns {Promise} Array of 5 values: [indexCount, instanceCount, firstIndex, baseVertex, firstInstance], or null if not ready. */ readIndirectArgs(): Promise; /** * Read back surviving instance IDs from GPU (debug/analysis). * * @returns {Promise} Array of survivor instance indices. */ readSurvivorIndicesAsync(): Promise; getInstanceIndexNode(): TSLUintNode | null; /** * Set bounding sphere for a specific instance. * Only effective when `perInstanceBoundingBox` is enabled. * * @param {number} instanceIndex Index of the instance. * @param {THREE.Vector3|{x: number, y: number, z: number}} center Center of the bounding sphere in local space. * @param {number} radius Radius of the bounding sphere. */ setBoundingSphereAt(instanceIndex: number, center: { x: number; y: number; z: number; }, radius: number): void; /** * Get bounding sphere for a specific instance. * Only effective when `perInstanceBoundingBox` is enabled. * * @param {number} instanceIndex Index of the instance. * @param {THREE.Vector4} [target] Optional target to store the result (x,y,z = center, w = radius). * @returns {{center: {x: number, y: number, z: number}, radius: number}|null} */ getBoundingSphereAt(instanceIndex: number, target?: Vector4): ComputeInstanceCullingBoundingSphereResult | null; /** * Set the shared bounding sphere used when `perInstanceBoundingBox` is disabled. * Computes the maximum bounding sphere that encompasses all provided per-instance bounds. * * @param {Float32Array|Array<{center: {x: number, y: number, z: number}, radius: number}>} boundsData * Either a Float32Array of vec4 (centerX, centerY, centerZ, radius) per instance, * or an array of objects with center and radius properties. */ setMaxBoundingSphere(boundsData: ComputeInstanceCullingBoundsData): void; /** * Initialize per-instance bounding sphere storage buffer. * Call this to enable per-instance culling after construction. * * @param {Float32Array} [data] Optional initial data (vec4 per instance: centerX, centerY, centerZ, radius). */ initBoundingSpheresStorage(data?: Float32Array): void; /** * Dispose of GPU resources. * * Call this method when the culler is no longer needed to free GPU memory. * After disposal, the culler should not be used. */ dispose(): void; } export { instanceCulling };