three-virtual-geometry 0.0.0-stage → 0.1.0
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 +87 -4
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Occlusion culling against a hierarchical depth buffer (HZB), on by default.
|
|
3
|
+
*
|
|
4
|
+
* Per frame, before the cut is computed:
|
|
5
|
+
* 1. occluder pass - the occluder meshes' previous-frame camera cuts (their index buffers still hold them)
|
|
6
|
+
* are drawn depth-only with the CURRENT camera into a small depth target. That geometry was visible last
|
|
7
|
+
* frame, so it is a subset of the real occluders.
|
|
8
|
+
* 2. HZB build - mip 0 is that depth (copied into a storage buffer), each further mip is the MAX of its
|
|
9
|
+
* 2x2 children: a texel only hides what lies behind all of its occluders.
|
|
10
|
+
* 3. cull - an instance or meshlet sphere is dropped from the camera cut when its projected box, read at
|
|
11
|
+
* the mip where it covers at most 2x2 texels, is entirely behind the max depth there.
|
|
12
|
+
*
|
|
13
|
+
* Why it is safe: removing occluders can only push the depth buffer back, so anything hidden by the subset
|
|
14
|
+
* is hidden by the full scene. Stale or missing occluders cost efficiency, never correctness. The occluders
|
|
15
|
+
* are LOD-simplified (they may bulge up to the error threshold in front of the true surface), so the tested
|
|
16
|
+
* sphere is grown by that error first.
|
|
17
|
+
*
|
|
18
|
+
* Only meshes flagged `occluder` are drawn in step 1 (by default: opaque, single-sided, no alpha test, i.e.
|
|
19
|
+
* solid geometry such as terrain, rocks, buildings; foliage is mostly holes). Every mesh is tested, unless
|
|
20
|
+
* its `occlusionCulling` is off. Shadow cuts are never occlusion culled: a caster hidden from the camera can
|
|
21
|
+
* still shadow something visible.
|
|
22
|
+
*
|
|
23
|
+
* Storage: the whole HZB (a header with each mip's offset and size, then all mips) is one storage buffer,
|
|
24
|
+
* sized for `maxSize`, so nothing is reallocated on resize.
|
|
25
|
+
*/
|
|
26
|
+
import * as THREE from 'three/webgpu';
|
|
27
|
+
import type { VirtualMesh } from './VirtualMesh';
|
|
28
|
+
type Node = any;
|
|
29
|
+
export declare class OcclusionCulling {
|
|
30
|
+
/** Lever: set false to turn occlusion culling off (no occluder pass, no HZB, nothing culled by it). */
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Lever: decide by measurement. Whether occlusion pays off depends on the scene and the GPU: the occluder
|
|
34
|
+
* pass has a fixed cost, which wins in cities and interiors and loses in open landscapes. With `auto`,
|
|
35
|
+
* frame times are compared with occlusion off and on (`autoProbeFrames` each), the faster setting is kept
|
|
36
|
+
* for `autoHoldFrames`, then measured again. Within 3% (e.g. both capped by vsync) it stays off.
|
|
37
|
+
* Set false to keep it on whenever `enabled`.
|
|
38
|
+
*/
|
|
39
|
+
auto: boolean;
|
|
40
|
+
autoProbeFrames: number;
|
|
41
|
+
autoHoldFrames: number;
|
|
42
|
+
/** The auto setting currently in use (true: occlusion on). */
|
|
43
|
+
autoChoice: boolean;
|
|
44
|
+
private phase;
|
|
45
|
+
private phaseFrames;
|
|
46
|
+
private samples;
|
|
47
|
+
private offMean;
|
|
48
|
+
private lastTick;
|
|
49
|
+
/** Depth resolution relative to the drawing buffer. Lower is cheaper and culls a little less. */
|
|
50
|
+
resolutionScale: number;
|
|
51
|
+
/** Largest HZB dimension in texels (memory ~1.33 * maxSize^2 * 4 bytes). */
|
|
52
|
+
readonly maxSize: number;
|
|
53
|
+
/** True when this frame's HZB holds at least one occluder (the cull kernels test against it only then). */
|
|
54
|
+
active: boolean;
|
|
55
|
+
/** 1 while this frame's HZB is valid; the cull kernels only occlusion-cull then. */
|
|
56
|
+
readonly activeNode: THREE.UniformNode<"uint", number>;
|
|
57
|
+
private readonly depthScene;
|
|
58
|
+
private readonly proxies;
|
|
59
|
+
private readonly projection;
|
|
60
|
+
private readonly near;
|
|
61
|
+
private readonly levelCapacity;
|
|
62
|
+
private readonly levelOffsets;
|
|
63
|
+
private readonly hzbAttribute;
|
|
64
|
+
private readonly hzbWrite;
|
|
65
|
+
private readonly hzbRead;
|
|
66
|
+
/** Copy + reduce kernels for the current depth size, dispatched with this frame's cut (one submission). */
|
|
67
|
+
private buildNodes;
|
|
68
|
+
private renderTarget;
|
|
69
|
+
private width;
|
|
70
|
+
private height;
|
|
71
|
+
private levels;
|
|
72
|
+
private readonly bufferSize;
|
|
73
|
+
constructor(projection: Node, near: Node, maxSize?: number);
|
|
74
|
+
/**
|
|
75
|
+
* True only when a sphere (view-space center, radius) is certainly hidden. Any doubt (touching the near
|
|
76
|
+
* plane, outside the screen) keeps it.
|
|
77
|
+
*/
|
|
78
|
+
occluded(viewCenter: Node, radius: Node): Node;
|
|
79
|
+
/** Called once per frame by the context: feeds the auto A/B measurement (see `auto`). */
|
|
80
|
+
tick(now: number): void;
|
|
81
|
+
private next;
|
|
82
|
+
private autoWanted;
|
|
83
|
+
/** Depth-only proxy of a mesh's camera cut, created on first use and kept in sync with its pool. */
|
|
84
|
+
private proxyOf;
|
|
85
|
+
unregisterMesh(mesh: VirtualMesh): void;
|
|
86
|
+
/**
|
|
87
|
+
* Whether occlusion culling runs this frame (enabled, a supported camera and depth mode, and the auto tuner
|
|
88
|
+
* wants it). Meshes get their per-mesh flag from this before the cut is computed.
|
|
89
|
+
*/
|
|
90
|
+
wantsFrame(renderer: THREE.WebGPURenderer, camera: THREE.Camera, _frame: number): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Renders the occluders' last camera cuts into the depth target and returns the compute kernels that turn
|
|
93
|
+
* it into the HZB; run them before the cut (the caller prepends them to the same compute submission).
|
|
94
|
+
* Sets `activeNode`, which the cull kernels check: without occluders this frame, nothing is occlusion culled.
|
|
95
|
+
*/
|
|
96
|
+
beginFrame(renderer: THREE.WebGPURenderer, camera: THREE.Camera, meshes: readonly VirtualMesh[]): THREE.ComputeNode[];
|
|
97
|
+
private resize;
|
|
98
|
+
dispose(): void;
|
|
99
|
+
}
|
|
100
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import type { VirtualMesh, VirtualMeshInstances, VirtualMeshOptions } from './VirtualMesh';
|
|
3
|
+
import { OcclusionCulling } from './OcclusionCulling';
|
|
4
|
+
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh';
|
|
5
|
+
import { GeometryPool } from './GeometryPool';
|
|
6
|
+
import { type VirtualGeometryImport, type VirtualGeometryImportOptions } from '../import/fromObject3D';
|
|
7
|
+
export { VG_DEBUG_MODES } from '../constants';
|
|
8
|
+
export interface VirtualGeometryStats {
|
|
9
|
+
drawnMeshlets: number;
|
|
10
|
+
drawnTriangles: number;
|
|
11
|
+
/** Triangles if every instance were drawn at full detail (no LOD, no culling). */
|
|
12
|
+
fullDetailTriangles: number;
|
|
13
|
+
instances: number;
|
|
14
|
+
/** Instances that survived instance culling (frustum, sub-pixel). */
|
|
15
|
+
visibleInstances: number;
|
|
16
|
+
/** Triangles the LOD cut asked for. Above drawnTriangles means some mesh ran out of index buffer. */
|
|
17
|
+
requestedTriangles: number;
|
|
18
|
+
/** True when a mesh dropped meshlets for lack of capacity (visible as flicker). */
|
|
19
|
+
overflow: boolean;
|
|
20
|
+
/** Fullest draw buffer of any mesh, as a fraction of its capacity (above 1: overflow). */
|
|
21
|
+
capacityUse: number;
|
|
22
|
+
/** Instances hidden from the camera by occlusion culling. */
|
|
23
|
+
occludedInstances: number;
|
|
24
|
+
/** Triangles drawn into shadow maps (the coarser shadow cut). */
|
|
25
|
+
shadowTriangles: number;
|
|
26
|
+
}
|
|
27
|
+
export interface VirtualGeometryOptions {
|
|
28
|
+
/** Max triangles drawn per frame per pool (camera). Default 10M (120 MB of indices, under the 128 MB binding limit). */
|
|
29
|
+
maxTriangles?: number;
|
|
30
|
+
/** Max triangles drawn into shadow maps per frame per pool. Default 4M. */
|
|
31
|
+
maxShadowTriangles?: number;
|
|
32
|
+
/** Max meshlets drawn per frame per pool. Default 1M. */
|
|
33
|
+
maxMeshlets?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Largest storage-buffer binding a pool may use, in bytes. A pool that would exceed it is closed and a new one
|
|
36
|
+
* opened. Default 120 MB (WebGPU guarantees 128 MB; raise it if you request a higher device limit).
|
|
37
|
+
*/
|
|
38
|
+
maxPoolBytes?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare class VirtualGeometry {
|
|
41
|
+
readonly viewMatrix: THREE.UniformNode<"mat4", THREE.Matrix4>;
|
|
42
|
+
readonly projectionMatrix: THREE.UniformNode<"mat4", THREE.Matrix4>;
|
|
43
|
+
/** `projection[1][1] * viewportHeight / 2`: converts view-space error to pixels. */
|
|
44
|
+
readonly projScale: THREE.UniformNode<"float", number>;
|
|
45
|
+
readonly near: THREE.UniformNode<"float", number>;
|
|
46
|
+
/** Max projected error, in pixels, a meshlet may have to be drawn. */
|
|
47
|
+
readonly errorThreshold: THREE.UniformNode<"float", number>;
|
|
48
|
+
/**
|
|
49
|
+
* Lever: target number of drawn triangles per frame. When > 0, the error threshold is adjusted (slowly, see
|
|
50
|
+
* adaptThreshold) to keep the drawn triangle count near this budget. Off by default: a fixed threshold gives
|
|
51
|
+
* the steadiest image, because LODs then only switch with distance, never all at once.
|
|
52
|
+
*/
|
|
53
|
+
triangleBudget: number;
|
|
54
|
+
/** Bounds for the automatically adjusted error threshold, in pixels. */
|
|
55
|
+
thresholdRange: [number, number];
|
|
56
|
+
/** Latest GPU readback, updated every `statsInterval` frames while a budget or HUD is active. */
|
|
57
|
+
lastStats: VirtualGeometryStats | null;
|
|
58
|
+
statsInterval: number;
|
|
59
|
+
/** Frames updated so far. */
|
|
60
|
+
frameCount: number;
|
|
61
|
+
/** The user's threshold while the capacity guard holds a coarser one (no budget), else null. */
|
|
62
|
+
private guardBase;
|
|
63
|
+
/** Consecutive stats readbacks with the drawn triangles outside the budget's deadband. */
|
|
64
|
+
private outOfBand;
|
|
65
|
+
private guardWritten;
|
|
66
|
+
private statsPending;
|
|
67
|
+
readonly frustumCulling: THREE.UniformNode<"uint", number>;
|
|
68
|
+
readonly debugMode: THREE.UniformNode<"uint", number>;
|
|
69
|
+
/**
|
|
70
|
+
* Instances whose bounding sphere projects to a smaller radius than this (pixels) are skipped. 0 disables.
|
|
71
|
+
* The sphere is never smaller than the object, so at 0.7 nothing wider than ~1.4 px on screen is dropped.
|
|
72
|
+
*/
|
|
73
|
+
readonly minPixelRadius: THREE.UniformNode<"float", number>;
|
|
74
|
+
readonly frustumPlanes: THREE.UniformNode<"vec4", THREE.Vector4>[];
|
|
75
|
+
/**
|
|
76
|
+
* Direction * length that light travels (e.g. sun direction * 300). When non-zero, frustum culling
|
|
77
|
+
* keeps meshlets whose shadow, swept along this vector, can reach the view frustum, so shadow
|
|
78
|
+
* casters just outside the view still cast shadows. Zero disables it.
|
|
79
|
+
*/
|
|
80
|
+
readonly shadowSweep: THREE.UniformNode<"vec3", THREE.Vector3>;
|
|
81
|
+
/**
|
|
82
|
+
* Set `shadowSweep` automatically from the shadow cameras of the lights that render the meshes (direction
|
|
83
|
+
* of an orthographic shadow camera, length = its larger extent). Turn off to set `shadowSweep` yourself.
|
|
84
|
+
*/
|
|
85
|
+
autoShadowSweep: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Shadow maps use a coarser cut: the error threshold times this. Shadows are soft and seen from the camera
|
|
88
|
+
* at a distance, so this saves most of the shadow-pass triangles with no visible change. 1 = camera detail.
|
|
89
|
+
*/
|
|
90
|
+
readonly shadowErrorScale: THREE.UniformNode<"float", number>;
|
|
91
|
+
private readonly detectedSweep;
|
|
92
|
+
private detectedSweepFrame;
|
|
93
|
+
/**
|
|
94
|
+
* Occlusion culling (on by default): geometry hidden behind solid meshes is not drawn. Levers:
|
|
95
|
+
* `occlusion.enabled`, `occlusion.resolutionScale`, and per mesh `occluder` / `occlusionCulling`.
|
|
96
|
+
*/
|
|
97
|
+
readonly occlusion: OcclusionCulling;
|
|
98
|
+
/** Skip LOD selection/culling and keep drawing the last selection (for inspecting the cut). */
|
|
99
|
+
freeze: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Run `update()` automatically before every render of a scene that contains this context's meshes, with the
|
|
102
|
+
* camera that render uses (default true). Shadow-map renders reuse the cut of the camera being rendered.
|
|
103
|
+
* Calling `update()` yourself still works: a render right after it with the same camera does not repeat it.
|
|
104
|
+
*/
|
|
105
|
+
autoUpdate: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Measure the error threshold in CSS pixels (default) instead of device pixels. On a 2x display,
|
|
108
|
+
* device pixels would request ~4x the triangles for detail nobody can see.
|
|
109
|
+
*/
|
|
110
|
+
errorInCssPixels: boolean;
|
|
111
|
+
readonly meshes: VirtualMesh[];
|
|
112
|
+
/** Shared buffers and compute passes; meshes go into the first pool with room (see GeometryPool). */
|
|
113
|
+
readonly pools: GeometryPool[];
|
|
114
|
+
private readonly capacity;
|
|
115
|
+
private readonly maxPoolBytes;
|
|
116
|
+
constructor(options?: VirtualGeometryOptions);
|
|
117
|
+
private readonly frustum;
|
|
118
|
+
private readonly projScreen;
|
|
119
|
+
private readonly size;
|
|
120
|
+
/** Compute nodes dispatched this frame (reused to avoid per-frame allocation). */
|
|
121
|
+
private readonly computeList;
|
|
122
|
+
private readonly dispatchedPools;
|
|
123
|
+
/** Frames where `update` ran no compute pass because nothing the cut depends on changed. */
|
|
124
|
+
skippedFrames: number;
|
|
125
|
+
/** Inputs of the cut as of the last frame that changed them (NaN until the first frame). */
|
|
126
|
+
private readonly cutInputs;
|
|
127
|
+
private readonly cutInputsScratch;
|
|
128
|
+
/** Scenes whose onBeforeRender runs the auto update (installed the first time one of our meshes renders in them). */
|
|
129
|
+
private readonly sceneHooks;
|
|
130
|
+
/** `renderer.info.render.calls` and camera of the last automatic and the last manual update. */
|
|
131
|
+
private autoCall;
|
|
132
|
+
private autoCamera;
|
|
133
|
+
private manualCall;
|
|
134
|
+
private manualCamera;
|
|
135
|
+
private updatingAuto;
|
|
136
|
+
/**
|
|
137
|
+
* Converts every static mesh under `object` (e.g. `gltf.scene`) to VirtualGeometry geometry, in place: meshes sharing
|
|
138
|
+
* geometry and material become one VirtualMesh with many instances, materials are converted to node materials
|
|
139
|
+
* with their textures, and the originals stop rendering. Skinned/morphing meshes, points and lines are left
|
|
140
|
+
* alone. Returns the created meshes and a `dispose()` that restores the originals. See `VirtualGeometryImportOptions`.
|
|
141
|
+
*/
|
|
142
|
+
add(object: THREE.Object3D, options?: VirtualGeometryImportOptions): Promise<VirtualGeometryImport>;
|
|
143
|
+
/** Create a renderable mesh from a built DAG. Add the result to any three.js scene. */
|
|
144
|
+
createMesh(data: VirtualMeshData, material: THREE.NodeMaterial, instances: VirtualMeshInstances, options?: VirtualMeshOptions): VirtualMesh;
|
|
145
|
+
/**
|
|
146
|
+
* Lever: compile a pool's compute pipelines in the background before its first dispatch (default), so adding
|
|
147
|
+
* meshes never freezes the page; they appear once ready. False: compile on first use (a hitch).
|
|
148
|
+
*/
|
|
149
|
+
asyncCompile: boolean;
|
|
150
|
+
private compilePool;
|
|
151
|
+
/** Builds pools whose buffers grew and points their meshes at the new buffers. */
|
|
152
|
+
private preparePools;
|
|
153
|
+
/**
|
|
154
|
+
* Optional, for loading screens: compiles every pipeline the scene needs (compute and render), so the first
|
|
155
|
+
* frames neither freeze nor show meshes popping in. Call after adding the meshes, before the first render.
|
|
156
|
+
*/
|
|
157
|
+
compileAsync(renderer: THREE.WebGPURenderer, scene: THREE.Object3D, camera: THREE.Camera): Promise<void>;
|
|
158
|
+
/** Called by meshes from onBeforeShadow: remembers the light direction for `autoShadowSweep`. */
|
|
159
|
+
noteShadowCamera(shadowCamera: THREE.Camera): void;
|
|
160
|
+
/** Puts the mesh's geometry and instances into the first pool with room (a new pool when none has). */
|
|
161
|
+
register(mesh: VirtualMesh, instances: VirtualMeshInstances): void;
|
|
162
|
+
/**
|
|
163
|
+
* Auto update, from a VirtualMesh's onBeforeRender. The first time, this also hooks the scene's onBeforeRender,
|
|
164
|
+
* which runs before the render pass starts (so shadow maps rendered inside the pass already see the new cut);
|
|
165
|
+
* later frames update from there. A compute submitted from inside a pass still runs before that pass on the
|
|
166
|
+
* GPU, because the pass is submitted when it ends.
|
|
167
|
+
*/
|
|
168
|
+
private autoUpdateFor;
|
|
169
|
+
unregister(mesh: VirtualMesh): void;
|
|
170
|
+
/** Dispose every mesh created by this context. */
|
|
171
|
+
dispose(): void;
|
|
172
|
+
/**
|
|
173
|
+
* Runs LOD selection + culling on the GPU for `camera`. With `autoUpdate` (default) the renderer calls this
|
|
174
|
+
* for you before each render; call it yourself before `renderer.render` when `autoUpdate` is false.
|
|
175
|
+
* When the camera, the settings and every mesh's instances are unchanged, the compute passes are
|
|
176
|
+
* skipped and the previous cut (index buffer + draw args) is drawn again.
|
|
177
|
+
*/
|
|
178
|
+
update(renderer: THREE.WebGPURenderer, camera: THREE.PerspectiveCamera): void;
|
|
179
|
+
/**
|
|
180
|
+
* True when anything the GPU cut reads from the context differs from the last frame that was computed
|
|
181
|
+
* (and records the new values). Exact comparison on purpose: an unmoved camera recomputes the same
|
|
182
|
+
* matrices bit for bit, while any real change, including a budget-controller step, differs.
|
|
183
|
+
*/
|
|
184
|
+
private cutInputsChanged;
|
|
185
|
+
/**
|
|
186
|
+
* Slow multiplicative controller. Drawn triangles grow roughly with 1/threshold^2. Every change of the
|
|
187
|
+
* threshold re-picks LODs across the whole screen, so it moves rarely (deadband) and in small steps.
|
|
188
|
+
*
|
|
189
|
+
* Draw capacity is a hard constraint: an overflowing mesh drops meshlets in a different order every frame,
|
|
190
|
+
* which flickers. So the controller coarsens *before* a buffer is full, never refines into one, and may go
|
|
191
|
+
* above `thresholdRange[1]` only for capacity, coming back down as soon as there is room.
|
|
192
|
+
*/
|
|
193
|
+
private adaptThreshold;
|
|
194
|
+
/**
|
|
195
|
+
* Without a budget the threshold is the user's, except that draw capacity stays a hard limit: coarsen
|
|
196
|
+
* temporarily before a buffer fills up, and return to the user's value once there is room again.
|
|
197
|
+
*/
|
|
198
|
+
private guardCapacity;
|
|
199
|
+
/** GPU -> CPU readback of the last culling result. Async; never call every frame. One small readback per pool. */
|
|
200
|
+
readStats(renderer: THREE.WebGPURenderer): Promise<VirtualGeometryStats>;
|
|
201
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A renderable set of instances of one preprocessed mesh (a three.js Mesh, so it goes into any scene).
|
|
3
|
+
*
|
|
4
|
+
* The geometry and instances live in a GeometryPool shared with other meshes; the pool's compute passes select
|
|
5
|
+
* the cut for all of its meshes at once (see GeometryPool). This object holds the material and two geometries,
|
|
6
|
+
* the camera cut and the shadow cut, which draw this mesh's region of the pool's index buffers.
|
|
7
|
+
*/
|
|
8
|
+
import * as THREE from 'three/webgpu';
|
|
9
|
+
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh';
|
|
10
|
+
import type { VirtualGeometry } from './VirtualGeometry';
|
|
11
|
+
import { type GeometryPool, type PoolEntry } from './GeometryPool';
|
|
12
|
+
export { vgWorldNormal, encodeOctNormal, packVertices, packUvs } from './GeometryPool';
|
|
13
|
+
export interface VirtualMeshInstances {
|
|
14
|
+
/** 16 floats (column-major Matrix4) per instance. */
|
|
15
|
+
matrices: Float32Array;
|
|
16
|
+
/** Optional rgba tint per instance (multiplied with the material color). */
|
|
17
|
+
colors?: Float32Array;
|
|
18
|
+
}
|
|
19
|
+
export interface VirtualMeshOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Cull distance in world units: instances farther than this are not drawn (and cost almost nothing, since
|
|
22
|
+
* whole cells are rejected at once). They shrink to nothing over the last 15% of the distance, so there is
|
|
23
|
+
* no visible pop. Default: Infinity. Can be changed later via `mesh.maxDrawDistance`.
|
|
24
|
+
*/
|
|
25
|
+
maxDrawDistance?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Hide instances whose bounding sphere is smaller than this many pixels (radius) on screen. Combined with
|
|
28
|
+
* `VirtualGeometry.minPixelRadius` (the larger one wins). Default: 0. Also settable via `mesh.minPixelRadius`.
|
|
29
|
+
*/
|
|
30
|
+
minPixelRadius?: number;
|
|
31
|
+
/** @deprecated Draw capacity is shared per pool now: see `VirtualGeometryOptions`. Ignored. */
|
|
32
|
+
maxDrawnMeshlets?: number;
|
|
33
|
+
/** @deprecated Draw capacity is shared per pool now: see `VirtualGeometryOptions`. Ignored. */
|
|
34
|
+
maxDrawnTriangles?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare class VirtualMesh extends THREE.Mesh<THREE.BufferGeometry, THREE.NodeMaterial> {
|
|
37
|
+
readonly isVirtualMesh = true;
|
|
38
|
+
readonly data: VirtualMeshData;
|
|
39
|
+
readonly instanceCount: number;
|
|
40
|
+
readonly fullDetailTriangles: number;
|
|
41
|
+
/** The pool holding this mesh's geometry and instances, and its record there. Set by `VirtualGeometry`. */
|
|
42
|
+
pool: GeometryPool;
|
|
43
|
+
entry: PoolEntry;
|
|
44
|
+
/** Geometries drawing this mesh's region of the pool's camera and shadow index buffers. */
|
|
45
|
+
readonly cameraGeometry: THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>;
|
|
46
|
+
readonly shadowGeometry: THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>;
|
|
47
|
+
/** `context.frameCount` of the last shadow-map render of this mesh. */
|
|
48
|
+
lastShadowFrame: number;
|
|
49
|
+
/**
|
|
50
|
+
* Lever: draw this mesh into the occlusion depth pass, so it can hide other geometry. Default: solid
|
|
51
|
+
* materials (opaque, single-sided, no alpha test). Foliage is mostly holes, so it only gets tested.
|
|
52
|
+
*/
|
|
53
|
+
occluder: boolean;
|
|
54
|
+
/** Lever: let occlusion culling remove hidden instances and meshlets of this mesh. */
|
|
55
|
+
occlusionCulling: boolean;
|
|
56
|
+
/** GPU instance slot of each caller instance (inverse of the Morton order). */
|
|
57
|
+
readonly gpuIndexOf: Uint32Array;
|
|
58
|
+
/** Morton order: caller instance of each GPU slot. */
|
|
59
|
+
readonly gpuToInput: Uint32Array;
|
|
60
|
+
private readonly dirtyCells;
|
|
61
|
+
private drawingShadow;
|
|
62
|
+
private poolVersion;
|
|
63
|
+
private readonly context;
|
|
64
|
+
/** Cull distance in world units (Infinity: never). See `VirtualMeshOptions.maxDrawDistance`. */
|
|
65
|
+
get maxDrawDistance(): number;
|
|
66
|
+
set maxDrawDistance(distance: number);
|
|
67
|
+
/** Per-mesh minimum on-screen radius in pixels. See `VirtualMeshOptions.minPixelRadius`. */
|
|
68
|
+
get minPixelRadius(): number;
|
|
69
|
+
set minPixelRadius(pixels: number);
|
|
70
|
+
/** The pool's per-frame draw capacity (shared by every mesh of the pool). */
|
|
71
|
+
get capacity(): {
|
|
72
|
+
meshlets: number;
|
|
73
|
+
triangles: number;
|
|
74
|
+
};
|
|
75
|
+
constructor(context: VirtualGeometry, data: VirtualMeshData, material: THREE.NodeMaterial, instances: VirtualMeshInstances, options?: VirtualMeshOptions);
|
|
76
|
+
/** Points the geometries and the material at the pool's current buffers and nodes (after a pool rebuild). */
|
|
77
|
+
syncPool(): void;
|
|
78
|
+
/** Force the next `VirtualGeometry.update` to recompute the cut. */
|
|
79
|
+
markCutDirty(): void;
|
|
80
|
+
/** Update one instance transform. Call `commitInstances()` after a batch of changes. */
|
|
81
|
+
setMatrixAt(index: number, matrix: THREE.Matrix4): void;
|
|
82
|
+
commitInstances(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Stop rendering this mesh and drop it from its context. The GPU buffers are released when the
|
|
85
|
+
* renderer frees them; call this before removing the object from the scene for good.
|
|
86
|
+
*/
|
|
87
|
+
dispose(context?: VirtualGeometry): void;
|
|
88
|
+
/** Triangles drawn for this mesh by the last computed cut (camera and shadow), from one readback. */
|
|
89
|
+
readStats(renderer: THREE.WebGPURenderer): Promise<{
|
|
90
|
+
drawnTriangles: number;
|
|
91
|
+
shadowTriangles: number;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh';
|
|
2
|
+
/** Camera parameters the LOD test needs. Same values the GPU pass uses. */
|
|
3
|
+
export interface CutView {
|
|
4
|
+
/** Column-major world-to-view matrix (camera.matrixWorldInverse.elements). */
|
|
5
|
+
viewMatrix: ArrayLike<number>;
|
|
6
|
+
/** projection[1][1] * viewportHeight / 2: converts view-space error to pixels. */
|
|
7
|
+
projScale: number;
|
|
8
|
+
near: number;
|
|
9
|
+
/**
|
|
10
|
+
* Uniform scale of the instance, applied to the sphere radii and errors as the GPU does. `viewMatrix`
|
|
11
|
+
* must then include the instance's scale in its centers too (view * model). Default 1.
|
|
12
|
+
*/
|
|
13
|
+
scale?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* CPU reference for the GPU LOD selection (no frustum culling, identity model matrix).
|
|
17
|
+
* A meshlet is in the cut when its own projected error is within `threshold` pixels and its
|
|
18
|
+
* parent's projected error is not. Returns one flag per meshlet.
|
|
19
|
+
*
|
|
20
|
+
* Use it to test the DAG (see `verifyCutCoverage`) or to pick LODs on the CPU, e.g. for physics or AI.
|
|
21
|
+
*/
|
|
22
|
+
export declare function selectCut(data: VirtualMeshData, view: CutView, threshold: number): Uint8Array;
|
|
23
|
+
/**
|
|
24
|
+
* Checks that a cut has no holes: every leaf meshlet (LOD 0) is covered by a selected meshlet on its
|
|
25
|
+
* path to the coarsest level (itself or one of its replacements, recursively).
|
|
26
|
+
* Returns the indices of uncovered leaves (empty array = valid cut).
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyCutCoverage(data: VirtualMeshData, selected: Uint8Array): number[];
|
|
29
|
+
/**
|
|
30
|
+
* CPU reference for the GPU instance pass: the contiguous range of meshlets (sorted by LOD level)
|
|
31
|
+
* that can possibly pass the LOD test for an instance, from distance bounds alone.
|
|
32
|
+
*
|
|
33
|
+
* Every LOD and parent sphere lies inside `lodBoundsSphere`, so the distance to any of them is within
|
|
34
|
+
* [D - R, D + R]. A level can contribute only if its smallest own error could be within the threshold
|
|
35
|
+
* at the farthest distance and its largest parent error could exceed it at the nearest.
|
|
36
|
+
* Returns [start, end) or [0, 0] when nothing can be selected.
|
|
37
|
+
*/
|
|
38
|
+
export declare function instanceMeshletRange(data: VirtualMeshData, view: CutView, threshold: number): [number, number];
|
|
39
|
+
/**
|
|
40
|
+
* CPU reference for the GPU fast path of the instance pass. Returns the range [start, end) when every
|
|
41
|
+
* meshlet in it is guaranteed to be selected by the LOD test, for any distance the instance can have, and
|
|
42
|
+
* the range is at most FAST_PATH_MAX_MESHLETS long. Otherwise returns null (use the per-meshlet pass).
|
|
43
|
+
*
|
|
44
|
+
* Guarantee per level, over the distance band [dMin, dMax] of `instanceMeshletRange`:
|
|
45
|
+
* - largest own error projects to <= threshold at dMin (its closest possible distance), and
|
|
46
|
+
* - smallest parent error projects to > threshold at dMax (its farthest possible distance).
|
|
47
|
+
* A small relative margin keeps float rounding on the GPU from flipping a borderline meshlet.
|
|
48
|
+
* The caller must also check that the instance is fully inside the frustum (see `cullBoundsSphere`).
|
|
49
|
+
*/
|
|
50
|
+
export declare function instanceGuaranteedRange(data: VirtualMeshData, view: CutView, threshold: number): [number, number] | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texture coordinates for materials on a VirtualMesh.
|
|
3
|
+
*
|
|
4
|
+
* A VirtualMesh pulls its vertices from storage buffers, so its placeholder geometry has no `uv` attribute and
|
|
5
|
+
* three's default texture lookups (`uv()`) would read nothing. The vertex stage writes the pulled UV into the
|
|
6
|
+
* `vgUv` varying, and `bindVirtualGeometryTextures` points the material's textures at it.
|
|
7
|
+
*/
|
|
8
|
+
import * as THREE from 'three/webgpu';
|
|
9
|
+
type Node = any;
|
|
10
|
+
/** Interpolated texture coordinates of the current fragment (vec2), for custom material nodes. */
|
|
11
|
+
export declare const vgUv: THREE.PropertyNode<"vec2">;
|
|
12
|
+
/** Texture lookup at `vgUv`, with the texture's offset/repeat/rotation applied like three's own maps. */
|
|
13
|
+
export declare function vgTexture(map: THREE.Texture): Node;
|
|
14
|
+
/**
|
|
15
|
+
* Normal map without precomputed tangents: the tangent frame comes from screen-space derivatives of the view
|
|
16
|
+
* position and `vgUv` (same construction as three's derivative tangents, http://www.thetenthplanet.de/archives/1180).
|
|
17
|
+
*/
|
|
18
|
+
export declare function vgNormalMap(map: THREE.Texture, scale: Node, type?: THREE.NormalMapTypes): Node;
|
|
19
|
+
/**
|
|
20
|
+
* Makes a material's textures sample `vgUv`. Called by VirtualMesh on the material it takes over.
|
|
21
|
+
*
|
|
22
|
+
* - `map` and `alphaMap` move into `colorNode` (and are cleared on the material): the shadow pass reads
|
|
23
|
+
* `colorNode.a` for alpha-tested shadows, but would sample the material's `map` with the missing `uv()`.
|
|
24
|
+
* - Normal, bump and clearcoat normal maps become explicit normal nodes, because three builds normals in a
|
|
25
|
+
* context that ignores custom UVs.
|
|
26
|
+
* - Every other texture lookup without explicit coordinates (roughness, metalness, emissive, AO, light,
|
|
27
|
+
* clearcoat, sheen, transmission maps, textures in your own nodes, ...) gets `vgUv` through
|
|
28
|
+
* `material.contextNode`. A `getUV` in your own `contextNode` takes precedence.
|
|
29
|
+
*
|
|
30
|
+
* Nodes the material already has are kept: only properties three would otherwise read implicitly are rewired.
|
|
31
|
+
*/
|
|
32
|
+
export declare function bindVirtualGeometryTextures(material: THREE.NodeMaterial): void;
|
|
33
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* three-virtual-geometry: Virtual geometry for three.js WebGPU.
|
|
3
|
+
*
|
|
4
|
+
* Quick start:
|
|
5
|
+
*
|
|
6
|
+
* import { VirtualGeometry } from 'three-virtual-geometry';
|
|
7
|
+
*
|
|
8
|
+
* const vg = new VirtualGeometry(); // once
|
|
9
|
+
* await vg.add(gltf.scene); // converts its static meshes in place (materials, textures, instancing)
|
|
10
|
+
* scene.add(gltf.scene);
|
|
11
|
+
* renderer.render(scene, camera); // LOD selection and culling run automatically before each render
|
|
12
|
+
*
|
|
13
|
+
* Lower level: `buildVirtualMeshCached(fromBufferGeometry(geometry))` once per mesh, then
|
|
14
|
+
* `vg.createMesh(data, material, { matrices })` (16 floats per instance) and add it to a scene.
|
|
15
|
+
*/
|
|
16
|
+
export { buildVirtualMesh } from './core/preprocess/buildVirtualMesh';
|
|
17
|
+
export type { VirtualMeshBuildOptions, VirtualMeshData, VirtualMeshSource } from './core/preprocess/buildVirtualMesh';
|
|
18
|
+
export { partitionMeshlets } from './core/preprocess/partition';
|
|
19
|
+
export { buildVirtualMeshCached, clearVirtualMeshCache, virtualMeshCacheKey, VG_BUILD_VERSION } from './core/io/cache';
|
|
20
|
+
export type { VirtualGeometryCacheOptions, VirtualGeometryCachedBuildOptions } from './core/io/cache';
|
|
21
|
+
export { decodeVirtualMesh, encodeVirtualMesh, loadVirtualMesh, VirtualMeshFormatError, VG_FORMAT_VERSION } from './core/io/serialize';
|
|
22
|
+
export type { VirtualMeshEncodeOptions } from './core/io/serialize';
|
|
23
|
+
export { instanceGuaranteedRange, instanceMeshletRange, selectCut, verifyCutCoverage } from './core/runtime/cut';
|
|
24
|
+
export type { CutView } from './core/runtime/cut';
|
|
25
|
+
export { VirtualMesh, vgWorldNormal } from './core/runtime/VirtualMesh';
|
|
26
|
+
export { vgUv, vgTexture, vgNormalMap, bindVirtualGeometryTextures } from './core/runtime/vgMaterial';
|
|
27
|
+
export { toNodeMaterial, canConvertToNodeMaterial } from './core/import/toNodeMaterial';
|
|
28
|
+
export { virtualMeshesFromObject3D, collectVirtualGeometryGroups, VirtualGeometryImport } from './core/import/fromObject3D';
|
|
29
|
+
export type { VirtualGeometryImportOptions, VirtualGeometryImportGroup, VirtualGeometryBuilder } from './core/import/fromObject3D';
|
|
30
|
+
export type { VirtualMeshInstances, VirtualMeshOptions } from './core/runtime/VirtualMesh';
|
|
31
|
+
export { VirtualGeometry } from './core/runtime/VirtualGeometry';
|
|
32
|
+
export type { VirtualGeometryStats, VirtualGeometryOptions } from './core/runtime/VirtualGeometry';
|
|
33
|
+
export { VG_DEBUG_MODES, MAX_MESHLET_TRIANGLES, MAX_MESHLET_VERTICES } from './core/constants';
|
|
34
|
+
export { fromBufferGeometry, mergeSources } from './source';
|
|
35
|
+
export type { FromGeometryOptions } from './source';
|