@drawcall/lod 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +83 -2
- package/dist/baked/codec.d.ts +10 -0
- package/dist/baked/codec.js +291 -0
- package/dist/baked/codec.js.map +1 -0
- package/dist/baked/coverage.d.ts +3 -0
- package/dist/baked/coverage.js +45 -0
- package/dist/baked/coverage.js.map +1 -0
- package/dist/baked/index.d.ts +51 -0
- package/dist/baked/index.js +151 -0
- package/dist/baked/index.js.map +1 -0
- package/dist/baked/interior/faces.d.ts +26 -0
- package/dist/baked/interior/faces.js +105 -0
- package/dist/baked/interior/faces.js.map +1 -0
- package/dist/baked/interior/material.d.ts +31 -0
- package/dist/baked/interior/material.js +157 -0
- package/dist/baked/interior/material.js.map +1 -0
- package/dist/baked/interior/rooms.d.ts +22 -0
- package/dist/baked/interior/rooms.js +78 -0
- package/dist/baked/interior/rooms.js.map +1 -0
- package/dist/baked/low.d.ts +20 -0
- package/dist/baked/low.js +75 -0
- package/dist/baked/low.js.map +1 -0
- package/dist/baked/raster.d.ts +20 -0
- package/dist/baked/raster.js +71 -0
- package/dist/baked/raster.js.map +1 -0
- package/dist/baked/transfer.d.ts +36 -0
- package/dist/baked/transfer.js +243 -0
- package/dist/baked/transfer.js.map +1 -0
- package/dist/baked/unwrap.d.ts +9 -0
- package/dist/baked/unwrap.js +167 -0
- package/dist/baked/unwrap.js.map +1 -0
- package/dist/baked/xatlas.d.ts +2 -0
- package/dist/baked/xatlas.js +31 -0
- package/dist/baked/xatlas.js.map +1 -0
- package/dist/bounds.d.ts +33 -0
- package/dist/bounds.js +127 -0
- package/dist/bounds.js.map +1 -0
- package/dist/dispose.d.ts +5 -0
- package/dist/dispose.js +28 -0
- package/dist/dispose.js.map +1 -0
- package/dist/fade.d.ts +44 -0
- package/dist/fade.js +78 -0
- package/dist/fade.js.map +1 -0
- package/dist/impostor/bake/atlas.d.ts +51 -0
- package/dist/impostor/bake/atlas.js +190 -0
- package/dist/impostor/bake/atlas.js.map +1 -0
- package/dist/impostor/bake/index.d.ts +50 -0
- package/dist/impostor/bake/index.js +221 -0
- package/dist/impostor/bake/index.js.map +1 -0
- package/dist/impostor/bake/materials.d.ts +29 -0
- package/dist/impostor/bake/materials.js +156 -0
- package/dist/impostor/bake/materials.js.map +1 -0
- package/dist/impostor/bake/source.d.ts +3 -0
- package/dist/impostor/bake/source.js +23 -0
- package/dist/impostor/bake/source.js.map +1 -0
- package/dist/impostor/cache.d.ts +15 -0
- package/dist/impostor/cache.js +44 -0
- package/dist/impostor/cache.js.map +1 -0
- package/dist/impostor/codec.d.ts +14 -0
- package/dist/impostor/codec.js +137 -0
- package/dist/impostor/codec.js.map +1 -0
- package/dist/impostor/impostor.d.ts +67 -0
- package/dist/impostor/impostor.js +79 -0
- package/dist/impostor/impostor.js.map +1 -0
- package/dist/impostor/mesh/card.d.ts +38 -0
- package/dist/impostor/mesh/card.js +79 -0
- package/dist/impostor/mesh/card.js.map +1 -0
- package/dist/impostor/mesh/index.d.ts +66 -0
- package/dist/impostor/mesh/index.js +174 -0
- package/dist/impostor/mesh/index.js.map +1 -0
- package/dist/impostor/mesh/material.d.ts +63 -0
- package/dist/impostor/mesh/material.js +199 -0
- package/dist/impostor/mesh/material.js.map +1 -0
- package/dist/impostor/mesh/surface.d.ts +63 -0
- package/dist/impostor/mesh/surface.js +162 -0
- package/dist/impostor/mesh/surface.js.map +1 -0
- package/dist/impostor/mesh/textures.d.ts +4 -0
- package/dist/impostor/mesh/textures.js +37 -0
- package/dist/impostor/mesh/textures.js.map +1 -0
- package/dist/impostor/octahedral.d.ts +29 -0
- package/dist/impostor/octahedral.js +83 -0
- package/dist/impostor/octahedral.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/instanced/args.d.ts +20 -0
- package/dist/instanced/args.js +77 -0
- package/dist/instanced/args.js.map +1 -0
- package/dist/instanced/bounds.d.ts +50 -0
- package/dist/instanced/bounds.js +271 -0
- package/dist/instanced/bounds.js.map +1 -0
- package/dist/instanced/dirty.d.ts +12 -0
- package/dist/instanced/dirty.js +61 -0
- package/dist/instanced/dirty.js.map +1 -0
- package/dist/instanced/index.d.ts +116 -0
- package/dist/instanced/index.js +573 -0
- package/dist/instanced/index.js.map +1 -0
- package/dist/instanced/meshes.d.ts +14 -0
- package/dist/instanced/meshes.js +54 -0
- package/dist/instanced/meshes.js.map +1 -0
- package/dist/instanced/select.d.ts +84 -0
- package/dist/instanced/select.js +264 -0
- package/dist/instanced/select.js.map +1 -0
- package/dist/lazy.d.ts +64 -0
- package/dist/lazy.js +187 -0
- package/dist/lazy.js.map +1 -0
- package/dist/levels.d.ts +72 -0
- package/dist/levels.js +146 -0
- package/dist/levels.js.map +1 -0
- package/dist/loader/cache.d.ts +15 -0
- package/dist/loader/cache.js +37 -0
- package/dist/loader/cache.js.map +1 -0
- package/dist/loader/dispose.d.ts +3 -0
- package/dist/loader/dispose.js +25 -0
- package/dist/loader/dispose.js.map +1 -0
- package/dist/loader.d.ts +99 -0
- package/dist/loader.js +234 -0
- package/dist/loader.js.map +1 -0
- package/dist/lod.d.ts +78 -0
- package/dist/lod.js +211 -0
- package/dist/lod.js.map +1 -0
- package/dist/material.d.ts +12 -0
- package/dist/material.js +52 -0
- package/dist/material.js.map +1 -0
- package/dist/pause.d.ts +6 -0
- package/dist/pause.js +20 -0
- package/dist/pause.js.map +1 -0
- package/dist/renderer.d.ts +21 -0
- package/dist/renderer.js +83 -0
- package/dist/renderer.js.map +1 -0
- package/dist/simplify.d.ts +24 -0
- package/dist/simplify.js +148 -0
- package/dist/simplify.js.map +1 -0
- package/dist/source/error.d.ts +14 -0
- package/dist/source/error.js +94 -0
- package/dist/source/error.js.map +1 -0
- package/dist/source/frame.d.ts +11 -0
- package/dist/source/frame.js +25 -0
- package/dist/source/frame.js.map +1 -0
- package/dist/source/shading.d.ts +96 -0
- package/dist/source/shading.js +224 -0
- package/dist/source/shading.js.map +1 -0
- package/dist/source/source.d.ts +23 -0
- package/dist/source/source.js +50 -0
- package/dist/source/source.js.map +1 -0
- package/dist/source/surface.d.ts +31 -0
- package/dist/source/surface.js +110 -0
- package/dist/source/surface.js.map +1 -0
- package/dist/source/texels.d.ts +45 -0
- package/dist/source/texels.js +191 -0
- package/dist/source/texels.js.map +1 -0
- package/dist/source/trace.d.ts +42 -0
- package/dist/source/trace.js +153 -0
- package/dist/source/trace.js.map +1 -0
- package/dist/three.d.ts +55 -0
- package/dist/three.js +103 -0
- package/dist/three.js.map +1 -0
- package/dist/tsl.d.ts +45 -0
- package/dist/tsl.js +99 -0
- package/dist/tsl.js.map +1 -0
- package/dist/view.d.ts +30 -0
- package/dist/view.js +75 -0
- package/dist/view.js.map +1 -0
- package/dist/wind/bounds.d.ts +3 -0
- package/dist/wind/bounds.js +47 -0
- package/dist/wind/bounds.js.map +1 -0
- package/dist/wind/hooks.d.ts +5 -0
- package/dist/wind/hooks.js +3 -0
- package/dist/wind/hooks.js.map +1 -0
- package/dist/wind/index.d.ts +57 -0
- package/dist/wind/index.js +196 -0
- package/dist/wind/index.js.map +1 -0
- package/dist/wind/sway.d.ts +29 -0
- package/dist/wind/sway.js +71 -0
- package/dist/wind/sway.js.map +1 -0
- package/package.json +69 -3
- package/skills/lod/SKILL.md +137 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Matrix4, Vector2 } from "three/webgpu";
|
|
2
|
+
import { Fn, dot, length, mat4, max, modelWorldMatrix, normalLocal, sin, uniform, vec2, vec3, vec4 } from "three/tsl";
|
|
3
|
+
import { instanceMatrixOf } from "../fade.js";
|
|
4
|
+
import { affine, column } from "../tsl.js";
|
|
5
|
+
const sways = new WeakMap();
|
|
6
|
+
/** Sway `mesh` as `sway` says. */
|
|
7
|
+
export const setSway = (mesh, sway) => void sways.set(mesh, sway);
|
|
8
|
+
const identity = new Matrix4();
|
|
9
|
+
const still = new Vector2();
|
|
10
|
+
const of = (object) => (object ? sways.get(object) : undefined);
|
|
11
|
+
const time = uniform(0).onObjectUpdate(({ object }) => of(object)?.breeze.time ?? 0);
|
|
12
|
+
const speed = uniform(0).onObjectUpdate(({ object }) => of(object)?.breeze.speed ?? 0);
|
|
13
|
+
const strength = uniform(0).onObjectUpdate(({ object }) => of(object)?.breeze.strength ?? 0);
|
|
14
|
+
const direction = uniform(new Vector2(1, 0)).onObjectUpdate(({ object }) => of(object)?.breeze.direction);
|
|
15
|
+
const shape = uniform(new Vector2()).onObjectUpdate(({ object }) => of(object)?.shape ?? still);
|
|
16
|
+
const placement = uniform(new Matrix4()).onObjectUpdate(({ object }) => of(object)?.placement ?? identity);
|
|
17
|
+
const fromRoot = uniform(new Matrix4()).onObjectUpdate(({ object }) => of(object)?.fromRoot ?? identity);
|
|
18
|
+
/** The sideways displacement per unit of height of a root placed in the world by `root`, in the root's space. */
|
|
19
|
+
function slopeAt(root) {
|
|
20
|
+
const breeze = vec3(direction.x, 0, direction.y);
|
|
21
|
+
// The world breeze in the root's horizontal plane, instance scale and shear included.
|
|
22
|
+
const x = column(root, 0).xyz;
|
|
23
|
+
const z = column(root, 2).xyz;
|
|
24
|
+
const shear = dot(x, z);
|
|
25
|
+
const along = vec2(dot(x, breeze), dot(z, breeze));
|
|
26
|
+
const local = vec2(dot(z, z).mul(along.x).sub(shear.mul(along.y)), dot(x, x).mul(along.y).sub(shear.mul(along.x)));
|
|
27
|
+
const unit = local.div(max(length(local), 1e-12));
|
|
28
|
+
// The phase follows the root's place in the world, so instances sway out of step.
|
|
29
|
+
const phase = dot(column(root, 3).xz, vec2(0.73, 0.51));
|
|
30
|
+
const swing = sin(time
|
|
31
|
+
.mul(speed)
|
|
32
|
+
.mul(2 * Math.PI)
|
|
33
|
+
.add(phase));
|
|
34
|
+
return vec3(unit.x, 0, unit.y).mul(shape.x.mul(strength).mul(swing));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The sway in the mesh's own space (before `instance`): a point p moves by `slope` ×
|
|
38
|
+
* (dot(axis, p) + base), its height above the base.
|
|
39
|
+
*/
|
|
40
|
+
function swayIn(instance) {
|
|
41
|
+
const root = (instance ? modelWorldMatrix.mul(instance) : modelWorldMatrix).mul(fromRoot);
|
|
42
|
+
return {
|
|
43
|
+
slope: affine(fromRoot).apply(slopeAt(root)),
|
|
44
|
+
axis: vec3(column(placement, 0).y, column(placement, 1).y, column(placement, 2).y),
|
|
45
|
+
base: column(placement, 3).y.sub(shape.y),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `position` (a mesh's, after its instance matrix) swayed, and its normal with it. three
|
|
50
|
+
* applies instance matrices first: the sway is carried into the instanced space.
|
|
51
|
+
*/
|
|
52
|
+
export const sway = (position) => Fn((builder) => {
|
|
53
|
+
const instance = instanceMatrixOf(builder.object);
|
|
54
|
+
const { slope, axis, base } = swayIn(instance);
|
|
55
|
+
// In the instanced space the height's gradient is the axis through the inverse
|
|
56
|
+
// transpose, and the slope goes through the instance's linear part.
|
|
57
|
+
const placed = instance && affine(instance);
|
|
58
|
+
const gradient = placed ? placed.normal(axis) : axis;
|
|
59
|
+
const along = placed ? placed.apply(slope) : slope;
|
|
60
|
+
const origin = instance ? column(instance, 3).xyz : vec3(0, 0, 0);
|
|
61
|
+
// First order, as the sway is small: normals turn by the shear's inverse transpose.
|
|
62
|
+
normalLocal.assign(normalLocal.sub(gradient.mul(dot(along, normalLocal))));
|
|
63
|
+
return position.add(along.mul(dot(gradient, position.sub(origin)).add(base)));
|
|
64
|
+
})();
|
|
65
|
+
/** The sway as a matrix in the mesh's own space, for an ImpostorMesh's card (which inverts it). */
|
|
66
|
+
export const swayMatrix = Fn((builder) => {
|
|
67
|
+
const { slope, axis, base } = swayIn(instanceMatrixOf(builder.object));
|
|
68
|
+
const column = (unit, height) => vec4(unit.add(slope.mul(height)), 0);
|
|
69
|
+
return mat4(column(vec3(1, 0, 0), axis.x), column(vec3(0, 1, 0), axis.y), column(vec3(0, 0, 1), axis.z), vec4(slope.mul(base), 1));
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=sway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sway.js","sourceRoot":"","sources":["../../src/wind/sway.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drawcall/lod",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Level of detail for three.js: octahedral impostors, simplified and baked meshes, with dithered switching by on-screen size. WebGPURenderer only.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"three",
|
|
8
|
+
"threejs",
|
|
9
|
+
"lod",
|
|
10
|
+
"hlod",
|
|
11
|
+
"impostor",
|
|
12
|
+
"octahedral-impostor",
|
|
13
|
+
"mesh-simplification",
|
|
14
|
+
"texture-baking"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"skills",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@drawcall/glts": "^2.0.0",
|
|
33
|
+
"fflate": "^0.8.2",
|
|
34
|
+
"ktx-parse": "^2.0.0",
|
|
35
|
+
"meshoptimizer": "^1.3.0",
|
|
36
|
+
"three-mesh-bvh": "^0.9.15",
|
|
37
|
+
"vitecache": "^0.1.2"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"three": ">=0.185.0 <0.186.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^24.13.6",
|
|
44
|
+
"@types/three": "^0.185.0",
|
|
45
|
+
"@vitest/browser-playwright": "4.1.11",
|
|
46
|
+
"@webgpu/types": "^0.1.74",
|
|
47
|
+
"playwright": "1.63.0",
|
|
48
|
+
"three": "^0.185.1",
|
|
49
|
+
"typescript": "^5.9.3",
|
|
50
|
+
"vitest": "^4.0.17",
|
|
51
|
+
"xatlas-wasm": "0.1.3"
|
|
52
|
+
},
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/drawcall-ai/lod.git",
|
|
57
|
+
"directory": "packages/lod"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/drawcall-ai/lod#readme",
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/drawcall-ai/lod/issues"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
68
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
69
|
+
"test": "vitest run",
|
|
70
|
+
"xatlas": "node --experimental-strip-types --no-warnings scripts/xatlas.ts"
|
|
71
|
+
}
|
|
6
72
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lod
|
|
3
|
+
description: Use @drawcall/lod to give three.js objects levels of detail — load a model and get a simplified mesh, a baked low-poly mesh or an octahedral impostor that switch by on-screen size with a dithered fade, or switch your own levels — including crowds of instances, vegetation swaying in wind, buildings whose windows show the rooms behind them (interior mapping), shadows over large worlds, and preparing models that bake into good impostors. Use when a WebGPURenderer scene (`three/webgpu`) has many or large objects seen from far away; not for WebGLRenderer, skinned animation, or glass objects.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Level of detail with @drawcall/lod
|
|
7
|
+
|
|
8
|
+
## Which part to reach for
|
|
9
|
+
|
|
10
|
+
- **A model file → `LodLoader`.** It loads the model and builds its cheaper levels; impostors can be deferred until a view needs them. The result is a `Lod` you add to the scene. Three's renderer updates every `Lod` while it renders.
|
|
11
|
+
- **Many copies of one object → `InstancedLod`** (`loadInstancesAsync`, or built from levels): every copy switches and fades on its own, with one draw per level and mesh.
|
|
12
|
+
- **Levels you already have → `new Lod([source, ...levels])`**: any `Object3D` per level, another `Lod` included, detailed to cheap. Levels made by this package know how far off they are and pick their own switch size; hand-made ones need a `maxPixels`.
|
|
13
|
+
- **Only ever far away, never switching → an `ImpostorMesh`** from `bakeImpostor`: one instanced draw for thousands of copies.
|
|
14
|
+
|
|
15
|
+
Which levels to build, by asset:
|
|
16
|
+
|
|
17
|
+
- **Foliage** — trees, bushes, grass, plants, anything of alpha-tested leaf cards → an impostor only (the loader's default). A simplified level in between tears the cards apart. A scan that models every leaf in many triangles is different: draw it from its own `simplify` copy (around a quarter of the triangles keeps the leaves' shape; far fewer smears them) and use that as the source.
|
|
18
|
+
- **Hard-surface props** — rocks, vehicles, furniture → simplified, then impostor. Low-poly props need no levels: an impostor costs more than they do.
|
|
19
|
+
- **Buildings** → baked (experimental), then impostor. The baked level is one low mesh carrying the source's colour, normals and roughness in its own textures, so window frames, gutters and doors survive as texture, onto a low shell you give it (see "Impostor-ready assets").
|
|
20
|
+
- **Buildings you can see into** → the same, plus an `interior`: the rooms as a model of their own. Up close the real rooms show through the real glass; the baked level shows each room through its windows by interior mapping — a picture of the room that shifts with the view, at no geometry cost.
|
|
21
|
+
- **Dense patches of plants** (a block of corn, a patch of grass) → the plants as an `InstancedLod` (source, impostor, then an empty level so far plants draw nothing), and one `bakeMesh` of a patch's plants onto a canopy as the far level of the patches (see "Impostor-ready assets").
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import * as THREE from "three/webgpu";
|
|
25
|
+
import { Lod, LodLoader } from "@drawcall/lod";
|
|
26
|
+
|
|
27
|
+
const manager = new THREE.LoadingManager();
|
|
28
|
+
manager.addHandler(/\.glb$/i, new GLTFLoader(manager).setDRACOLoader(draco)); // LodLoader uses it
|
|
29
|
+
const loader = new LodLoader(manager);
|
|
30
|
+
|
|
31
|
+
const tree = await loader.loadAsync({ url: "/tree.glb", levels: [{ type: "impostor", layout: "hemi" }] });
|
|
32
|
+
const shop = await loader.loadAsync({
|
|
33
|
+
url: "/shop.glb",
|
|
34
|
+
levels: [
|
|
35
|
+
{ type: "baked", low: "/shop-low.glb", interior: "/shop-rooms.glb" },
|
|
36
|
+
{ type: "impostor", layout: "hemi" },
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
scene.add(tree, shop);
|
|
40
|
+
|
|
41
|
+
const forest = await loader.loadInstancesAsync("/tree.glb", spots.length);
|
|
42
|
+
spots.forEach((spot, i) => forest.setMatrixAt(i, new THREE.Matrix4().setPosition(spot)));
|
|
43
|
+
scene.add(forest);
|
|
44
|
+
|
|
45
|
+
Lod.debug = true; // tint levels to see where each draws
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## How the parts combine
|
|
49
|
+
|
|
50
|
+
- **Vegetation in wind:** build the resting asset and its levels, then apply one `Wind` to the `Lod` or `InstancedLod`. Deferred impostors inherit that wind when they arrive. Update its time before rendering; share the same breeze across plants. Apply to individual plants or their instance batches, rather than a whole forest hierarchy. Broad sway moves the whole plant; it does not animate individual branches or leaves. Independent objects attached to a plant need to follow its motion too.
|
|
51
|
+
|
|
52
|
+
- **Switching is by on-screen height in CSS pixels:** a high-density screen picks the levels a standard one does and pays for its extra pixels in fill only. `Lod.quality` scales every measurement: below 1, cheaper levels take over sooner — the global performance knob (set it to the pixel ratio to switch by device pixels).
|
|
53
|
+
- **Near a switch both levels draw** and split the pixels with a dither, so nothing pops. Elsewhere only one level is visible: hidden levels submit no draws or shadows, but their geometry and textures stay resident. Each level's switch size must stay far enough below the previous one's for one fade to end before the next begins; the `Lod` throws otherwise.
|
|
54
|
+
- **Nested `Lod`s** switch their own levels; the parent hands over to and from them without the dither, and a hidden parent level hides everything inside.
|
|
55
|
+
- **Clone instead of loading twice:** `clone()` shares geometry, textures and deferred atlas requests. A persistent cache avoids rebaking, but each separate load still reconstructs the model and its other levels. For hundreds of copies use `InstancedLod`, as a `Lod` per copy costs draws per copy.
|
|
56
|
+
- **Split large crowds into regions:** use one `InstancedLod` per region so regions outside the view or a shadow map can skip their draws. Keep nearby instances together in the batch; one scattered world-sized crowd is hard to cull, while many tiny crowds spend the frame submitting draws. Cheap far cards can use larger batches than detailed source models; compare their draw overhead and culling cost separately. Check that your target devices offer the required WebGPU feature `indirect-first-instance`. Keep the batch's own transform uniformly scaled (instances may scale any way). Build its levels as templates without nested `Lod`s or InstancedMeshes; use `Lod` for hierarchies.
|
|
57
|
+
- **Hierarchies on top** (a field whose plants exist only near the camera): level 0 may be an empty `Object3D`, and the `Lod` measures by the first level that draws. Draw the detail yourself where level 0 shows: `levelOf` on an `InstancedLod`, `visible` on a `Lod`'s level. `Lod.fade` is the share of a level's `maxPixels` its fade spans.
|
|
58
|
+
- **Saving results:** persist impostors with `encodeImpostor` and baked meshes with `encodeBakedMesh`, before applying runtime material changes or adding them to a `Lod`. Decode cached results instead of repeating their bakes; the asynchronous impostor decoder avoids long synchronous JavaScript decompression where native streams are available. Simplified levels still need construction. An incompatible encoding change requires rebaking old files, and results need regeneration when their inputs or baking algorithm change.
|
|
59
|
+
- **`dispose()` frees everything,** the source included. Clones share geometry and textures: dispose only once no clone draws any more.
|
|
60
|
+
|
|
61
|
+
## Cached and deferred impostors
|
|
62
|
+
|
|
63
|
+
Use `LodLoader` with a GLTS asset to cache its impostors through vitecache. A whole authored GLTS asset is one cache unit: its source URL, observed dependency URLs and bake settings identify the result. Direct object input belongs inside an active GLTS execution; use the complete asset, and finish loading its resources before requesting levels. Outside GLTS, load a URL or compose `Lod`/`InstancedLod` from the building blocks. See the [GLTS skill](https://github.com/drawcall-ai/glts/blob/main/packages/glts/skills/glts/SKILL.md) for contextual loaders and sharing package instances.
|
|
64
|
+
|
|
65
|
+
Enable vitecache in the host Vite app, and generate the public cache files before deployment. Follow the [vitecache skill](https://github.com/drawcall-ai/vitecache/blob/main/packages/vitecache/skills/vitecache/SKILL.md) for dependency declarations and deployment. Match the loader's cache location to the app's URL base; asset paths and cache paths are separate. GLTS tracks observed files, so declare additional generator packages or other inputs in the loader's cache dependencies. Procedural assets outside GLTS can combine `cachedImpostor` with explicit file and parameter dependencies; include every input that changes the asset, including helper code and textures. Do not reuse one dependency set for different asset variants.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const trees = await loader.loadInstancesAsync(
|
|
69
|
+
{
|
|
70
|
+
url: "/assets/tree.glts",
|
|
71
|
+
levels: [{ type: "impostor", layout: "hemi", lazy: true, maxPixels: 128 }],
|
|
72
|
+
},
|
|
73
|
+
spots.length,
|
|
74
|
+
);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Use `LazyImpostor` when assembling levels yourself. Keep an eager source and a known switch height; while an atlas loads, a ready finer level stays visible. Keep the bake input alive and unchanged until loading finishes. If batching consumes or replaces its geometry, retain an authored snapshot for the deferred bake. Apply custom impostor material setup and refresh custom draw or shadow registries on `levelschange`, as well as at construction. Use `load()` to prefetch or explicitly retry; failures reject and automatic demand reports the error while retaining the finer fallback.
|
|
78
|
+
|
|
79
|
+
Measure the first visible frame, a warm reload, and the first traversal separately. Cached atlas bytes avoid baking; deferred levels avoid reading and uploading unused atlases. They do not remove source loading, other derived-level construction, or first-use shader preparation. Check caches before constructing their expensive inputs, and persist baked meshes too when those dominate startup. Prefetch only the initial view if its fallback would be too expensive; include any custom instance-streaming updates before measuring demand.
|
|
80
|
+
|
|
81
|
+
When preparing a view before its first render, seed its viewport with `setViewportHeight` before updating or prefetching LODs. This lets the first update exclude tiers that cannot draw; otherwise their shaders and textures can be prepared unnecessarily. Refresh the supplied height on resize, especially when every LOD is currently culled; normal draws continue measuring the viewport automatically.
|
|
82
|
+
|
|
83
|
+
## Impostor-ready assets
|
|
84
|
+
|
|
85
|
+
- **Units and pivot:** Y up, metres, origin at the ground under the object, so instance matrices place it standing. Vary copies cheaply with per-instance colour (`setColorAt`): every variant model costs its own levels and bakes.
|
|
86
|
+
- **Foliage leaves are alpha-tested** (glTF `MASK`, never `BLEND`), the alpha in the base colour texture, and the colour bled out into the transparent texels: black there darkens every mip, so the source darkens with distance and its bakes with it.
|
|
87
|
+
- **Custom lighting is not baked.** Impostors and baked levels keep the surface (colour, normal, roughness) and light it as a standard material: lighting a source's node material changes on top, such as leaves glowing in back light, is lost, and such foliage reads darker once its impostor shows, worst against the light. Give each `ImpostorMesh`'s material (a `MeshStandardNodeMaterial`) the same change once the `Lod` or `InstancedLod` is built, for example as an `emissiveNode`.
|
|
88
|
+
- **Jitter dense plantings** in position and rotation. Bakes and impostor views look along parallel rays: plants on a grid leave straight gaps there that no perspective view of the real plants shows.
|
|
89
|
+
- **A building's low shell** is geometry only, generated from the same parameters as the building and in its space. Parts it leaves out (a chimney) show only as texture and lower the level's switch size. Close it where the source is closed (a floor for a floor); leave out faces nobody sees, which take texture space from those that are seen. Make glass see-through and the rooms their own model (see "Interiors").
|
|
90
|
+
- **A canopy for a patch of plants** sits at the leaf tops, with side walls only on the outer edge of the whole field: inner walls show as a cliff where plants hand over. Rays that find no leaf at the top bake the leaves further down, so the canopy needs nothing under it. Whatever stands above the leaves (tassels, flowers) covers far more of a top-down bake than of any view across the field: bake the canopy from plants with fewer of them. Lay out the patch so rows run on across its borders, then copies tile without seams. Switch the plants on top per plant, by their own distance, not per patch: the hand-over then follows a smooth contour around the camera instead of patch edges.
|
|
91
|
+
|
|
92
|
+
## Performance
|
|
93
|
+
|
|
94
|
+
- **Tune switch sizes on a standard screen.** They are in CSS pixels, so the choice holds on a high-density one; size anything of your own that reacts to on-screen size (how far plants exist, how many) in CSS pixels too, or a high-density screen multiplies it.
|
|
95
|
+
- **When source geometry dominates,** lighten the source first, then let its impostor take over earlier — a larger `maxPixels` on the impostor level: lowering it keeps more copies at the source. Bake that impostor with the same `maxPixels`: above its default it shows its top mip magnified, which otherwise keeps the look of the source at the bake's size — foliage denser and darker than the leaves it replaces up close.
|
|
96
|
+
- **Impostors cost fill.** Few triangles can still cover the screen many times: atlas sampling, lighting and overlapping cards can dominate even with parallax off. Compare dense individual plants with a patch's baked far level, not just triangle counts. Keep parallax for impostors seen large — without it they ghost (doubled trunks) — and turn it off only for impostors that only ever show small or densely packed, such as a field's plants.
|
|
97
|
+
- **Count material groups as well as triangles.** Instancing combines copies of a model, but its separate materials still require separate draws in each pass. Merge compatible parts before creating LOD batches; packing textures alone does not combine draws. Preserve material and shadow behavior, and keep overlapping or thin parts separate if batching changes their appearance. For `bakeMesh`, bake from the authored materials before replacing them with GPU-only atlas materials.
|
|
98
|
+
- **Keep the rendering conditions fixed when comparing performance:** viewport, pixel ratio, multisampling, shadows, wind and switch sizes. Disabling an effect can identify its cost, but is not an equivalent-looking optimization. Check moving transitions as well as stationary images; multisampling matters especially for thin twigs and grass.
|
|
99
|
+
- **Judge changes by completed frame time.** Fewer draws, triangles or buffer bindings can still cost more if culling becomes coarser or preparation, vertex work or shading becomes heavier. Merged shadow casters can process parts that were previously skipped. Atlases can increase texture memory; preserve resolution and mip quality in the comparison. Measure CPU work and GPU completion separately; overlapping GPU pass durations do not add up to frame time. Recheck the original setup after each experiment to catch background-load drift. Compare images after the same movement history: streamed instance slots and cached shadows can differ even when the final camera pose is identical.
|
|
100
|
+
- **Stream into existing batches:** reuse nearby regions' slots and change only the matrices or colours that actually changed. Rebuilding batches or rewriting the whole crowd during flight adds preparation and upload work even when few instances draw.
|
|
101
|
+
- **Check the first flight as well as a warmed repeat.** Preparing the starting view does not prepare every later level and shadow variant. Measure first-use shader preparation and streaming separately from steady-state throughput. Any startup warmup needs its own loading-time and appearance checks, including simulation and streaming state, the first visible frame and later transitions. Free flight can still reveal new variants. Measure rendering separately from an overlay that counts every instance; count queries and temporary objects can cause periodic CPU and garbage-collection stalls.
|
|
102
|
+
|
|
103
|
+
## Shadows
|
|
104
|
+
|
|
105
|
+
- **Levels are chosen from the viewing camera,** and every shadow map draws those same levels, including a finer fallback while an impostor loads. During a fade both levels cast, dithered as they draw, so the object casts one shadow.
|
|
106
|
+
- **Casters narrower than a texel** of a directional light's shadow map draw nothing into it, so coarse cascades skip small casters by themselves.
|
|
107
|
+
- **Shadow setups on the light or renderer reach every level**; impostors cast and receive at the surface they resolve. A per-material setup must run on the level meshes' materials after the `Lod` is built: the material you passed in is no longer drawn.
|
|
108
|
+
- **Open models leak light** (eaves, window reveals, single walls): three casts with back faces only. Set `shadowSide = DoubleSide` on their materials, with about a texel of shadow `bias` against acne.
|
|
109
|
+
|
|
110
|
+
## Pitfalls
|
|
111
|
+
|
|
112
|
+
- **Materials are replaced.** A `Lod` draws each mesh with a faded node-material copy of its material; the material you passed in is no longer drawn. Edit the material on the level's mesh after building. `ShaderMaterial`s are refused: write them as node materials.
|
|
113
|
+
- **Authored levels are read once,** when the `Lod` is built: arbitrary meshes added or moved later are neither faded nor measured right. Build a new `Lod` instead. `LazyImpostor` is the supported way to defer an impostor level.
|
|
114
|
+
- **Skinned meshes are refused,** and animation is ignored.
|
|
115
|
+
- **Orthographic cameras** switch by on-screen size too, but swap levels at once.
|
|
116
|
+
- **Several cameras:** each is measured at the viewport height it last drew the `Lod` at; a camera's first frame assumes a typical screen. An `InstancedLod` holds one selection at a time: a render nested in another (a reflection, a `CubeCamera`) that sees it replaces the selection the outer render draws, so keep it off the layers such cameras see. Renders one after the other are fine.
|
|
117
|
+
- **Many `Lod`s appearing at once hitch** while three sets up each mesh's material; draw crowds with `InstancedLod`.
|
|
118
|
+
- **Lines, points and sprites** in a level draw but do not fade. Raycasts hit the source only, whatever level shows.
|
|
119
|
+
- **Canvas alpha:** impostor edges use alpha-to-coverage, which leaves fractional alpha in the frame, so the page shows through: create the renderer with `alpha: false`.
|
|
120
|
+
|
|
121
|
+
- **Impostor layout `"hemi"`** covers views from the horizon up only: right for upright trees, rocks and buildings, wrong for anything seen from below.
|
|
122
|
+
- **`.glts` URLs need a registered loader:** `manager.addHandler(/\.glts(?:[?#]|$)/i, new GLTSLoader(manager))`. Inside a `.glts` script, register the contextual `gltsLoader` on the contextual `loadingManager` instead. Share the host's package through GLTS when using `instanceof`; separately loaded package copies have different constructors.
|
|
123
|
+
- **Baking a mesh is slow and CPU-bound** on the loading thread (it yields between chunks), and each baked level has its own textures in GPU memory. Bake once per model and clone; time and memory grow with `textureSize` squared, so small or only-far objects bake faster at a smaller one.
|
|
124
|
+
- **Baked levels read textures on the CPU:** compressed textures (KTX2) fail the bake. Emissive, clearcoat and transmission are not baked; glass bakes opaque in its own colour unless it looks into an interior. Only alpha-tested surfaces cut holes.
|
|
125
|
+
- **Specular level** (`ior`, `specularIntensity`, `specularColor`, glTF `KHR_materials_specular`) reaches an impostor only as one average for the whole object, so parts or maps that differ in it reflect alike; baked levels reflect like a plain dielectric, so a source with its specular turned down shows brighter at them.
|
|
126
|
+
- **Clones of a built level** made by hand lose the measured error their switch size comes from; clone the whole `Lod` instead.
|
|
127
|
+
- **Large worlds** (kilometres to millimetres) z-fight with the default depth buffer. Every level works with the renderer's `reversedDepthBuffer`.
|
|
128
|
+
|
|
129
|
+
## Interiors
|
|
130
|
+
|
|
131
|
+
- **Rooms are their own model,** never part of the building's: only rooms given as the interior are left out of the baked exterior and shown through its windows. One baked level per model takes an interior.
|
|
132
|
+
- **One direct child per room, filling its box:** walls, floor and ceiling on the box's faces, facing in. Rooms must not overlap; doorways between them, stairwells and several storeys are fine. A room's box should begin at the inside of the wall its windows are in; glass with no room just behind it stays plain glass.
|
|
133
|
+
- **The glass must be see-through in the source:** transparent with opacity below 1, or transmissive. Dark but opaque glass gives no windows, and the bake fails when no glass looks into a room.
|
|
134
|
+
- **Rooms are lit by the light reaching the window** — ambient, light probes and the environment, not the sun, which the walls would block — so they darken with the scene. For a dimmer room, darken its materials.
|
|
135
|
+
- **Lamps glow through emissive (or unlit) materials.** Lights in an interior are refused: wherever the source draws they would be scene lights, recompiling every lit material at each switch.
|
|
136
|
+
- **Furniture against a wall looks right;** things standing free in the room flatten onto the walls behind them.
|
|
137
|
+
- **Calling `bakeMesh` yourself:** pass the interior and put it inside the source, so the source shows the real rooms; the bake leaves it out of the exterior either way.
|