three-virtual-geometry 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +65 -0
- package/LICENSE +23 -0
- package/README.md +165 -2
- package/THIRD_PARTY_NOTICES.md +42 -0
- package/dist/bake.mjs +1378 -0
- package/dist/core/constants.d.ts +27 -0
- package/dist/core/import/fromObject3D.d.ts +116 -0
- package/dist/core/import/toNodeMaterial.d.ts +10 -0
- package/dist/core/io/cache.d.ts +32 -0
- package/dist/core/io/serialize.d.ts +21 -0
- package/dist/core/preprocess/buildVirtualMesh.d.ts +105 -0
- package/dist/core/preprocess/partition.d.ts +16 -0
- package/dist/core/preprocess/voxelProxy.d.ts +30 -0
- package/dist/core/runtime/GeometryPool.d.ts +219 -0
- package/dist/core/runtime/OcclusionCulling.d.ts +100 -0
- package/dist/core/runtime/VirtualGeometry.d.ts +201 -0
- package/dist/core/runtime/VirtualMesh.d.ts +93 -0
- package/dist/core/runtime/cut.d.ts +50 -0
- package/dist/core/runtime/vgMaterial.d.ts +33 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +2470 -0
- package/dist/index.js.map +1 -0
- package/dist/source.d.ts +34 -0
- package/dist/three-virtual-geometry.all.min.js +585 -0
- package/dist/three-virtual-geometry.all.min.js.map +6 -0
- package/dist/three-virtual-geometry.min.js +7 -0
- package/dist/three-virtual-geometry.min.js.map +6 -0
- package/docs/public/llms.txt +101 -0
- package/package.json +89 -4
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Max triangles per meshlet. The renderer always issues `MAX_MESHLET_TRIANGLES * 3` vertices per drawn meshlet. */
|
|
2
|
+
export declare const MAX_MESHLET_TRIANGLES = 128;
|
|
3
|
+
/** Max unique vertices per meshlet (meshoptimizer allows up to 256). */
|
|
4
|
+
export declare const MAX_MESHLET_VERTICES = 128;
|
|
5
|
+
/** Stand-in for `Infinity` on the GPU: the parent error of root meshlets. */
|
|
6
|
+
export declare const ERROR_INFINITY = 1e+30;
|
|
7
|
+
/**
|
|
8
|
+
* Instances whose LOD range is at most this many meshlets, and where every meshlet is guaranteed to be
|
|
9
|
+
* selected, emit their meshlets directly from the instance pass (no meshlet pass for them).
|
|
10
|
+
*/
|
|
11
|
+
export declare const FAST_PATH_MAX_MESHLETS = 4;
|
|
12
|
+
/**
|
|
13
|
+
* Relative margin the fast path keeps from the LOD threshold, so float rounding between the bound and the
|
|
14
|
+
* per-meshlet test can never make the fast path select a meshlet the full test would reject.
|
|
15
|
+
*/
|
|
16
|
+
export declare const FAST_PATH_MARGIN = 0.001;
|
|
17
|
+
/** Floats per meshlet in `VirtualMeshData.meshletBounds` (4 x vec4). */
|
|
18
|
+
export declare const MESHLET_BOUNDS_STRIDE = 16;
|
|
19
|
+
/** Uints per meshlet in `VirtualMeshData.meshletInfo` (1 x uvec4). */
|
|
20
|
+
export declare const MESHLET_INFO_STRIDE = 4;
|
|
21
|
+
/** Debug views for `VirtualGeometry.debugMode`. */
|
|
22
|
+
export declare const VG_DEBUG_MODES: {
|
|
23
|
+
readonly shaded: 0;
|
|
24
|
+
readonly meshlets: 1;
|
|
25
|
+
readonly lodLevel: 2;
|
|
26
|
+
readonly instances: 3;
|
|
27
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-call scene import: every static mesh of an Object3D (e.g. a loaded glTF scene) becomes VirtualGeometry geometry.
|
|
3
|
+
*
|
|
4
|
+
* Meshes are grouped by (geometry, geometry group, material, render flags), so repeated geometry becomes one
|
|
5
|
+
* VirtualMesh with one instance per occurrence (InstancedMesh instances included). Each unique geometry range
|
|
6
|
+
* is built once. Skinned and morphing meshes, points, lines and sprites are left alone and keep rendering.
|
|
7
|
+
*/
|
|
8
|
+
import * as THREE from 'three/webgpu';
|
|
9
|
+
import { type VirtualMeshBuildOptions, type VirtualMeshData, type VirtualMeshSource } from '../preprocess/buildVirtualMesh.js';
|
|
10
|
+
import type { VirtualGeometry } from '../runtime/VirtualGeometry.js';
|
|
11
|
+
import type { VirtualMesh, VirtualMeshOptions } from '../runtime/VirtualMesh.js';
|
|
12
|
+
/** Builds one DAG. Same signature as `buildVirtualMesh`, so a caching builder drops in here or via `options.builder`. */
|
|
13
|
+
export type VirtualGeometryBuilder = (source: VirtualMeshSource, options?: VirtualMeshBuildOptions) => Promise<VirtualMeshData>;
|
|
14
|
+
export interface VirtualGeometryImportOptions {
|
|
15
|
+
/** DAG build options for every geometry (`onProgress` is driven by the import; use `onProgress` below). */
|
|
16
|
+
build?: Omit<VirtualMeshBuildOptions, 'onProgress'>;
|
|
17
|
+
/** Options for each created VirtualMesh (e.g. `maxDrawDistance`), or a function returning them per group. */
|
|
18
|
+
mesh?: VirtualMeshOptions | ((group: VirtualGeometryImportGroup) => VirtualMeshOptions | undefined);
|
|
19
|
+
/** Return false to leave a mesh as it is (it keeps rendering normally). Called for every compatible mesh. */
|
|
20
|
+
filter?: (mesh: THREE.Mesh) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* true (default): the VirtualMeshes are added to `object` and the converted meshes stop rendering (their
|
|
23
|
+
* leaf meshes are taken out of the graph, meshes with children stay with their layers cleared; `dispose()`
|
|
24
|
+
* restores both). false: nothing in the scene changes; add `result.object` yourself.
|
|
25
|
+
*/
|
|
26
|
+
replace?: boolean;
|
|
27
|
+
/** Overall progress in 0..1 while building. May be async (awaited), e.g. to let the page repaint. */
|
|
28
|
+
onProgress?: (fraction: number) => void | Promise<void>;
|
|
29
|
+
/** Converts a source material. Default: node materials are cloned, classic mesh materials go through `toNodeMaterial`. */
|
|
30
|
+
material?: (material: THREE.Material, group: VirtualGeometryImportGroup) => THREE.NodeMaterial | null;
|
|
31
|
+
/** DAG builder (default `buildVirtualMesh`), e.g. a cached one with the same signature. */
|
|
32
|
+
builder?: VirtualGeometryBuilder;
|
|
33
|
+
}
|
|
34
|
+
/** Meshes that share geometry, geometry range, material and render flags: one VirtualMesh. */
|
|
35
|
+
export interface VirtualGeometryImportGroup {
|
|
36
|
+
geometry: THREE.BufferGeometry;
|
|
37
|
+
/** Index range (vertex range for non-indexed geometry) this group draws: one entry of `geometry.groups`, clipped to the draw range. */
|
|
38
|
+
range: {
|
|
39
|
+
start: number;
|
|
40
|
+
count: number;
|
|
41
|
+
};
|
|
42
|
+
material: THREE.Material;
|
|
43
|
+
/** Column-major world matrix per instance (16 floats each), in the order of `instances`. */
|
|
44
|
+
matrices: Float32Array;
|
|
45
|
+
/** rgba per instance, from `InstancedMesh.instanceColor` (null when no source has instance colors). */
|
|
46
|
+
colors: Float32Array | null;
|
|
47
|
+
/** Source of each instance: the mesh, and the instance index for an InstancedMesh (else -1). */
|
|
48
|
+
instances: {
|
|
49
|
+
object: THREE.Mesh;
|
|
50
|
+
index: number;
|
|
51
|
+
}[];
|
|
52
|
+
/** Per-vertex colors are used (`material.vertexColors` and a `color` attribute), like three would. */
|
|
53
|
+
vertexColors: boolean;
|
|
54
|
+
castShadow: boolean;
|
|
55
|
+
receiveShadow: boolean;
|
|
56
|
+
/** False when the mesh or one of its ancestors is hidden. */
|
|
57
|
+
visible: boolean;
|
|
58
|
+
renderOrder: number;
|
|
59
|
+
layers: number;
|
|
60
|
+
/** From the first source mesh. */
|
|
61
|
+
name: string;
|
|
62
|
+
userData: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/** Why a mesh is left alone, or null when it can be converted. */
|
|
65
|
+
export declare function incompatibility(mesh: THREE.Object3D): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* Collects the convertible meshes under `root` into instance groups (pure CPU work, no GPU, testable in Node).
|
|
68
|
+
* World matrices are read after updating them, so the result matches what three would render now.
|
|
69
|
+
*/
|
|
70
|
+
export declare function collectVirtualGeometryGroups(root: THREE.Object3D, filter?: (mesh: THREE.Mesh) => boolean): {
|
|
71
|
+
groups: VirtualGeometryImportGroup[];
|
|
72
|
+
meshes: THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>, THREE.Material<THREE.MaterialEventMap>[] | THREE.Material<THREE.MaterialEventMap>, THREE.Object3DEventMap>[];
|
|
73
|
+
skipped: {
|
|
74
|
+
object: THREE.Object3D;
|
|
75
|
+
reason: string;
|
|
76
|
+
}[];
|
|
77
|
+
};
|
|
78
|
+
/** Result of `VirtualGeometry.add()`: the created meshes, and how to undo the import. */
|
|
79
|
+
export declare class VirtualGeometryImport {
|
|
80
|
+
private readonly context;
|
|
81
|
+
/** Holds every created VirtualMesh. Added to the imported object unless `replace: false`. */
|
|
82
|
+
readonly object: THREE.Group<THREE.Object3DEventMap>;
|
|
83
|
+
/** One VirtualMesh per entry of `groups`. */
|
|
84
|
+
readonly meshes: VirtualMesh[];
|
|
85
|
+
groups: VirtualGeometryImportGroup[];
|
|
86
|
+
/** Meshes that were left alone, and why. */
|
|
87
|
+
readonly skipped: {
|
|
88
|
+
object: THREE.Object3D;
|
|
89
|
+
reason: string;
|
|
90
|
+
}[];
|
|
91
|
+
readonly stats: {
|
|
92
|
+
sourceMeshes: number;
|
|
93
|
+
instances: number;
|
|
94
|
+
uniqueGeometries: number;
|
|
95
|
+
buildMs: number;
|
|
96
|
+
};
|
|
97
|
+
private readonly hidden;
|
|
98
|
+
/** Converted leaf meshes taken out of the graph, with their former parent (restored by `dispose`). */
|
|
99
|
+
private readonly detached;
|
|
100
|
+
constructor(context: VirtualGeometry, collected: ReturnType<typeof collectVirtualGeometryGroups>);
|
|
101
|
+
/** @internal Stops the given source meshes from rendering (restored by `dispose`). */
|
|
102
|
+
hide(meshes: THREE.Mesh[], root: THREE.Object3D): void;
|
|
103
|
+
/** World matrix of a source object, also when it (or its ancestors) were taken out of the graph. */
|
|
104
|
+
private worldMatrixOf;
|
|
105
|
+
/**
|
|
106
|
+
* Re-reads every source mesh's current world matrix into the instances (call after moving the imported
|
|
107
|
+
* object or animating its nodes). Instances are baked in world space at import time otherwise.
|
|
108
|
+
*/
|
|
109
|
+
syncTransforms(): void;
|
|
110
|
+
/** Removes the VirtualMeshes and lets the source meshes render again. */
|
|
111
|
+
dispose(): void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Converts every compatible mesh under `object` to VirtualGeometry geometry. See `VirtualGeometry.add`.
|
|
115
|
+
*/
|
|
116
|
+
export declare function virtualMeshesFromObject3D(context: VirtualGeometry, object: THREE.Object3D, options?: VirtualGeometryImportOptions): Promise<VirtualGeometryImport>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
/** True when `toNodeMaterial` can convert the material (node materials, and the classic mesh materials). */
|
|
3
|
+
export declare function canConvertToNodeMaterial(material: THREE.Material): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* The node-material equivalent of a classic mesh material: MeshStandardMaterial -> MeshStandardNodeMaterial,
|
|
6
|
+
* and likewise for Physical, Phong, Lambert, Basic, Toon, Matcap and Normal. Every property is copied (colors,
|
|
7
|
+
* maps and their texture objects, transparency, alphaTest, side, blending, userData, ...). Node materials are
|
|
8
|
+
* returned as they are; materials without a node equivalent (ShaderMaterial, ...) return null.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toNodeMaterial(material: THREE.Material): THREE.NodeMaterial | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `buildVirtualMeshCached`: `buildVirtualMesh` behind a persistent IndexedDB cache, keyed by a SHA-256 of the
|
|
3
|
+
* source arrays and build options. The first load builds and stores the encoded result; later loads decode
|
|
4
|
+
* it. Any cache problem (no IndexedDB, private mode, quota, corrupt entry) falls back to building.
|
|
5
|
+
*/
|
|
6
|
+
import { type VirtualMeshBuildOptions, type VirtualMeshData, type VirtualMeshSource } from '../preprocess/buildVirtualMesh.js';
|
|
7
|
+
/**
|
|
8
|
+
* Part of every cache key. Bump it whenever `buildVirtualMesh` produces different output for the same input
|
|
9
|
+
* (algorithm or default changes), so stale cached builds are never returned.
|
|
10
|
+
*/
|
|
11
|
+
export declare const VG_BUILD_VERSION = 3;
|
|
12
|
+
export interface VirtualGeometryCacheOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Fixed cache key instead of a content hash (skips hashing the source). You must change it whenever the
|
|
15
|
+
* source mesh or build options change.
|
|
16
|
+
*/
|
|
17
|
+
key?: string;
|
|
18
|
+
/** 'indexeddb' (default) or false to always build. */
|
|
19
|
+
store?: 'indexeddb' | false;
|
|
20
|
+
}
|
|
21
|
+
export interface VirtualGeometryCachedBuildOptions extends VirtualMeshBuildOptions {
|
|
22
|
+
cache?: VirtualGeometryCacheOptions;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Same as `buildVirtualMesh`, plus a persistent cache. On a hit the build is skipped, `onProgress(1)` is still
|
|
26
|
+
* called, and `stats.buildMs` reports the time this call took (hashing, lookup and decoding).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildVirtualMeshCached(source: VirtualMeshSource, options?: VirtualGeometryCachedBuildOptions): Promise<VirtualMeshData>;
|
|
29
|
+
/** Deletes every cached build. Resolves even if IndexedDB is unavailable. */
|
|
30
|
+
export declare function clearVirtualMeshCache(): Promise<void>;
|
|
31
|
+
/** SHA-256 over every source field (typed arrays by content), the build options and the format versions. */
|
|
32
|
+
export declare function virtualMeshCacheKey(source: VirtualMeshSource, options?: VirtualMeshBuildOptions, customKey?: string): Promise<string>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh.js';
|
|
2
|
+
/** Bumped whenever the container layout or an encoding changes; older readers reject newer files. */
|
|
3
|
+
export declare const VG_FORMAT_VERSION = 1;
|
|
4
|
+
export interface VirtualMeshEncodeOptions {
|
|
5
|
+
/** Store `indices` instead of rebuilding it from the meshlet arrays on decode. Default false. */
|
|
6
|
+
keepIndices?: boolean;
|
|
7
|
+
/** Lossless compression with meshoptimizer's codecs. Default true; false stores every array uncompressed. */
|
|
8
|
+
compress?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Also deflate fields where that makes them smaller (about 30% smaller files, about 2x slower decoding).
|
|
11
|
+
* Default true; ignored where `CompressionStream` is unavailable. The IndexedDB cache turns it off.
|
|
12
|
+
*/
|
|
13
|
+
deflate?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class VirtualMeshFormatError extends Error {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
export declare function encodeVirtualMesh(data: VirtualMeshData, options?: VirtualMeshEncodeOptions): Promise<Uint8Array>;
|
|
19
|
+
export declare function decodeVirtualMesh(input: ArrayBuffer | ArrayBufferView): Promise<VirtualMeshData>;
|
|
20
|
+
/** Fetches and decodes a baked `.vgeo` file, or decodes bytes that are already in memory. */
|
|
21
|
+
export declare function loadVirtualMesh(source: string | URL | ArrayBuffer | ArrayBufferView, init?: RequestInit): Promise<VirtualMeshData>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface VirtualMeshSource {
|
|
2
|
+
/** xyz per vertex */
|
|
3
|
+
positions: Float32Array;
|
|
4
|
+
/** xyz per vertex */
|
|
5
|
+
normals: Float32Array;
|
|
6
|
+
/** Optional rgb per vertex, multiplied with the material color. */
|
|
7
|
+
colors?: Float32Array;
|
|
8
|
+
/** Optional uv per vertex (texture coordinates). Vertices on a UV seam must be separate vertices. */
|
|
9
|
+
uvs?: Float32Array;
|
|
10
|
+
indices: Uint32Array;
|
|
11
|
+
}
|
|
12
|
+
export interface VirtualMeshBuildOptions {
|
|
13
|
+
/** Meshlets per group before simplification. */
|
|
14
|
+
groupSize?: number;
|
|
15
|
+
maxLodLevels?: number;
|
|
16
|
+
/** A group that keeps more than this fraction of its triangles stops refining (its meshlets become roots). */
|
|
17
|
+
maxGroupKeepRatio?: number;
|
|
18
|
+
/** Fraction of a group's triangles kept by its simplification (the per-level reduction factor). */
|
|
19
|
+
simplifyRatio?: number;
|
|
20
|
+
/** Stop when a whole level removed less than this fraction of triangles. */
|
|
21
|
+
minLevelReduction?: number;
|
|
22
|
+
/** Remove small disconnected components while simplifying (good for foliage). */
|
|
23
|
+
prune?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Keep simplifying below a single meshlet once the remaining roots form the whole mesh, so distant
|
|
26
|
+
* instances can draw a handful of triangles (or nothing, with `prune`) instead of a full meshlet.
|
|
27
|
+
*/
|
|
28
|
+
tailSimplify?: boolean;
|
|
29
|
+
/** Stop the tail at this many triangles. */
|
|
30
|
+
minRootTriangles?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Let coarse LODs switch to voxel proxies when those are more accurate than simplified triangles
|
|
33
|
+
* (foliage and other aggregate geometry; solid meshes keep their triangles). Default true.
|
|
34
|
+
*/
|
|
35
|
+
voxelLods?: boolean;
|
|
36
|
+
onProgress?: (fraction: number) => void | Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export interface VirtualMeshData {
|
|
39
|
+
positions: Float32Array;
|
|
40
|
+
normals: Float32Array;
|
|
41
|
+
colors: Float32Array | null;
|
|
42
|
+
/** uv per vertex, or null when the source had none. */
|
|
43
|
+
uvs: Float32Array | null;
|
|
44
|
+
/** Concatenated meshlet triangle lists, indexing `positions`. */
|
|
45
|
+
indices: Uint32Array;
|
|
46
|
+
meshletCount: number;
|
|
47
|
+
/**
|
|
48
|
+
* 4 x vec4 per meshlet:
|
|
49
|
+
* [0] LOD sphere (center, radius) - shared by all meshlets created from the same group
|
|
50
|
+
* [1] parent LOD sphere (center, radius)
|
|
51
|
+
* [2] (error, parentError, 0, 0) - object-space; parentError = ERROR_INFINITY for roots
|
|
52
|
+
* [3] culling sphere (center, radius) - the meshlet's own geometry
|
|
53
|
+
*/
|
|
54
|
+
meshletBounds: Float32Array;
|
|
55
|
+
/**
|
|
56
|
+
* uvec4 per meshlet: (triangleCount, firstIndex, lodLevel, vertexOffset).
|
|
57
|
+
* `firstIndex / 3` is also the meshlet's first entry in `meshletTriangles`.
|
|
58
|
+
*/
|
|
59
|
+
meshletInfo: Uint32Array;
|
|
60
|
+
/** Global vertex ids, per meshlet, in meshlet-local order (`vertexOffset` indexes this). */
|
|
61
|
+
meshletVertices: Uint32Array;
|
|
62
|
+
/** One packed triangle per entry: three meshlet-local vertex ids in bits 0-7, 8-15, 16-23. */
|
|
63
|
+
meshletTriangles: Uint32Array;
|
|
64
|
+
/** Meshlets are sorted by LOD level. Per level: [firstMeshlet, meshletCount]. */
|
|
65
|
+
levelRanges: Uint32Array;
|
|
66
|
+
/** Per level: [smallest own error, largest parent error] in object space (parent may be ERROR_INFINITY). */
|
|
67
|
+
levelErrors: Float32Array;
|
|
68
|
+
/**
|
|
69
|
+
* Per level: [largest own error, smallest parent error] in object space. Used by the fast path: if every
|
|
70
|
+
* meshlet of a level passes the LOD test at the nearest distance (largest own error) and at the farthest
|
|
71
|
+
* (smallest parent error), the level needs no per-meshlet test.
|
|
72
|
+
*/
|
|
73
|
+
levelGuardErrors: Float32Array;
|
|
74
|
+
/** Sphere enclosing every LOD and parent sphere: bounds the distances the LOD test can see. */
|
|
75
|
+
lodBoundsSphere: [number, number, number, number];
|
|
76
|
+
/** Sphere enclosing every meshlet's culling sphere (all LODs): if an instance sphere is inside the frustum, so are they. */
|
|
77
|
+
cullBoundsSphere: [number, number, number, number];
|
|
78
|
+
/**
|
|
79
|
+
* DAG links, CSR form: meshlet i is replaced by coarser meshlets
|
|
80
|
+
* `replacementIndices[replacementStart[i] .. replacementStart[i + 1]]`. Empty for coarsest meshlets.
|
|
81
|
+
*/
|
|
82
|
+
replacementStart: Uint32Array;
|
|
83
|
+
replacementIndices: Uint32Array;
|
|
84
|
+
/** Whole-object bounding sphere (center xyz, radius). */
|
|
85
|
+
boundingSphere: [number, number, number, number];
|
|
86
|
+
stats: {
|
|
87
|
+
leafTriangles: number;
|
|
88
|
+
leafMeshlets: number;
|
|
89
|
+
rootTriangles: number;
|
|
90
|
+
rootMeshlets: number;
|
|
91
|
+
lodLevels: number;
|
|
92
|
+
/** Coarse levels built from voxel proxies (see voxelProxy.ts). */
|
|
93
|
+
voxelLevels: number;
|
|
94
|
+
buildMs: number;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export declare function buildVirtualMesh(src: VirtualMeshSource, options?: VirtualMeshBuildOptions): Promise<VirtualMeshData>;
|
|
98
|
+
/**
|
|
99
|
+
* How far the simplified mesh's bounding box retreats from the original's, per side (max over the 6
|
|
100
|
+
* sides). An empty result counts as losing the whole extent. Used as a floor for the LOD error so a
|
|
101
|
+
* version that lost its silhouette is only drawn once that loss is below the pixel threshold.
|
|
102
|
+
*/
|
|
103
|
+
export declare function extentShrink(before: Uint32Array, after: Uint32Array, positions: Float32Array): number;
|
|
104
|
+
/** Order-independent edge id. Safe while vertex ids stay below 2^26. */
|
|
105
|
+
export declare const edgeKey: (a: number, b: number) => number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groups meshlets into clusters of ~`groupSize` that share as many boundary
|
|
3
|
+
* edges as possible. This stands in for METIS (the graph partitioner nanite-webgpu uses):
|
|
4
|
+
* a greedy region-growing pass over the meshlet adjacency graph, seeded in
|
|
5
|
+
* Morton order so groups stay spatially compact. Meshlets without enough
|
|
6
|
+
* neighbours (e.g. disconnected leaves or grass blades) are then merged with
|
|
7
|
+
* spatially close leftovers so they can still be simplified together.
|
|
8
|
+
*/
|
|
9
|
+
export interface PartitionInput {
|
|
10
|
+
/** Boundary edge keys (position-welded vertex ids, see `edgeKey`). */
|
|
11
|
+
boundaryEdges: number[];
|
|
12
|
+
center: [number, number, number];
|
|
13
|
+
}
|
|
14
|
+
export declare function partitionMeshlets(meshlets: PartitionInput[], groupSize: number): number[][];
|
|
15
|
+
/** Indices sorted along a 30-bit Morton curve of the given points. */
|
|
16
|
+
export declare function mortonOrder(points: [number, number, number][]): number[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voxel proxies for coarse LODs, for foliage and other aggregate geometry.
|
|
3
|
+
*
|
|
4
|
+
* Triangle simplification breaks down on aggregate geometry such as a tree crown made of hundreds of
|
|
5
|
+
* separate leaf clumps: below a few hundred triangles it can only delete or collapse whole clumps, so
|
|
6
|
+
* the silhouette shrinks and the LOD error jumps. A voxel proxy keeps the volume instead: the mesh is
|
|
7
|
+
* voxelized, gaps between clumps are closed, and a smooth closed surface is extracted (surface nets).
|
|
8
|
+
* That surface is one connected shape, which simplifies gracefully to a handful of triangles.
|
|
9
|
+
*
|
|
10
|
+
* Every proxy vertex lies within about one cell of the original surface, and closing fills gaps of at most
|
|
11
|
+
* two cells, so `VOXEL_ERROR_CELLS * cellSize` bounds how far the proxy deviates from the original.
|
|
12
|
+
*/
|
|
13
|
+
/** Deviation of a proxy from the source mesh, in cells (surface offset plus closed gaps). */
|
|
14
|
+
export declare const VOXEL_ERROR_CELLS = 2;
|
|
15
|
+
export interface VoxelProxy {
|
|
16
|
+
positions: Float32Array;
|
|
17
|
+
normals: Float32Array;
|
|
18
|
+
colors: Float32Array | null;
|
|
19
|
+
/** UV of a representative source sample near each vertex (null when the source has no UVs). */
|
|
20
|
+
uvs: Float32Array | null;
|
|
21
|
+
indices: Uint32Array;
|
|
22
|
+
cellSize: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Voxelizes the triangles `indices` of a mesh with `resolution` cells along its longest side and returns
|
|
26
|
+
* a closed, smooth surface around the occupied volume (null if the mesh is empty).
|
|
27
|
+
* With `uvs`, each proxy vertex takes the UV of the first source sample in its cell's voxels, so textured
|
|
28
|
+
* geometry keeps plausible colors at a distance (UVs cannot be averaged across islands).
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildVoxelProxy(positions: Float32Array, colors: Float32Array | null | undefined, indices: Uint32Array, resolution: number, uvs?: Float32Array | null): VoxelProxy | null;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared GPU buffers and compute passes for many VirtualMeshes.
|
|
3
|
+
*
|
|
4
|
+
* Every mesh's vertices, meshlets, level table and instances are appended to one set of storage buffers, and one
|
|
5
|
+
* fixed set of compute passes selects the cut for all of them. CPU cost, shader count and pipeline compilation
|
|
6
|
+
* therefore do not grow with the number of unique meshes: a level with hundreds of different props costs the
|
|
7
|
+
* same 12 dispatches per frame as a scene with one. Each mesh still gets its own indexed indirect draw (its own
|
|
8
|
+
* material), into a region of the pool's shared index buffer laid out each frame by a prefix sum.
|
|
9
|
+
*
|
|
10
|
+
* Per frame (all on the GPU):
|
|
11
|
+
* 0. reset - clears the counters.
|
|
12
|
+
* 1. cells - one thread per cell (CELL_SIZE instances of one mesh, Morton-ordered): frustum, sub-pixel and
|
|
13
|
+
* draw-distance tests on the cell's bounding sphere. Visible cells are appended to a list.
|
|
14
|
+
* 2. instance - one workgroup per visible cell: per-instance tests, occlusion, then the LOD level range per
|
|
15
|
+
* cut (camera and shadow). Instances whose few meshlets are all certain to pass emit them
|
|
16
|
+
* directly (fast path); the others go to a work list.
|
|
17
|
+
* 3. meshlet - one workgroup per work item: per-meshlet frustum/occlusion culling and the LOD test
|
|
18
|
+
* `parentError > t && error <= t`. Selected meshlets get a draw slot and a range inside their
|
|
19
|
+
* mesh's region of the index buffer.
|
|
20
|
+
* 4. prefix - one thread: lays the meshes' regions out one after another and writes each mesh's draw args.
|
|
21
|
+
* 5. expand - one workgroup per drawn meshlet: writes 3 indices per triangle. An index encodes
|
|
22
|
+
* `drawSlot * 128 + meshletLocalVertex`; the material's positionNode decodes it and pulls the
|
|
23
|
+
* vertex from storage buffers, so any three.js NodeMaterial works unchanged.
|
|
24
|
+
*
|
|
25
|
+
* A pool is bounded by WebGPU's storage-buffer binding size; VirtualGeometry opens another pool when one is full.
|
|
26
|
+
* Buffers grow by reallocation (the nodes are rebuilt, and the pipelines recompiled in the background).
|
|
27
|
+
*
|
|
28
|
+
* Based on nanite-webgpu (MIT, Marcin Matuszczyk): cullMeshletsPass + rasterizeHwPass.
|
|
29
|
+
*/
|
|
30
|
+
import * as THREE from 'three/webgpu';
|
|
31
|
+
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh.js';
|
|
32
|
+
import type { VirtualGeometry } from './VirtualGeometry.js';
|
|
33
|
+
type Node = any;
|
|
34
|
+
/** World-space normal of the current vertex, usable in material color/roughness nodes. */
|
|
35
|
+
export declare const vgWorldNormal: THREE.PropertyNode<"vec3">;
|
|
36
|
+
export declare const vgMeshletVarying: THREE.PropertyNode<"float">;
|
|
37
|
+
export declare const vgLodVarying: THREE.PropertyNode<"float">;
|
|
38
|
+
export declare const vgInstanceVarying: THREE.PropertyNode<"float">;
|
|
39
|
+
export declare const vgTintVarying: THREE.PropertyNode<"vec3">;
|
|
40
|
+
/** Bits of an index used for the meshlet-local vertex; the rest is the draw slot. */
|
|
41
|
+
export declare const LOCAL_VERTEX_BITS: number;
|
|
42
|
+
/** Instances per cell. A cell is a contiguous range of one mesh's Morton-sorted instances. */
|
|
43
|
+
export declare const CELL_SIZE = 128;
|
|
44
|
+
/** Fraction of `maxDrawDistance` over which instances shrink to nothing before they are culled. */
|
|
45
|
+
export declare const DRAW_DISTANCE_FADE = 0.15;
|
|
46
|
+
/** Stand-in for an infinite draw distance on the GPU. */
|
|
47
|
+
export declare const NO_DRAW_DISTANCE = 1e+30;
|
|
48
|
+
export interface PoolCapacity {
|
|
49
|
+
/** Max meshlets drawn per frame, per cut. */
|
|
50
|
+
meshlets: number;
|
|
51
|
+
/** Max triangles drawn per frame by the camera cut (its index buffer). */
|
|
52
|
+
triangles: number;
|
|
53
|
+
/** Max triangles drawn per frame into shadow maps. */
|
|
54
|
+
shadowTriangles: number;
|
|
55
|
+
}
|
|
56
|
+
/** CPU-side record of one mesh's place in the pool. */
|
|
57
|
+
export interface PoolEntry {
|
|
58
|
+
slot: number;
|
|
59
|
+
data: VirtualMeshData;
|
|
60
|
+
vertexBase: number;
|
|
61
|
+
meshletBase: number;
|
|
62
|
+
meshletVertexBase: number;
|
|
63
|
+
triangleBase: number;
|
|
64
|
+
levelBase: number;
|
|
65
|
+
levelCount: number;
|
|
66
|
+
instanceBase: number;
|
|
67
|
+
instanceCount: number;
|
|
68
|
+
cellBase: number;
|
|
69
|
+
cellCount: number;
|
|
70
|
+
uvScale: [number, number];
|
|
71
|
+
uvOffset: [number, number];
|
|
72
|
+
}
|
|
73
|
+
/** Growable typed array with a GPU attribute; growth replaces the attribute (the pool then rebuilds its nodes). */
|
|
74
|
+
declare class GrowBuffer<T extends Float32Array | Uint32Array> {
|
|
75
|
+
private readonly make;
|
|
76
|
+
readonly itemSize: number;
|
|
77
|
+
/** Largest capacity (items) whose binding stays under the pool's byte limit. */
|
|
78
|
+
private readonly maxItems;
|
|
79
|
+
array: T;
|
|
80
|
+
attribute: THREE.StorageBufferAttribute;
|
|
81
|
+
used: number;
|
|
82
|
+
constructor(make: (n: number) => T, itemSize: number, capacity: number,
|
|
83
|
+
/** Largest capacity (items) whose binding stays under the pool's byte limit. */
|
|
84
|
+
maxItems?: number);
|
|
85
|
+
get capacity(): number;
|
|
86
|
+
/** Reserves `count` items; returns the first, or -1 when the buffer must grow first. */
|
|
87
|
+
reserve(count: number): number;
|
|
88
|
+
/** Reallocates to hold at least `needed` items (keeps the contents). */
|
|
89
|
+
grow(needed: number): void;
|
|
90
|
+
/** Marks items [first, first + count) for upload. */
|
|
91
|
+
touch(first: number, count: number): void;
|
|
92
|
+
}
|
|
93
|
+
export declare class GeometryPool {
|
|
94
|
+
private readonly context;
|
|
95
|
+
readonly entries: (PoolEntry | null)[];
|
|
96
|
+
/** Highest used mesh slot + 1 (the prefix pass loops over this many). */
|
|
97
|
+
readonly meshSlots: THREE.UniformNode<"uint", number>;
|
|
98
|
+
readonly cellCount: THREE.UniformNode<"uint", number>;
|
|
99
|
+
readonly maxBytes: number;
|
|
100
|
+
readonly capacity: PoolCapacity;
|
|
101
|
+
readonly vertexData: GrowBuffer<Uint32Array>;
|
|
102
|
+
readonly vertexAttrs: GrowBuffer<Uint32Array>;
|
|
103
|
+
readonly meshletBounds: GrowBuffer<Float32Array>;
|
|
104
|
+
readonly meshletInfo: GrowBuffer<Uint32Array>;
|
|
105
|
+
readonly meshletVertices: GrowBuffer<Uint32Array>;
|
|
106
|
+
readonly meshletTriangles: GrowBuffer<Uint32Array>;
|
|
107
|
+
readonly meshTable: GrowBuffer<Float32Array>;
|
|
108
|
+
readonly instances: GrowBuffer<Float32Array>;
|
|
109
|
+
readonly cells: GrowBuffer<Float32Array>;
|
|
110
|
+
private meshSlotCapacity;
|
|
111
|
+
private levelUsed;
|
|
112
|
+
private countersAttribute;
|
|
113
|
+
drawArgsAttribute: THREE.IndirectStorageBufferAttribute;
|
|
114
|
+
cameraIndex: THREE.StorageBufferAttribute;
|
|
115
|
+
shadowIndex: THREE.StorageBufferAttribute;
|
|
116
|
+
computeNodes: THREE.ComputeNode[];
|
|
117
|
+
positionNode: Node;
|
|
118
|
+
/** Bumped on every rebuild: meshes re-point their geometry and material when it changes. */
|
|
119
|
+
version: number;
|
|
120
|
+
private needsRebuild;
|
|
121
|
+
private indexBuffersReady;
|
|
122
|
+
pipelineState: 'none' | 'compiling' | 'ready';
|
|
123
|
+
/** The cut needs recomputing (camera moved, instances or settings changed). */
|
|
124
|
+
dirty: boolean;
|
|
125
|
+
/** True once a cut was computed (its camera index buffer can then serve as occluders). */
|
|
126
|
+
hasCut: boolean;
|
|
127
|
+
constructor(context: VirtualGeometry, capacity: PoolCapacity, maxBytes: number);
|
|
128
|
+
/** Bytes the geometry of `data` takes in this pool (the largest single binding is what must fit). */
|
|
129
|
+
static bytesOf(data: VirtualMeshData): number;
|
|
130
|
+
/** True when `data` (and `instanceCount` instances) still fit under the binding-size limit. */
|
|
131
|
+
fits(data: VirtualMeshData, instanceCount: number): boolean;
|
|
132
|
+
/** Appends a mesh's geometry and instances. Returns its entry (`slot` is its index in the mesh table). */
|
|
133
|
+
add(data: VirtualMeshData, matrices: Float32Array, colors: Float32Array | undefined, gpuToInput: Uint32Array): PoolEntry;
|
|
134
|
+
/** Frees a mesh's slot (its buffer ranges are reclaimed when the pool is next repacked). */
|
|
135
|
+
remove(entry: PoolEntry): void;
|
|
136
|
+
private growMeshSlots;
|
|
137
|
+
private writeGeometry;
|
|
138
|
+
/** Writes a mesh record: bounding spheres, levers, level table location, uv packing, flags. */
|
|
139
|
+
writeMeshRecord(e: PoolEntry, o: {
|
|
140
|
+
maxDrawDistance: number;
|
|
141
|
+
minPixelRadius: number;
|
|
142
|
+
enabled: number;
|
|
143
|
+
shadow: number;
|
|
144
|
+
occlusion: number;
|
|
145
|
+
}): void;
|
|
146
|
+
/** Updates some of a mesh's per-frame levers (only what changed is uploaded). */
|
|
147
|
+
writeFlags(e: PoolEntry, o: {
|
|
148
|
+
maxDrawDistance?: number;
|
|
149
|
+
minPixelRadius?: number;
|
|
150
|
+
enabled?: number;
|
|
151
|
+
shadow?: number;
|
|
152
|
+
occlusion?: number;
|
|
153
|
+
}): void;
|
|
154
|
+
readFlag(e: PoolEntry, index: 'maxDrawDistance' | 'minPixelRadius' | 'enabled' | 'shadow' | 'occlusion'): number;
|
|
155
|
+
private writeInstances;
|
|
156
|
+
/** Writes one instance matrix (GPU order index within the mesh). Call `commitInstances` afterwards. */
|
|
157
|
+
setMatrix(e: PoolEntry, gpuIndex: number, matrix: THREE.Matrix4): void;
|
|
158
|
+
/** Uploads changed instances and refreshes the bounds of the given cells. */
|
|
159
|
+
commitInstances(e: PoolEntry, cells: Iterable<number>): void;
|
|
160
|
+
/**
|
|
161
|
+
* Bounding sphere of one cell's instances: the AABB of their world-space object spheres gives the center, and
|
|
162
|
+
* the radius reaches every instance sphere. Both radii are inflated a little so float32 rounding on the GPU can
|
|
163
|
+
* never make the cell test stricter than the per-instance test.
|
|
164
|
+
*/
|
|
165
|
+
private computeCell;
|
|
166
|
+
/**
|
|
167
|
+
* Index buffers are written by compute and read by draws. three.js picks GPU buffer usage from the first use,
|
|
168
|
+
* so create them as index buffers (INDEX | STORAGE) before any compute. Also rebuilds the nodes after growth.
|
|
169
|
+
*/
|
|
170
|
+
prepare(renderer: THREE.WebGPURenderer): void;
|
|
171
|
+
/** Builds the nodes if the pool's buffers grew since the last build (or it was never built). */
|
|
172
|
+
ensureBuilt(): void;
|
|
173
|
+
/** Recreates the per-frame buffers and every node (after the pool's buffers grew). */
|
|
174
|
+
build(): void;
|
|
175
|
+
/** Stats of the last computed cut, from a readback of the counters buffer. */
|
|
176
|
+
readCounters(renderer: THREE.WebGPURenderer): Promise<{
|
|
177
|
+
camera: {
|
|
178
|
+
selected: number;
|
|
179
|
+
drawn: number;
|
|
180
|
+
survived: number;
|
|
181
|
+
requestedTriangles: number;
|
|
182
|
+
};
|
|
183
|
+
shadow: {
|
|
184
|
+
selected: number;
|
|
185
|
+
drawn: number;
|
|
186
|
+
survived: number;
|
|
187
|
+
requestedTriangles: number;
|
|
188
|
+
};
|
|
189
|
+
occluded: number;
|
|
190
|
+
perMesh: (c: number, slot: number) => number;
|
|
191
|
+
}>;
|
|
192
|
+
/** Byte offset of a mesh's draw args for a cut (0: camera, 1: shadow) in `drawArgsAttribute`. */
|
|
193
|
+
drawArgsOffset(cut: number, slot: number): number;
|
|
194
|
+
dispose(): void;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* World-space bounding sphere of the instance whose column-major matrix starts at `base`, computed the same way
|
|
198
|
+
* the instance pass does: center = model * sphere center, radius = sphere radius * max column length.
|
|
199
|
+
*/
|
|
200
|
+
export declare function instanceSphere(m: ArrayLike<number>, base: number, sphere: number[], out: Float64Array): void;
|
|
201
|
+
export declare function encodeOctNormal(nx: number, ny: number, nz: number): number;
|
|
202
|
+
/** xyz as float bits + octahedral-encoded normal, 4 x u32 per vertex. */
|
|
203
|
+
export declare function packVertices(positions: Float32Array, normals: Float32Array): Uint32Array;
|
|
204
|
+
/**
|
|
205
|
+
* uv per vertex as two unorm16 in one u32, quantized over the bounds of all UVs (also tiling UVs outside 0..1).
|
|
206
|
+
* Decode: unpackUnorm2x16(packed) * scale + offset. One step is 1/65535 of the range: well below a texel of a 4K
|
|
207
|
+
* texture for 0..1 UVs.
|
|
208
|
+
*/
|
|
209
|
+
export declare function packUvs(uvs: Float32Array): {
|
|
210
|
+
packed: Uint32Array;
|
|
211
|
+
scale: [number, number];
|
|
212
|
+
offset: [number, number];
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Instance indices sorted along a 30-bit Morton curve over the instance centers, so that consecutive slots are
|
|
216
|
+
* spatially close and a fixed-size run of slots forms a compact cell.
|
|
217
|
+
*/
|
|
218
|
+
export declare function mortonOrder(matrices: Float32Array, sphere: number[]): Uint32Array;
|
|
219
|
+
export {};
|