# Three Blocks - API Documentation for LLMs > GPU-accelerated simulation and rendering toolkit for Three.js WebGPU ## Overview Three Blocks provides high-performance GPU compute utilities, TSL (Three.js Shading Language) nodes, simulation systems (Boids, SPH, PBF), and rendering tools optimized for Three.js WebGPU. ## Installation ```bash npm install @three-blocks/core ``` ```js import * as THREE from 'three/webgpu'; import { Boids, SPH, Text, IndirectBatchedMesh } from '@three-blocks/core'; ``` ## API Reference ## Compute GPU compute utilities for culling, sorting, sampling, and SDF generation. ### ComputeBatchCulling *Class* ```js import { ComputeBatchCulling } from '@three-blocks/core'; ``` GPU-driven culling for `IndirectBatchedMesh` with matrix-based reference positions. **Architecture** - Optimized for `IndirectBatchedMesh` with per-instance matrices - Two-pass block compaction algorithm for efficient parallel writes - Supports both ref-position and matrix-based culling - Per-geometry bounding sphere tests (reads from mesh geometry info) - Writes survivor IDs for indirect instanced draw **Limitations** - **Transparency is not supported**: IndirectBatchedMesh does not support transparent materials. Use opaque materials only. Depth sorting is not available. **Block Compaction Algorithm** - **Count Pass**: Each instance atomically increments its block counter if visible - **Prefix Sum**: CPU computes block offsets (serial over blocks, parallel within) - **Scatter Pass**: Each visible instance writes to its block's allocated slot **Matrix vs Position Mode** - **Position Mode** (default): Uses `refPosition`/`refNormal` arrays - **Matrix Mode** (via `bindMatricesAsReference()`): Extracts position from matrices - Enables per-geometry bounding sphere culling - Ideal for `IndirectBatchedMesh` integration **Integration with IndirectBatchedMesh** **Properties:** - `indirect`: THREE.IndirectStorageBufferAttribute - Get indirect draw buffer. - `outIdSSBO`: THREE.StorageBufferAttribute - Get survivor ID buffer. - `outIdNode`: StorageNode - Get survivor ID storage node (TSL). **Methods:** #### setCameraUniforms(camera) Update camera uniforms for frustum culling. Call before `update()` each frame. Parameters: - `camera`: THREE.Camera - Active camera for culling tests. #### attachGUI(folder) Attach culling controls to a GUI folder. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `folder`: Object - GUI folder instance (e.g., from lil-gui or renderer.inspector.createParameters()). #### disposeGUI() Detach and destroy the GUI folder. #### update() Execute GPU culling with two-pass block compaction. Must be called every frame before rendering. #### attachGeometry(geometry) Attach geometry to receive indirect draw args. Parameters: - `geometry`: THREE.BufferGeometry - Target geometry for indirect rendering. #### attachMesh(mesh) Attach mesh and configure survivor buffer. Parameters: - `mesh`: THREE.Mesh - Target mesh instance. #### setReferenceMatrixNode(resolverFn) Override the matrix used for culling (e.g., animation texture). Parameters: - `resolverFn`: function | null - Function receiving { i, baseMatrix, gid } and returning a mat4 node. Returns: `this` #### bindMatricesAsReference(mesh) Bind mesh matrices as culling reference (enables matrix mode). Extracts positions from instance matrices and enables per-geometry sphere tests. Automatically called by `IndirectBatchedMesh` when culling is enabled. Parameters: - `mesh`: IndirectBatchedMesh - Mesh with `matricesSB` storage buffer. #### readIndirectArgs() Read back indirect draw arguments from GPU (debug/stats). Returns: `Promise.` - Array of [indexCount, instanceCount, firstIndex, baseVertex, firstInstance]. #### readSurvivorIndicesAsync() Read back surviving instance IDs from GPU (debug/analysis). Returns: `Promise.` - Array of survivor instance indices. #### dispose() Dispose of GPU resources. **Example:** ```js import { IndirectBatchedMesh, ComputeBatchCulling, indirectBatch } from '@three-blocks/core'; // Create batched mesh const count = 10000; const mesh = new IndirectBatchedMesh(count, 50000, 150000, material); mesh.frustumCulled = false; // Disable CPU culling // Add geometries and instances const sphereId = mesh.addGeometry(new SphereGeometry(1, 16, 16)); for (let i = 0; i < count; i++) { const id = mesh.addInstance(sphereId); const matrix = new Matrix4(); matrix.makeTranslation( Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50 ); mesh.setMatrixAt(id, matrix); } // Setup material with indirect batch node material.positionNode = Fn(() => { indirectBatch(mesh).toStack(); return positionLocal; })(); // Create culler const culler = new ComputeBatchCulling({ renderer, count, indexCount: sphereGeometry.index.count, useFrustumCulling: true }); // Bind mesh matrices as culling reference culler.bindMatricesAsReference(mesh); culler.attachGeometry(mesh.geometry); culler.attachMesh(mesh); // Render loop function animate() { culler.setCameraUniforms(camera); culler.update(); // GPU culling renderer.render(scene, camera); } ``` See also: IndirectBatchedMesh, ComputeInstanceCulling --- ### ComputeBitonicSort *Class* ```js import { ComputeBitonicSort } from '@three-blocks/core'; ``` GPU-accelerated parallel bitonic sort for Three.js TSL compute shaders. ComputeBitonicSort provides an efficient O(n log²n) parallel sorting algorithm that runs entirely on the GPU. It's designed for sorting large arrays of key-value pairs where deterministic, stable ordering is required. It is used in SPH, PBF, Boids, IndirectBatchedMesh, ComputeInstanceCulling. Features - **Fully GPU-accelerated**: All sorting operations execute as compute shaders - **Stable sorting**: Elements with equal keys maintain their relative order via tie-breaker IDs - **Deterministic**: Produces identical results across frames and devices - **Dual-backend support**: Optimized path for WebGPU, fallback for WebGL - **Workgroup optimization**: Uses shared memory for efficient local sorting (WebGPU) - **Bounds-safe**: Handles non-power-of-two counts with sentinel values Usage Data Format The data buffer must contain `uvec2` elements where: - **x**: The primary sort key (e.g., spatial hash, distance, priority) - **y**: A unique stable ID to break ties deterministically Elements are sorted in ascending order by (x, y) lexicographically. Performance Considerations - Buffer count should ideally be a power of 2 for optimal performance - Larger workgroup sizes (64-256) typically perform better on modern GPUs - The `compute()` method executes all steps synchronously; use `computeStep()` for amortized sorting across multiple frames **Constructor Parameters:** - `dataBuffer`: StorageBufferNode - The storage buffer containing uvec2 elements to sort. Each element should be a uvec2 where x is the sort key and y is a stable tie-breaker ID. - `options?`: Object = {} - Configuration options. - `options.workgroupSize?`: number = 64 - The workgroup size for compute shaders. Must be a power of 2. Larger values (64-256) typically perform better but are limited by GPU capabilities. Will be clamped to valid ranges automatically. **Properties:** - `dataBuffer`: StorageBufferNode - The storage buffer containing the data to be sorted. Elements are uvec2 pairs: (sortKey, stableId). - `count`: number - The number of elements in the data buffer. - `dispatchSize`: number - The number of thread groups to dispatch for compute operations. Each thread handles one comparison, so dispatchSize = count / 2. - `workgroupSize`: number - The number of threads per workgroup. Determines how many elements can be sorted in local shared memory. Each workgroup sorts 2 * workgroupSize elements locally. - `localStorage`: WorkgroupInfoNode | null - Workgroup-scoped shared memory buffer for local sorting operations. Holds 2 * workgroupSize elements during local sort phases. Only initialized for WebGPU backends. - `tempBuffer`: StorageBufferNode - Temporary storage buffer for ping-pong operations during global swaps. Global operations read from one buffer and write to the other to avoid read-after-write hazards. - `infoStorage`: StorageBufferNode - Storage buffer containing algorithm state: [stepType, currentSwapSpan, maxSwapSpan]. Used to coordinate multi-dispatch sorting operations. - `swapOpCount`: number - Total number of distinct swap operations (flips + disperses) in a complete sort. For n elements: (log2(n) * (log2(n) + 1)) / 2 - `stepCount`: number - Total number of compute dispatches needed for a complete sort. Depends on whether local sorting is available (WebGPU) or not (WebGL). - `readBufferName`: string - Name of the buffer currently being read from ('Data' or 'Temp'). Used for ping-pong buffer management during global operations. - `flipGlobalNodes`: Object. - Compute nodes for global flip operations, keyed by source buffer name. - `disperseGlobalNodes`: Object. - Compute nodes for global disperse operations, keyed by source buffer name. - `swapLocalFn`: ComputeNode | null - Compute node for complete local sorting (flip + all disperse stages). Only available on WebGPU backends. - `disperseLocalNodes`: Object. | null - Compute nodes for local disperse operations, keyed by source buffer name. Only available on WebGPU backends. - `setAlgoFn`: ComputeNode | null - Compute node that advances the algorithm state to the next step. - `alignFn`: ComputeNode - Compute node that copies data from temp buffer back to data buffer. Used to ensure results end up in the original data buffer. - `resetFn`: ComputeNode | null - Compute node that resets algorithm state for a new sort operation. - `currentDispatch`: number - Current dispatch index within the sort operation (0 to stepCount - 1). - `globalOpsRemaining`: number - Number of remaining global operations before switching to local disperse. Used for WebGPU path state management. - `globalOpsInSpan`: number - Total global operations in the current span. Increments as we process larger swap spans. - `initialized`: boolean - Whether the sorter has been initialized with a renderer. - `isWebGL`: boolean - Whether the current backend is WebGL (lacks workgroup shared memory). **Methods:** #### init(renderer) Initializes the sorter for the given renderer. This method detects the backend type (WebGL vs WebGPU) and creates the appropriate compute shaders. Must be called before sorting, but is automatically invoked by `compute()` and `computeStep()` if needed. Parameters: - `renderer`: WebGPURenderer - The Three.js WebGPU renderer. #### computeStep(renderer) Executes a single step of the bitonic sort. Call this method repeatedly (once per frame) to amortize sorting cost over multiple frames. A complete sort requires `stepCount` calls. The method automatically: - Initializes on first call - Advances through flip/disperse stages - Handles buffer ping-ponging - Resets state when sort completes Parameters: - `renderer`: WebGPURenderer - The Three.js WebGPU renderer. #### compute(renderer) Executes a complete bitonic sort in a single call. This method runs all sorting steps synchronously, which may cause frame drops for large arrays. For real-time applications, consider using `computeStep()` to spread the work across multiple frames. Parameters: - `renderer`: WebGPURenderer - The Three.js WebGPU renderer. **Example:** ```js import { instancedArray } from 'three/tsl'; import { ComputeBitonicSort } from './ComputeBitonicSort.js'; // Create a buffer of uvec2 pairs: (sortKey, stableId) const count = 1024; const dataBuffer = instancedArray( count, 'uvec2' ); // Initialize the sorter const sorter = new ComputeBitonicSort( dataBuffer, { workgroupSize: 64 } ); // In your render loop: sorter.compute( renderer ); // Full sort in one call // Or for amortized sorting across frames: sorter.computeStep( renderer ); // One step per frame ``` --- ### ComputeBVHSampler *Class* ```js import { ComputeBVHSampler } from '@three-blocks/core'; ``` GPU-accelerated point sampler for SDF volumes. **Architecture** - Uses a pre-computed SDF (Signed Distance Field) to efficiently test volume containment. - Employs **Blue Noise Sampling** for temporally stable, low-discrepancy point distribution. - Supports **Rejection Sampling** on the GPU to conform to complex shapes. - Outputs transformation matrices directly compatible with `THREE.InstancedMesh`. **Sampling Strategies** - **Uniform**: Distributes points uniformly within the volume. - **Surface**: (Planned) Distributes points near the surface. - **Custom**: (Planned) Uses a custom density function. **Usage** This class is typically used in conjunction with `ComputeSDFGenerator`. **Properties:** - `sdfGenerator`: ComputeSDFGenerator - `renderer`: THREE.WebGPURenderer - `count`: number - `strategy`: string - `alignToNormal`: boolean - `samplingBounds`: THREE.Box3 | null - `outMatrixSSBO`: THREE.StorageInstancedBufferAttribute - `outPositionsSSBO`: THREE.StorageInstancedBufferAttribute - `output`: any - Gets the output transformation matrices. - `positionsBuffer`: any - Gets the output positions buffer (vec3 per sample). Useful for particle systems like SPH that only need positions. **Methods:** #### compute(options?, options.sdfThreshold?, options.debug?) Computes the volume sampling. Parameters: - `options?`: Object - Compute options - `options.sdfThreshold?`: number - Override SDF threshold - `options.debug?`: boolean = false - Enable debug logging Returns: `Promise.` #### updateSDF(sdfGenerator) Updates the SDF generator reference (useful when SDF is regenerated). Parameters: - `sdfGenerator`: ComputeSDFGenerator - New SDF generator #### readback() Performs CPU readback of transformation matrices. Returns: `Promise.` #### calculateValidRate(positions) Calculates the valid sample rate (non-zero positions). Accounts for potential vec4 padding in GPU buffers. Parameters: - `positions`: Float32Array - Positions array from readbackPositions() Returns: `number` - Valid sample percentage (0-100) #### dispose() Disposes GPU resources. See also: {@link ComputeSDFGenerator} --- ### ComputeInstanceCulling *Class* ```js import { ComputeInstanceCulling } from '@three-blocks/core'; ``` 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**: Stochastic sampling based on distance (reduces far instances) - **Max Distance**: Hard cutoff radius for visibility **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 **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: **Example: Advanced culling with custom visibility logic** For advanced use cases, you can access the culler's internal buffers directly: **Methods:** #### setCameraUniforms(camera) Update camera uniforms for frustum culling. Call before `update()` each frame. Parameters: - `camera`: THREE.Camera - Active camera for culling tests. #### attachGUI(folder) Attach culling controls to a GUI folder. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `folder`: Object - GUI folder instance (e.g., from lil-gui or renderer.inspector.createParameters()). #### disposeGUI() Detach and destroy the GUI folder. #### update() Execute GPU culling and compaction. Runs compute shaders to test visibility, compact survivors, and optionally sort. Must be called every frame before rendering. #### attachGeometry(geometry) Attach geometry to receive indirect draw args. Parameters: - `geometry`: THREE.BufferGeometry - Target geometry for indirect rendering. #### attachMesh(mesh) Attach mesh and auto-disable sorting for opaque materials. Parameters: - `mesh`: THREE.Mesh - Target mesh instance. #### readIndirectArgs() Read back indirect draw arguments from GPU (debug/stats). Returns: `Promise.<(Uint32Array|null)>` - Array of 5 values: [indexCount, instanceCount, firstIndex, baseVertex, firstInstance], or null if not ready. #### readSurvivorIndicesAsync() Read back surviving instance IDs from GPU (debug/analysis). Returns: `Promise.` - Array of survivor instance indices. #### setBoundingSphereAt(instanceIndex, center, radius) Set bounding sphere for a specific instance. Only effective when `perInstanceBoundingBox` is enabled. Parameters: - `instanceIndex`: number - Index of the instance. - `center`: THREE.Vector3 | Object - Center of the bounding sphere in local space. - `radius`: number - Radius of the bounding sphere. #### getBoundingSphereAt(instanceIndex, target?) Get bounding sphere for a specific instance. Only effective when `perInstanceBoundingBox` is enabled. Parameters: - `instanceIndex`: number - Index of the instance. - `target?`: THREE.Vector4 - Optional target to store the result (x,y,z = center, w = radius). Returns: `Object | null` #### setMaxBoundingSphere(boundsData) Set the shared bounding sphere used when `perInstanceBoundingBox` is disabled. Computes the maximum bounding sphere that encompasses all provided per-instance bounds. Parameters: - `boundsData`: Float32Array | Array.<{center: {x: number, y: number, z: number}, radius: number}> - Either a Float32Array of vec4 (centerX, centerY, centerZ, radius) per instance, or an array of objects with center and radius properties. #### initBoundingSpheresStorage(data?) Initialize per-instance bounding sphere storage buffer. Call this to enable per-instance culling after construction. Parameters: - `data?`: Float32Array - Optional initial data (vec4 per instance: centerX, centerY, centerZ, radius). #### dispose() Dispose of GPU resources. **Example:** ```js import { ComputeInstanceCulling } from '@three-blocks/core'; 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); } ``` See also: {@link instanceCulling} - TSL node for applying culled instance transformations, {@link IndirectBatchedMesh}, {@link ComputeBatchCulling} Demo: https://three-blocks.com/docs/demos/compute-instance-culling/ --- ### ComputeMeshSurfaceSampler *Class* ```js import { ComputeMeshSurfaceSampler } from '@three-blocks/core'; ``` GPU mesh surface sampler using TSL. **Overview** This class implements a high-performance surface sampling algorithm that runs entirely on the GPU. It is designed to generate thousands or millions of instances distributed on the surface of a mesh. **Algorithm** - **CPU Pre-processing**: Builds a weighted distribution (CDF) and Alias Table based on triangle areas. - **GPU Sampling**: Uses the Alias Method (O(1)) to select triangles and Blue Noise to pick barycentric coordinates. - **Output**: Writes transformation matrices to a storage buffer, ready for `InstancedMesh`. **Features** - **Blue Noise**: Ensures samples are well-distributed (low discrepancy) and temporally stable. - **Alias Method**: Efficient O(1) selection of weighted triangles. - **Normal Alignment**: Aligns instance Y-axis to the surface normal. **Example: Grass distribution on terrain** **Example: Resampling specific instances** **Properties:** - `renderer`: THREE.WebGPURenderer - `count`: number - `output`: any - Returns the GPU storage buffer containing instance transformation matrices. Can be directly assigned to InstancedMesh.instanceMatrix. - `outputNormal`: any - Returns the GPU storage buffer containing world-space surface normals per instance. **Methods:** #### compute(options?, options.resampleIndex?, options.trackTimestamp?) Recompute on GPU. Pass {resampleIndex} to update just one instance. Parameters: - `options?`: Object - Options - `options.resampleIndex?`: number - If provided, updates only this instance index; otherwise all instances are recomputed. - `options.trackTimestamp?`: boolean - If true, returns the GPU timestamp. Returns: `Promise.<(number|undefined)>` - GPU timestamp if {trackTimestamp} is true, otherwise null. #### readback() (optional) Read back transformation matrices to CPU Returns: `Promise.` - Array of mat4 matrices (16 floats per instance) **Example:** ```js import { ComputeMeshSurfaceSampler } from '@three-blocks/core'; import * as THREE from 'three/webgpu'; // Load terrain mesh const terrain = await loadTerrain(); // Create sampler for 100,000 grass blades const sampler = new ComputeMeshSurfaceSampler(terrain, renderer, 100000, { seed: 42, useVertexNormals: true }); // Run sampling on GPU await sampler.compute(); // Create instanced grass using the sampled transforms const grassGeometry = new THREE.PlaneGeometry(0.1, 0.5); const grassMaterial = new THREE.MeshStandardNodeMaterial({ color: 0x3d9140 }); const grass = new THREE.InstancedMesh(grassGeometry, grassMaterial, 100000); // Assign the GPU-generated transforms directly grass.instanceMatrix = sampler.output; scene.add(grass); ``` --- ### ComputeMipAwareBlueNoise *Class* ```js import { ComputeMipAwareBlueNoise } from '@three-blocks/core'; ``` Generates a mip-aware blue noise texture using the Hilbert R1 blue noise algorithm. **Overview** Blue noise textures are essential for high-quality rendering techniques such as: - **Dithering**: Reducing quantization artifacts in gradients. - **Stochastic Sampling**: Monte Carlo integration, soft shadows, and ambient occlusion. - **Temporal Anti-Aliasing (TAA)**: Providing stable, high-frequency noise that integrates well over time. **Features** - **Mip-Awareness**: Generates noise that remains blue (high-frequency) across mip levels, preventing aliasing artifacts when the texture is minified. - **Hilbert R1 Algorithm**: Uses a low-discrepancy sequence mapped to a Hilbert curve for optimal spatial distribution. - **GPU Generation**: Fully computed on the GPU for fast initialization. **Properties:** - `storageTexture`: THREE.StorageTexture | null **Methods:** #### init(renderer) Initializes and generates the blue noise texture using compute shaders. This method must be called before using the texture. Parameters: - `renderer`: THREE.WebGPURenderer - The WebGPU renderer instance used for computation. Returns: `THREE.StorageTexture` - The generated storage texture. #### getTexture() Returns the generated blue noise texture. Returns: `THREE.StorageTexture | null` - The generated texture, or null if `init()` has not been called. --- ### ComputeSDFGenerator *Class* ```js import { ComputeSDFGenerator } from '@three-blocks/core'; ``` GPU-accelerated SDF (Signed Distance Field) generator using mesh BVH. **Overview** Generates a 3D texture containing signed distances from a mesh surface. It uses a BVH (Bounding Volume Hierarchy) acceleration structure to efficiently query the closest point on the mesh for each voxel. **Features** - **Fast Generation**: Uses compute shaders to generate SDFs in parallel. - **BVH Acceleration**: Leverages `three-mesh-bvh` for O(log N) distance queries. - **Dynamic Updates**: Can update the SDF if the mesh deforms (provided the BVH is updated). **Usage** The generated 3D texture can be used for: - **Volume Rendering**: Raymarching clouds, smoke, or fluids. - **Collision Detection**: GPU-based particle collisions. - **VFX**: Attracting/repelling particles from a surface. **Properties:** - `resolution`: number - `margin`: number - `threshold`: number - `customBounds`: THREE.Box3 | null - `workgroupSize`: THREE.Vector3 - `sdfTexture`: any - Gets the generated SDF texture. - `boundsMatrix`: any - Gets the bounds transformation matrix (local to world). - `inverseBoundsMatrix`: any - Gets the inverse bounds matrix (world to local). - `bounds`: any - Gets the computed bounding box (includes margin). - `geometryBounds`: any - Gets the tight geometry bounding box (without margin). Useful for focused sampling in ComputeBVHSampler. **Methods:** #### generate(geometry, bvh, renderer) Generates SDF texture from mesh and BVH. Parameters: - `geometry`: THREE.BufferGeometry - Source geometry - `bvh`: * - BVH from three-mesh-bvh (with _roots array) - `renderer`: THREE.WebGPURenderer - WebGPU renderer Returns: `Promise.` - Generated SDF texture #### update(geometry, bvh, renderer) Updates SDF texture with potentially modified mesh/BVH. More efficient than full regeneration if structure hasn't changed. Parameters: - `geometry`: THREE.BufferGeometry - Source geometry - `bvh`: * - BVH from three-mesh-bvh - `renderer`: THREE.WebGPURenderer - WebGPU renderer Returns: `Promise.` - Updated SDF texture #### dispose() Disposes GPU resources. --- ### SDFVolumeHelpers *Module* Collection of helper utilities for debugging and visualizing SDF volumes. **Overview** These helpers provide visual feedback for SDF generation and sampling, which is crucial for debugging: - **Bounds Helper**: Visualizes the volume extent. - **Point Cloud**: Visualizes sampled points to verify distribution and containment. - **Debug Grid**: Visualizes the SDF values (distance field) directly using a grid of spheres. **Methods:** #### createSDFBoundsHelper(sdfGenerator, color?) Creates a Box3Helper for visualizing SDF volume bounds. Parameters: - `sdfGenerator`: ComputeSDFGenerator - SDF generator instance. - `color?`: number = 0xffff00 - Helper line color. Returns: `THREE.Box3Helper` - Box helper for SDF bounds. #### updateSDFBoundsHelper(helper, sdfGenerator) Updates an existing Box3Helper to match current SDF bounds. Parameters: - `helper`: THREE.Box3Helper - Existing Box3Helper. - `sdfGenerator`: ComputeSDFGenerator - SDF generator instance. #### createSDFPointCloudHelper(sampler, options?, options.pointSize?, options.color?) Creates a point cloud visualization of sampled positions. Parameters: - `sampler`: ComputeBVHSampler - BVH sampler instance. - `options?`: Object - Configuration options. - `options.pointSize?`: number = 2 - Point size in pixels. - `options.color?`: number = 0x00ffff - Point color. Returns: `Promise.` - Points mesh for visualization. #### createSDFDebugGrid(sdfGenerator, options?, options.gridSize?, options.sphereRadius?, options.colorFn?) Creates a grid of spheres showing SDF sample locations (for debugging sampling). Parameters: - `sdfGenerator`: ComputeSDFGenerator - SDF generator instance. - `options?`: Object - Configuration options. - `options.gridSize?`: number = 8 - Number of samples per axis. - `options.sphereRadius?`: number = 0.02 - Sphere radius. - `options.colorFn?`: function - Custom color function: `(sdfValue) => THREE.Color`. Returns: `THREE.Group` - Group containing sphere instances. --- ## TSL Three.js Shading Language nodes for custom shader effects. ### animationTextureMatrix [TSL] *Function* ```js import { animationTextureMatrix } from '@three-blocks/core'; ``` Sample a 4×4 transformation matrix from a baked animation texture (VAT/OAT). Supports optional frame interpolation for smooth playback. **Algorithm** - Computes texel address from `idIndex * framesCount + frameIndex` - Fetches 4 consecutive texels representing matrix columns - When interpolation > 0, blends with next frame's matrix - Returns composed 4×4 transformation matrix **Texture Layout** - Each matrix occupies 4 consecutive texels (columns stored as RGBA) - Matrices are stored sequentially: `idIndex * framesCount + frameIndex` - Compatible with EXR textures exported by {@link AnimationBakeMixer} **Use Cases** - Vertex Animation Textures (VAT) with `vertexIndex` - Object Animation Textures (OAT) with `instanceIndex` - Custom animation sampling with manual frame control **Parameters:** - `textureNode`: Node - Baked animation texture (EXR with transformation data). - `frameIndex`: Node. - Current frame index to sample. - `framesCount`: Node. - Total number of frames in the animation. - `timeInterpolation?`: Node. = 0 - Interpolation factor [0..1] between current and next frame. - `textureOffset?`: Node. = 0 - Offset in the texture for packed animations. - `idIndex?`: Node. = vertexIndex - Index used to identify the vertex or instance (VAT uses `vertexIndex`, OAT uses `instanceIndex`). **Returns: `Node.` - Transformation matrix for the specified frame and ID.** **Example:** ```js import { AnimationBakeMixer } from '@three-blocks/core'; import { animationTextureMatrix } from '@three-blocks/core'; import { MeshBasicNodeMaterial, instanceIndex } from 'three/tsl'; // Using AnimationBakeMixer (recommended) const mixer = new AnimationBakeMixer(texture, { mode: 'object', fps: 60 }); mixer.registerMaterial(material); mixer.play(); // Manual usage with node material const material = new MeshBasicNodeMaterial(); material.positionNode = animationTextureMatrix( texture, mixer.frameIndexUniform, mixer.framesCountUniform, mixer.frameTimeUniform, mixer.textureOffsetUniform, instanceIndex // Use instanceIndex for OAT, vertexIndex for VAT ).mul(vec4(positionLocal, 1.0)).xyz; ``` See also: AnimationBakeMixer, AnimationBakeLoader, animationTexturePosition, animationTextureNormal --- ### animationTextureNormal [TSL] *Function* ```js import { animationTextureNormal } from '@three-blocks/core'; ``` Sample transformed normal from a baked animation texture. Correctly handles non-uniform scaling by normalizing the basis vectors. **Algorithm** - Retrieves transformation matrix via {@link animationTextureMatrix} - Extracts 3×3 rotation/scale basis from mat4 - Computes per-axis scale factors via dot products - Divides normal by scale factors before matrix multiplication - Returns properly transformed normal vector **Technical Details** - Handles non-uniform scaling correctly (unlike naive normal transformation) - Renormalization prevents lighting artifacts on scaled geometries - Compatible with both VAT (vertex normals) and OAT (instance normals) **Parameters:** - `textureNode`: Node - Baked animation texture. - `frameIndex`: Node. - Current frame index. - `framesCount`: Node. - Total frame count. - `timeInterpolation?`: Node. = 0 - Interpolation factor [0..1]. - `textureOffset?`: Node. = 0 - Offset in the texture for packed animations. - `idIndex?`: Node. = vertexIndex - Vertex or instance index. **Returns: `Node.` - Transformed normal.** **Example:** ```js import { AnimationBakeMixer, animationTextureNormal } from '@three-blocks/core'; import { MeshStandardNodeMaterial, instanceIndex } from 'three/tsl'; // Using AnimationBakeMixer (recommended - sets both position and normal) const mixer = new AnimationBakeMixer(texture, { mode: 'object', fps: 60 }); mixer.registerMaterial(material); // Manual node material setup const material = new MeshStandardNodeMaterial(); material.normalNode = animationTextureNormal( texture, frameUniform, framesCountUniform, interpolationUniform, textureOffsetUniform, instanceIndex // Use instanceIndex for OAT, vertexIndex for VAT ); ``` See also: AnimationBakeMixer, AnimationBakeLoader, animationTextureMatrix, animationTexturePosition --- ### animationTexturePosition [TSL] *Function* ```js import { animationTexturePosition } from '@three-blocks/core'; ``` Sample transformed position from a baked animation texture. Convenience wrapper that applies the animation matrix to `positionLocal`. **Algorithm** - Retrieves transformation matrix via {@link animationTextureMatrix} - Multiplies matrix by `vec4(positionLocal, 1.0)` for proper translation - Returns transformed XYZ position **Use Cases** - Quick setup for vertex/object animation with {@link AnimationBakeMixer} - Procedural mesh deformation with baked keyframes - Instanced object animation with unique timelines per instance **Parameters:** - `textureNode`: Node - Baked animation texture. - `frameIndex`: Node. - Current frame index. - `framesCount`: Node. - Total frame count. - `timeInterpolation?`: Node. = 0 - Interpolation factor [0..1]. - `textureOffset?`: Node. = 0 - Offset in the texture for packed animations. - `idIndex?`: Node. = vertexIndex - Vertex or instance index. **Returns: `Node.` - Transformed position.** **Example:** ```js import { AnimationBakeMixer, animationTexturePosition } from '@three-blocks/core'; import { MeshBasicNodeMaterial, vertexIndex } from 'three/tsl'; // Using AnimationBakeMixer (recommended - handles all setup) const mixer = new AnimationBakeMixer(texture, { mode: 'vertex', fps: 30 }); mixer.registerMaterial(material); // Manual node material setup const material = new MeshBasicNodeMaterial(); material.positionNode = animationTexturePosition( texture, frameUniform, framesCountUniform, interpolationUniform, textureOffsetUniform, vertexIndex // Use vertexIndex for VAT, instanceIndex for OAT ); ``` See also: AnimationBakeMixer, AnimationBakeLoader, animationTextureMatrix, animationTextureNormal --- ### biplanarTexture [TSL] *Function* ```js import { biplanarTexture } from '@three-blocks/core'; ``` Efficient biplanar texture mapping for world-space triplanar-style effects. Samples only the two most dominant axis projections and blends them, reducing texture fetches compared to full triplanar mapping. **Algorithm** - Determines the two dominant axes based on surface normal - Projects position onto corresponding planes (XY, YZ, ZX) - Samples textures with proper derivatives for mipmapping - Blends samples using weighted power curve for smooth transitions **Benefits** - 33% fewer texture samples than triplanar (2 vs 3) - Avoids visible seams on axis-aligned surfaces - Supports per-axis texture variation - Automatic mipmap derivatives via `grad()` **Parameters:** - `textureXNode`: Node - Texture sampled on YZ plane (X-axis projection). - `textureYNode?`: Node = textureXNode - Texture sampled on ZX plane (Y-axis projection). - `textureZNode?`: Node = textureXNode - Texture sampled on XY plane (Z-axis projection). - `scaleNode?`: Node. = 1 - UV coordinate scale multiplier. - `positionNode?`: Node. = positionLocal - Fragment position for UV projection. - `normalNode?`: Node. = normalLocal - Surface normal for axis weight calculation. - `hardnessNode?`: Node. = 8 - Blend sharpness (higher = sharper transitions). **Returns: `Node.` - Blended texture color (RGBA).** **Example:** ```js import { biplanarTexture } from '@three-blocks/core'; import { texture, float } from 'three/tsl'; const diffuseTex = texture(diffuseMap); const normalTex = texture(normalMap); // Same texture on all axes with 2x tiling material.colorNode = biplanarTexture(diffuseTex, null, null, float(2)); // Different textures per axis material.colorNode = biplanarTexture( texture(texX), // YZ plane texture(texY), // ZX plane texture(texZ), // XY plane float(1), // scale positionLocal, // position normalLocal, // normal float(8) // blend hardness ); ``` --- ### blueNoise [TSL] *Function* ```js import { blueNoise } from '@three-blocks/core'; ``` Hilbert R1 Blue Noise generator with normalized float output. Combines Hilbert curve spatial ordering with R1 low-discrepancy sequence to produce high-quality blue noise values in [0,1] range. **Algorithm** - Maps 2D coordinates to 1D via Hilbert curve (space-filling curve) - Applies R1 sequence (low-discrepancy) for temporal stability - Produces blue noise spectrum (minimal low-frequency content) **Use Cases** - Temporal anti-aliasing (TAA) jitter patterns - Dithering and ordered dithering - Stochastic sampling for ray tracing - Volumetric noise without banding **Parameters:** - `p`: Node. - 2D screen-space integer coordinates. - `level`: Node. - Hilbert curve recursion level (typically 5-10, higher = finer pattern). **Returns: `Node.` - Normalized blue noise value in [0..1].** **Example:** ```js import { blueNoise } from '@three-blocks/core'; import { int, screenCoordinate, uniform } from 'three/tsl'; // Temporal AA jitter const screenCoord = screenCoordinate.xy.mul(resolution); const jitter = blueNoise(screenCoord.toInt(), int(8)); const offset = jitter.mul(2.0).sub(1.0).mul(pixelSize); const jitteredUV = uv.add(offset); // Dithering threshold const threshold = blueNoise(fragCoord.toInt(), int(6)); const dithered = color.greaterThan(threshold).select(1.0, 0.0); ``` --- ### CurlNoise *Module* **What is curl noise?** Curl noise constructs a *vector field* by taking the curl of a vector potential: `flow = ∇ × A`. Because `div(∇ × A) = 0`, the resulting field is divergence-free. **Usual usage** - Advect particles: `pos += curlNoise(pos) * dt` (or integrate multiple substeps for prettier flow) - UV/normal distortion: flow-based warping without obvious compress/expand artifacts - Stylized fluid-like motion and turbulence fields **Performance notes** - Uses analytic derivatives of simplex noise ⇒ **3 noise evaluations** per sample - Avoids finite differences ⇒ typically far faster than naive curl implementations **Code example** **Methods:** #### curlNoise(position, frequency?, time?, amplitude?, normalize?) Divergence-free curl noise vector field (incompressible swirling flow). **Note** This function returns a velocity field. For “pretty curl-noise visuals”, integrate it over time (see `curlAdvect`) or use it to advect particles. Parameters: - `position`: vec3 - World/texture position. - `frequency?`: number | float = 1.0 - Spatial frequency (detail scale). - `time?`: number | float = 0.0 - Animation time (translation in noise space). - `amplitude?`: number | float = 1.0 - Output magnitude. - `normalize?`: boolean | bool = false - When true, output is unit length (direction only). Returns: `vec3` - Divergence-free flow vector. #### curlNoiseFbm(position, baseFrequency?, time?, octaves?, lacunarity?, gain?, amplitude?, normalize?) Multi-octave curl noise (FBM) for richer turbulence. Keep octaves low for realtime (2–4). Each octave is still efficient (3 derivative-noise evaluations), but it adds up quickly. Parameters: - `position`: vec3 - `baseFrequency?`: number | float = 1.0 - `time?`: number | float = 0.0 - `octaves?`: number | int = 3 - `lacunarity?`: number | float = 2.0 - `gain?`: number | float = 0.5 - `amplitude?`: number | float = 1.0 - `normalize?`: boolean | bool = false Returns: `vec3` #### curlAdvect(position, frequency?, time?, steps?, dt?, warp?, amplitude?, useFbm?, octaves?, lacunarity?, gain?) Integrate (advect) a position through a curl-noise field to reveal coherent vortices. This is the “missing piece” when demos look like generic displacement. Advection performs: `p = p + v(p) * dt` repeatedly. Even 4–8 steps can transform the look dramatically. **Design choices** - The field is kept mostly stable by using a very small internal time drift. - A cheap domain warp breaks axis-aligned / lattice artifacts (especially in instanced grids). Parameters: - `position`: vec3 - Starting position. - `frequency?`: number | float = 1.0 - Field frequency (detail scale). - `time?`: number | float = 0.0 - Advection time driver (used for subtle drift). - `steps?`: number | int = 6 - Number of substeps (4–8 typical). - `dt?`: number | float = 0.18 - Step size per substep (0.10–0.25 typical). - `warp?`: number | float = 0.75 - Domain warp strength (0 disables). - `amplitude?`: number | float = 1.0 - Scales final displacement from the original position. - `useFbm?`: boolean | bool = true - Uses FBM curl field when true, single-octave when false. - `octaves?`: number | int = 3 - FBM octaves (used if useFbm=true). - `lacunarity?`: number | float = 2.0 - FBM lacunarity. - `gain?`: number | float = 0.5 - FBM gain. Returns: `vec3` - Advected position. **Example:** ```js const positionBuffer = instancedArray(COUNT, 'vec3'); const velocityBuffer = instancedArray(COUNT, 'vec3'); const prevVelocityBuffer = instancedArray(COUNT, 'vec3'); const matrixBuffer = instancedArray(COUNT, 'mat4'); const aoBuffer = instancedArray(COUNT, 'float'); // Build lookAt matrix from direction (reusable in compute) const buildLookAtMatrix = Fn(([direction, position]) => { const up = vec3(0, 1, 0); const dir = normalize(direction); const right = normalize(cross(up, dir)); const correctedUp = normalize(cross(dir, right)); // Build full transformation matrix with rotation + translation return mat4( vec4(right, 0), vec4(correctedUp, 0), vec4(dir, 0), vec4(position, 1) ); }); // computeInit: spawn particles at center with curl noise distribution const computeInit = Fn(() => { const i = float(instanceIndex); // Spherical distribution from center const phi = i.mul(2.399963); // golden angle const theta = i.div(COUNT).mul(Math.PI); const r = i.div(COUNT).sqrt().mul(3.0); const pos = vec3( r.mul(theta.sin()).mul(phi.cos()), r.mul(theta.cos()), r.mul(theta.sin()).mul(phi.sin()) ); const initVel = vec3(0, 1, 0); positionBuffer.element(instanceIndex).assign(pos); velocityBuffer.element(instanceIndex).assign(initVel); prevVelocityBuffer.element(instanceIndex).assign(initVel); matrixBuffer.element(instanceIndex).assign(buildLookAtMatrix(initVel, pos)); aoBuffer.element(instanceIndex).assign(0.0); }); renderer.compute(computeInit().compute(COUNT)); // Uniforms const uFrequency = uniform(FREQUENCY); const uAmplitude = uniform(AMPLITUDE); const uSpeed = uniform(SPEED); const uSteps = uniform(STEPS); const uDt = uniform(DT); // computeUpdate: advect with curl noise, compute mat4 const computeUpdateFn = Fn(() => { const pos = positionBuffer.element(instanceIndex).toVar(); const vel = velocityBuffer.element(instanceIndex); const prevVel = prevVelocityBuffer.element(instanceIndex); // Store previous velocity before computing new one prevVel.assign(vel); // Oscillating scale for grow/shrink effect const scale = sin(time.mul(uSpeed)).mul(0.5).add(1.0); const dt = float(uDt).mul(scale); // Advect through curl noise field Loop(int(uSteps), () => { const v = curlNoise(pos, uFrequency, time.mul(0.05), uAmplitude, false); pos.addAssign(v.mul(dt)); }); // Store current velocity vel.assign(pos.sub(positionBuffer.element(instanceIndex))); // Smooth direction from mix of prev and current velocity const smoothDir = normalize(mix(prevVel, vel, 0.5).add(vec3(0.0001))); // Compute and store mat4 matrixBuffer.element(instanceIndex).assign(buildLookAtMatrix(smoothDir, pos)); // Fake AO based on multiple factors: // 1. Distance from center (darker at center where particles cluster) const distFromCenter = length(pos); const centerAo = float(1.0).sub(clamp(distFromCenter.div(5.0), 0.0, 1.0)); // 2. Vertical position (darker at bottom) const heightAo = clamp(pos.z.div(4.8).add(0.5), 0.0, 1.0); // 3. Velocity magnitude (slower = more clustered = darker) const speed = length(vel); const speedAo = float(1.0).sub(clamp(speed.mul(50.0), 0.0, 1.0)); // Combine AO factors const ao = clamp(centerAo.oneMinus().mul(1).add(heightAo.mul(0.3)).add(speedAo.mul(0.3)), 0.0, 1.0); aoBuffer.element(instanceIndex).assign(ao); positionBuffer.element(instanceIndex).assign(pos); }); computeUpdate = computeUpdateFn().compute(COUNT); // Material with direction-based coloring const material = new THREE.MeshPhysicalNodeMaterial({ metalness: 0.8, roughness: 0.3 }); const vel = velocityBuffer.element(instanceIndex); const prevVel = prevVelocityBuffer.element(instanceIndex); // Color based on velocity direction const smoothDir = normalize(mix(prevVel, vel, 0.5).add(vec3(0.0001))); const colX = abs(smoothDir.x); const colY = abs(smoothDir.y); const colZ = abs(smoothDir.z); const cA = color(0x00ffff); // cyan const cB = color(0xff00ff); // magenta const cC = color(0xffff00); // yellow material.colorNode = mix(mix(cA, cB, colX), cC, colY.add(colZ).mul(0.5)); // Apply fake AO from buffer material.aoNode = aoBuffer.element(instanceIndex); // Mesh with small box geometry const geometry = new THREE.BoxGeometry(0.08, 0.28, 0.08); const mesh = new THREE.InstancedMesh(geometry, material, COUNT); // Use mat4 buffer directly for instance matrices mesh.instanceMatrix = matrixBuffer.value; scene.add(mesh); ``` Demo: https://three-blocks.com/docs/demos/curlnoise/ --- ### filmHD [TSL] *Function* ```js import { filmHD } from '@three-blocks/core'; ``` Cinematic film grain with blue noise, scanlines, and temporal stability. **Features** - Temporally stable grain (no flickering) using blue noise + white noise blend - Shadow-weighted grain (more visible in darks via `grainResponseNode`) - Animated scanlines for CRT/film look - Frame interpolation for smooth temporal evolution - Artist-friendly controls for all parameters **Algorithm** - Blends blue noise (65%) + white noise (35%) for quality grain - Per-frame jitter and rotation for temporal variation - Luminance-based weighting (darks get more grain) - Optional scanline overlay with sine wave **Parameters:** - `inputNode`: Node - Source color buffer. - `options?`: FilmHDOptions = {} - Optional configuration nodes (defaults: intensity=0.82, grainScale=1.5, grainSpeed=12.0, grainResponse=0.85, grainContrast=1.25, scanlineIntensity=0.075, scanlineFrequency=900.0). **Returns: `FilmHDNode` - Film grain effect node.** **Example:** ```js import { filmHD } from '@three-blocks/core'; import { pass, uniform } from 'three/tsl'; const scenePass = pass(scene, camera); const grainEffect = filmHD(scenePass, { intensityNode: uniform(0.8), // Strong grain grainScaleNode: uniform(1.5), // Fine grain grainSpeedNode: uniform(12.0), // Moderate animation grainResponseNode: uniform(0.85), // More grain in shadows scanlineIntensityNode: uniform(0.075), // Subtle scanlines scanlineFrequencyNode: uniform(900.0) }); postProcessing.outputNode = grainEffect; ``` --- ### fresnel [TSL] *Function* ```js import { fresnel } from '@three-blocks/core'; ``` Fresnel effect TSL function. Computes a view-dependent falloff based on the angle between surface normal and view direction. **Parameters:** - `power?`: Node. = 2.2 - Exponent controlling the falloff curve - `falloff?`: Node. = 1.5 - Smoothstep range for the effect **Returns: `Node.` - Fresnel factor [0..1]** **Example:** ```js import * as THREE from 'three/webgpu'; import { float, color } from 'three/tsl'; import { fresnel } from '@three-blocks/core'; const material = new THREE.MeshStandardNodeMaterial({color: 0x111111}); const rim = fresnel(float(3.5), float(0.9)); const rimCol = color('#F5B700'); material.emissiveNode = rimCol.mul(rim); ``` Demo: https://three-blocks.com/docs/demos/fresnel/ --- ### instanceCulling [TSL] *Function* ```js import { instanceCulling } from '@three-blocks/core'; ``` TSL function for creating an instance culling node that applies GPU-culled instance transforms to vertex positions and normals. This is the main entry point for enabling GPU culling in your material. The node automatically handles the indirection from draw index to original instance ID. **Example: Basic GPU frustum culling** **Parameters:** - `culler`: ComputeInstanceCulling - The GPU instance culler. **Returns: `InstanceCullingNode` - Node that applies culled instance transforms.** **Example:** ```js import { ComputeInstanceCulling, instanceCulling } from '@three-blocks/core'; import * as THREE from 'three/webgpu'; import { Fn, positionLocal } from 'three/tsl'; // Create instanced mesh with 100,000 instances const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshStandardNodeMaterial(); const mesh = new THREE.InstancedMesh(geometry, material, 100000); // Scatter instances randomly const matrix = new THREE.Matrix4(); for (let i = 0; i < 100000; i++) { matrix.makeTranslation( (Math.random() - 0.5) * 1000, (Math.random() - 0.5) * 100, (Math.random() - 0.5) * 1000 ); mesh.setMatrixAt(i, matrix); } // Create GPU culler const culler = new ComputeInstanceCulling(mesh, renderer); // Apply culling to the vertex stage (position + normal) material.positionNode = Fn(() => { instanceCulling(culler).toStack(); return positionLocal; })(); scene.add(mesh); ``` --- ### instanceCullingIndex [TSL] *Function* ```js import { instanceCullingIndex } from '@three-blocks/core'; ``` TSL function that returns the culled instance ID for the current draw call. Unlike `instanceIndex` which gives the draw index (0 to survivorCount), this returns the actual original instance ID from the culled set. Use this in custom material nodes when you need to access per-instance data from the original instance arrays, such as: - Per-instance random values (via hash) - Animation offsets - Custom attribute lookups **Example: Per-instance rotation with stable IDs** **Example: Custom per-instance data lookup** **Parameters:** - `culler`: ComputeInstanceCulling - The GPU instance culler. **Returns: `Node.` - The culled instance ID node.** **Example:** ```js import { ComputeInstanceCulling, instanceCulling, instanceCullingIndex } from '@three-blocks/core'; import { Fn, hash, time, rotate, positionLocal, normalLocal, transformNormalToView } from 'three/tsl'; import * as THREE from 'three/webgpu'; const mesh = new THREE.InstancedMesh(geometry, material, 10000); const culler = new ComputeInstanceCulling(mesh, renderer); // Get the original instance ID (stable across culling changes) const originalId = instanceCullingIndex(culler); // Create per-instance rotation that persists when instances are culled/unculled const randomOffset = hash(originalId).mul(Math.PI * 2); const angle = time.mul(0.5).add(randomOffset); // Apply rotation before instance transform, then cull material.positionNode = Fn(() => { positionLocal.assign(rotate(positionLocal, angle)); normalLocal.assign(rotate(normalLocal, angle)); instanceCulling(culler).toStack(); return positionLocal; })(); material.normalNode = transformNormalToView(normalLocal).normalize(); ``` --- ### kuwahara [TSL] *Function* ```js import { kuwahara } from '@three-blocks/core'; ``` Generalized anisotropic Kuwahara painterly filter with multi-sector sampling. **Algorithm** - Evaluates angular sectors around each pixel - Structure tensor drives dominant orientation, coherence, and anisotropic scaling - Selects sector with minimum variance for painterly effect - More sectors/angle steps = smoother results but slower performance - Using the structure tensor aligns the brush with image edges (high coherence) and reduces smearing across strong gradients; disabling it falls back to isotropic blurring. **Performance Tuning** - Increase `stepSize` for sparse sampling (large radius with similar sample count) - Reduce `sectors` or `angleSteps` for speed - Enable `useSimpleWeight` for flatter look with fewer math ops **Debug Output** - R: Selected sector id (0..1) - G: Edge coherence from structure tensor - B: Normalized variance (higher = noisier) **Parameters:** - `originalTextureNode`: Node - Source texture to filter. - `tensorTextureNode`: Node - Structure tensor texture (from `structureTensor()`). - `options?`: KuwaharaOptions = {} - Optional parameters. **Returns: `Node.` - Filtered painterly color.** **Example:** ```js import { kuwahara, structureTensor } from '@three-blocks/core'; import { pass } from 'three/tsl'; const scenePass = pass(scene, camera); const tensorPass = structureTensor(scenePass); const painterly = kuwahara(scenePass, tensorPass, { radius: 6, sectors: 8, angleSteps: 3, stepSize: 1 }); ``` See also: structureTensor --- ### parallaxOcclusion [TSL] *Function* ```js import { parallaxOcclusion } from '@three-blocks/core'; ``` Parallax Occlusion Mapping (POM) node for high-quality surface displacement. **Algorithm** - Ray-marches through displacement texture along view direction - Adaptive sampling: more samples at grazing angles - Binary search refinement for sub-texel precision - Blue noise jittering to eliminate banding artifacts - Edge fading prevents UV stretching at borders - Computes depth offset for correct depth buffer integration **Benefits** - Creates deep surface detail without tessellation - Accurate self-shadowing and occlusion - Smooth interpolation via binary refinement - Perceptually uniform with blue noise sampling - Automatic LOD via adaptive sample count **Parameters:** - `v_uv`: Node. - UV coordinates node. - `dispTex`: Node. - Displacement/height map texture (white=raised, black=recessed). - `parallaxScale?`: Node. = 0.05 - Scale factor controlling depth of parallax effect. - `blueNoiseTex?`: Node. | null = null - Optional blue noise texture for jitter (reduces banding). - `minSamples?`: Node. = 24 - Minimum ray-march samples (used at perpendicular view). - `maxSamples?`: Node. = 96 - Maximum ray-march samples (used at grazing angles). - `refineSteps?`: Node. = 3 - Binary search refinement iterations for precision. - `edgeMargin?`: Node. = 0.02 - UV margin for edge fade (prevents stretching). **Returns: `Node.` - Displaced UV coordinates (xy) and depth pixel offset (z).** **Example:** ```js import { parallaxOcclusion } from '@three-blocks/core'; import { texture, uv } from 'three/tsl'; const material = new MeshStandardNodeMaterial(); const heightMap = texture(heightTexture); const blueNoise = texture(blueNoiseTexture); // Apply POM to displace UVs const result = parallaxOcclusion( uv(), heightMap, 0.08, // parallax depth scale blueNoise, // optional noise for quality 32, // min samples 128, // max samples 4 // refinement steps ); // Use displaced UVs for texture sampling material.colorNode = texture(diffuseTex, result.xy); material.normalNode = normalMap(texture(normalTex, result.xy)); ``` See also: {@link https://web.archive.org/web/20150419215321/http://sunandblackcat.com/tipFullView.php?l=eng&topicid=28}, {@link https://learnopengl.com/Advanced-Lighting/Parallax-Mapping} --- ### shadowMapFastNoise [TSL] *Function* ```js import { shadowMapFastNoise } from '@three-blocks/core'; ``` Fast shadow map filtering using spatial noise and Poisson disk sampling. **Algorithm** - Generates 2 Poisson disk samples per fragment - Uses world-position-based noise to rotate sample pattern - Reduces banding artifacts with minimal performance cost - Provides softer shadow edges than standard PCF **Usage** **Performance** - Only 2 shadow map samples per fragment (vs 4-16 for standard PCF) - Noise rotation provides perceptual quality of more samples - Suitable for real-time applications **Parameters:** - `params`: Object - Shadow sampling parameters. - `params.depthTexture`: Node. - Shadow depth texture. - `params.shadowCoord`: Node. - Projected shadow coordinates. - `params.shadow`: Object - Shadow reference object containing `mapSize`. **Returns: `Node.` - Shadow factor in [0..1] (0=fully shadowed, 1=fully lit).** **Example:** ```js import { shadowMapFastNoise } from '@three-blocks/core'; const dirLight = new THREE.DirectionalLight(0xffffff, 1); dirLight.castShadow = true; dirLight.shadow.filterNode = shadowMapFastNoise; ``` --- ### smoke [TSL] *Function* ```js import { smoke } from '@three-blocks/core'; ``` Smoke Simulation (TSL) – a 2D velocity–pressure solver that runs in screen space. Turns a fullscreen pass into a stylized, real-time smoke/ink flow using WebGPU compute through Three.js TSL. Uses StorageTexture resources and takes advantage of Tier2 read–write access whenever the adapter exposes it. It owns and ping-pongs its internal textures (velocity, pressure, density, curl, divergence) and updates itself every frame when used in a PostProcessing chain. **Features** - Semi-Lagrangian advection for velocity and density - Vorticity confinement (curl) and divergence-free projection (pressure solve) - Configurable dissipation, pressure iterations and curl strength - Optional pointer-driven splats via a `vec2` uniform (no global dispatcher) - Drop-in for post-processing: `postProcessing.outputNode = smoke(scenePass, { ... })` **Example: Basic postprocessing setup** **Example: Interactive pointer-driven splats** **Parameters:** - `node?`: Node. - Scene/texture input to chain from (e.g. result of `pass(scene, camera)`). - `options?`: SmokeOptions - Configuration options. **Returns: `Node` - Color node sampling the current dye texture of the fluid simulation.** **Example:** ```js import * as THREE from 'three/webgpu'; import { pass } from 'three/tsl'; import { smoke } from '@three-blocks/core'; // Create renderer, scene, camera const renderer = new THREE.WebGPURenderer(); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight); // Setup postprocessing with smoke effect const postProcessing = new THREE.PostProcessing(renderer); const scenePass = pass(scene, camera); // Apply smoke simulation as post effect const fluidNode = smoke(scenePass, { simRes: 128, dyeRes: 512, curlStrength: 30, densityDissipation: 0.98 }); postProcessing.outputNode = fluidNode; // Render loop function animate() { requestAnimationFrame(animate); postProcessing.render(); } ``` Demo: https://three-blocks.com/docs/demos/fluid/ --- ### structureTensor [TSL] *Function* ```js import { structureTensor } from '@three-blocks/core'; ``` Compute structure tensor for edge-aware image filtering. **Algorithm** - Computes image gradients using Sobel operator (3×3 kernel) - Calculates outer product of gradient: `J = [∇I ⊗ ∇I]` - Outputs (Jxx, Jyy, Jxy) in RGB channels for downstream use **Use Cases** - Input for Kuwahara filter (`kuwahara`) - Edge detection and orientation analysis - Anisotropic filtering and diffusion **Pipeline** Structure tensor is typically used as a first pass before applying edge-aware filters like Kuwahara. **Parameters:** - `textureNode`: Node - Source texture for gradient analysis. **Returns: `Node.` - Structure tensor (Jxx, Jyy, Jxy, 1.0) in RGBA.** **Example:** ```js import { structureTensor, kuwahara } from '@three-blocks/core'; import { pass } from 'three/tsl'; // First pass: compute structure tensor const scenePass = pass(scene, camera); const tensorPass = structureTensor(scenePass); // Second pass: use tensor for edge-aware filtering const filtered = kuwahara(scenePass, tensorPass, { radius: 5 }); postProcessing.outputNode = filtered; ``` See also: kuwahara --- ### traaHD [TSL] *Function* ```js import { traaHD } from '@three-blocks/core'; ``` Temporal Reprojection Anti-Aliasing for high-quality edge smoothing with motion. **Algorithm** - Jitters camera each frame (32-sample Halton sequence) - Reprojects previous frame using velocity buffer - Clamps history to 3×3 neighborhood min/max (reduces ghosting) - Blends current frame with clamped history (adaptive blending) - Depth-based rejection for disoccluded pixels **Features** - Excellent edge quality (rivals MSAA/SSAA) - Temporal stability (no flickering) - Motion-aware blending (reduces trails) - Disocclusion handling (depth-based rejection) - Optional super-resolution mode (>1x output resolution) **Requirements** - Velocity buffer (built-in with Three.js WebGPU) - Depth buffer - Post-processing pipeline **Parameters:** - `beautyNode`: Node - Scene color texture (from pass). - `depthNode`: Node - Scene depth texture. - `velocityNode`: Node - Scene velocity texture (motion vectors). - `camera`: THREE.Camera - Active camera (receives jitter). **Returns: `TRAANodeHD` - TRAA effect node with `.superResolutionScale` property.** **Example:** ```js import { traaHD } from '@three-blocks/core'; import { velocity, pass } from 'three/tsl'; // Setup post-processing with TRAA const scenePass = pass(scene, camera); const scenePassVelocity = scenePass.getTextureNode('velocity'); const scenePassDepth = scenePass.getTextureNode('depth'); const traa = traaHD( scenePass, scenePassDepth, scenePassVelocity, camera ); // Optional: enable super-resolution (experimental) traa.superResolutionScale = 1.5; // 1.5x output resolution postProcessing.outputNode = traa; ``` See also: {@link https://alextardif.com/TAA.html}, {@link https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail/} --- ## Geometries Custom geometry classes for specialized rendering. ### BirdGeometry *Class* ```js import { BirdGeometry } from '@three-blocks/core'; ``` Low-poly bird geometry for instanced flocking simulations. Matches the bird model from three.js WebGPU compute birds example. **Structure** - 3 triangles: body, left wing, right wing - Total 9 vertices (3 per triangle) **Vertex Layout** - Vertices 0-2: Body (center tail to head) - Vertices 3-5: Left wing - Vertices 6-8: Right wing Extends: THREE.BufferGeometry **Example:** ```js import { BirdGeometry } from '@three-blocks/core'; import { InstancedMesh, MeshStandardNodeMaterial } from 'three/webgpu'; import { If, vertexIndex, sin } from 'three/tsl'; const geometry = new BirdGeometry(); const material = new MeshStandardNodeMaterial({ color: 0x0088ff }); // Animate wing flapping in vertex shader material.positionNode = Fn(() => { const pos = positionLocal.toVar(); // Wing vertices (4,7) flap with phase If(vertexIndex.equal(4).or(vertexIndex.equal(7)), () => { pos.y = sin(phaseUniform).mul(5.0); }); return pos; })(); const birds = new InstancedMesh(geometry, material, 1000); scene.add(birds); ``` See also: Boids --- ### BoxNoFaceGeometry *Class* ```js import { BoxNoFaceGeometry } from '@three-blocks/core'; ``` Wireframe box geometry with explicit edge pairs for line rendering. Renders only the 12 edges of a box without any faces. **Architecture** - 24 vertices (2 per edge) for clean LINE_SEGMENTS rendering - No indices - direct vertex pairs - Pre-computed bounds for efficient culling **Use Cases** - Bounding box visualization - Debug wireframe overlays - Minimalist architectural elements - Selection indicators Extends: THREE.BufferGeometry **Example:** ```js import { BoxNoFaceGeometry } from '@three-blocks/core'; import { LineSegments, LineBasicMaterial } from 'three/webgpu'; // Visualize a bounding box const boxGeo = new BoxNoFaceGeometry(10, 5, 8); const boxMat = new LineBasicMaterial({ color: 0x00ff00 }); const wireframe = new LineSegments(boxGeo, boxMat); scene.add(wireframe); // Animate with transform wireframe.position.set(0, 2.5, 0); wireframe.rotation.y = Math.PI / 4; ``` --- ### TriangleGeometry *Class* ```js import { TriangleGeometry } from '@three-blocks/core'; ``` Single large triangle covering NDC space for fullscreen post-processing. **Purpose** - Efficient fullscreen pass geometry (1 triangle vs 2 for quad) - Covers entire screen in clip space: (-1,-1) to (3,-1) to (-1,3) - Eliminates center seam artifact present in traditional screen quads **Usage** Extends: THREE.BufferGeometry **Example:** ```js import { TriangleGeometry } from '@three-blocks/core'; import { MeshBasicNodeMaterial } from 'three/webgpu'; import { pass, screenUV } from 'three/tsl'; // Fullscreen post-processing const scenePass = pass(scene, camera); const postGeo = new TriangleGeometry(); const postMat = new MeshBasicNodeMaterial(); postMat.colorNode = scenePass.mul(0.5); // Darken scene const postMesh = new Mesh(postGeo, postMat); postMesh.frustumCulled = false; ``` --- ## Materials Node-based materials with advanced features. ### MeshTransmissionNodeMaterial *Class* ```js import { MeshTransmissionNodeMaterial } from '@three-blocks/core'; ``` Constructs a new transmission node material. Use `setValues(parameters)` to override properties; see property docs below. Extends: THREE.MeshPhysicalNodeMaterial **Constructor Parameters:** - `parameters?`: Object = {} - Optional material parameters. - `parameters.chromaticAberration?`: number = 0.4 - Strength of per-channel IOR dispersion. - `parameters.anisotropicBlur?`: number = 0.38 - Minimum smear in the thickness direction for rough surfaces. - `parameters.time?`: number = 0 - Time uniform used by temporal distortion. - `parameters.distortion?`: number = 0 - Distortion amount applied to the refraction normal via noise; scales amplitude, 0 disables. - `parameters.distortionScale?`: number = 0 - Distortion noise scale in world units. - `parameters.temporalDistortion?`: number = 0 - Strength of time-based distortion animation. - `parameters.ditherStrength?`: number = 0.5 - Blue-noise jitter strength for sampling stability. - `parameters.ditherScale?`: number = 128 - Blue-noise tiling frequency (higher values tile smaller). - `parameters.color?`: THREE.Color | string | number = 0xffffff - Base albedo color. - `parameters.viewportBuffer?`: * = null - Optional TSL sampler/texture node used to sample the scene; defaults to renderer viewport mip texture. **Properties:** - `isMeshTransmissionNodeMaterial`: boolean (default: true) - This flag can be used for type testing. - `forceSinglePass`: boolean (default: true) - Render in a single pass. Yield better visual and performance results when sampling the backdrop texture. - `chromaticAberration`: number (default: 0.4) - Strength of per-channel IOR dispersion. Higher values increase color fringing. - `transmissionMap`: THREE.Texture | null (default: null) - Optional map kept for API parity with other materials. Not sampled by this material. - `attenuationDistance`: number (default: Infinity) - Beer–Lambert attenuation distance; set to `Infinity` to disable attenuation. - `anisotropicBlur`: number (default: 0.38) - Minimum smear in the thickness direction for rough surfaces. - `time`: number (default: 0) - Time value used for temporal distortion animation. - `distortion`: number (default: 0) - Distortion amount applied to the perturbed refraction normal via noise. Values scale the noise amplitude; 0 disables the effect. - `distortionScale`: number (default: 0) - Distortion noise scale in world units. - `temporalDistortion`: number (default: 0) - Strength of time-based distortion animation. - `ditherStrength`: number (default: 0.5) - Blue-noise jitter strength for sampling stability. - `ditherScale`: number (default: 128) - Blue-noise tiling frequency (higher values tile smaller). - `viewportBuffer`: * (default: null) - Optional TSL sampler/texture node used to sample the scene/backdrop. If `null`, the renderer's viewport mip texture is used. - `backdropNode`: * - Backdrop refraction node injected into the physical transmission pipeline. **Methods:** #### attachGUI(gui) Attaches a debug UI for tuning transmission parameters. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `gui`: * - GUI instance (e.g., lil-gui/dat.gui or renderer.inspector.createParameters()). Returns: `void` #### disposeGUI() Destroys and clears the debug folder if attached. Returns: `this` #### dispose() Disposes material resources and detaches any debug UI. **Example:** ```js import { MeshTransmissionNodeMaterial } from '@three-blocks/core'; import * as THREE from 'three/webgpu'; // Create transmission material const mat = new MeshTransmissionNodeMaterial({ color: new THREE.Color('#ffffff'), roughness: 0.2, thickness: 0.5, ior: 1.5, chromaticAberration: 0.4, anisotropicBlur: 0.1, distortion: 0.0, attenuationDistance: 0.5, attenuationColor: new THREE.Color('#ffffff') }); const geometry = new THREE.TorusKnotGeometry(1, 0.4, 128, 32); const mesh = new THREE.Mesh(geometry, mat); scene.add(mesh); ``` Demo: https://three-blocks.com/docs/demos/mesh-transmission/ --- ## Meshes Specialized mesh classes with GPU optimizations. ### GridPristine *Class* ```js import { GridPristine } from '@three-blocks/core'; ``` Infinite grid mesh with anti-aliased lines and dual-layer composition. **Features** - Two independent grid layers (A and B) with configurable cell size, width, color, and opacity - Analytic derivative-based rendering keeps line thickness stable across all zoom levels - World-space positioning using positionWorld (grid doesn't move with camera) - Smooth anti-aliasing at any distance or angle - Full TSL/NodeMaterial implementation for WebGPU compatibility **Rendering** - Uses fragment shader derivatives (dFdx/dFdy) to compute pixel-space line width - Lines automatically adapt thickness based on screen-space density - Composite blend: background → layer B → layer A - No texture lookups or geometry subdivision required **Use Cases** - Scene reference grids with major/minor divisions - CAD/3D editor floor grids - Architectural visualization ground planes - Debug visualizations requiring stable grid lines Extends: THREE.Mesh **Methods:** #### attachGUI(gui) Attach a GUI folder with common controls. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `gui`: * - GUI instance (e.g., lil-gui/dat.gui or renderer.inspector.createParameters()). Returns: `GridPristine` #### disposeGUI() Detach and destroy the GUI folder. Returns: `GridPristine` #### dispose() Dispose GPU resources and detach GUI. Returns: `void` **Example:** ```js import { GridPristine } from '@three-blocks/core'; import * as THREE from 'three/webgpu'; // Basic grid with default settings const grid = new GridPristine(); scene.add(grid); ``` Demo: https://three-blocks.com/docs/demos/grid-pristine/ --- ## Animation GPU-accelerated animation systems using baked texture data. ### AnimationBakeLoader *Class* ```js import { AnimationBakeLoader } from '@three-blocks/core'; ``` Loader for baked animation textures (VAT/OAT). Given a base URL (or a URL ending with .exr or .json), it loads the EXR texture and its paired JSON metadata, then constructs and returns an initialized AnimationBakeMixer. URL resolution rules: - If the URL ends with .exr, the JSON URL is derived by replacing with .json. - If the URL ends with .json, the EXR URL is derived by replacing with .exr. - Otherwise, ".exr" and ".json" are used. Extends: Loader **Methods:** #### load(url, onLoad?, onProgress?, onError?) Load an EXR and its paired JSON metadata and return an initialized AnimationBakeMixer. Parameters: - `url`: string - Base URL or .exr/.json URL. - `onLoad?`: function - Callback fired after the EXR and metadata are loaded. - `onProgress?`: function - Progress callback invoked with loader events. - `onError?`: function - Error callback invoked when load fails. #### loadAsync(url, onProgress?) Promise-based variant of load. Parameters: - `url`: string - `onProgress?`: function - Optional progress callback invoked with loader events. Returns: `Promise.` - Promise that resolves with the initialized mixer. **Example:** ```js import { AnimationBakeLoader } from '@three-blocks/core'; const loader = new AnimationBakeLoader(); loader.load('/files/baked_vat', (mixers, meta) => { const clips = Array.isArray(mixers) ? mixers : [ mixers ]; const metaList = Array.isArray(meta) ? meta : [ meta ]; clips.forEach((mixer, index) => { const info = metaList[ index ] ?? {}; const instanceCount = info.instances?.length ?? info.idCount ?? 0; console.log('Instances in clip', index, instanceCount); mixer.play(); }); }); // or use the async variant const clipMixers = await loader.loadAsync('/files/baked_vat'); const mixers = Array.isArray(clipMixers) ? clipMixers : [ clipMixers ]; mixers.forEach((mixer) => mixer.setConfig({ loop: false }).play()); ``` See also: AnimationBakeMixer --- ### AnimationBakeMixer *Class* ```js import { AnimationBakeMixer } from '@three-blocks/core'; ``` Playback controller for baked animation textures (VAT/OAT). - Manages frame uniforms: `frameIndexUniform`, `framesCountUniform`, `frameTimeUniform` - Supports modes: 'vertex' (VAT) and 'object' (OAT) - Provides playback controls and frame interpolation **Properties:** - `duration`: number - Total animation length in seconds. Filled from metadata when available, otherwise derived from frame count and frames per second. - `time`: number - Current playback time in seconds within the animation clip. - `metadata`: AnimationBakeMetadata | undefined - Last metadata object passed to {@link AnimationBakeMixer#init}. **Methods:** #### init(meta?) Initialize from metadata JSON (single entry point for configuration). Expected VAT metadata keys: `mode='vertex'`, `framesOut`, `vertexCount`, `width`, `height`, etc. Expected OAT metadata keys: `mode='object'`, `framesOut`, `idCount`, `width`, `height`, etc. Parameters: - `meta?`: AnimationBakeMetadata - Metadata exported by the baking pipeline. Returns: `this` #### setConfig(config?) Update runtime playback configuration values. Parameters: - `config?`: AnimationBakeConfig - Partial configuration overrides. #### getDuration() Returns the effective duration of the baked animation in seconds. Falls back to frames / fps when metadata does not provide a duration. Returns: `number` #### getTime() Returns the current playback time in seconds. Returns: `number` #### setTime(seconds, options?, options.wrap?) Set playback time in seconds. When `wrap` is true and looping is enabled the provided value wraps into the current loop duration, otherwise it is clamped. Parameters: - `seconds`: number - Target playback time. - `options?`: Object - `options.wrap?`: boolean = true Returns: `this` #### setInterpolationOverride(mode) Set interpolation override. Parameters: - `mode`: 'auto' | 'force0' | 'force1' #### registerMaterial(material, mode?) Register a NodeMaterial to use the baked animation. Pass `opts.mode` to override the mixer's mode per-material. Note: You can also wire manually using Parameters: - `material`: THREE.NodeMaterial - NodeMaterial instance to bind uniforms to. - `mode?`: 'vertex' | 'object' | Object - Force a specific sampling mode for this material. When an object is passed, it can contain `{ mode, geometry, positionAttribute, normalAttribute, vertexCount, instanceCount, frameIndexNode, framesCountNode, textureOffsetNode, idNode, positionInputNode, normalInputNode }` Returns: `THREE.NodeMaterial` #### play() Begin playback of the baked animation using the current configuration. #### pause() Pause playback while preserving the current frame and interpolation value. #### stop() Stop playback and seek back to the first frame. #### seekFrame(frameIndex) Jump to a specific baked frame index. Parameters: - `frameIndex`: number - Zero-based frame index to seek to. #### seekSeconds(seconds) Seek to a specific playback time in seconds. Parameters: - `seconds`: number - Target playback time in seconds. #### update(deltaSeconds) Advance playback by the provided delta time. Parameters: - `deltaSeconds`: number - Elapsed time in seconds since the previous update. #### attachGUI(gui, opts?, opts.folderName?) Attach a standard playback GUI to this mixer. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `gui`: object - Instance from `lil-gui` or `renderer.inspector.createParameters()`. - `opts?`: Object - Additional GUI options. - `opts.folderName?`: string - Custom name for the created folder. Returns: `this` #### inferFrames(texture, options?, options.vertexCount?, options.objectCount?, options.mode?) Infer frame count from a baked animation texture. Parameters: - `texture`: THREE.DataTexture - EXR texture to inspect. - `options?`: Object - Additional hints for the inference. - `options.vertexCount?`: number - Vertex count for VAT data. - `options.objectCount?`: number - Instance/object count for OAT data. - `options.mode?`: 'vertex' | 'object' = 'vertex' - Specifies how to interpret the texture layout. Returns: `number` - Inferred number of frames. **Example:** ```js import { AnimationBakeMixer } from '@three-blocks/core'; const mixer = new AnimationBakeMixer(texture, { fps: 60, loop: true }); mixer.registerMaterial(material); function render(deltaSeconds) { mixer.update(deltaSeconds); renderer.render(scene, camera); } ``` See also: AnimationBakeLoader --- ## Text High-performance text rendering with MSDF fonts. ### batchedText [TSL] *Function* ```js import { batchedText } from '@three-blocks/core'; ``` Creates a TSL Fn that computes BatchedText vertex transforms. This function: - Reads per-member parameters from storage buffers - Uses shared textGlyphTransform for glyph geometry (code reuse with TextNode) - Applies per-member matrix transformation - Handles billboarding and curvature This node should be used in `material.setupVertex` to handle batched text geometry transformation. **Parameters:** - `batchedText`: BatchedText - The BatchedText instance - `uBillboard`: UniformNode - Billboard uniform node **Returns: `Fn.` - A callable TSL function that returns the transformed position** --- ### BatchedText *Class* ```js import { BatchedText } from '@three-blocks/core'; ``` WebGL Support has been temporarily disabled. High-performance batched rendering for multiple `Text` instances with GPU-accelerated culling, sorting, and instancing. Renders thousands of text labels in a single draw call with automatic transparency sorting for correct z-ordering. Why BatchedText is Fast **Single Draw Call Architecture** - Renders all text instances in ONE draw call vs N draw calls - Eliminates CPU overhead from state changes and draw call submission - Uses GPU instancing with `StorageBufferAttribute` for transforms and styles - Shared SDF atlas texture across all text members **GPU Compute Pipeline (WebGPU)** - Frustum culling runs entirely on GPU via compute shaders - LOD-based glyph sampling reduces overdraw for distant text - Automatic back-to-front sorting for transparent SDF text in the GPU via bitonic sorting in compute shaders - Indirect draw commands updated on GPU without CPU readback - Dynamic workgroup dispatch via `computeIndirect` for efficient scaling **Transparency Z-Ordering** - Sorted glyph packing ensures correct depth ordering for SDF anti-aliasing - Per-member prefix sum computes glyph offsets in sorted order - Glyphs scattered to packed buffer preserving back-to-front order - Eliminates z-fighting between overlapping transparent text **Dynamic Updates with Pre-allocation** - Set `maxTextCount` and `maxGlyphCount` to pre-allocate buffers for dynamic `addText()` - Without pre-allocation, buffers are sized to initial content (no dynamic growth) - `computeIndirect` dynamically adjusts GPU workgroup counts - Uniform-based count tracking avoids shader recompilation within capacity Features - **Batched Rendering**: Single draw call for unlimited text instances - **GPU Frustum Culling**: Skip rendering off-screen text (WebGPU only) - **LOD Sampling**: Reduce glyph count for distant text - **Billboarding**: Camera-facing text in world space - **Per-Instance Styling**: Color, outline, opacity per text member - **Dynamic Updates**: Add/remove/modify text at runtime via `sync()` - **Static Mode**: `batch.staticMode = true`. Lock layout after initial pack for maximum performance - **Transparency Sorting**: Correct z-order for SDF anti-aliased edges - **TSL Hooks**: Custom `positionNode`, `opacityNode`, `colorNode` still work Usage Performance Tips - Use `static: true` if text content never changes after initial setup - Enable `perObjectFrustumCulled` for large scenes with many off-screen labels - Tune `_cullOptions.maxDistance` to cull distant text early - Use `_cullOptions.useLod` to reduce glyph count for far text - Call `sync()` only when text content changes, not every frame WebGL Limitations - GPU frustum culling requires WebGPU (compute shaders not available in WebGL) - `perObjectFrustumCulled` automatically disabled on WebGL backend - All other features (batching, billboarding, styling) work in WebGL Extends: Text **Properties:** - `count`: any - Get the number of batched text members. - `count`: any - Ignored. `count` is read-only. - `isWebGL`: boolean | null - Returns true if running on WebGL backend (no compute shader support). Returns null if backend hasn't been detected yet. - `isCullingActive`: boolean - Returns true if GPU culling is active (WebGPU only, and culling requested). - `perTextBoundingBox`: boolean - Get or set per-text bounding box mode. When true, each text instance gets its own bounding sphere for more accurate culling. When false (default), uses the maximum bounding sphere of all instances for faster culling. **Methods:** #### add(objs) Add objects to batch. `Text` instances become batched members. Non-Text objects are added to scene graph normally. Parameters: - `objs`: THREE.Object3D - Objects to add. Returns: `this` #### remove(objs) Remove objects from batch. Parameters: - `objs`: THREE.Object3D - Objects to remove. Returns: `this` #### addText(text) Register a Text instance as a batched member. Returns an instance ID that can be used with setMatrixAt, setColorAt, etc. If `maxTextCount` was specified in constructor options, this will return -1 and log a warning when the limit is exceeded. Parameters: - `text`: Text - Text instance to batch. Returns: `number` - The instance ID for this text, or -1 if capacity exceeded or already added with same index. #### removeText(text) Unregister a Text instance from batch. Parameters: - `text`: Text - Text instance to remove. #### getTextAt(instanceId) Get the Text instance at the given instance ID. Parameters: - `instanceId`: number - The instance ID. Returns: `Text | null` - The Text instance, or null if not found. #### setMatrixAt(instanceId, matrix) Sets the given local transformation matrix to the defined text instance. This updates the text's matrix and marks it for update. Parameters: - `instanceId`: number - The instance ID of the text to set the matrix of. - `matrix`: THREE.Matrix4 - A 4x4 matrix representing the local transformation. Returns: `BatchedText` - A reference to this batched text. #### getMatrixAt(instanceId, matrix) Returns the local transformation matrix of the defined text instance. Parameters: - `instanceId`: number - The instance ID of the text to get the matrix of. - `matrix`: THREE.Matrix4 - The target object that is used to store the result. Returns: `THREE.Matrix4` - The text instance's local transformation matrix. #### setColorAt(instanceId, color) Sets the given color to the defined text instance. Parameters: - `instanceId`: number - The instance ID of the text to set the color of. - `color`: THREE.Color | number | string - The color to set the instance to. Returns: `BatchedText` - A reference to this batched text. #### getColorAt(instanceId, color) Returns the color of the defined text instance. Parameters: - `instanceId`: number - The instance ID of the text to get the color of. - `color`: THREE.Color - The target object that is used to store the result. Returns: `THREE.Color` - The text instance's color. #### getGlyphAt(instanceId, target?) Returns glyph data for the given text instance without triggering a full sync. Only the first glyph range for the member is returned; multi-glyph members are supported via glyphOffset/count. Parameters: - `instanceId`: number - `target?`: object - Optional target object to populate. Returns: `Object | null` #### setGlyphAt(instanceId, glyph) Quickly update glyph data (atlas index, bounds, letter index) for a specific text instance without triggering a full text sync/layout. Useful for single-glyph members such as counters. Parameters: - `instanceId`: number - `glyph`: Object Returns: `this` #### updateMatrixWorld(force?) Update world matrices and recompute bounds. Parameters: - `force?`: boolean - Force update even if matrices haven't changed. #### updateBounds() Recompute bounding volumes from all member bounds. #### onBeforeRender() Internal render hook. Ensures children are synced, updates/creates the GPU culler, refreshes visibility buffers and material flags, and lazily prepares storage buffers. #### onAfterRender() Internal render hook. Restore material-side settings after render. #### sync(callback?, renderer?) Synchronize all member `Text` instances. Triggers repacking of instance attributes when any member has changed, and rebuilds glyph data arrays. Parameters: - `callback?`: function - `renderer?`: THREE.WebGPURenderer #### getTextBoundingSphereAt(instanceId) Get the bounding sphere for a specific text instance (local space). Parameters: - `instanceId`: number - The instance ID of the text. Returns: `Object | null` #### updateMemberMatrixWorld(text) Update a single member's instance matrix in the storage buffer using its current world matrix. Does not recompute style parameters. Parameters: - `text`: Text #### updateCullingAndPacking(renderer, camera) Update culling (camera uniforms and per-member ref positions) and run glyph packing compute passes. This is called automatically in `onBeforeRender`, so manual calls are typically not needed. Parameters: - `renderer`: THREE.WebGPURenderer - `camera`: THREE.Camera #### attachGUI(folder) Attach a simple GUI folder with controls for culling and LOD settings. Compatible with lil-gui, dat.gui, and Three.js Inspector. Note: Culling options are only shown on WebGPU backend. Parameters: - `folder`: object - GUI instance (e.g., lil-gui/dat.gui or renderer.inspector.createParameters()). #### disposeGUI() Destroy the attached GUI and clear reference. #### dispose() Dispose resources associated with this helper and detach debug UI. **Example:** ```js import { BatchedText, Text } from '@three-blocks/core'; // maxTextCount= 5000 -Pre-allocate for up to 5000 text instances // maxGlyphCount= 100000 - Pre-allocate for ~100k total glyphs (~20 per text) // Create batched text container with pre-allocation for dynamic updates const batch = new BatchedText(5000, 100000, material); batch.billboarding = true batch.staticMode = true // Add initial text instances for (let i = 0; i < 1000; i++) { const text = new Text(); text.text = `Label ${i}`; text.fontSize = 1; text.color.setHSL(Math.random(), 0.7, 0.6); text.position.set( Math.random()*20-10, Math.random()*20-10, Math.random()*20-10 ); text.updateMatrixWorld(); const id = batch.addText(text); // Set transform (like BatchedMesh API) are also available // matrix.compose(position, quaternion, scale); // batch.setMatrixAt(id, matrix); // batch.setColorAt(id, color); } scene.add(batch); // Dynamically add more text later (within maxTextCount/maxGlyphCount limits) const newText = new Text(); newText.text = 'Dynamic!'; const newId = batch.addText(newText); // Returns -1 if capacity exceeded if (newId >= 0) { batch.setMatrixAt(newId, matrix); } // Update existing text content const text = batch.getTextAt(0); text.text = 'Updated!'; ``` Demo: https://three-blocks.com/docs/demos/text_batched/ --- ### batchedTextColor [TSL] *Function* ```js import { batchedTextColor } from '@three-blocks/core'; ``` TSL function for creating a BatchedTextColorNode. This node handles SDF-based rendering for BatchedText, computing fill and outline colors and opacity based on signed distance fields. It reads per-member colors from storage buffers and applies visibility masking for GPU culling. This node should be used in `material.setupDiffuseColor` to override the diffuse color with SDF rendering. **Parameters:** - `batchedText`: BatchedText - Reference to the BatchedText instance. - `baseDiffuse?`: Node. - Base diffuse color/alpha computed before SDF. **Returns: `BatchedTextColorNode`** --- ### text [TSL] *Function* ```js import { text } from '@three-blocks/core'; ``` TSL function for creating a TextNode. TextNode implements vertex shader logic for Text rendering. It transforms positionLocal based on glyph bounds and SDF atlas data. This node should be used in `material.setupVertex` to handle text geometry transformation. Uses textGlyphTransform with uniform-based parameters for single Text instances. **Parameters:** - `text`: Text - Reference to the Text instance. **Returns: `Fn.` - A callable TSL function that returns the transformed position** --- ### Text *Class* ```js import { Text } from '@three-blocks/core'; ``` High-quality SDF-based text rendering with GPU-accelerated glyph generation. **Architecture** - Multi-channel SDF (Signed Distance Field) atlas for sharp text at any scale - Dynamic glyph packing and layout via `troika-three-text` - TSL node-based material for advanced effects (outlines, fills) - Async `sync()` generates glyphs on-demand with automatic atlas management **Features** - Crisp text rendering at any distance/angle - Fill and outline with independent colors/opacity - Text alignment, wrapping, and layout control - Custom positioning/coloring via TSL node hooks - Efficient instancing for many text instances via `BatchedText` **Events** - `syncstart`: Fired when layout/atlas generation begins - `synccomplete`: Fired when geometry and material are ready Extends: THREE.Mesh **Properties:** - `textRenderInfo`: Object | null - Get the computed text layout and SDF atlas information. - `billboarding`: boolean - Enable yaw-only billboarding so text always faces the camera. When enabled, text rotates around the Y-axis to face the camera while remaining upright. - `glyphGeometryDetail`: number - Geometry tessellation detail level (1-3). **Methods:** #### sync(callback?, renderer?) Trigger asynchronous glyph layout and SDF atlas generation. Called automatically before render if text properties changed. Parameters: - `callback?`: function - Called when sync completes. - `renderer?`: THREE.WebGPURenderer - Renderer for GPU SDF generation. #### onBeforeRender(renderer, scene, camera, geometry, material) Pre-render hook: ensures text is synced and material configured. Parameters: - `renderer`: THREE.WebGPURenderer - `scene`: THREE.Scene - `camera`: THREE.Camera - `geometry`: THREE.BufferGeometry - `material`: THREE.Material #### onAfterRender(renderer, scene, camera, geometry, material) Post-render hook: restores material side setting. Parameters: - `renderer`: THREE.WebGPURenderer - `scene`: THREE.Scene - `camera`: THREE.Camera - `geometry`: THREE.BufferGeometry - `material`: THREE.Material #### dispose() Dispose of geometry resources. **Example:** ```js import { Text, textLetterId } from '@three-blocks/core'; import { sin, positionLocal, Fn } from 'three/tsl'; const text = new Text(); text.text = 'Hello Three Blocks!'; text.fontSize = 0.5; text.color = 0x00ff00; text.anchorX = 'center'; text.anchorY = 'middle'; // Optional outline text.outlineWidth = '5%'; text.outlineColor = 0x000000; scene.add(text); // Custom TSL position hook using standard NodeMaterial API text.material.positionNode = Fn(() => { const pos = positionLocal.toVar(); pos.y.addAssign(sin(textLetterId.mul(10)).mul(0.1)); return pos; })(); ``` Demo: https://three-blocks.com/docs/demos/text/ --- ### textColor [TSL] *Function* ```js import { textColor } from '@three-blocks/core'; ``` TSL function for creating a TextColorNode. This node handles SDF-based rendering of text, computing fill and outline colors and opacity based on signed distance fields. It should be used in `material.setupDiffuseColor` to override the diffuse color with SDF rendering. **Parameters:** - `text`: Text - Reference to the Text instance. - `baseDiffuse?`: Node. - Base diffuse color/alpha computed before SDF. **Returns: `TextColorNode`** --- ### textDrawId [TSL] *Constant* ```js import { textDrawId } from '@three-blocks/core'; ``` Draw/member ID as a float. For single Text instances, this is always 0. For BatchedText, this is the index of the text member within the batch. Works in both vertex shaders (positionNode) and fragment shaders (colorNode). --- ### textGlyphDimensions [TSL] *Constant* ```js import { textGlyphDimensions } from '@three-blocks/core'; ``` Glyph dimensions as a vec2 (width, height) in local units. --- ### textGlyphUV [TSL] *Constant* ```js import { textGlyphUV } from '@three-blocks/core'; ``` Glyph UV coordinates as a vec2. These are the UV coordinates within the current glyph's bounding box (0-1 range within the glyph). --- ### textLetterId [TSL] *Constant* ```js import { textLetterId } from '@three-blocks/core'; ``` Letter ID as a normalized float (0-1) representing the position of the current glyph within the text. First letter is 0, last letter is 1. Works in both vertex shaders (positionNode) and fragment shaders (colorNode). For BatchedText, this is the letter position within each individual text member. --- ### textUV [TSL] *Constant* ```js import { textUV } from '@three-blocks/core'; ``` UV coordinates across the entire text block (0-1 range). This is useful for effects that span the whole text. --- ## IndirectBatchedMesh Indirect instanced rendering with GPU-driven culling. ### indirectBatch [TSL] *Function* ```js import { indirectBatch } from '@three-blocks/core'; ``` TSL helper for applying indirect batched transforms to vertices. Automatically resolves instance IDs, applies transforms, and handles geometry masking. **Example: Basic setup** **Parameters:** - `batchMesh`: IndirectBatchedMesh - The batched mesh instance to read data from. **Returns: `IndirectBatchNode` - Node that transforms vertices using batched instance data.** **Example:** ```js import * as THREE from 'three/webgpu'; import { IndirectBatchedMesh, indirectBatch } from '@three-blocks/core'; import { positionLocal, morphReference, skinning, uniform, subBuild, normalLocal, Fn } from 'three/tsl'; const geometry = new THREE.BoxGeometry(1, 1, 1); const vertexCount = geometry.getAttribute('position').count; const indexCount = geometry.getIndex()?.count || 0; const instanceCount = 512; const material = new THREE.MeshBasicNodeMaterial(); const mesh = new IndirectBatchedMesh(instanceCount, vertexCount, indexCount, material); // Disable internal culling for this simple demo mesh.perObjectFrustumCulled = false; const geoId = mesh.addGeometry(geometry); // Setup the indirect batch transform material.setupPosition = function(builder) { const { object, geometry } = builder; if (geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color) { morphReference(object).toStack(); } if (object.isSkinnedMesh === true) { skinning(object).toStack(); } if (object.isIndirectBatchedMesh) { // inject here indirectBatch(object).toStack(); const worldMatrix = uniform(new THREE.Matrix4()).onObjectUpdate((_, self) => self.value.copy(object.matrixWorld)).setName('batchWorldMatrix'); object.setCullingMatrixNode(({ baseMatrix }) => worldMatrix.mul(baseMatrix)); } if (this.positionNode !== null) { positionLocal.assign(subBuild(this.positionNode, 'POSITION', 'vec3')); } return positionLocal; }; // Use normal-based coloring to verify transforms work correctly material.colorNode = Fn(() => { return normalLocal.mul(0.5).add(0.5); })(); // Add instances in a grid pattern const tmpMatrix = new THREE.Matrix4(); const gridSize = Math.ceil(Math.cbrt(instanceCount)); const spacing = 2.5; let idx = 0; for (let x = 0; x < gridSize && idx < instanceCount; x++) { for (let y = 0; y < gridSize && idx < instanceCount; y++) { for (let z = 0; z < gridSize && idx < instanceCount; z++) { const id = mesh.addInstance(geoId); tmpMatrix.makeTranslation( (x - gridSize / 2) * spacing, (y - gridSize / 2) * spacing, (z - gridSize / 2) * spacing ); mesh.setMatrixAt(id, tmpMatrix); idx++; } } } mesh.needsUpdate = true; ``` Demo: https://three-blocks.com/docs/demos/indirect-batch/ --- ### indirectBatchColor [TSL] *Function* ```js import { indirectBatchColor } from '@three-blocks/core'; ``` Access the per-instance color written by {@link IndirectBatchNode}. This node reads the varying `vBatchColor` populated automatically when: - `IndirectBatchedMesh.setColorAt()` is called, and - the material uses `indirectBatch(mesh)` (so the varying is assigned). Works in both vertex and fragment stages. **Returns: `Node.` - Per-instance color varying.** Demo: https://three-blocks.com/docs/demos/indirect-batch-color/ --- ### IndirectBatchedMesh *Class* ```js import { IndirectBatchedMesh } from '@three-blocks/core'; ``` WebGPU-first batched mesh using indirect instanced rendering with GPU-driven culling. **Architecture Overview** - Packs multiple geometries into a single `BufferGeometry` with indirect draw commands - Always uses multi-draw indirect with `firstInstance` offsets (even without GPU culling) - Stores per-instance data in GPU `StorageBufferAttributes` (transforms, colors, geometry IDs) - Delegates culling/sorting to compute pipelines (e.g., `ComputeBatchCulling`) - Supports optional internal culling or external compute pipeline attachment **Limitations** - **Transparency is not supported**: This mesh class is designed for opaque materials only. Transparent materials will not render correctly due to the lack of depth sorting. Use standard `Mesh` or `InstancedMesh` with ComputeInstanceCulling for transparent objects. **Culling Flow** - `ComputeBatchCulling` evaluates visibility/LOD and compacts survivor IDs - Mesh reads from buffers via `setIndirect()` and `setSurvivorIdBuffer()` - Renderer draws using buffers while compute updates for next frame **Buffer Roles** - `matricesSB`: Authoritative per-instance transforms - `survivorIdSB`: Maps `instanceIndex` → original instance ID after culling - `geometryIdSB`: Identifies which geometry segment each instance uses - `indirect`: Controls final `instanceCount` for GPU draw **Example: Basic setup with multiple geometries** Extends: THREE.Mesh **Properties:** - `frustumCulled`: boolean (default: false) - Disable frustum culling for the indirect batched mesh. - `perObjectFrustumCulled`: boolean (default: true) - Enable per-instance frustum culling when using internal culling. - `maxInstanceCount`: number - Maximum instance capacity. - `instanceCount`: number - Current number of active instances. - `unusedVertexCount`: number - Remaining vertex capacity. - `unusedIndexCount`: number - Remaining index capacity. **Methods:** #### beginBulkUpdate() Defer CPU indirect rebuilds while performing many mutations (add/remove instances). Call `endBulkUpdate()` to rebuild once after the batch. Returns: `this` #### endBulkUpdate(rebuild?) Resume CPU indirect rebuilds after `beginBulkUpdate()`. Parameters: - `rebuild?`: boolean = true - Rebuild immediately when pending. Returns: `this` #### enableWorkgroupIndirect(workgroupSize?) Optional helper: allocate and update an indirect workgroup buffer for compute passes. Writes ceil(instanceCount / workgroupSize) into element 0 each frame. Parameters: - `workgroupSize?`: number = 64 - Workgroup size used by your compute shader. Returns: `THREE.IndirectStorageBufferAttribute` - Indirect buffer with [x=workgroups, y=1, z=1]. #### enableInternalCulling(renderer) Enable built-in compute-based culling using `ComputeBatchCulling`. Automatically creates and attaches a culling pipeline. Parameters: - `renderer`: THREE.WebGPURenderer - WebGPU renderer instance. Returns: `this` #### setCullingMatrixNode(resolverFn) Provide a custom mat4 node for culling/sorting (e.g., animation texture). Parameters: - `resolverFn`: function | null - Function receiving { i, baseMatrix, gid } and returning a mat4 node. Returns: `this` #### attachGUI(folder) Attach culling controls to a GUI folder. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `folder`: Object - GUI folder instance (e.g., from lil-gui or renderer.inspector.createParameters()). #### disposeGUI() Detach and destroy the GUI folder. #### computeBoundingBox() Compute the world-space bounding box for all active instances. Updates `this.boundingBox`. Returns: `void` #### computeBoundingSphere() Compute the world-space bounding sphere for all active instances. Updates `this.boundingSphere`. Returns: `void` #### updateInternalCulling(camera?) Update the internal culling system for the current frame. Swaps buffers and runs the compute culling pass. Parameters: - `camera?`: THREE.Camera - Camera to use for frustum culling. Returns: `this` #### setSurvivorIdBuffer(sbAttr, isInternal?) Attach a survivor ID mapping buffer produced by external compute culling. Parameters: - `sbAttr`: THREE.StorageBufferAttribute - StorageBufferAttribute (Uint32, itemSize=1). - `isInternal?`: boolean = false - If true, marks this as an internal buffer (won't disable internal culling). Returns: `this` #### setDrawFirstInstanceBuffer(sbAttr) Attach a per-draw firstInstance buffer (geometry offsets) produced by compute culling. Parameters: - `sbAttr`: THREE.StorageBufferAttribute | null - StorageBufferAttribute (Uint32, itemSize=1). Returns: `this` #### setIndirect(indirectAttribute) Set the indirect draw command buffer for this mesh. Parameters: - `indirectAttribute`: THREE.IndirectStorageBufferAttribute - Indirect draw args. Returns: `this` #### setInstanceCount(maxInstanceCount) Resize the instance pool capacity. Preserves existing data and initializes new slots to identity transforms. Parameters: - `maxInstanceCount`: number - New maximum instance count. #### addInstance(geometryId) Add a new instance referencing a previously added geometry. Parameters: - `geometryId`: number - Geometry ID returned by `addGeometry()`. Returns: `number` - The new instance ID. #### addGeometry(geometry, reservedVertexCount?, reservedIndexCount?) Add a geometry to the batch, allocating space in the merged buffers. Parameters: - `geometry`: THREE.BufferGeometry - Source geometry to add. - `reservedVertexCount?`: number = -1 - Vertex capacity to reserve (-1 = exact fit). - `reservedIndexCount?`: number = -1 - Index capacity to reserve (-1 = exact fit). Returns: `number` - Geometry ID for creating instances. #### setGeometryAt(geometryId, geometry) Replace or update geometry data at a specific geometry slot. Parameters: - `geometryId`: number - Target geometry ID. - `geometry`: THREE.BufferGeometry - New geometry data. Returns: `number` - The geometry ID. #### deleteGeometry(geometryId) Mark a geometry as inactive and delete all associated instances. Parameters: - `geometryId`: number - Geometry ID to delete. Returns: `this` #### deleteInstance(instanceId) Mark an instance as inactive and return its slot to the pool. Parameters: - `instanceId`: number - Instance ID to delete. Returns: `this` #### optimize() Defragment geometry data by compacting active geometry segments. Reduces gaps and improves memory layout. Returns: `this` #### getBoundingBoxAt(geometryId, target) Get the bounding box for a specific geometry. Parameters: - `geometryId`: number - Target geometry ID. - `target`: THREE.Box3 - Box3 to store the result. Returns: `THREE.Box3 | null` - The bounding box or null if invalid. #### getBoundingSphereAt(geometryId, target) Get the bounding sphere for a specific geometry. Parameters: - `geometryId`: number - Target geometry ID. - `target`: THREE.Sphere - Sphere to store the result. Returns: `THREE.Sphere | null` - The bounding sphere or null if invalid. #### setMatrixAt(instanceId, matrix) Set the transform matrix for an instance. Parameters: - `instanceId`: number - Target instance ID. - `matrix`: THREE.Matrix4 - Transform matrix to apply. Returns: `this` #### getMatrixAt(instanceId, matrix) Get the transform matrix for an instance. Parameters: - `instanceId`: number - Target instance ID. - `matrix`: THREE.Matrix4 - Matrix to store the result. Returns: `THREE.Matrix4` #### setColorAt(instanceId, color) Set the color for an instance. Automatically enables instance colors if not already enabled. Parameters: - `instanceId`: number - Target instance ID. - `color`: THREE.Color - Color to apply. Returns: `this` #### getColorAt(instanceId, color) Get the color for an instance. Parameters: - `instanceId`: number - Target instance ID. - `color`: THREE.Color - Color to store the result. Returns: `THREE.Color` #### setGeometryIdAt(instanceId, geometryId) Change which geometry an instance references. Parameters: - `instanceId`: number - Target instance ID. - `geometryId`: number - New geometry ID. Returns: `this` #### getGeometryIdAt(instanceId) Get the geometry ID for an instance. Parameters: - `instanceId`: number - Target instance ID. Returns: `number` - Geometry ID. #### copy(source) Create a deep copy of this IndirectBatchedMesh. Parameters: - `source`: IndirectBatchedMesh - Source mesh to copy from. Returns: `this` #### dispose() Dispose of GPU resources. Call this when the mesh is no longer needed. Returns: `void` **Example:** ```js import { IndirectBatchedMesh, indirectBatch } from '@three-blocks/core'; import * as THREE from 'three/webgpu'; // Create batched mesh: 10000 instances, 50000 vertices, 100000 indices const material = new THREE.MeshStandardNodeMaterial({ color: 0x4488ff }); const mesh = new IndirectBatchedMesh(10000, 50000, 100000, material); // Add multiple geometry types const boxId = mesh.addGeometry(new THREE.BoxGeometry(1, 1, 1)); const sphereId = mesh.addGeometry(new THREE.SphereGeometry(0.5, 16, 16)); const coneId = mesh.addGeometry(new THREE.ConeGeometry(0.5, 1, 8)); // Scatter instances of different geometries const matrix = new THREE.Matrix4(); const geometries = [boxId, sphereId, coneId]; for (let i = 0; i < 5000; i++) { const geomId = geometries[i % 3]; const instanceId = mesh.addInstance(geomId); matrix.makeTranslation( (Math.random() - 0.5) * 200, (Math.random() - 0.5) * 50, (Math.random() - 0.5) * 200 ); mesh.setMatrixAt(instanceId, matrix); } scene.add(mesh); ``` See also: ComputeBatchCulling Demo: https://three-blocks.com/docs/demos/indirect-batched-mesh/ --- ### indirectBatchGeometryId [TSL] *Function* ```js import { indirectBatchGeometryId } from '@three-blocks/core'; ``` Access the geometry ID for a given instance in an {@link IndirectBatchedMesh}. Use this to apply different material effects per geometry type (e.g., different colors for boxes vs spheres in the same batched mesh). **Example: Per-geometry-type coloring** **Parameters:** - `batchMesh`: IndirectBatchedMesh - The batched mesh instance. - `idNode?`: Node. - Instance id to sample. If not provided, resolves automatically using survivorIdSB. **Returns: `Node.` - Geometry ID for the instance.** **Example:** ```js import { IndirectBatchedMesh, indirectBatch, indirectBatchGeometryId } from '@three-blocks/core'; import { vec3 } from 'three/tsl'; import { MeshStandardNodeMaterial } from 'three/webgpu'; const material = new MeshStandardNodeMaterial(); const mesh = new IndirectBatchedMesh(1000, 10000, 30000, material); const boxId = mesh.addGeometry(new BoxGeometry(1, 1, 1)); const sphereId = mesh.addGeometry(new SphereGeometry(0.5)); // Color based on geometry type const geomId = indirectBatchGeometryId(mesh); const colors = uniformArray([ new THREE.Vector3(1.0, 0.3, 0.2), // Red for boxes new THREE.Vector3(0.2, 0.8, 0.4), // Green for spheres new THREE.Vector3(0.3, 0.5, 1.0) // Blue for cones ], 'vec3'); material.colorNode = vec4(colors.element(geomId), 1.0); ``` Demo: https://three-blocks.com/docs/demos/indirect-batch-geometry-id/ --- ### indirectBatchId [TSL] *Function* ```js import { indirectBatchId } from '@three-blocks/core'; ``` Return the stable instance id for an {@link IndirectBatchedMesh}, honoring survivorIdSB when present. Use this to access per-instance data in custom material nodes. When GPU culling is active, `instanceIndex` gives the draw index (0 to survivorCount), while `indirectBatchId` returns the original instance ID from before culling. **Example: Per-instance color variation** **Parameters:** - `batchMesh`: IndirectBatchedMesh - The batched mesh instance. **Returns: `Node.` - Stable instance id after culling/sorting.** **Example:** ```js import { IndirectBatchedMesh, indirectBatch, indirectBatchId } from '@three-blocks/core'; import { hash, mix, vec3 } from 'three/tsl'; import { MeshStandardNodeMaterial } from 'three/webgpu'; const material = new MeshStandardNodeMaterial(); const mesh = new IndirectBatchedMesh(1000, 10000, 30000, material); // Get the stable instance ID (works with culling) const instanceId = indirectBatchId(mesh); // Use hash for per-instance random color const randomHue = hash(instanceId); material.colorNode = mix(vec3(1, 0, 0), vec3(0, 0, 1), randomHue); ``` Demo: https://three-blocks.com/docs/demos/indirect-batch-id/ --- ### indirectBatchMatrix [TSL] *Function* ```js import { indirectBatchMatrix } from '@three-blocks/core'; ``` Fetch the batch matrix for a given instance id in an {@link IndirectBatchedMesh}. Use this to access instance transforms in custom vertex shaders or for computing world-space positions in fragment shaders. **Example: Custom vertex animation with instance matrix** **Parameters:** - `batchMesh`: IndirectBatchedMesh - The batched mesh instance. - `idNode?`: Node. = instanceIndex - Instance id to sample. **Returns: `Node.` - Batch transform matrix.** **Example:** ```js import { IndirectBatchedMesh, indirectBatch, indirectBatchId, indirectBatchMatrix } from '@three-blocks/core'; import { positionLocal, time, sin, vec3 } from 'three/tsl'; import { MeshStandardNodeMaterial } from 'three/webgpu'; const material = new MeshStandardNodeMaterial(); const mesh = new IndirectBatchedMesh(1000, 10000, 30000, material); // Get instance ID and matrix const instanceId = indirectBatchId(mesh); const matrix = indirectBatchMatrix(mesh, instanceId); // Extract position from matrix (column 3) const instancePos = vec3(matrix[3].x, matrix[3].y, matrix[3].z); // Add wave animation based on instance world position const wave = sin(time.add(instancePos.x.mul(0.5))).mul(0.2); material.positionNode = positionLocal.add(vec3(0, wave, 0)); ``` Demo: https://three-blocks.com/docs/demos/indirect-batch-matrix/ --- ## Simulation Particle and fluid simulation systems (Boids, SPH, PBF). ### Boids *Class* ```js import { Boids } from '@three-blocks/core'; ``` GPU-accelerated boids flocking simulation with spatial grid optimization. **Features** - Classic boids rules: separation, alignment, cohesion with configurable weights - Spatial grid acceleration (default) or naive O(N²) neighbor search - 2D and 3D modes with boundary reflection - Pointer-based interaction for user-driven steering - Optional instanced mesh with automatic orientation from velocities - Phase tracking for wing flapping animation sync - Supports initialization from external samplers (e.g., ComputeBVHSampler) - GPU culling support with custom instanceMatrix (Three.js r182+) **Architecture** - Compute passes: GPU init → velocity (neighbor influence) → position (integration) - Spatial grid reduces neighbor search from O(N²) to O(N) via cell hashing - Three behavioral zones: separation (repel), alignment (match velocity), cohesion (attract) - Node-based instancing helper provides per-boid transform matrices for rendering **Properties:** - `sdfVolumeConstraint`: SDFVolumeConstraint | null **Methods:** #### attachSpatialGrid(grid) Attach an external `SpatialGrid` instance for neighbor acceleration. Rebuilds compute passes to use grid-accelerated neighbor lookups. Parameters: - `grid`: SpatialGrid - External SpatialGrid instance. Returns: `this` #### enableSpatialGrid(options?) Create and attach an internal spatial grid for neighbor acceleration. Automatically configures grid based on current domain and zone radius. Parameters: - `options?`: Object = {} - Options forwarded to `SpatialGrid` constructor. Returns: `this` #### setSpatialGridEnabled(enabled, options?) Toggle spatial grid acceleration. Parameters: - `enabled`: boolean - Enable (true) or disable (false) spatial grid. - `options?`: Object - Options passed to `enableSpatialGrid()` if enabling. Returns: `this` #### detachSpatialGrid() Detach and dispose of the spatial grid, reverting to naive O(N²) neighbor search. Returns: `this` #### setDomainDimensions(dimensions) Manually set the simulation domain dimensions. Updates UBOs and synchronizes the spatial grid. Parameters: - `dimensions`: THREE.Vector3 - New domain dimensions. Returns: `this` #### syncSpatialGrid() Synchronize spatial grid configuration with current zone radius and domain. Automatically called when parameters change. Returns: `this` #### step(renderer) Advance the simulation by one frame using GPU compute passes. Executes: grid update (if enabled) → GPU init (first frame) → velocity → position. Parameters: - `renderer`: THREE.WebGPURenderer - WebGPU renderer instance. Returns: `Promise.` **Example:** ```js import { Boids } from '@three-blocks/core'; import { ConeGeometry, InstancedMesh, MeshStandardNodeMaterial } from 'three/webgpu'; import { instanceIndex } from 'three/tsl'; // Basic usage with material positionNode const boids = new Boids({ count: 5000, is3D: true, domainDimensions: new THREE.Vector3(200, 200, 200), separation: 0.025, alignment: 0.03, cohesion: 0.08, useRelativeParameters: true, useMatrices: true }); // Create instanced mesh for rendering const geometry = new ConeGeometry(0.5, 1.5, 4); const material = new MeshStandardNodeMaterial(); material.positionNode = boids.instanceMatrix().element( instanceIndex ); const mesh = new InstancedMesh(geometry, material, boids.particleCount); // When useMatrices is enabled, you can feed instance matrices directly mesh.instanceMatrix = boids.buffers.instanceMatrices.value; scene.add(mesh); // Simulation loop function animate() { boids.step(renderer); renderer.render(scene, camera); requestAnimationFrame(animate); } ``` See also: SpatialGrid Demo: https://three-blocks.com/docs/demos/boids/ --- ### PBF *Class* ```js import { PBF } from '@three-blocks/core'; ``` Position Based Fluids (PBF) particle simulation optimized for GPU compute with optional spatial grid acceleration. **Properties:** - `sdfVolumeConstraint`: SDFVolumeConstraint | null **Methods:** #### setDomainDimensions(dimensions) Manually set the simulation domain dimensions. Updates UBOs and synchronizes the spatial grid. Parameters: - `dimensions`: THREE.Vector3 - New domain dimensions. Returns: `this` #### setDomainFromObject(object, options?, options.padding?, options.autoUpdate?, options.simulationScale?) Bind simulation domain to a Three.js object's world-space bounds. Particles will be constrained to the object's local space and follow its transforms. Parameters: - `object`: THREE.Object3D - Target object (mesh or group). - `options?`: Object = {} - Configuration options. - `options.padding?`: number | THREE.Vector3 = 0 - Extend domain bounds by padding. - `options.autoUpdate?`: boolean = true - Update domain matrices every frame. - `options.simulationScale?`: number | THREE.Vector3 = null - Override domain scale for visualization. Returns: `this` #### attachSpatialGrid(grid) Attach an external `SpatialGrid` instance for neighbor acceleration. Rebuilds compute passes to use grid-accelerated lookups. Parameters: - `grid`: SpatialGrid - External SpatialGrid instance. Returns: `this` #### setSmoothingRadius(radius) Update the PBF smoothing kernel radius (h parameter). Recomputes kernel coefficients and syncs spatial grid cell size. Parameters: - `radius`: number - New smoothing radius (h). Returns: `this` #### setRestDensity(value) Set rest density manually. Passing null/undefined re-enables auto rest density. Parameters: - `value`: number | null | undefined Returns: `this` #### setMass(value) Set particle mass. Recomputes auto rest density when enabled. Parameters: - `value`: number Returns: `this` #### setKernelScaleEnabled(enabled) Enable/disable automatic kernel radius scaling with domain transformations. Parameters: - `enabled`: boolean - Whether to scale h with domain scale. Returns: `this` #### setGridUpdatePerIteration(enabled) Control whether the spatial grid rebuilds each solver iteration or once per step. Parameters: - `enabled`: boolean - When true, rebuild on every solver iteration (slower, more accurate). Returns: `this` #### enableSpatialGrid(options?) Create and attach an internal spatial grid for neighbor acceleration. Automatically configures grid based on current domain and kernel radius. Parameters: - `options?`: Object = {} - Options forwarded to `SpatialGrid` constructor. Returns: `this` #### setSpatialGridEnabled(enabled, options?) Toggle spatial grid acceleration. Parameters: - `enabled`: boolean - Enable (true) or disable (false) spatial grid. - `options?`: Object - Options passed to `enableSpatialGrid()` if enabling. Returns: `this` #### detachSpatialGrid() Detach and dispose of the spatial grid, reverting to naive O(N²) neighbor search. Returns: `this` #### syncSpatialGrid() Synchronize spatial grid configuration with current domain and kernel radius. Automatically called when domain or kernel changes. Returns: `this` #### step(renderer) Advance the simulation by one frame using GPU compute passes. Executes: grid update → density → pressure → forces → interaction → integration. Parameters: - `renderer`: THREE.WebGPURenderer - WebGPU renderer instance. Returns: `Promise.` #### attachGUI(gui, options?, options.folderName?, options.open?) Attach PBF parameters to a GUI for interactive tuning. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `gui`: Object - GUI instance (e.g., `new GUI()` from lil-gui or renderer.inspector.createParameters()). - `options?`: Object = {} - Configuration options. - `options.folderName?`: string = 'PBF' - Name for the main folder. - `options.open?`: boolean = false - Whether folders start open. Returns: `Object` - Object containing created GUI folders: `{ main, physics, simulation, domain, pointer }`. #### disposeGUI() Detach the PBF parameters GUI. **Example:** ```js import { PBF } from '@three-blocks/core'; import { SphereGeometry, InstancedMesh, MeshStandardNodeMaterial } from 'three/webgpu'; import { storage, instanceIndex } from 'three/tsl'; const pbf = new PBF({ count: 2000, is3D: true, domainDimensions: new THREE.Vector3(20, 20, 20), h: 1.2, viscosityMu: 0.15, useMatrices: true }); // Create instanced mesh for rendering particles const geometry = new SphereGeometry(0.3, 8, 8); const material = new MeshStandardNodeMaterial(); const positionNode = storage(pbf.buffers.positions, 'vec3', pbf.particleCount); material.positionNode = positionNode.element(instanceIndex); const mesh = new InstancedMesh(geometry, material, pbf.particleCount); // When useMatrices is enabled, you can feed instance matrices directly mesh.instanceMatrix = pbf.buffers.instanceMatrices.value; scene.add(mesh); // Simulation loop async function animate() { await pbf.step(renderer); renderer.render(scene, camera); requestAnimationFrame(animate); } ``` See also: SpatialGrid Demo: https://three-blocks.com/docs/demos/pbf/ --- ### SpatialGridHelper *Class* ```js import { SpatialGridHelper } from '@three-blocks/core'; ``` Helper for visualizing the spatial grid used by particle simulations. **Constructor Parameters:** - `simulation`: Object - The simulation object (SPH, Boids) to which the helper is attached. - `showTexts?`: boolean = true - Whether to show cell IDs as text. --- ### SPH *Class* ```js import { SPH } from '@three-blocks/core'; ``` Smoothed Particle Hydrodynamics (SPH) fluid simulation with spatial grid acceleration. **Features** - Pressure, viscosity, and gravity forces with configurable SPH kernels (Poly6, Spiky, Viscosity) - Spatial grid acceleration (default) or naive O(N²) neighbor search - 2D and 3D modes with appropriate kernel functions - Domain attachment to Three.js objects for dynamic boundary transforms - Pointer-based interaction for user-driven forces - Optional direction storage for oriented particle rendering **Architecture** - Compute passes: density → pressure → forces (pressure + viscosity) → interaction → integration - Spatial grid reduces neighbor search from O(N²) to O(N) via cell hashing - Domain matrix transforms allow particles to follow moving/rotating objects - Kernel radius auto-scales with domain transformations when `scaleKernelWithDomain` is enabled **Properties:** - `sdfVolumeConstraint`: SDFVolumeConstraint | null **Methods:** #### setDomainDimensions(dimensions) Manually set the simulation domain dimensions. Updates UBOs and synchronizes the spatial grid. Parameters: - `dimensions`: THREE.Vector3 - New domain dimensions. Returns: `this` #### setDomainFromObject(object, options?, options.padding?, options.autoUpdate?, options.simulationScale?) Bind simulation domain to a Three.js object's world-space bounds. Particles will be constrained to the object's local space and follow its transforms. Parameters: - `object`: THREE.Object3D - Target object (mesh or group). - `options?`: Object = {} - Configuration options. - `options.padding?`: number | THREE.Vector3 = 0 - Extend domain bounds by padding. - `options.autoUpdate?`: boolean = true - Update domain matrices every frame. - `options.simulationScale?`: number | THREE.Vector3 = null - Override domain scale for visualization. Returns: `this` #### attachSpatialGrid(grid) Attach an external `SpatialGrid` instance for neighbor acceleration. Rebuilds compute passes to use grid-accelerated lookups. Parameters: - `grid`: SpatialGrid - External SpatialGrid instance. Returns: `this` #### setSmoothingRadius(radius) Update the SPH smoothing kernel radius (h parameter). Recomputes kernel coefficients and syncs spatial grid cell size. Parameters: - `radius`: number - New smoothing radius (h). Returns: `this` #### setRestDensity(value) Set rest density manually. Passing null/undefined re-enables auto rest density. Parameters: - `value`: number | null | undefined Returns: `this` #### setMass(value) Set particle mass. Recomputes auto rest density when enabled. Parameters: - `value`: number Returns: `this` #### setKernelScaleEnabled(enabled) Enable/disable automatic kernel radius scaling with domain transformations. Parameters: - `enabled`: boolean - Whether to scale h with domain scale. Returns: `this` #### enableSpatialGrid(options?) Create and attach an internal spatial grid for neighbor acceleration. Automatically configures grid based on current domain and kernel radius. Parameters: - `options?`: Object = {} - Options forwarded to `SpatialGrid` constructor. Returns: `this` #### setSpatialGridEnabled(enabled, options?) Toggle spatial grid acceleration. Parameters: - `enabled`: boolean - Enable (true) or disable (false) spatial grid. - `options?`: Object - Options passed to `enableSpatialGrid()` if enabling. Returns: `this` #### detachSpatialGrid() Detach and dispose of the spatial grid, reverting to naive O(N²) neighbor search. Returns: `this` #### syncSpatialGrid() Synchronize spatial grid configuration with current domain and kernel radius. Automatically called when domain or kernel changes. Returns: `this` #### step(renderer) Advance the simulation by one frame using GPU compute passes. Executes: grid update → density → pressure → forces → interaction → integration. Parameters: - `renderer`: THREE.WebGPURenderer - WebGPU renderer instance. Returns: `Promise.` #### attachGUI(gui, options?, options.folderName?, options.open?) Attach SPH parameters to a GUI for interactive tuning. Compatible with lil-gui, dat.gui, and Three.js Inspector. Parameters: - `gui`: Object - GUI instance (e.g., `new GUI()` from lil-gui or renderer.inspector.createParameters()). - `options?`: Object = {} - Configuration options. - `options.folderName?`: string = 'SPH' - Name for the main folder. - `options.open?`: boolean = false - Whether folders start open. Returns: `Object` - Object containing created GUI folders: `{ main, physics, simulation, domain, pointer }`. #### disposeGUI() Detach the SPH parameters GUI. **Example:** ```js import { SPH } from '@three-blocks/core'; import { SphereGeometry, InstancedMesh, MeshStandardNodeMaterial } from 'three/webgpu'; import { storage, instanceIndex } from 'three/tsl'; const sph = new SPH({ count: 2000, is3D: true, domainDimensions: new THREE.Vector3(20, 20, 20), h: 1.2, viscosityMu: 0.15, useMatrices: true }); // Create instanced mesh for rendering particles const geometry = new SphereGeometry(0.3, 8, 8); const material = new MeshStandardNodeMaterial(); const positionNode = storage(sph.buffers.positions, 'vec3', sph.particleCount); material.positionNode = positionNode.element(instanceIndex); const mesh = new InstancedMesh(geometry, material, sph.particleCount); // When useMatrices is enabled, you can feed instance matrices directly mesh.instanceMatrix = sph.buffers.instanceMatrices.value; scene.add(mesh); // Simulation loop async function animate() { await sph.step(renderer); renderer.render(scene, camera); requestAnimationFrame(animate); } ``` See also: SpatialGrid Demo: https://three-blocks.com/docs/demos/sph/ --- ## Additional Resources - Documentation: https://three-blocks.com/docs - Examples: https://three-blocks.com/docs/demos - GitHub: https://github.com/three-blocks Generated: 2025-12-15T14:07:08.227Z