Packs one or more scene objects into GPU-accessible BVH buffers (TLAS + BLAS) for use
in WebGPU compute shaders via the Three.js TSL node system. After construction, call
BVHComputeData#update to populate the storage buffers, then reference
this.storage and this.fns in your compute shader nodes.
import { Matrix4, Mesh, MeshBasicNodeMaterial, SRGBColorSpace, StorageTexture, TorusKnotGeometry } from 'three/webgpu';
import { colorSpaceToWorking, globalId, texture, textureStore, uniform, wgslFn } from 'three/tsl';
import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
import { BVHComputeData, ndcToCameraRay } from 'three-mesh-bvh/webgpu';
// scene, camera and renderer are initialized here
const WORKGROUP_SIZE = [ 8, 8, 1 ];
const mesh = new Mesh( new TorusKnotGeometry( 1, 0.3, 300, 50 ) );
const bvhData = new BVHComputeData( mesh, { attributes: { position: 'vec4f', normal: 'vec4f' } } );
bvhData.update();
const outputTex = new StorageTexture( window.innerWidth, window.innerHeight );
const cameraToWorld = uniform( new Matrix4() );
const inverseProjection = uniform( new Matrix4() );
const raytrace = wgslFn( /* wgsl */`
fn compute(
outputTex: texture_storage_2d<rgba8unorm, write>,
inverseProjectionMatrix: mat4x4f,
cameraToWorldMatrix: mat4x4f,
globalId: vec3u,
) -> void {
let dimensions = textureDimensions( outputTex );
let uv = vec2f( globalId.xy ) / vec2f( dimensions );
var ray = ndcToCameraRay( uv * 2.0 - vec2f( 1.0 ), cameraToWorldMatrix * inverseProjectionMatrix );
var hit: IntersectionResult;
bvh_RaycastFirstHit( ray, &hit );
if ( hit.didHit ) {
let normal = normalize( bvh_sampleTrianglePoint( hit.barycoord, hit.indices.xyz ).normal.xyz );
textureStore( outputTex, globalId.xy, vec4f( normal, 1.0 ) );
} else {
textureStore( outputTex, globalId.xy, vec4f( 0.0366, 0.0813, 0.1057, 1.0 ) );
}
}
`, [ ndcToCameraRay, bvhData.fns.raycastFirstHit, bvhData.fns.sampleTrianglePoint ] );
const kernel = raytrace( {
outputTex: textureStore( outputTex ),
inverseProjectionMatrix: inverseProjection,
cameraToWorldMatrix: cameraToWorld,
globalId,
} ).computeKernel( WORKGROUP_SIZE );
const material = new MeshBasicNodeMaterial();
material.colorNode = colorSpaceToWorking( texture( outputTex ), SRGBColorSpace );
const quad = new FullScreenQuad( material );
const dispatchSize = [ Math.ceil( outputTex.width / 8 ), Math.ceil( outputTex.height / 8 ) ];
renderer.setAnimationLoop( () => {
camera.updateMatrixWorld();
cameraToWorld.value.copy( camera.matrixWorld );
inverseProjection.value.copy( camera.projectionMatrixInverse );
renderer.compute( kernel, dispatchSize );
quad.render( renderer );
} );Methods
new BVHComputeData(
// Scene objects to include. A single item or array of Object3D, BufferGeometry, or GeometryBVH instances are all accepted and wrapped automatically in a BVH.
objects: Object3D | BufferGeometry | GeometryBVH | Array,
options?: {
// WGSL type map for the interleaved per-vertex attribute buffer. Keys are geometry attribute names; values are WGSL type strings (e.g. `'vec3f'`, `'vec4f'`).
attributes: Record<string, string> = { position: 'vec4f' },
// When true, a MeshBVH is automatically built for any object that does not already have `geometry.boundsTree` set.
autogenerateBvh: boolean = true
}
)getRootObject(): Object3DReturns the representative root object for the scene to be constructed.
getShapecastFn(
options: {
// Function name. Defaults to a random identifier.
name?: string,
// TSL struct or definition describing the query shape.
shapeStruct: StructTypeNode,
// TSL struct for the accumulated result, or null.
resultStruct?: StructTypeNode | null,
// function node that runs before the bvh traversal - useful for resetting or initializing necessary module variables.
prefixFn?: function | null,
// function node controlling left/right child traversal order.
boundsOrderFn?: function | null,
// function node testing the shape against a BVH node's bounds.
intersectsBoundsFn: function,
// function node testing the shape against a leaf triangle range.
intersectRangeFn: function,
// function node that transforms the shape into object local space.
transformShapeFn?: function | null,
// function node that transforms a hit result back to world space.
transformResultFn?: function | null,
// function node called after each BLAS traversal to reset any per-object state set by `transformShapeFn`.
resetShapeFn?: function | null
}
): functionBuilds a WGSL shapecast function that traverses the TLAS and per-cluster BLAS in a single
merged stack/loop for a custom shape type. The returned function signature is:
fn name( shape: ShapeStruct[, result: ptr<function, ResultStruct>] ) -> bool
update(): voidRebuilds all GPU storage buffers from the current scene state. Must be called at least
once before using this.storage or this.fns in a shader, and again whenever the
scene topology changes (objects added/removed, geometry modified).
updateTransforms(): voidRefits the clustered BVH and rewrites every entry in the transform buffer from the objects'
current world matrices. Call this when object transforms or visibility change but the scene
topology does not. The transform slots are derived from the clustered BVH's primitive buffer,
so they match those written by BVHComputeData#update.
dispose(): voidReleases GPU resources held by this instance.