three-virtual-geometry
Version:
Virtual geometry for three.js WebGPU: automatic cluster LOD and GPU-driven culling for billions of triangles. Independent implementation, not affiliated with Epic Games.
131 lines (130 loc) • 7.08 kB
TypeScript
/**
* A renderable set of instances of one preprocessed mesh (a three.js Mesh, so it goes into any scene).
*
* The geometry and instances live in a GeometryPool shared with other meshes; the pool's compute passes select
* the cut for all of its meshes at once (see GeometryPool). This object holds the material and two geometries,
* the camera cut and the shadow cut, which draw this mesh's region of the pool's index buffers.
*/
import * as THREE from 'three/webgpu';
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh.js';
import type { VirtualGeometry } from './VirtualGeometry.js';
import { type GeometryPool, type PoolEntry } from './GeometryPool.js';
export { vgWorldNormal, vgInstanceOrigin, encodeOctNormal, packVertices, packUvs } from './GeometryPool.js';
export interface VirtualMeshInstances {
/** 16 floats (column-major Matrix4) per instance. */
matrices: Float32Array;
/** Optional rgba tint per instance (multiplied with the material color). */
colors?: Float32Array;
}
export interface VirtualMeshOptions {
/**
* Cull distance in world units: instances farther than this are not drawn (and cost almost nothing, since
* whole cells are rejected at once). They shrink to nothing over the last 15% of the distance, so there is
* no visible pop. Default: Infinity. Can be changed later via `mesh.maxDrawDistance`.
*/
maxDrawDistance?: number;
/**
* Hide instances whose bounding sphere is smaller than this many pixels (radius) on screen. Combined with
* `VirtualGeometry.minPixelRadius` (the larger one wins). Default: 0. Also settable via `mesh.minPixelRadius`.
*/
minPixelRadius?: number;
/**
* Moves vertices in the vertex shader, e.g. foliage swaying in the wind: gets the world-space position node and
* returns a displaced one (three.js TSL). `instanceOrigin` is the instance's world position, so a tree can bend
* from its base. Culling and level of detail use the undisplaced geometry, so keep the displacement small (well
* under a metre).
*/
deform?: (worldPosition: any, context: {
instanceOrigin: any;
}) => any;
/** @deprecated Draw capacity is shared per pool now: see `VirtualGeometryOptions`. Ignored. */
maxDrawnMeshlets?: number;
/** @deprecated Draw capacity is shared per pool now: see `VirtualGeometryOptions`. Ignored. */
maxDrawnTriangles?: number;
}
export declare class VirtualMesh extends THREE.Mesh<THREE.BufferGeometry, THREE.NodeMaterial> {
readonly isVirtualMesh = true;
readonly data: VirtualMeshData;
readonly instanceCount: number;
readonly fullDetailTriangles: number;
/** The pool holding this mesh's geometry and instances, and its record there. Set by `VirtualGeometry`. */
pool: GeometryPool;
entry: PoolEntry;
/**
* Geometries drawing this mesh's region of the pool's camera and shadow index buffers: the meshlets drawn over
* every pixel. Those inside an LOD blend band are drawn by `blendPart`.
*/
readonly cameraGeometry: THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>;
readonly shadowGeometry: THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>;
/**
* Child mesh drawing the meshlets inside an LOD blend band (see lodBlend.ts), with a copy of the material that
* keeps only the fragments of each pixel's own level. Kept apart because a shader that may discard fragments
* loses early depth testing, which would slow down every other meshlet.
*/
readonly blendPart: THREE.Mesh<THREE.BufferGeometry, THREE.NodeMaterial>;
private readonly cameraBlendGeometry;
private readonly shadowBlendGeometry;
private blendSourceVersion;
/** `context.frameCount` of the last shadow-map render of this mesh. */
lastShadowFrame: number;
/**
* Lever: draw this mesh into the occlusion depth pass, so it can hide other geometry. Default: solid
* materials (opaque, single-sided, no alpha test). Foliage is mostly holes, so it only gets tested.
*/
occluder: boolean;
/** Lever: let occlusion culling remove hidden instances and meshlets of this mesh. */
occlusionCulling: boolean;
/** GPU instance slot of each caller instance (inverse of the Morton order). */
readonly gpuIndexOf: Uint32Array;
/** Morton order: caller instance of each GPU slot. */
readonly gpuToInput: Uint32Array;
private readonly dirtyCells;
private drawingShadow;
private poolVersion;
/** The material's own MRT output, and whether the velocity output is merged into it (see setVelocityOutput). */
private readonly ownMrt;
private velocityOutput;
/** `options.deform`, applied to the pool's position node. */
private readonly deform;
private readonly context;
/** Cull distance in world units (Infinity: never). See `VirtualMeshOptions.maxDrawDistance`. */
get maxDrawDistance(): number;
set maxDrawDistance(distance: number);
/** Per-mesh minimum on-screen radius in pixels. See `VirtualMeshOptions.minPixelRadius`. */
get minPixelRadius(): number;
set minPixelRadius(pixels: number);
/** The pool's per-frame draw capacity (shared by every mesh of the pool). */
get capacity(): {
meshlets: number;
triangles: number;
};
constructor(context: VirtualGeometry, data: VirtualMeshData, material: THREE.NodeMaterial, instances: VirtualMeshInstances, options?: VirtualMeshOptions);
/**
* Keeps the blend part's material and flags in step with this mesh (called by VirtualGeometry every frame): values
* such as colours, maps and uniforms are copied each frame; a material change that needs a recompile (its version
* changed, or a different material) is copied as a whole.
*/
syncBlendPart(): void;
/** Points the geometries and the material at the pool's current buffers and nodes (after a pool rebuild). */
syncPool(): void;
/**
* Outputs the motion of the pulled vertices as `velocity` (for TAA), merged into the material's own MRT. Only
* while a pass asks for it: a material MRT in a render target without MRT would replace the color output.
*/
setVelocityOutput(on: boolean): void;
/** Force the next `VirtualGeometry.update` to recompute the cut. */
markCutDirty(): void;
/** Update one instance transform. Call `commitInstances()` after a batch of changes. */
setMatrixAt(index: number, matrix: THREE.Matrix4): void;
commitInstances(): void;
/**
* Stop rendering this mesh and drop it from its context. The GPU buffers are released when the
* renderer frees them; call this before removing the object from the scene for good.
*/
dispose(context?: VirtualGeometry): void;
/** Triangles drawn for this mesh by the last computed cut (camera and shadow), from one readback. */
readStats(renderer: THREE.WebGPURenderer): Promise<{
drawnTriangles: number;
shadowTriangles: number;
}>;
}