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.
123 lines (122 loc) • 6.39 kB
TypeScript
export interface VirtualMeshSource {
/** xyz per vertex */
positions: Float32Array;
/** xyz per vertex */
normals: Float32Array;
/** Optional rgb per vertex, multiplied with the material color. */
colors?: Float32Array;
/** Optional uv per vertex (texture coordinates). Vertices on a UV seam must be separate vertices. */
uvs?: Float32Array;
indices: Uint32Array;
}
export interface VirtualMeshBuildOptions {
/** Meshlets per group before simplification. */
groupSize?: number;
maxLodLevels?: number;
/** A group that keeps more than this fraction of its triangles stops refining (its meshlets become roots). */
maxGroupKeepRatio?: number;
/** Fraction of a group's triangles kept by its simplification (the per-level reduction factor). */
simplifyRatio?: number;
/** Stop when a whole level removed less than this fraction of triangles. */
minLevelReduction?: number;
/** Remove small disconnected components while simplifying (good for foliage). */
prune?: boolean;
/**
* Keep simplifying below a single meshlet once the remaining roots form the whole mesh, so distant
* instances can draw a handful of triangles (or nothing, with `prune`) instead of a full meshlet.
*/
tailSimplify?: boolean;
/** Stop the tail at this many triangles. */
minRootTriangles?: number;
/**
* Let coarse LODs switch to voxel proxies when those are more accurate than simplified triangles
* (foliage and other aggregate geometry; solid meshes keep their triangles). Default true.
*/
voxelLods?: boolean;
/**
* Finest voxel grid for those proxies, in cells along the longest side. Default 64. A finer grid (128) lowers the
* smallest error a voxel level can have, so foliage made of many separate pieces (palm fronds, leaves) switches to
* its cheap coarse levels closer to the camera. Builds take longer and use more memory.
*/
voxelResolution?: number;
/**
* Keep simplifying groups of separate small pieces (leaf cards, grass blades) when edge collapses stall: each piece
* is simplified on its own, then neighbouring pieces are thinned in pairs, the kept piece widened to cover both
* (see aggregate.ts). Without it such groups keep every piece up to the voxel levels. Default true.
*/
aggregateLods?: boolean;
onProgress?: (fraction: number) => void | Promise<void>;
}
export interface VirtualMeshData {
positions: Float32Array;
normals: Float32Array;
colors: Float32Array | null;
/** uv per vertex, or null when the source had none. */
uvs: Float32Array | null;
/** Concatenated meshlet triangle lists, indexing `positions`. */
indices: Uint32Array;
meshletCount: number;
/**
* 4 x vec4 per meshlet:
* [0] LOD sphere (center, radius) - shared by all meshlets created from the same group
* [1] parent LOD sphere (center, radius)
* [2] (error, parentError, 0, 0) - object-space; parentError = ERROR_INFINITY for roots
* [3] culling sphere (center, radius) - the meshlet's own geometry
*/
meshletBounds: Float32Array;
/**
* uvec4 per meshlet: (triangleCount, firstIndex, lodLevel, vertexOffset).
* `firstIndex / 3` is also the meshlet's first entry in `meshletTriangles`.
*/
meshletInfo: Uint32Array;
/** Global vertex ids, per meshlet, in meshlet-local order (`vertexOffset` indexes this). */
meshletVertices: Uint32Array;
/** One packed triangle per entry: three meshlet-local vertex ids in bits 0-7, 8-15, 16-23. */
meshletTriangles: Uint32Array;
/** Meshlets are sorted by LOD level. Per level: [firstMeshlet, meshletCount]. */
levelRanges: Uint32Array;
/** Per level: [smallest own error, largest parent error] in object space (parent may be ERROR_INFINITY). */
levelErrors: Float32Array;
/**
* Per level: [largest own error, smallest parent error] in object space. Used by the fast path: if every
* meshlet of a level passes the LOD test at the nearest distance (largest own error) and at the farthest
* (smallest parent error), the level needs no per-meshlet test.
*/
levelGuardErrors: Float32Array;
/** Sphere enclosing every LOD and parent sphere: bounds the distances the LOD test can see. */
lodBoundsSphere: [number, number, number, number];
/** Sphere enclosing every meshlet's culling sphere (all LODs): if an instance sphere is inside the frustum, so are they. */
cullBoundsSphere: [number, number, number, number];
/**
* DAG links (CPU only, for `verifyCutCoverage`; not stored in .vgeo files). Meshlet i belongs to the group that
* `replacementGroup[i]` names (-1 for the coarsest meshlets), and that group is replaced by the coarser meshlets
* `replacementIndices[replacementStart[g] .. replacementStart[g + 1]]`. Every meshlet of a group shares one list, so
* the size stays linear even when a simplification step merges thousands of meshlets at once.
*/
replacementGroup: Int32Array;
replacementStart: Uint32Array;
replacementIndices: Uint32Array;
/** Whole-object bounding sphere (center xyz, radius). */
boundingSphere: [number, number, number, number];
stats: {
leafTriangles: number;
leafMeshlets: number;
rootTriangles: number;
rootMeshlets: number;
lodLevels: number;
/** Coarse levels built from voxel proxies (see voxelProxy.ts). */
voxelLevels: number;
/** Groups simplified piece by piece (see aggregate.ts; absent in files baked before it). */
aggregateGroups?: number;
buildMs: number;
};
}
export declare function buildVirtualMesh(src: VirtualMeshSource, options?: VirtualMeshBuildOptions): Promise<VirtualMeshData>;
/**
* How far the simplified mesh's bounding box retreats from the original's, per side (max over the 6
* sides). An empty result counts as losing the whole extent. Used as a floor for the LOD error so a
* version that lost its silhouette is only drawn once that loss is below the pixel threshold.
*/
export declare function extentShrink(before: Uint32Array, after: Uint32Array, positions: Float32Array): number;
/** Order-independent edge id. Safe while vertex ids stay below 2^26. */
export declare const edgeKey: (a: number, b: number) => number;