@drawcall/lod 0.1.2 → 0.3.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.
Files changed (358) hide show
  1. package/README.md +13 -70
  2. package/dist/at.d.ts +2 -0
  3. package/dist/at.js +8 -0
  4. package/dist/at.js.map +1 -0
  5. package/dist/bake.d.ts +22 -0
  6. package/dist/bake.js +88 -0
  7. package/dist/bake.js.map +1 -0
  8. package/dist/baked/codec.d.ts +5 -5
  9. package/dist/baked/codec.js +58 -158
  10. package/dist/baked/codec.js.map +1 -1
  11. package/dist/baked/coverage.d.ts +6 -3
  12. package/dist/baked/coverage.js +6 -3
  13. package/dist/baked/coverage.js.map +1 -1
  14. package/dist/baked/fields.d.ts +9 -0
  15. package/dist/baked/fields.js +22 -0
  16. package/dist/baked/fields.js.map +1 -0
  17. package/dist/baked/index.d.ts +16 -6
  18. package/dist/baked/index.js +23 -21
  19. package/dist/baked/index.js.map +1 -1
  20. package/dist/baked/interior/faces.d.ts +4 -3
  21. package/dist/baked/interior/faces.js +13 -10
  22. package/dist/baked/interior/faces.js.map +1 -1
  23. package/dist/baked/interior/material.d.ts +7 -10
  24. package/dist/baked/interior/material.js +29 -36
  25. package/dist/baked/interior/material.js.map +1 -1
  26. package/dist/baked/interior/rooms.js +1 -1
  27. package/dist/baked/interior/rooms.js.map +1 -1
  28. package/dist/baked/low.d.ts +2 -2
  29. package/dist/baked/low.js +9 -8
  30. package/dist/baked/low.js.map +1 -1
  31. package/dist/baked/raster.js +6 -6
  32. package/dist/baked/raster.js.map +1 -1
  33. package/dist/baked/textures.d.ts +17 -0
  34. package/dist/baked/textures.js +95 -0
  35. package/dist/baked/textures.js.map +1 -0
  36. package/dist/baked/transfer.js +19 -18
  37. package/dist/baked/transfer.js.map +1 -1
  38. package/dist/baked/unwrap.js +4 -3
  39. package/dist/baked/unwrap.js.map +1 -1
  40. package/dist/baked/xatlas.js.map +1 -1
  41. package/dist/codec/container.d.ts +8 -0
  42. package/dist/codec/container.js +63 -0
  43. package/dist/codec/container.js.map +1 -0
  44. package/dist/codec/mesh.d.ts +8 -0
  45. package/dist/codec/mesh.js +220 -0
  46. package/dist/codec/mesh.js.map +1 -0
  47. package/dist/dispose.d.ts +11 -4
  48. package/dist/dispose.js +39 -12
  49. package/dist/dispose.js.map +1 -1
  50. package/dist/gpu.d.ts +39 -0
  51. package/dist/gpu.js +41 -0
  52. package/dist/gpu.js.map +1 -0
  53. package/dist/impostor/bake/atlas.d.ts +4 -4
  54. package/dist/impostor/bake/atlas.js +12 -12
  55. package/dist/impostor/bake/atlas.js.map +1 -1
  56. package/dist/impostor/bake/fit.d.ts +8 -0
  57. package/dist/impostor/bake/fit.js +28 -0
  58. package/dist/impostor/bake/fit.js.map +1 -0
  59. package/dist/impostor/bake/index.d.ts +23 -14
  60. package/dist/impostor/bake/index.js +42 -134
  61. package/dist/impostor/bake/index.js.map +1 -1
  62. package/dist/impostor/bake/materials.d.ts +3 -3
  63. package/dist/impostor/bake/materials.js +60 -12
  64. package/dist/impostor/bake/materials.js.map +1 -1
  65. package/dist/impostor/bake/sheet.d.ts +8 -0
  66. package/dist/impostor/bake/sheet.js +90 -0
  67. package/dist/impostor/bake/sheet.js.map +1 -0
  68. package/dist/impostor/bake/source.d.ts +2 -2
  69. package/dist/impostor/bake/source.js +8 -19
  70. package/dist/impostor/bake/source.js.map +1 -1
  71. package/dist/impostor/codec.d.ts +5 -11
  72. package/dist/impostor/codec.js +37 -121
  73. package/dist/impostor/codec.js.map +1 -1
  74. package/dist/impostor/impostor.d.ts +31 -7
  75. package/dist/impostor/impostor.js +21 -6
  76. package/dist/impostor/impostor.js.map +1 -1
  77. package/dist/impostor/ktx2/encode.d.ts +26 -0
  78. package/dist/impostor/ktx2/encode.js +117 -0
  79. package/dist/impostor/ktx2/encode.js.map +1 -0
  80. package/dist/impostor/ktx2/transcode.d.ts +3 -0
  81. package/dist/impostor/ktx2/transcode.js +99 -0
  82. package/dist/impostor/ktx2/transcode.js.map +1 -0
  83. package/dist/impostor/ktx2/uastc.d.ts +7 -0
  84. package/dist/impostor/ktx2/uastc.js +64 -0
  85. package/dist/impostor/ktx2/uastc.js.map +1 -0
  86. package/dist/impostor/ktx2/wasm/encoder.d.ts +4 -0
  87. package/dist/impostor/ktx2/wasm/encoder.js +10 -0
  88. package/dist/impostor/ktx2/wasm/encoder.js.map +1 -0
  89. package/dist/impostor/ktx2/wasm/transcoder.d.ts +3 -0
  90. package/dist/impostor/ktx2/wasm/transcoder.js +9 -0
  91. package/dist/impostor/ktx2/wasm/transcoder.js.map +1 -0
  92. package/dist/impostor/ktx2/worker.d.ts +1 -0
  93. package/dist/impostor/ktx2/worker.js +12 -0
  94. package/dist/impostor/ktx2/worker.js.map +1 -0
  95. package/dist/impostor/mesh/card.d.ts +3 -3
  96. package/dist/impostor/mesh/card.js +11 -8
  97. package/dist/impostor/mesh/card.js.map +1 -1
  98. package/dist/impostor/mesh/index.d.ts +10 -7
  99. package/dist/impostor/mesh/index.js +15 -18
  100. package/dist/impostor/mesh/index.js.map +1 -1
  101. package/dist/impostor/mesh/material.d.ts +24 -13
  102. package/dist/impostor/mesh/material.js +67 -51
  103. package/dist/impostor/mesh/material.js.map +1 -1
  104. package/dist/impostor/mesh/surface.d.ts +20 -16
  105. package/dist/impostor/mesh/surface.js +81 -58
  106. package/dist/impostor/mesh/surface.js.map +1 -1
  107. package/dist/impostor/mesh/textures.d.ts +3 -3
  108. package/dist/impostor/mesh/textures.js +24 -34
  109. package/dist/impostor/mesh/textures.js.map +1 -1
  110. package/dist/impostor/octahedral.d.ts +1 -8
  111. package/dist/impostor/octahedral.js +3 -18
  112. package/dist/impostor/octahedral.js.map +1 -1
  113. package/dist/index.d.ts +16 -10
  114. package/dist/index.js +14 -8
  115. package/dist/index.js.map +1 -1
  116. package/dist/instanced/camera.d.ts +46 -0
  117. package/dist/instanced/camera.js +70 -0
  118. package/dist/instanced/camera.js.map +1 -0
  119. package/dist/instanced/chunk.d.ts +97 -0
  120. package/dist/instanced/chunk.js +156 -0
  121. package/dist/instanced/chunk.js.map +1 -0
  122. package/dist/instanced/chunks.d.ts +60 -0
  123. package/dist/instanced/chunks.js +208 -0
  124. package/dist/instanced/chunks.js.map +1 -0
  125. package/dist/instanced/cull.d.ts +28 -0
  126. package/dist/instanced/cull.js +22 -0
  127. package/dist/instanced/cull.js.map +1 -0
  128. package/dist/instanced/index.d.ts +96 -92
  129. package/dist/instanced/index.js +229 -512
  130. package/dist/instanced/index.js.map +1 -1
  131. package/dist/instanced/key.d.ts +19 -0
  132. package/dist/instanced/key.js +45 -0
  133. package/dist/instanced/key.js.map +1 -0
  134. package/dist/instanced/levels.d.ts +60 -0
  135. package/dist/instanced/levels.js +130 -0
  136. package/dist/instanced/levels.js.map +1 -0
  137. package/dist/instanced/lists.d.ts +70 -0
  138. package/dist/instanced/lists.js +133 -0
  139. package/dist/instanced/lists.js.map +1 -0
  140. package/dist/instanced/measure.d.ts +28 -0
  141. package/dist/instanced/measure.js +203 -0
  142. package/dist/instanced/measure.js.map +1 -0
  143. package/dist/instanced/meshes.d.ts +46 -14
  144. package/dist/instanced/meshes.js +137 -22
  145. package/dist/instanced/meshes.js.map +1 -1
  146. package/dist/instanced/mirror.d.ts +20 -0
  147. package/dist/instanced/mirror.js +37 -0
  148. package/dist/instanced/mirror.js.map +1 -0
  149. package/dist/instanced/pages/buffers.d.ts +37 -0
  150. package/dist/instanced/pages/buffers.js +15 -0
  151. package/dist/instanced/pages/buffers.js.map +1 -0
  152. package/dist/instanced/pages/chunk.d.ts +51 -0
  153. package/dist/instanced/pages/chunk.js +81 -0
  154. package/dist/instanced/pages/chunk.js.map +1 -0
  155. package/dist/instanced/pages/index.d.ts +74 -0
  156. package/dist/instanced/pages/index.js +269 -0
  157. package/dist/instanced/pages/index.js.map +1 -0
  158. package/dist/instanced/pages/passes.d.ts +21 -0
  159. package/dist/instanced/pages/passes.js +52 -0
  160. package/dist/instanced/pages/passes.js.map +1 -0
  161. package/dist/instanced/pages/slots.d.ts +11 -0
  162. package/dist/instanced/pages/slots.js +90 -0
  163. package/dist/instanced/pages/slots.js.map +1 -0
  164. package/dist/instanced/pages/workgroup.d.ts +14 -0
  165. package/dist/instanced/pages/workgroup.js +38 -0
  166. package/dist/instanced/pages/workgroup.js.map +1 -0
  167. package/dist/instanced/raycast.d.ts +15 -0
  168. package/dist/instanced/raycast.js +46 -0
  169. package/dist/instanced/raycast.js.map +1 -0
  170. package/dist/instanced/rows.d.ts +108 -0
  171. package/dist/instanced/rows.js +209 -0
  172. package/dist/instanced/rows.js.map +1 -0
  173. package/dist/instanced/shape.d.ts +25 -0
  174. package/dist/instanced/shape.js +51 -0
  175. package/dist/instanced/shape.js.map +1 -0
  176. package/dist/instanced/sort.d.ts +63 -0
  177. package/dist/instanced/sort.js +162 -0
  178. package/dist/instanced/sort.js.map +1 -0
  179. package/dist/instanced/update.d.ts +30 -0
  180. package/dist/instanced/update.js +207 -0
  181. package/dist/instanced/update.js.map +1 -0
  182. package/dist/levels/bounds.d.ts +21 -0
  183. package/dist/levels/bounds.js +42 -0
  184. package/dist/levels/bounds.js.map +1 -0
  185. package/dist/levels/cameras.d.ts +48 -0
  186. package/dist/levels/cameras.js +122 -0
  187. package/dist/levels/cameras.js.map +1 -0
  188. package/dist/levels/compile/contexts.d.ts +30 -0
  189. package/dist/levels/compile/contexts.js +18 -0
  190. package/dist/levels/compile/contexts.js.map +1 -0
  191. package/dist/levels/compile/index.d.ts +39 -0
  192. package/dist/levels/compile/index.js +185 -0
  193. package/dist/levels/compile/index.js.map +1 -0
  194. package/dist/levels/compile/pipelines.d.ts +26 -0
  195. package/dist/levels/compile/pipelines.js +177 -0
  196. package/dist/levels/compile/pipelines.js.map +1 -0
  197. package/dist/levels/compiles.d.ts +29 -0
  198. package/dist/levels/compiles.js +50 -0
  199. package/dist/levels/compiles.js.map +1 -0
  200. package/dist/levels/draw.d.ts +102 -0
  201. package/dist/levels/draw.js +401 -0
  202. package/dist/levels/draw.js.map +1 -0
  203. package/dist/levels/fade.d.ts +69 -0
  204. package/dist/levels/fade.js +150 -0
  205. package/dist/levels/fade.js.map +1 -0
  206. package/dist/levels/index.d.ts +99 -0
  207. package/dist/levels/index.js +155 -0
  208. package/dist/levels/index.js.map +1 -0
  209. package/dist/levels/item.d.ts +38 -0
  210. package/dist/levels/item.js +97 -0
  211. package/dist/levels/item.js.map +1 -0
  212. package/dist/levels/load.d.ts +31 -0
  213. package/dist/levels/load.js +142 -0
  214. package/dist/levels/load.js.map +1 -0
  215. package/dist/levels/manager.d.ts +159 -0
  216. package/dist/levels/manager.js +339 -0
  217. package/dist/levels/manager.js.map +1 -0
  218. package/dist/{material.js → levels/material.js} +13 -7
  219. package/dist/levels/material.js.map +1 -0
  220. package/dist/levels/renderer.d.ts +29 -0
  221. package/dist/levels/renderer.js +41 -0
  222. package/dist/levels/renderer.js.map +1 -0
  223. package/dist/levels/requests.d.ts +49 -0
  224. package/dist/levels/requests.js +95 -0
  225. package/dist/levels/requests.js.map +1 -0
  226. package/dist/levels/screen.d.ts +14 -0
  227. package/dist/levels/screen.js +21 -0
  228. package/dist/levels/screen.js.map +1 -0
  229. package/dist/levels/wait.d.ts +27 -0
  230. package/dist/levels/wait.js +61 -0
  231. package/dist/levels/wait.js.map +1 -0
  232. package/dist/loader/build.d.ts +10 -0
  233. package/dist/loader/build.js +28 -0
  234. package/dist/loader/build.js.map +1 -0
  235. package/dist/loader/cache.d.ts +12 -15
  236. package/dist/loader/cache.js +64 -33
  237. package/dist/loader/cache.js.map +1 -1
  238. package/dist/loader/context.d.ts +46 -0
  239. package/dist/loader/context.js +91 -0
  240. package/dist/loader/context.js.map +1 -0
  241. package/dist/loader/dependencies.d.ts +32 -0
  242. package/dist/loader/dependencies.js +113 -0
  243. package/dist/loader/dependencies.js.map +1 -0
  244. package/dist/loader/index.d.ts +62 -0
  245. package/dist/loader/index.js +284 -0
  246. package/dist/loader/index.js.map +1 -0
  247. package/dist/loader/level.d.ts +41 -0
  248. package/dist/loader/level.js +140 -0
  249. package/dist/loader/level.js.map +1 -0
  250. package/dist/loader/request.d.ts +87 -0
  251. package/dist/loader/request.js +59 -0
  252. package/dist/loader/request.js.map +1 -0
  253. package/dist/loader/url.d.ts +16 -0
  254. package/dist/loader/url.js +35 -0
  255. package/dist/loader/url.js.map +1 -0
  256. package/dist/lod.d.ts +79 -49
  257. package/dist/lod.js +209 -153
  258. package/dist/lod.js.map +1 -1
  259. package/dist/meshopt.d.ts +6 -0
  260. package/dist/meshopt.js +19 -0
  261. package/dist/meshopt.js.map +1 -0
  262. package/dist/simplify/cards.d.ts +14 -0
  263. package/dist/simplify/cards.js +128 -0
  264. package/dist/simplify/cards.js.map +1 -0
  265. package/dist/{simplify.d.ts → simplify/index.d.ts} +7 -4
  266. package/dist/{simplify.js → simplify/index.js} +42 -22
  267. package/dist/simplify/index.js.map +1 -0
  268. package/dist/{bounds.d.ts → source/detail.d.ts} +4 -13
  269. package/dist/{bounds.js → source/detail.js} +62 -32
  270. package/dist/source/detail.js.map +1 -0
  271. package/dist/source/{error.d.ts → deviation.d.ts} +3 -3
  272. package/dist/source/{error.js → deviation.js} +12 -12
  273. package/dist/source/deviation.js.map +1 -0
  274. package/dist/source/{shading.d.ts → evaluator.d.ts} +10 -10
  275. package/dist/source/{shading.js → evaluator.js} +5 -5
  276. package/dist/source/evaluator.js.map +1 -0
  277. package/dist/source/{trace.d.ts → raycaster.d.ts} +6 -6
  278. package/dist/source/{trace.js → raycaster.js} +8 -8
  279. package/dist/source/raycaster.js.map +1 -0
  280. package/dist/source/{texels.d.ts → sampler.d.ts} +1 -1
  281. package/dist/source/{texels.js → sampler.js} +3 -3
  282. package/dist/source/sampler.js.map +1 -0
  283. package/dist/source/source.d.ts +2 -2
  284. package/dist/source/source.js +2 -2
  285. package/dist/source/source.js.map +1 -1
  286. package/dist/source/surface.js +1 -1
  287. package/dist/source/surface.js.map +1 -1
  288. package/dist/task.d.ts +6 -0
  289. package/dist/{pause.js → task.js} +5 -5
  290. package/dist/task.js.map +1 -0
  291. package/dist/three.d.ts +20 -19
  292. package/dist/three.js +29 -35
  293. package/dist/three.js.map +1 -1
  294. package/dist/tsl.d.ts +10 -14
  295. package/dist/tsl.js +12 -47
  296. package/dist/tsl.js.map +1 -1
  297. package/dist/typegpu.d.ts +37 -0
  298. package/dist/typegpu.js +37 -0
  299. package/dist/typegpu.js.map +1 -0
  300. package/dist/wind/bounds.js +2 -2
  301. package/dist/wind/bounds.js.map +1 -1
  302. package/dist/wind/hooks.d.ts +7 -4
  303. package/dist/wind/hooks.js +5 -2
  304. package/dist/wind/hooks.js.map +1 -1
  305. package/dist/wind/index.d.ts +4 -4
  306. package/dist/wind/index.js +43 -40
  307. package/dist/wind/index.js.map +1 -1
  308. package/dist/wind/sway.d.ts +4 -1
  309. package/dist/wind/sway.js +4 -1
  310. package/dist/wind/sway.js.map +1 -1
  311. package/package.json +24 -8
  312. package/skills/lod/SKILL.md +147 -108
  313. package/dist/bounds.js.map +0 -1
  314. package/dist/fade.d.ts +0 -44
  315. package/dist/fade.js +0 -78
  316. package/dist/fade.js.map +0 -1
  317. package/dist/impostor/cache.d.ts +0 -15
  318. package/dist/impostor/cache.js +0 -44
  319. package/dist/impostor/cache.js.map +0 -1
  320. package/dist/instanced/args.d.ts +0 -20
  321. package/dist/instanced/args.js +0 -77
  322. package/dist/instanced/args.js.map +0 -1
  323. package/dist/instanced/bounds.d.ts +0 -50
  324. package/dist/instanced/bounds.js +0 -271
  325. package/dist/instanced/bounds.js.map +0 -1
  326. package/dist/instanced/dirty.d.ts +0 -12
  327. package/dist/instanced/dirty.js +0 -61
  328. package/dist/instanced/dirty.js.map +0 -1
  329. package/dist/instanced/select.d.ts +0 -84
  330. package/dist/instanced/select.js +0 -264
  331. package/dist/instanced/select.js.map +0 -1
  332. package/dist/lazy.d.ts +0 -64
  333. package/dist/lazy.js +0 -187
  334. package/dist/lazy.js.map +0 -1
  335. package/dist/levels.d.ts +0 -72
  336. package/dist/levels.js +0 -146
  337. package/dist/levels.js.map +0 -1
  338. package/dist/loader/dispose.d.ts +0 -3
  339. package/dist/loader/dispose.js +0 -25
  340. package/dist/loader/dispose.js.map +0 -1
  341. package/dist/loader.d.ts +0 -99
  342. package/dist/loader.js +0 -234
  343. package/dist/loader.js.map +0 -1
  344. package/dist/material.js.map +0 -1
  345. package/dist/pause.d.ts +0 -6
  346. package/dist/pause.js.map +0 -1
  347. package/dist/renderer.d.ts +0 -21
  348. package/dist/renderer.js +0 -83
  349. package/dist/renderer.js.map +0 -1
  350. package/dist/simplify.js.map +0 -1
  351. package/dist/source/error.js.map +0 -1
  352. package/dist/source/shading.js.map +0 -1
  353. package/dist/source/texels.js.map +0 -1
  354. package/dist/source/trace.js.map +0 -1
  355. package/dist/view.d.ts +0 -30
  356. package/dist/view.js +0 -75
  357. package/dist/view.js.map +0 -1
  358. /package/dist/{material.d.ts → levels/material.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawcall/lod",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Level of detail for three.js: octahedral impostors, simplified and baked meshes, with dithered switching by on-screen size. WebGPURenderer only.",
6
6
  "keywords": [
@@ -29,24 +29,34 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "dependencies": {
32
- "@drawcall/glts": "^2.0.0",
33
32
  "fflate": "^0.8.2",
34
33
  "ktx-parse": "^2.0.0",
35
34
  "meshoptimizer": "^1.3.0",
36
35
  "three-mesh-bvh": "^0.9.15",
37
- "vitecache": "^0.1.2"
36
+ "vitecache": "^0.2.1"
38
37
  },
39
38
  "peerDependencies": {
40
- "three": ">=0.185.0 <0.186.0"
39
+ "@drawcall/glts": "^4.1.0",
40
+ "three": ">=0.186.0",
41
+ "typegpu": "^0.12.9"
41
42
  },
42
43
  "devDependencies": {
44
+ "@babel/core": "^7.29.13",
45
+ "@drawcall/glts": "^4.1.0",
46
+ "@drawcall/lod": "link:",
47
+ "@typegpu/gl": "^0.12.9",
48
+ "@typegpu/three": "^0.12.2",
49
+ "@types/babel__core": "^7.20.5",
43
50
  "@types/node": "^24.13.6",
44
- "@types/three": "^0.185.0",
51
+ "@types/three": "^0.186.0",
45
52
  "@vitest/browser-playwright": "4.1.11",
46
53
  "@webgpu/types": "^0.1.74",
54
+ "ktx2-encoder": "0.6.0",
47
55
  "playwright": "1.63.0",
48
- "three": "^0.185.1",
56
+ "three": "^0.186.1",
57
+ "typegpu": "0.12.9",
49
58
  "typescript": "^5.9.3",
59
+ "unplugin-typegpu": "^0.12.8",
50
60
  "vitest": "^4.0.17",
51
61
  "xatlas-wasm": "0.1.3"
52
62
  },
@@ -63,10 +73,16 @@
63
73
  "publishConfig": {
64
74
  "access": "public"
65
75
  },
76
+ "peerDependenciesMeta": {
77
+ "@drawcall/glts": {
78
+ "optional": true
79
+ }
80
+ },
66
81
  "scripts": {
67
- "build": "rm -rf dist && tsc -p tsconfig.json",
82
+ "build": "rm -rf dist && tsc -p tsconfig.json && node --experimental-strip-types --no-warnings scripts/typegpu.ts",
68
83
  "typecheck": "tsc -p tsconfig.test.json",
69
84
  "test": "vitest run",
70
- "xatlas": "node --experimental-strip-types --no-warnings scripts/xatlas.ts"
85
+ "xatlas": "node --experimental-strip-types --no-warnings scripts/xatlas.ts",
86
+ "basis": "node --experimental-strip-types --no-warnings scripts/basis.ts"
71
87
  }
72
88
  }
@@ -1,137 +1,176 @@
1
1
  ---
2
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.
3
+ description: Use @drawcall/lod for cached, progressively loaded levels of detail in three.js WebGPURenderer scenes. Covers model and procedural assets, mesh simplification, baked meshes, impostors, instanced copies, wind, interior mapping, asset preparation and capturing fully loaded frames; not WebGLRenderer or skinned animation.
4
4
  ---
5
5
 
6
6
  # Level of detail with @drawcall/lod
7
7
 
8
- ## Which part to reach for
8
+ ## Start with the loader
9
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.
10
+ Give the host's `LodLoader` an asset URL and the representations to derive from it. Use `.glb` or `.gltf` for stored models and `.glts` for procedural assets. Put generation parameters in the asset URL's query: each distinct query is a distinct cache identity. Build a `Lod` from the returned levels for an independently placed object, or an `InstancedLod` for repeated objects, whose instances arrive and leave in chunks of matrices and colours, from the CPU or from a compute pass of yours.
14
11
 
15
- Which levels to build, by asset:
12
+ Make a `LodGpu` once the renderer is initialised and give it to every `Lod` and `InstancedLod` drawing with that renderer. Every InstancedLod of one LodGpu sorts for a camera in one compute call. A camera that draws only a few InstancedLods, such as a minimap's, can give those a LodGpu of their own, which bounds the per-camera list memory. `LodLoader` needs no renderer.
16
13
 
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").
14
+ `loadAsync` resolves with the ladder before its levels load; each level loads when a camera first needs it. On a cold cache a derived level is built when it first loads, so a cold far camera builds only the coarse levels it needs. Give simplified and baked levels an explicit `maxPixels`: without one, a level is built with the ladder to measure its default. A simplified level also loads the source for its original materials; a source-only ladder loads its source eagerly. Prefer baked or impostor middle levels for an expensive `.glts`: a simplified level brings the script run forward to that viewing range.
15
+
16
+ Choose levels from the asset's silhouette, materials and expected viewing range. Alpha-tested leaf cards often work best as source → impostor; a simplified level has fewer of them, whole and enlarged to the same coverage, so at a low ratio its foliage reads coarser than the source up close. Hard-surface props often suit source → simplified → impostor. Detailed structures can use source → baked shell → impostor, with interiors supplied separately for windows. A very heavy source, such as a two-million-triangle scan, needs a simplified level between source and impostor, or every copy in mid range draws the full mesh. Add an empty final level when the object should disappear at distance.
17
+
18
+ Register the source loaders on the loading manager before loading: a `GLTSLoader` for `.glts` URLs, and a `GLTFLoader` with the decoders for models compressed with Draco or KTX2. Follow the [GLTS skill](https://github.com/drawcall-ai/glts/blob/main/packages/glts/skills/glts/SKILL.md) for the Vite plugin, script authoring and resource lifetimes. The package carries its own workers and WebAssembly; it needs no Vite configuration beyond the vitecache plugin.
19
+
20
+ This example draws many copies of one procedural asset. `positions` contains their placements in the InstancedLod's space, `renderer` is the application's WebGPURenderer and `scene` its scene.
22
21
 
23
22
  ```ts
24
- import * as THREE from "three/webgpu";
25
- import { Lod, LodLoader } from "@drawcall/lod";
23
+ import { InstancedBufferAttribute, LoadingManager, Matrix4 } from "three/webgpu";
24
+ import { GLTSLoader } from "@drawcall/glts";
25
+ import { InstancedLod, LodGpu, LodLoader } from "@drawcall/lod";
26
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);
27
+ await renderer.init();
28
+ const gpu = new LodGpu(renderer);
30
29
 
31
- const tree = await loader.loadAsync({ url: "/tree.glb", levels: [{ type: "impostor", layout: "hemi" }] });
32
- const shop = await loader.loadAsync({
33
- url: "/shop.glb",
30
+ const manager = new LoadingManager();
31
+ manager.addHandler(/\.glts$/, new GLTSLoader(manager));
32
+ const loader = new LodLoader(manager);
33
+ const levels = await loader.loadAsync({
34
+ url: "/trees/oak.glts?seed=2",
34
35
  levels: [
35
- { type: "baked", low: "/shop-low.glb", interior: "/shop-rooms.glb" },
36
- { type: "impostor", layout: "hemi" },
36
+ { type: "impostor", layout: "hemi", maxPixels: 120, draw: { parallax: 2 } },
37
+ { type: "empty", maxPixels: 4 },
37
38
  ],
38
39
  });
39
- scene.add(tree, shop);
40
+ const trees = new InstancedLod(levels, gpu);
41
+
42
+ const matrix = new Matrix4();
43
+ const matrices = new Float32Array(positions.flatMap((position) => matrix.makeTranslation(position).toArray()));
44
+ trees.addChunk({ instanceMatrix: new InstancedBufferAttribute(matrices, 16) });
45
+ scene.add(trees);
46
+ ```
47
+
48
+ Supply authored low shells and baking interiors as asset URLs on a baked level; both resolve relative to the source asset as ordinary URLs, so a query-only value replaces the source's whole query. For a variant of the same script, repeat the source's generation parameters, for example the source URL plus `&part=low`. The source asset should be complete at full detail; a baking interior supplies room structure for the distant windows. Author all three in the same coordinate system.
49
+
50
+ ## Put each cache with its computation
51
+
52
+ Follow the [vitecache skill](https://github.com/drawcall-ai/vitecache/blob/main/packages/vitecache/skills/vitecache/SKILL.md) to configure the host. `LodLoader` writes each derived level to the cache directory when the app first loads that level under the Vite dev server with the vitecache plugin. A deployed miss builds the missing level in the browser each time it loads, without persisting it. When the app is deployed under a sub-path, pass that location as `new LodLoader(manager, { basePath })`.
53
+
54
+ To deploy every level prebuilt, load the ladders in a public `.glts` script, which `vite build` runs in Node as the [GLTS skill](https://github.com/drawcall-ai/glts/blob/main/packages/glts/skills/glts/SKILL.md#precomputing-into-the-build) describes. There `LodLoader` builds and saves every level of each ladder it loads, deferred ones included. Request the same URLs and levels as the app, from a helper beside the script that the app imports too; `onLevel` changes nothing cached, so the app adds its effects to the shared requests. Register the contextual `gltsLoader` for `.glts` sources, as the app registers its `GLTSLoader`, and add every level to `scene`: a level can hold a `.glts` source the script loaded, which GLTS requires beneath `scene`.
55
+
56
+ ```ts
57
+ // public/precompute.glts
58
+ import { gltsLoader, loadingManager, scene } from "@drawcall/glts";
59
+ import { LodLoader } from "@drawcall/lod";
60
+ import { ladders } from "./ladders.ts";
61
+
62
+ loadingManager.addHandler(/\.glts$/i, gltsLoader);
63
+ const loader = new LodLoader(loadingManager);
64
+ for (const ladder of ladders) {
65
+ for (const level of await loader.loadAsync(ladder)) scene.add(level.object ?? (await level.load()));
66
+ }
67
+ ```
68
+
69
+ Edits to the script, its helpers, packages and loaded files invalidate the derived levels; no dependency list is needed. A level rebuilds on every load when its source or anything the script loads cannot be tied to a project file: packages imported from a CDN at runtime or not installed by the app, resources from another origin, and same-origin URLs without a file, such as URLs a server answers dynamically. Precomputing such a level fails the build, so install every package the precomputed sources import.
70
+
71
+ Cache expensive generation inside a `.glts` asset, such as geometry, rooms or generated texture data that would otherwise stall the first approach, as the GLTS skill describes; leave cheap generation uncached. Use the `encodeMeshAsync` / `decodeMeshAsync` codec for geometry an asset caches itself.
72
+
73
+ ## Place, share and dispose levels
74
+
75
+ Load an asset once and build an `InstancedLod` per spatial region from its levels, each with its own chunks of placements and colours; clone a `Lod` for further independently placed copies. Reuse the loaded levels across Lods; adjust each Lod's resolved thresholds through its `levels`. Clones and InstancedLods share geometry, materials, textures and deferred loads. Disposing a `Lod` or `InstancedLod` frees only what it made of its levels: objects you give or load are yours to dispose once every Lod drawing them is disposed, while `LodLoader` frees what it loaded when the last Lod holding its levels is disposed.
76
+
77
+ Use `onLevel` for application effects and shadow settings, `castShadow` and `shadowSide` included. Put source generation and its materials in the asset so that the same URL also loads correctly outside the LOD host.
78
+
79
+ Levels draw with copies of the source's materials, made once through three's `clone`. A custom material class must carry its own state in `copy`, and changes meant to reach drawn levels belong in shared TSL uniforms rather than in properties set on the original afterwards.
80
+
81
+ Apply a shared `Wind` after assembling a LOD and before its first render; arriving levels inherit the effect. A `Lod` or `InstancedLod` that has rendered refuses wind: its shaders would build in a frame. Apply it separately to nested LODs and independently managed batches.
82
+
83
+ Levels are chosen by on-screen size in CSS pixels on the actual render target, so a lower renderer pixel ratio saves shading, not geometry. Compile the initial scene with the actual camera and renderer before revealing it. Render every frame while finer levels load: they upload and compile only inside renders.
84
+
85
+ ## Add instances in chunks and thin them with distance
40
86
 
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);
87
+ A fixed set of copies is one chunk. For copies that exist only near the camera, such as plants on patches of ground, add a chunk when a patch comes into reach and remove it when the camera moves on; a few chunks a frame cost about what they hold. Prefer whole chunks over parking unused instances at scale 0. When a compute pass of your own writes the copies, give `addChunk` its storage attributes and the bounds the CPU cannot measure (see `InstancedLodChunkParameters`).
44
88
 
45
- Lod.debug = true; // tint levels to see where each draws
89
+ Give an InstancedLod a `cullDistance` to thin it out towards a distance instead of cutting it off. `scaleSurvivors` enlarges the copies that remain so that small things like grass cover about as much; leave it off for objects whose size reads, such as trees. Thinning does not change which levels load: let the ladder's last drawn level end near `far`, or add an empty level there.
90
+
91
+ In this example `grass` is an InstancedLod, `camera` the camera it thins from, and `matrices` a patch's instance matrices, 16 floats each.
92
+
93
+ ```ts
94
+ grass.cullDistance = { camera, near: 35, far: 60, scaleSurvivors: true };
95
+ const chunk = grass.addChunk({ instanceMatrix: new InstancedBufferAttribute(matrices, 16) });
96
+ // Later, when the patch leaves reach:
97
+ grass.removeChunk(chunk);
98
+ ```
99
+
100
+ ## Assemble levels you already have
101
+
102
+ Pass levels in finest-to-coarsest order. An empty object can be a deliberate culled level. A ladder needs measurable bounds.
103
+
104
+ Describe a deferred representation with a load function, bounds and a transition height. The function returns the object already placed in the Lod's space; the bounds cover it in that same space, including its children. For a cached impostor, decode the atlas and return an `ImpostorMesh` with the intended draw settings.
105
+
106
+ Here, `loadDetailedMesh` loads the detailed object, `sourceBox` describes its bounds in the Lod's space, `coarseMesh` is already available, and `gpu` is the renderer's LodGpu.
107
+
108
+ ```ts
109
+ import { Lod } from "@drawcall/lod";
110
+
111
+ const object = new Lod([{ load: loadDetailedMesh, maxPixels: Infinity, box: sourceBox }, { object: coarseMesh }], gpu);
112
+ scene.add(object);
46
113
  ```
47
114
 
48
- ## How the parts combine
115
+ A level fades in over the coarser one drawn in its place once it arrives. A `Lod` loads the levels its on-screen height needs; an `InstancedLod` loads every level the heights of its chunks' instances can need for a camera, judged from each chunk's bounds, so give chunks tight bounds: a chunk reaching from the camera to the horizon loads every level. The resources a deferred load uses must stay alive until it finishes.
116
+
117
+ For direct preparation and storage outside `LodLoader`, combine `simplify`, `bakeMesh`, or `bakeImpostor` with the corresponding codecs and drawing objects. Bakes that draw take a renderer made with `createBakeRenderer`, never the application's, whose canvas a bake would draw over. Use `ImpostorMesh` directly when copies stay distant throughout their lifetime. For external detail systems, combine an empty near level with a coarse representation and ask `lod.getLevel(camera)` or `chunk.getLevelAt(index, camera)` where to supply detail.
49
118
 
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.
119
+ ## Prepare assets that survive transitions
51
120
 
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.
121
+ Ship source models with deduplicated textures, sized to their on-screen use and GPU-compressed as KTX2, using established tools such as gltf-transform. Uploads are paced per frame, so variants that each carry their own uncompressed 2048² textures add up to gigabytes of uploads and long waits before a close-up is complete. Use instance colour variation when it can replace separately textured variants.
60
122
 
61
- ## Cached and deferred impostors
123
+ Author sources, simplified geometry, low shells and interiors in one coordinate system. A ground-level pivot helps upright repeated assets.
62
124
 
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.
125
+ Author foliage with alpha-tested cut-outs and colour beyond the visible texture edges. The package supplies no foliage material: give cut-out source materials alpha to coverage and an alpha that holds coverage in smaller mips, for the camera and the shadow maps, or distant leaves thin out and vanish. Fill a cut-out's transparent texels with the mean colour of its opaque texels, as the last step, in a format that preserves colour under zero alpha: lossy webp discards it, PNG preserves it, and a KTX2 encoder needs checking. Mips average those texels in, so left dark they darken the distant source while an impostor, baked from full detail, holds its colour and turns brighter where the two switch. Dilating from the edges instead spreads their often dark fringe. Match visual effects across representations so foliage does not change lighting at a transition.
64
126
 
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.
127
+ Custom shading on an impostor has little room: an impostor uses close to WebGPU's 16 varyings, so a colour node of your own may find none left; read per pixel what a mesh would carry. An impostor's `normalView` is one unit normal per texel, however many leaves that texel averages. For a lighting term of your own, read `cosine(impostor.material.spread, lightDirection)`, the mean of the clamped cosine over those leaves. Here `impostor` is the level's `ImpostorMesh` and `lightDirection` the view-space direction towards the light.
128
+
129
+ Match a low shell to the source's occupied shape and fit it within the source's bounds: `LodLoader` rejects a level that reaches beyond them. Preserve important silhouette features and remove faces that will never be seen. Generate shell and source from the same parameters when possible. Author rooms separately, with their boundaries just behind windows and furnishings near visible walls; include rooms in the detailed source when they should appear up close too.
130
+
131
+ For dense cover, combine near individuals with a baked distant patch. Jitter position and rotation to avoid aligned gaps, preserve continuity across tile borders, and use side walls only on exposed boundaries. An empty far level can stop drawing individuals once the patch carries their appearance.
132
+
133
+ ## Wait for a complete frame before a screenshot
134
+
135
+ Levels load as the camera comes to need them, so a frame drawn soon after loading can lack them. Give the Lods and InstancedLods a `manager` of their own, so that it tracks only them: each holds an item there while the levels its cameras need load, compile or fade in, and ends it once they are complete. Render the `Scene` that holds them each frame, since a Lod loads nothing before such a render.
136
+
137
+ To capture one frame, even with nothing cached, place the camera and render, then wait a frame; while the manager is loading, compile with `renderer.compileAsync(scene, camera)`, render again and wait a frame; capture after a quiet frame, one in which no item started or ended. After moving the camera, wait again. In this example `levels` and `treeLevels` are ladders from `LodLoader`, `gpu` the renderer's LodGpu, `instanceMatrix` an attribute of tree matrices, and `viewpoint` the camera position to capture.
66
138
 
67
139
  ```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
- );
140
+ import { LoadingManager } from "three/webgpu";
141
+ import { InstancedLod, Lod } from "@drawcall/lod";
142
+
143
+ const manager = new LoadingManager();
144
+ const lod = new Lod(levels, gpu, { manager });
145
+ const trees = new InstancedLod(treeLevels, gpu, { manager });
146
+ trees.addChunk({ instanceMatrix });
147
+ scene.add(lod, trees);
148
+
149
+ camera.position.copy(viewpoint);
150
+ let loading = false;
151
+ manager.onStart = () => (loading = true);
152
+ manager.onLoad = () => (loading = false);
153
+ const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
154
+ renderer.render(scene, camera);
155
+ await frame();
156
+ while (loading) {
157
+ await renderer.compileAsync(scene, camera);
158
+ renderer.render(scene, camera);
159
+ await frame();
160
+ }
161
+ // The last render drew the complete frame: capture it.
75
162
  ```
76
163
 
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.
164
+ Loaded levels stay until their Lod is disposed, which nothing does for you: in a world larger than memory, dispose the Lods the camera has left far behind.
165
+
166
+ ## Tune the complete scene
167
+
168
+ Tune transitions in the intended viewport with `Lod.debug`, then inspect motion through them. Use `Lod.quality` for a scene-wide adjustment and individual thresholds for particular assets. Give an impostor bake the intended screen size at its switch, then inspect its coverage and colour from several angles. Compare both sides of a switch under one camera, drawing each with the Lod's `forceLevel`, looking toward the sun as well as away: a mismatch can show in one light direction only.
169
+
170
+ Group nearby copies into spatial regions. Broad batches can lose useful culling, while tiny batches spend too much time submitting draws. Add and remove chunks and move changed instances instead of rebuilding whole InstancedLods. Count material groups as well as triangles; an atlas alone does not combine draws. Each distinct material structure costs a pipeline compile on first use, so variants should share material structure.
171
+
172
+ Avoid custom shadow nodes on lights that LOD objects should cast into. An `InstancedLod` draws for each camera, shadow maps' included, only the instances inside its frustum, at the level the map's texels resolve, so shadow casting costs about what the map can show. When it still dominates, turn `castShadow` off in `onLevel` on the levels that matter least to the shadow, such as small plants.
173
+
174
+ Judge impostors by covered screen area and overlap as well as triangle count. Compare individual cards with baked patches for dense cover, and inspect parallax and mip quality during transitions.
175
+
176
+ Compare cold startup, warm startup and first traversal under the same camera movement, viewport, shadows, wind and thresholds. Leave expensive diagnostic overlays out of measurements. A warm repeat does not reveal first-use work, and fewer draws can still lose to worse culling, shading or memory costs.
@@ -1 +0,0 @@
1
- {"version":3,"file":"bounds.js","sourceRoot":"","sources":["../src/bounds.ts"],"names":[],"mappings":"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"}
package/dist/fade.d.ts DELETED
@@ -1,44 +0,0 @@
1
- import { Color, type Node, type Object3D, type StorageBufferAttribute } from "three/webgpu";
2
- /** How a mesh of a level is drawn, which says where its fade and shadow size come from. */
3
- export type Draw = {
4
- kind: "object";
5
- /** the signed fade */
6
- fade: number;
7
- /** the whole object's bounding diameter, world units */
8
- diameter: number;
9
- /** debug tint, white when off */
10
- tint: Color;
11
- } | {
12
- kind: "instances";
13
- /** one signed fade per instance slot; null for none (an ImpostorMesh of its own) */
14
- fades: StorageBufferAttribute | null;
15
- /** one instance's bounding radius in its own space */
16
- radius: number;
17
- tint: Color;
18
- };
19
- /** Draw `mesh` as `draw` says: its materials read it while it renders. */
20
- export declare const setDraw: (mesh: Object3D, draw: Draw) => void;
21
- export declare const drawOf: (mesh: Object3D) => Draw | undefined;
22
- /** This draw's debug tint (see `Lod.debug`). */
23
- export declare const tint: import("three/webgpu").UniformNode<"color", Color>;
24
- /** Interleaved gradient noise over the pixels of the target being drawn. */
25
- export declare const dither: (pixel: Node<"vec2">) => Node<"float">;
26
- /**
27
- * Whether `object` is an ImpostorMesh of one instance, which reads it per object: more,
28
- * or slots the GPU writes (an InstancedLod's indirect draws), come from its storage buffer.
29
- */
30
- export declare const isSingle: (object: Object3D) => boolean;
31
- /** The instance matrix of this draw, for an InstancedMesh or ImpostorMesh with its matrices in a storage buffer; else null. */
32
- export declare function instanceMatrixOf(object: Object3D): Node<"mat4"> | null;
33
- /** This draw's signed fade. */
34
- export declare const fade: import("three/src/nodes/TSL.js").FnNode<[], Node<"float">>;
35
- /** Whether this pixel is this draw's, by its fade: false on the other level's pixels. */
36
- export declare const keepsPixel: import("three/src/nodes/TSL.js").FnNode<[], Node<"bool">>;
37
- /**
38
- * Whether this draw is too small for the shadow map being drawn: narrower than a texel
39
- * of an orthographic (directional light's) map, it would cover a texel centre only by
40
- * chance, flickering as it or the map moves. Only valid in shadow passes.
41
- */
42
- export declare const tooSmallToCast: import("three/src/nodes/TSL.js").FnNode<[], Node<"bool">>;
43
- /** `position` in shadow passes, or nothing (a point) when the draw is too small to cast. */
44
- export declare const castPosition: (position: Node<"vec3">) => Node<"vec3">;
package/dist/fade.js DELETED
@@ -1,78 +0,0 @@
1
- import { Color, Matrix4 } from "three/webgpu";
2
- import { Fn, cameraProjectionMatrix, dot, float, fract, instanceIndex, modelWorldMatrix, screenCoordinate, screenSize, select, uniform, varying, vec2, vec3, } from "three/tsl";
3
- import { isImpostorMesh, storageMatrices } from "./three.js";
4
- import { column, instanceFades, instanceMatrices, isOrthographic, maxScale } from "./tsl.js";
5
- const draws = new WeakMap();
6
- /** Draw `mesh` as `draw` says: its materials read it while it renders. */
7
- export const setDraw = (mesh, draw) => void draws.set(mesh, draw);
8
- export const drawOf = (mesh) => draws.get(mesh);
9
- const WHITE = new Color(1, 1, 1);
10
- const of = (object) => (object ? draws.get(object) : undefined);
11
- // Per-object values, shared by every material: each draw reads its own.
12
- const objectFade = uniform(0).onObjectUpdate(({ object }) => {
13
- const draw = of(object);
14
- return draw?.kind === "object" ? draw.fade : 0;
15
- });
16
- const placement = new Matrix4();
17
- // A mesh no level draws is never too small to cast. An ImpostorMesh of one instance
18
- // reads its own here too, however it is drawn: such meshes share one shader.
19
- const objectDiameter = uniform(0).onObjectUpdate(({ object }) => {
20
- const draw = of(object);
21
- if (draw?.kind === "object")
22
- return draw.diameter;
23
- if (draw?.kind !== "instances" || !object || !isImpostorMesh(object) || !isSingle(object))
24
- return 3.4e38;
25
- return 2 * draw.radius * placement.fromArray(object.instanceMatrix.array).premultiply(object.matrixWorld).getMaxScaleOnAxis();
26
- });
27
- const instanceRadius = uniform(0).onObjectUpdate(({ object }) => {
28
- const draw = of(object);
29
- return draw?.kind === "instances" ? draw.radius : 0;
30
- });
31
- /** This draw's debug tint (see `Lod.debug`). */
32
- export const tint = uniform(new Color(1, 1, 1)).onObjectUpdate(({ object }) => of(object)?.tint ?? WHITE);
33
- /** Interleaved gradient noise over the pixels of the target being drawn. */
34
- export const dither = (pixel) => fract(float(52.9829189).mul(fract(dot(pixel, vec2(0.06711056, 0.00583715)))));
35
- /** The matrix of an ImpostorMesh of one instance, read per object: such meshes share one shader. */
36
- const singleMatrix = uniform(new Matrix4()).onObjectUpdate(({ object }, self) => object && isImpostorMesh(object) ? self.value.fromArray(object.instanceMatrix.array) : undefined);
37
- /**
38
- * Whether `object` is an ImpostorMesh of one instance, which reads it per object: more,
39
- * or slots the GPU writes (an InstancedLod's indirect draws), come from its storage buffer.
40
- */
41
- export const isSingle = (object) => isImpostorMesh(object) && object.count === 1 && object.geometry.getIndirect() === null;
42
- /** The instance matrix of this draw, for an InstancedMesh or ImpostorMesh with its matrices in a storage buffer; else null. */
43
- export function instanceMatrixOf(object) {
44
- if (isSingle(object))
45
- return singleMatrix;
46
- const attribute = storageMatrices(object);
47
- return attribute && instanceMatrices(attribute).element(instanceIndex);
48
- }
49
- /** This draw's signed fade. */
50
- export const fade = Fn((builder) => {
51
- const draw = draws.get(builder.object);
52
- if (draw?.kind !== "instances" || !draw.fades)
53
- return objectFade;
54
- // Constant across the primitive: keep compute-written slots out of fragment bindings.
55
- return varying(instanceFades(draw.fades).element(instanceIndex)).setInterpolation("flat");
56
- });
57
- /** Whether this pixel is this draw's, by its fade: false on the other level's pixels. */
58
- export const keepsPixel = Fn(() => {
59
- const f = fade();
60
- const d = dither(screenCoordinate.xy);
61
- return select(f.greaterThanEqual(0), d.greaterThanEqual(f), d.lessThan(f.negate()));
62
- });
63
- /**
64
- * Whether this draw is too small for the shadow map being drawn: narrower than a texel
65
- * of an orthographic (directional light's) map, it would cover a texel centre only by
66
- * chance, flickering as it or the map moves. Only valid in shadow passes.
67
- */
68
- export const tooSmallToCast = Fn((builder) => {
69
- const object = builder.object;
70
- const instance = draws.get(object)?.kind === "instances" && !isSingle(object) ? instanceMatrixOf(object) : null;
71
- const diameter = instance ? maxScale(modelWorldMatrix.mul(instance)).mul(instanceRadius).mul(2) : objectDiameter;
72
- // projection[1][1] is 2 × zoom / (top - bottom)
73
- const texel = float(2).div(column(cameraProjectionMatrix, 1).y.mul(screenSize.y));
74
- return isOrthographic(cameraProjectionMatrix).and(diameter.lessThan(texel));
75
- });
76
- /** `position` in shadow passes, or nothing (a point) when the draw is too small to cast. */
77
- export const castPosition = (position) => select(tooSmallToCast(), vec3(0), position);
78
- //# sourceMappingURL=fade.js.map
package/dist/fade.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"fade.js","sourceRoot":"","sources":["../src/fade.ts"],"names":[],"mappings":"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"}
@@ -1,15 +0,0 @@
1
- import type { Object3D } from "three/webgpu";
2
- import { type CacheEntryOptions, type Dependencies } from "vitecache";
3
- import { type BakeImpostorOptions } from "./bake/index.js";
4
- import type { Impostor } from "./impostor.js";
5
- /** Cache location; browser basePath and Node root follow vitecache's conventions. */
6
- export type ImpostorCacheOptions = Pick<CacheEntryOptions, "basePath" | "root">;
7
- /**
8
- * Read or bake an impostor with vitecache. Declare every source input as a dependency;
9
- * bake options and the lod/three package versions are added automatically. Dependency
10
- * names beginning with `lod:` are reserved. URL dependencies need the vitecache Vite
11
- * plugin during development and build. Cache errors reject instead of hiding stale data.
12
- */
13
- export declare function cachedImpostor(source: Object3D, options: BakeImpostorOptions, dependencies: Dependencies, cacheOptions?: ImpostorCacheOptions): Promise<Impostor>;
14
- /** Shared persistence for the public bake and the loader's source-excluding bake. */
15
- export declare function cached(options: BakeImpostorOptions, dependencies: Dependencies, make: (options: BakeImpostorOptions) => Promise<Impostor>, cacheOptions?: ImpostorCacheOptions): Promise<Impostor>;