@zephyr3d/scene 0.9.26 → 0.10.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 (506) hide show
  1. package/README.md +60 -27
  2. package/dist/animation/animation_bank.js +634 -0
  3. package/dist/animation/animation_bank.js.map +1 -0
  4. package/dist/animation/animation_bank_gpu.js +256 -0
  5. package/dist/animation/animation_bank_gpu.js.map +1 -0
  6. package/dist/animation/animationset.js +146 -30
  7. package/dist/animation/animationset.js.map +1 -1
  8. package/dist/animation/cloth/gpu_cloth_component.js +406 -0
  9. package/dist/animation/cloth/gpu_cloth_component.js.map +1 -0
  10. package/dist/animation/cloth/gpu_cloth_system.js +332 -202
  11. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
  12. package/dist/animation/gpu_animation_driver.js +289 -0
  13. package/dist/animation/gpu_animation_driver.js.map +1 -0
  14. package/dist/animation/joint_dynamics/collision.js +112 -1
  15. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  16. package/dist/animation/joint_dynamics/controller.js +6 -0
  17. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js +11 -0
  19. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  20. package/dist/animation/joint_dynamics/solver.js +50 -12
  21. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  22. package/dist/animation/joint_dynamics/types.js.map +1 -1
  23. package/dist/animation/skeleton.js +300 -56
  24. package/dist/animation/skeleton.js.map +1 -1
  25. package/dist/animation/skin_palette_atlas.js +252 -0
  26. package/dist/animation/skin_palette_atlas.js.map +1 -0
  27. package/dist/animation/spring/multi_chain_spring_system.js +298 -38
  28. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  29. package/dist/animation/spring/spring_chain.js +2 -0
  30. package/dist/animation/spring/spring_chain.js.map +1 -1
  31. package/dist/animation/spring/spring_collider.js +159 -21
  32. package/dist/animation/spring/spring_collider.js.map +1 -1
  33. package/dist/animation/spring/spring_particle.js +1 -0
  34. package/dist/animation/spring/spring_particle.js.map +1 -1
  35. package/dist/animation/spring/spring_solver.js +173 -0
  36. package/dist/animation/spring/spring_solver.js.map +1 -0
  37. package/dist/animation/spring/spring_system.js +249 -24
  38. package/dist/animation/spring/spring_system.js.map +1 -1
  39. package/dist/animation/spring_modifier.js +32 -1
  40. package/dist/animation/spring_modifier.js.map +1 -1
  41. package/dist/app/app.js +26 -0
  42. package/dist/app/app.js.map +1 -1
  43. package/dist/app/engine.js +86 -15
  44. package/dist/app/engine.js.map +1 -1
  45. package/dist/app/gamepad.js +191 -0
  46. package/dist/app/gamepad.js.map +1 -0
  47. package/dist/app/inputactions.js +271 -0
  48. package/dist/app/inputactions.js.map +1 -0
  49. package/dist/app/inputmgr.js +331 -22
  50. package/dist/app/inputmgr.js.map +1 -1
  51. package/dist/app/loading.js +138 -0
  52. package/dist/app/loading.js.map +1 -0
  53. package/dist/app/pipelinecache.js +209 -0
  54. package/dist/app/pipelinecache.js.map +1 -0
  55. package/dist/app/runtimescript.js +25 -0
  56. package/dist/app/runtimescript.js.map +1 -1
  57. package/dist/app/scriptingsystem.js +54 -0
  58. package/dist/app/scriptingsystem.js.map +1 -1
  59. package/dist/app/scriptregistry.js +7 -2
  60. package/dist/app/scriptregistry.js.map +1 -1
  61. package/dist/asset/asset_manifest.js +59 -0
  62. package/dist/asset/asset_manifest.js.map +1 -0
  63. package/dist/asset/asset_preload.js +258 -0
  64. package/dist/asset/asset_preload.js.map +1 -0
  65. package/dist/asset/assetmanager.js +117 -65
  66. package/dist/asset/assetmanager.js.map +1 -1
  67. package/dist/asset/loaders/ktx2/basis/LICENSE +201 -0
  68. package/dist/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  69. package/dist/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  70. package/dist/asset/loaders/ktx2/basis_location.js +17 -0
  71. package/dist/asset/loaders/ktx2/basis_location.js.map +1 -0
  72. package/dist/asset/loaders/ktx2/ktx2_loader.js +88 -0
  73. package/dist/asset/loaders/ktx2/ktx2_loader.js.map +1 -0
  74. package/dist/asset/loaders/ktx2/ktx2_targets.js +118 -0
  75. package/dist/asset/loaders/ktx2/ktx2_targets.js.map +1 -0
  76. package/dist/asset/loaders/ktx2/transcoder_pool.js +259 -0
  77. package/dist/asset/loaders/ktx2/transcoder_pool.js.map +1 -0
  78. package/dist/asset/mesh_lod_builder.js +104 -0
  79. package/dist/asset/mesh_lod_builder.js.map +1 -0
  80. package/dist/asset/mesh_settings.js +111 -0
  81. package/dist/asset/mesh_settings.js.map +1 -0
  82. package/dist/asset/meshopt_decoder.js +18 -0
  83. package/dist/asset/meshopt_decoder.js.map +1 -0
  84. package/dist/asset/meshopt_simplifier.js +26 -0
  85. package/dist/asset/meshopt_simplifier.js.map +1 -0
  86. package/dist/asset/model.js +177 -3
  87. package/dist/asset/model.js.map +1 -1
  88. package/dist/asset/texture_settings.js +231 -0
  89. package/dist/asset/texture_settings.js.map +1 -0
  90. package/dist/asset/zmsh_binary.js +131 -0
  91. package/dist/asset/zmsh_binary.js.map +1 -0
  92. package/dist/asset/zsdf_binary.js +111 -0
  93. package/dist/asset/zsdf_binary.js.map +1 -0
  94. package/dist/blitter/bilateralblur.js +1 -2
  95. package/dist/blitter/bilateralblur.js.map +1 -1
  96. package/dist/blitter/gaussianblur.js +1 -6
  97. package/dist/blitter/gaussianblur.js.map +1 -1
  98. package/dist/camera/base.js +31 -10
  99. package/dist/camera/base.js.map +1 -1
  100. package/dist/camera/camera.js +178 -200
  101. package/dist/camera/camera.js.map +1 -1
  102. package/dist/camera/follow.js +342 -0
  103. package/dist/camera/follow.js.map +1 -0
  104. package/dist/camera/fps.js +9 -7
  105. package/dist/camera/fps.js.map +1 -1
  106. package/dist/camera/orbit.js +46 -19
  107. package/dist/camera/orbit.js.map +1 -1
  108. package/dist/index.d.ts +30475 -19865
  109. package/dist/index.js +67 -12
  110. package/dist/index.js.map +1 -1
  111. package/dist/material/blinn.js +3 -0
  112. package/dist/material/blinn.js.map +1 -1
  113. package/dist/material/eye.js +1 -1
  114. package/dist/material/eye.js.map +1 -1
  115. package/dist/material/hair.js +2 -3
  116. package/dist/material/hair.js.map +1 -1
  117. package/dist/material/hairstrand.js +235 -235
  118. package/dist/material/hairstrand.js.map +1 -1
  119. package/dist/material/lambert.js +4 -1
  120. package/dist/material/lambert.js.map +1 -1
  121. package/dist/material/material.js +362 -300
  122. package/dist/material/material.js.map +1 -1
  123. package/dist/material/meshmaterial.js +118 -45
  124. package/dist/material/meshmaterial.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  126. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  128. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  130. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  132. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  133. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  134. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  135. package/dist/material/mixins/lit.js +223 -83
  136. package/dist/material/mixins/lit.js.map +1 -1
  137. package/dist/material/mixins/pbr/common.js +31 -206
  138. package/dist/material/mixins/pbr/common.js.map +1 -1
  139. package/dist/material/mtoon.js +1 -1
  140. package/dist/material/mtoon.js.map +1 -1
  141. package/dist/material/pbrmr.js +3 -0
  142. package/dist/material/pbrmr.js.map +1 -1
  143. package/dist/material/pbrsg.js +3 -0
  144. package/dist/material/pbrsg.js.map +1 -1
  145. package/dist/material/sdf_debug.js +78 -0
  146. package/dist/material/sdf_debug.js.map +1 -0
  147. package/dist/material/shader/helper.js +733 -190
  148. package/dist/material/shader/helper.js.map +1 -1
  149. package/dist/material/shader/terrain_blend.js +204 -43
  150. package/dist/material/shader/terrain_blend.js.map +1 -1
  151. package/dist/material/skin.js +331 -297
  152. package/dist/material/skin.js.map +1 -1
  153. package/dist/material/skinprofile.js +1058 -0
  154. package/dist/material/skinprofile.js.map +1 -0
  155. package/dist/material/sss.js +353 -0
  156. package/dist/material/sss.js.map +1 -0
  157. package/dist/material/sss_kernel.js +265 -0
  158. package/dist/material/sss_kernel.js.map +1 -0
  159. package/dist/material/sssprofile.js +879 -0
  160. package/dist/material/sssprofile.js.map +1 -0
  161. package/dist/material/subsurfaceprofile.js +4 -8
  162. package/dist/material/subsurfaceprofile.js.map +1 -1
  163. package/dist/material/terrain-cm.js +464 -188
  164. package/dist/material/terrain-cm.js.map +1 -1
  165. package/dist/material/unlit.js +3 -0
  166. package/dist/material/unlit.js.map +1 -1
  167. package/dist/material/water.js +2009 -201
  168. package/dist/material/water.js.map +1 -1
  169. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  172. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  173. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  174. package/dist/physics/character.js +226 -0
  175. package/dist/physics/character.js.map +1 -0
  176. package/dist/physics/collider.js +209 -0
  177. package/dist/physics/collider.js.map +1 -0
  178. package/dist/physics/events.js +79 -0
  179. package/dist/physics/events.js.map +1 -0
  180. package/dist/physics/joint.js +272 -0
  181. package/dist/physics/joint.js.map +1 -0
  182. package/dist/physics/node_physics.js +234 -0
  183. package/dist/physics/node_physics.js.map +1 -0
  184. package/dist/physics/part.js +91 -0
  185. package/dist/physics/part.js.map +1 -0
  186. package/dist/physics/rigid_body.js +379 -0
  187. package/dist/physics/rigid_body.js.map +1 -0
  188. package/dist/physics/vehicle.js +335 -0
  189. package/dist/physics/vehicle.js.map +1 -0
  190. package/dist/physics/world.js +25 -0
  191. package/dist/physics/world.js.map +1 -0
  192. package/dist/posteffect/bloom.js +49 -14
  193. package/dist/posteffect/bloom.js.map +1 -1
  194. package/dist/posteffect/motionblur.js +668 -63
  195. package/dist/posteffect/motionblur.js.map +1 -1
  196. package/dist/posteffect/posteffect.js +12 -1
  197. package/dist/posteffect/posteffect.js.map +1 -1
  198. package/dist/posteffect/postsss.js +572 -0
  199. package/dist/posteffect/postsss.js.map +1 -0
  200. package/dist/posteffect/sao.js +48 -50
  201. package/dist/posteffect/sao.js.map +1 -1
  202. package/dist/posteffect/skinsss.js +605 -355
  203. package/dist/posteffect/skinsss.js.map +1 -1
  204. package/dist/posteffect/ssgi.js +91 -162
  205. package/dist/posteffect/ssgi.js.map +1 -1
  206. package/dist/posteffect/ssr.js +3 -11
  207. package/dist/posteffect/ssr.js.map +1 -1
  208. package/dist/posteffect/sss.js +34 -15
  209. package/dist/posteffect/sss.js.map +1 -1
  210. package/dist/posteffect/taa.js +1 -0
  211. package/dist/posteffect/taa.js.map +1 -1
  212. package/dist/render/abuffer_oit.js +40 -39
  213. package/dist/render/abuffer_oit.js.map +1 -1
  214. package/dist/render/clipmap.js +328 -62
  215. package/dist/render/clipmap.js.map +1 -1
  216. package/dist/render/cluster_light.js +958 -224
  217. package/dist/render/cluster_light.js.map +1 -1
  218. package/dist/render/cull_visitor.js +26 -5
  219. package/dist/render/cull_visitor.js.map +1 -1
  220. package/dist/render/debug_views.js +37 -0
  221. package/dist/render/debug_views.js.map +1 -0
  222. package/dist/render/depthpass.js +5 -1
  223. package/dist/render/depthpass.js.map +1 -1
  224. package/dist/render/drawable_mixin.js +53 -6
  225. package/dist/render/drawable_mixin.js.map +1 -1
  226. package/dist/render/dualdepthpeeling_oit.js +53 -53
  227. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  228. package/dist/render/envlight.js +196 -113
  229. package/dist/render/envlight.js.map +1 -1
  230. package/dist/render/fbm_wavegenerator.js +8 -13
  231. package/dist/render/fbm_wavegenerator.js.map +1 -1
  232. package/dist/render/fft_wavegenerator.js +268 -136
  233. package/dist/render/fft_wavegenerator.js.map +1 -1
  234. package/dist/render/gerstner_wavegenerator.js +113 -28
  235. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  236. package/dist/render/gi/probe_volume.js +1334 -0
  237. package/dist/render/gi/probe_volume.js.map +1 -0
  238. package/dist/render/gi/scene_gi.js +282 -0
  239. package/dist/render/gi/scene_gi.js.map +1 -0
  240. package/dist/render/gi/voxel_atlas.js +198 -0
  241. package/dist/render/gi/voxel_atlas.js.map +1 -0
  242. package/dist/render/gi/voxel_lighting.js +652 -0
  243. package/dist/render/gi/voxel_lighting.js.map +1 -0
  244. package/dist/render/gi/voxel_scene.js +642 -0
  245. package/dist/render/gi/voxel_scene.js.map +1 -0
  246. package/dist/render/gi/voxelize_pass.js +223 -0
  247. package/dist/render/gi/voxelize_pass.js.map +1 -0
  248. package/dist/render/globalbindgroup_allocator.js +3 -2
  249. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  250. package/dist/render/gpu_picking.js +9 -36
  251. package/dist/render/gpu_picking.js.map +1 -1
  252. package/dist/render/hzb.js +80 -22
  253. package/dist/render/hzb.js.map +1 -1
  254. package/dist/render/instance_culling.js +499 -0
  255. package/dist/render/instance_culling.js.map +1 -0
  256. package/dist/render/lightpass.js +23 -9
  257. package/dist/render/lightpass.js.map +1 -1
  258. package/dist/render/lod.js +46 -0
  259. package/dist/render/lod.js.map +1 -0
  260. package/dist/render/oit.js +11 -11
  261. package/dist/render/oit.js.map +1 -1
  262. package/dist/render/primitive.js +301 -9
  263. package/dist/render/primitive.js.map +1 -1
  264. package/dist/render/primitive_readback.js +209 -0
  265. package/dist/render/primitive_readback.js.map +1 -0
  266. package/dist/render/render_queue.js +198 -11
  267. package/dist/render/render_queue.js.map +1 -1
  268. package/dist/render/renderer.js +14 -7
  269. package/dist/render/renderer.js.map +1 -1
  270. package/dist/render/rendergraph/blackboard.js +15 -1
  271. package/dist/render/rendergraph/blackboard.js.map +1 -1
  272. package/dist/render/rendergraph/executor.js +136 -0
  273. package/dist/render/rendergraph/executor.js.map +1 -1
  274. package/dist/render/rendergraph/forward_plus_builder.js +585 -59
  275. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  276. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  277. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  278. package/dist/render/rendergraph/history_resources.js +8 -1
  279. package/dist/render/rendergraph/history_resources.js.map +1 -1
  280. package/dist/render/rendergraph/scene_render_context.js +31 -2
  281. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  282. package/dist/render/rendergraph/types.js.map +1 -1
  283. package/dist/render/renderpass.js +28 -5
  284. package/dist/render/renderpass.js.map +1 -1
  285. package/dist/render/sdf/box_allocator.js +98 -0
  286. package/dist/render/sdf/box_allocator.js.map +1 -0
  287. package/dist/render/sdf/global_sdf.js +865 -0
  288. package/dist/render/sdf/global_sdf.js.map +1 -0
  289. package/dist/render/sdf/scene_sdf.js +562 -0
  290. package/dist/render/sdf/scene_sdf.js.map +1 -0
  291. package/dist/render/sdf/sdf_atlas.js +227 -0
  292. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  293. package/dist/render/sdf/sdf_programs.js +42 -0
  294. package/dist/render/sdf/sdf_programs.js.map +1 -0
  295. package/dist/render/sdf/sdf_view.js +199 -0
  296. package/dist/render/sdf/sdf_view.js.map +1 -0
  297. package/dist/render/sdf/waterline.js +610 -0
  298. package/dist/render/sdf/waterline.js.map +1 -0
  299. package/dist/render/shadow_mask_pass.js +142 -25
  300. package/dist/render/shadow_mask_pass.js.map +1 -1
  301. package/dist/render/shore_wave_generator.js +1193 -0
  302. package/dist/render/shore_wave_generator.js.map +1 -0
  303. package/dist/render/sky.js +516 -86
  304. package/dist/render/sky.js.map +1 -1
  305. package/dist/render/transmission_thickness_pass.js +477 -0
  306. package/dist/render/transmission_thickness_pass.js.map +1 -0
  307. package/dist/render/underwater.js +524 -0
  308. package/dist/render/underwater.js.map +1 -0
  309. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  310. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  311. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  312. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  313. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  314. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  315. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  316. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  317. package/dist/render/volumetric_cloud/resources.js +380 -0
  318. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  319. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  320. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  321. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  322. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  323. package/dist/render/water_caustics.js +1195 -0
  324. package/dist/render/water_caustics.js.map +1 -0
  325. package/dist/render/water_foam.js +627 -0
  326. package/dist/render/water_foam.js.map +1 -0
  327. package/dist/render/water_interaction.js +1795 -0
  328. package/dist/render/water_interaction.js.map +1 -0
  329. package/dist/render/weightedblended_oit.js +36 -36
  330. package/dist/render/weightedblended_oit.js.map +1 -1
  331. package/dist/scene/basesprite.js +9 -4
  332. package/dist/scene/basesprite.js.map +1 -1
  333. package/dist/scene/batchgroup.js +108 -12
  334. package/dist/scene/batchgroup.js.map +1 -1
  335. package/dist/scene/environment.js +28 -7
  336. package/dist/scene/environment.js.map +1 -1
  337. package/dist/scene/foliage.js +1020 -0
  338. package/dist/scene/foliage.js.map +1 -0
  339. package/dist/scene/foliage_buckets.js +147 -0
  340. package/dist/scene/foliage_buckets.js.map +1 -0
  341. package/dist/scene/foliage_mask.js +358 -0
  342. package/dist/scene/foliage_mask.js.map +1 -0
  343. package/dist/scene/hair.js +15 -4
  344. package/dist/scene/hair.js.map +1 -1
  345. package/dist/scene/light.js +179 -13
  346. package/dist/scene/light.js.map +1 -1
  347. package/dist/scene/mesh.js +327 -45
  348. package/dist/scene/mesh.js.map +1 -1
  349. package/dist/scene/meshdrawable.js +6 -0
  350. package/dist/scene/meshdrawable.js.map +1 -1
  351. package/dist/scene/msdftext.js +9 -3
  352. package/dist/scene/msdftext.js.map +1 -1
  353. package/dist/scene/msdftextsprite.js +9 -3
  354. package/dist/scene/msdftextsprite.js.map +1 -1
  355. package/dist/scene/particlesys.js +8 -2
  356. package/dist/scene/particlesys.js.map +1 -1
  357. package/dist/scene/physics_settings.js +157 -0
  358. package/dist/scene/physics_settings.js.map +1 -0
  359. package/dist/scene/scene.js +190 -44
  360. package/dist/scene/scene.js.map +1 -1
  361. package/dist/scene/scene_node.js +147 -6
  362. package/dist/scene/scene_node.js.map +1 -1
  363. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  364. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  365. package/dist/scene/terrain-cm/grass.js +881 -42
  366. package/dist/scene/terrain-cm/grass.js.map +1 -1
  367. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  368. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  369. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  370. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  371. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  372. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  373. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  374. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  375. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  376. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  377. package/dist/scene/update_queue.js +54 -0
  378. package/dist/scene/update_queue.js.map +1 -0
  379. package/dist/scene/water.js +997 -96
  380. package/dist/scene/water.js.map +1 -1
  381. package/dist/scene/water_surface_sampler.js +324 -0
  382. package/dist/scene/water_surface_sampler.js.map +1 -0
  383. package/dist/scene/wind.js +317 -0
  384. package/dist/scene/wind.js.map +1 -0
  385. package/dist/shaders/atmosphere.js +388 -166
  386. package/dist/shaders/atmosphere.js.map +1 -1
  387. package/dist/shaders/fog.js +100 -32
  388. package/dist/shaders/fog.js.map +1 -1
  389. package/dist/shaders/gi.js +365 -0
  390. package/dist/shaders/gi.js.map +1 -0
  391. package/dist/shaders/ltc_rect.js +202 -0
  392. package/dist/shaders/ltc_rect.js.map +1 -0
  393. package/dist/shaders/misc.js +1 -10
  394. package/dist/shaders/misc.js.map +1 -1
  395. package/dist/shaders/noise.js +5 -1
  396. package/dist/shaders/noise.js.map +1 -1
  397. package/dist/shaders/probes.js +309 -0
  398. package/dist/shaders/probes.js.map +1 -0
  399. package/dist/shaders/sdf.js +537 -0
  400. package/dist/shaders/sdf.js.map +1 -0
  401. package/dist/shaders/shadow.js +104 -70
  402. package/dist/shaders/shadow.js.map +1 -1
  403. package/dist/shaders/skin_brdf.js +243 -0
  404. package/dist/shaders/skin_brdf.js.map +1 -0
  405. package/dist/shaders/ssr.js +6 -159
  406. package/dist/shaders/ssr.js.map +1 -1
  407. package/dist/shaders/temporal.js +38 -27
  408. package/dist/shaders/temporal.js.map +1 -1
  409. package/dist/shaders/water.js +40 -87
  410. package/dist/shaders/water.js.map +1 -1
  411. package/dist/shaders/water_medium.js +42 -0
  412. package/dist/shaders/water_medium.js.map +1 -0
  413. package/dist/shadow/dom.js +110 -19
  414. package/dist/shadow/dom.js.map +1 -1
  415. package/dist/shadow/esm.js +3 -3
  416. package/dist/shadow/esm.js.map +1 -1
  417. package/dist/shadow/pcf_opt.js +15 -48
  418. package/dist/shadow/pcf_opt.js.map +1 -1
  419. package/dist/shadow/pcf_pd.js +15 -53
  420. package/dist/shadow/pcf_pd.js.map +1 -1
  421. package/dist/shadow/pcss.js +34 -9
  422. package/dist/shadow/pcss.js.map +1 -1
  423. package/dist/shadow/shadow_impl.js +3 -8
  424. package/dist/shadow/shadow_impl.js.map +1 -1
  425. package/dist/shadow/shadow_region.js +87 -49
  426. package/dist/shadow/shadow_region.js.map +1 -1
  427. package/dist/shadow/shadowmapper.js +93 -53
  428. package/dist/shadow/shadowmapper.js.map +1 -1
  429. package/dist/shadow/ssm.js +18 -72
  430. package/dist/shadow/ssm.js.map +1 -1
  431. package/dist/shadow/vsm.js +3 -3
  432. package/dist/shadow/vsm.js.map +1 -1
  433. package/dist/shapes/shape.js +44 -1
  434. package/dist/shapes/shape.js.map +1 -1
  435. package/dist/utility/buoyancy.js +287 -0
  436. package/dist/utility/buoyancy.js.map +1 -0
  437. package/dist/utility/physical.js +10 -1
  438. package/dist/utility/physical.js.map +1 -1
  439. package/dist/utility/pmrem.js +17 -43
  440. package/dist/utility/pmrem.js.map +1 -1
  441. package/dist/utility/sdf/bake.js +435 -0
  442. package/dist/utility/sdf/bake.js.map +1 -0
  443. package/dist/utility/sdf/bvh.js +576 -0
  444. package/dist/utility/sdf/bvh.js.map +1 -0
  445. package/dist/utility/serialization/manager.js +36 -6
  446. package/dist/utility/serialization/manager.js.map +1 -1
  447. package/dist/utility/serialization/scene/animation.js +77 -7
  448. package/dist/utility/serialization/scene/animation.js.map +1 -1
  449. package/dist/utility/serialization/scene/batch.js +11 -4
  450. package/dist/utility/serialization/scene/batch.js.map +1 -1
  451. package/dist/utility/serialization/scene/camera.js +140 -279
  452. package/dist/utility/serialization/scene/camera.js.map +1 -1
  453. package/dist/utility/serialization/scene/cloth.js +25 -0
  454. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  455. package/dist/utility/serialization/scene/common.js +14 -6
  456. package/dist/utility/serialization/scene/common.js.map +1 -1
  457. package/dist/utility/serialization/scene/foliage.js +821 -0
  458. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  459. package/dist/utility/serialization/scene/hair.js +11 -4
  460. package/dist/utility/serialization/scene/hair.js.map +1 -1
  461. package/dist/utility/serialization/scene/light.js +115 -7
  462. package/dist/utility/serialization/scene/light.js.map +1 -1
  463. package/dist/utility/serialization/scene/material.js +388 -220
  464. package/dist/utility/serialization/scene/material.js.map +1 -1
  465. package/dist/utility/serialization/scene/mesh.js +69 -4
  466. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  467. package/dist/utility/serialization/scene/node.js +140 -5
  468. package/dist/utility/serialization/scene/node.js.map +1 -1
  469. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  470. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  471. package/dist/utility/serialization/scene/particle.js +11 -4
  472. package/dist/utility/serialization/scene/particle.js.map +1 -1
  473. package/dist/utility/serialization/scene/physics.js +120 -0
  474. package/dist/utility/serialization/scene/physics.js.map +1 -0
  475. package/dist/utility/serialization/scene/primitive.js +124 -10
  476. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  477. package/dist/utility/serialization/scene/scene.js +1687 -51
  478. package/dist/utility/serialization/scene/scene.js.map +1 -1
  479. package/dist/utility/serialization/scene/sprite.js +11 -4
  480. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  481. package/dist/utility/serialization/scene/terrain.js +163 -33
  482. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  483. package/dist/utility/serialization/scene/text.js +11 -4
  484. package/dist/utility/serialization/scene/text.js.map +1 -1
  485. package/dist/utility/serialization/scene/water.js +2473 -114
  486. package/dist/utility/serialization/scene/water.js.map +1 -1
  487. package/dist/utility/serialization/types.js.map +1 -1
  488. package/dist/utility/shprojector.js +97 -109
  489. package/dist/utility/shprojector.js.map +1 -1
  490. package/dist/utility/textures/foam.js +170 -0
  491. package/dist/utility/textures/foam.js.map +1 -0
  492. package/dist/utility/textures/ggxlut.js +17 -43
  493. package/dist/utility/textures/ggxlut.js.map +1 -1
  494. package/dist/utility/textures/ltcdata.js +4 -4
  495. package/dist/utility/textures/ltcdata.js.map +1 -1
  496. package/dist/utility/textures/ltclut.js +16 -6
  497. package/dist/utility/textures/ltclut.js.map +1 -1
  498. package/dist/utility/unsupported.js +24 -0
  499. package/dist/utility/unsupported.js.map +1 -0
  500. package/dist/values.js +13 -1
  501. package/dist/values.js.map +1 -1
  502. package/package.json +25 -12
  503. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  504. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  505. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  506. package/src/asset/meshoptimizer/LICENSE +21 -0
@@ -0,0 +1,1334 @@
1
+ import { Vector4, PRNG, half2float } from '@zephyr3d/base';
2
+ import { getDevice } from '../../app/api.js';
3
+ import { buildSDFProgram } from '../sdf/sdf_programs.js';
4
+ import { drawFullscreenQuad } from '../fullscreenquad.js';
5
+ import { fetchSampler } from '../../utility/misc.js';
6
+ import { getGICacheExposure } from '../../utility/physical.js';
7
+ import { RENDER_PASS_TYPE_LIGHT } from '../../values.js';
8
+ import { RenderGraphExecutor } from '../rendergraph/executor.js';
9
+ import { declareGlobalSDFUniforms, declareDynamicSDFUniforms, GLOBAL_SDF_LEVELS, sampleSceneSDF, sampleGlobalSDF, sampleSceneSDFHinted, sdfSurfaceExpand, sdfRefineHit, sampleGlobalSDFLevel } from '../../shaders/sdf.js';
10
+ import { VOXEL_SCENE_ALBEDO, VOXEL_SCENE_EMISSIVE, VOXEL_SCENE_RADIANCE, VOXEL_SCENE_ANISO0, VOXEL_SCENE_ANISO1, declareVoxelSceneUniforms, sampleVoxelSceneRadiance } from '../../shaders/gi.js';
11
+ import { PROBE_MAX_LEVELS, PROBE_PARAM_VEC4S, PROBE_IRRADIANCE, PROBE_VISIBILITY, PROBE_DATA, PROBE_PARAMS, PROBE_STATE_INACTIVE, probeTexelFromIndex, probeWorldFromTexel, probeLevelOrigin, probePosition, probeLevelGrid, probeTileIndex, probeTexelFromWorld, probeActive, probeTileOrigin, quatRotate, sphericalFibonacci, octDecode } from '../../shaders/probes.js';
12
+
13
+ /** Texels of an irradiance tile, border included. */ const IRRADIANCE_TEXELS = 8;
14
+ /** Texels of a visibility tile, border included. */ const VISIBILITY_TEXELS = 16;
15
+ /** Tiles per row of the blend outputs, which hold the tiles of one update by selection slot. */ const STAGING_COLS = 64;
16
+ /** Rays a blend workgroup stages in shared memory at a time. */ const BLEND_BATCH = 128;
17
+ /** Sphere tracing steps of a probe ray. */ const TRACE_STEPS = 48;
18
+ /** Visibility distance clamp, in probe spacings: just enough to separate blockers between neighbouring probes. */ const VISIBILITY_RANGE = 2.6;
19
+ /** Frames between relocations of an updated probe. */ const RELOCATE_EVERY = 8;
20
+ /** Bits of a selection entry: tile index, level, first-update flag. */ const SELECT_INDEX_MASK = 0x00ffffff;
21
+ const SELECT_LEVEL_SHIFT = 24;
22
+ const SELECT_FIRST_BIT = 0x80000000;
23
+ /** Magic and version of a saved probe state. */ const STATE_MAGIC = 0x50494a5a; // 'ZJIP'
24
+ /** Bump when the saved layout or meaning changes. */ const STATE_VERSION = 5;
25
+ /** Hit distance written by a sky-escaping ray: beyond any trace length, within half float range. */ const SKY_ESCAPE_DISTANCE = 16384;
26
+ /**
27
+ * The probe volume read by the draw, or null outside the light pass, with the
28
+ * environment light off, or with GI disabled or not yet built.
29
+ * @internal
30
+ */ function getProbeVolumeForDraw(ctx) {
31
+ if (!ctx || ctx.renderPass?.type !== RENDER_PASS_TYPE_LIGHT || !ctx.drawEnvLight || !ctx.scene?.gi.enabled) {
32
+ return null;
33
+ }
34
+ const probes = ctx.scene.gi.probes;
35
+ return probes?.ready ? probes : null;
36
+ }
37
+ /**
38
+ * The scene's built probe volume when GI is on, for passes other than the light pass.
39
+ * @internal
40
+ */ function getActiveProbeVolume(scene) {
41
+ if (!scene.gi.enabled) {
42
+ return null;
43
+ }
44
+ const probes = scene.gi.probes;
45
+ return probes?.ready ? probes : null;
46
+ }
47
+ /**
48
+ * A camera-centred clipmap of DDGI light probes (Majercik et al. JCGT 2019/2021)
49
+ * holding octahedral irradiance and visibility maps. Each frame a budget of probes
50
+ * traces rays through the scene SDF, shading hits from the voxel scene and misses
51
+ * from the sky. Windows scroll toroidally; entering probes are seeded from the level
52
+ * above.
53
+ * Read via {@link sampleProbeIrradiance}; state can be saved with
54
+ * {@link ProbeVolume.exportState}. WebGPU only.
55
+ *
56
+ * @public
57
+ */ class ProbeVolume {
58
+ _scene;
59
+ _options;
60
+ _levels;
61
+ _count;
62
+ _tileCols;
63
+ _rays;
64
+ _budget;
65
+ _irradiance;
66
+ _irradianceNext;
67
+ /** Irradiance seeds of one update's probes starting over, by selection slot like the blend outputs. */ _irradianceSeed;
68
+ _visibility;
69
+ _visibilityNext;
70
+ /** Two distance moments; rg16f where storage-writable (texture-formats-tier1), else rgba16f. */ _visibilityFormat;
71
+ _rayTexture;
72
+ _rayFramebuffer;
73
+ _data;
74
+ _selected;
75
+ _selection;
76
+ _dataReset;
77
+ _frame;
78
+ _updates;
79
+ _lastUpdated;
80
+ _params;
81
+ /** Live camera exposure over the cache exposure, see {@link ProbeVolume.setReadExposure}. */ _exposureScale;
82
+ _rotation;
83
+ _prng;
84
+ _relocateProgram;
85
+ _relocateBindGroup;
86
+ _seedProgram;
87
+ _seedBindGroup;
88
+ _traceProgram;
89
+ _traceBindGroup;
90
+ _traceKey;
91
+ _blendIrrProgram;
92
+ _blendIrrBindGroup;
93
+ _blendVisProgram;
94
+ _blendVisBindGroup;
95
+ _commitProgram;
96
+ _commitBindGroup;
97
+ _renderStates;
98
+ _scratch;
99
+ constructor(scene, options){
100
+ this._scene = scene;
101
+ this._options = options;
102
+ const levels = Math.max(1, Math.min(PROBE_MAX_LEVELS, Math.floor(options.levels)));
103
+ const scale = Math.max(1.5, options.levelScale);
104
+ const grid = options.grid.map((v)=>Math.max(2, Math.floor(v)));
105
+ this._levels = [];
106
+ let tileBase = 0;
107
+ for(let l = 0; l < levels; l++){
108
+ const count = grid[0] * grid[1] * grid[2];
109
+ this._levels.push({
110
+ grid: [
111
+ ...grid
112
+ ],
113
+ spacing: options.spacing * scale ** l,
114
+ count,
115
+ tileBase,
116
+ origin: null,
117
+ state: new Uint8Array(count),
118
+ lastRelocated: new Int32Array(count).fill(-1e3),
119
+ lastSelected: new Int32Array(count).fill(-1),
120
+ cursor: 0
121
+ });
122
+ tileBase += count;
123
+ }
124
+ this._count = tileBase;
125
+ this._tileCols = Math.ceil(Math.sqrt(this._count));
126
+ this._rays = Math.max(16, Math.min(1024, Math.floor(options.raysPerProbe)));
127
+ this._budget = Math.max(1, Math.min(this._count, Math.floor(options.budget)));
128
+ this._irradiance = null;
129
+ this._irradianceNext = null;
130
+ this._irradianceSeed = null;
131
+ this._visibility = null;
132
+ this._visibilityNext = null;
133
+ this._visibilityFormat = getDevice().getDeviceCaps().textureCaps.getTextureFormatInfo('rg16f')?.writable ? 'rg16f' : 'rgba16f';
134
+ this._rayTexture = null;
135
+ this._rayFramebuffer = null;
136
+ this._data = null;
137
+ this._selected = null;
138
+ this._selection = new Uint32Array(new ArrayBuffer(this._budget * 4));
139
+ this._dataReset = new Float32Array(new ArrayBuffer(16));
140
+ this._frame = 0;
141
+ this._updates = 0;
142
+ this._lastUpdated = 0;
143
+ this._params = new Float32Array(new ArrayBuffer(PROBE_PARAM_VEC4S * 16));
144
+ this._exposureScale = 1;
145
+ this._rotation = new Vector4(0, 0, 0, 1);
146
+ this._prng = new PRNG(1234);
147
+ this._relocateProgram = null;
148
+ this._relocateBindGroup = null;
149
+ this._seedProgram = null;
150
+ this._seedBindGroup = null;
151
+ this._traceProgram = null;
152
+ this._traceBindGroup = null;
153
+ this._traceKey = '';
154
+ this._blendIrrProgram = null;
155
+ this._blendIrrBindGroup = null;
156
+ this._blendVisProgram = null;
157
+ this._blendVisBindGroup = null;
158
+ this._commitProgram = null;
159
+ this._commitBindGroup = null;
160
+ this._renderStates = null;
161
+ this._scratch = new Map();
162
+ }
163
+ /** Clipmap levels. */ get levels() {
164
+ return this._levels.length;
165
+ }
166
+ /** Probes of every level together. */ get count() {
167
+ return this._count;
168
+ }
169
+ /** Spacing of the finest level. */ get spacing() {
170
+ return this._options.spacing;
171
+ }
172
+ /** True once the volume has been placed and updated, or a state restored. */ get ready() {
173
+ return !!this._levels[0].origin && this._updates > 0;
174
+ }
175
+ getStats() {
176
+ let uninitialized = 0;
177
+ for (const level of this._levels){
178
+ for(let i = 0; i < level.count; i++){
179
+ uninitialized += level.state[i] ? 0 : 1;
180
+ }
181
+ }
182
+ return {
183
+ levels: this._levels.length,
184
+ probes: this._count,
185
+ uninitialized,
186
+ lastUpdated: this._lastUpdated,
187
+ updates: this._updates,
188
+ levelInfo: this._levels.map((level)=>({
189
+ grid: [
190
+ ...level.grid
191
+ ],
192
+ spacing: level.spacing,
193
+ origin: level.origin ? [
194
+ ...level.origin
195
+ ] : [
196
+ 0,
197
+ 0,
198
+ 0
199
+ ]
200
+ }))
201
+ };
202
+ }
203
+ /** Binds the volume for {@link sampleProbeIrradiance} in a program that declared it. */ applyBindGroup(bg) {
204
+ if (!this._irradiance || !this._visibility || !this._data) {
205
+ return;
206
+ }
207
+ bg.setTexture(PROBE_IRRADIANCE, this._irradiance, fetchSampler('clamp_linear_nomip'));
208
+ bg.setTexture(PROBE_VISIBILITY, this._visibility, fetchSampler('clamp_linear_nomip'));
209
+ bg.setBuffer(PROBE_DATA, this._data);
210
+ bg.setValue(PROBE_PARAMS, this._params);
211
+ }
212
+ /**
213
+ * Rescales probe reads from the fixed cache exposure to the camera's. Call
214
+ * every frame, also while frozen, so params follow exposure and SDF windows.
215
+ */ setReadExposure(camera) {
216
+ const scene = this._scene;
217
+ const live = scene.lightingMode === 'physical' ? camera.exposure : 1;
218
+ this._exposureScale = live / getGICacheExposure(scene.lightingMode);
219
+ // Before the first update there are no maps; that update writes the params
220
+ if (this._irradiance && this._visibility) {
221
+ this.updateParams();
222
+ }
223
+ }
224
+ /**
225
+ * Follows the camera and updates a budget of probes. Called by the scene
226
+ * once a frame after the voxel scene is lit, which the rays read.
227
+ */ update(sdf, voxels, camera) {
228
+ const global = sdf.enabled ? sdf.global : null;
229
+ if (!global || !voxels.lit) {
230
+ return;
231
+ }
232
+ this.ensureResources();
233
+ const cameraPos = camera.getWorldPosition();
234
+ for (const level of this._levels){
235
+ this.moveWindow(level, cameraPos);
236
+ }
237
+ this._frame++;
238
+ const selected = this.select();
239
+ this.updateParams();
240
+ if (selected === 0) {
241
+ return;
242
+ }
243
+ this._selected.bufferSubData(0, this._selection);
244
+ // A fresh random rotation of the ray set each update
245
+ this.randomRotation();
246
+ RenderGraphExecutor.profileScope('ProbeRelocate', ()=>this.runRelocate(global, selected));
247
+ // After the relocation, which decides which probes start over, and before
248
+ // the blend, which reads the seeds
249
+ RenderGraphExecutor.profileScope('ProbeSeed', ()=>this.runSeed(selected));
250
+ RenderGraphExecutor.profileScope('ProbeTrace', ()=>this.runTrace(global, voxels, camera, selected));
251
+ this.runBlend(selected);
252
+ RenderGraphExecutor.profileScope('ProbeCommit', ()=>this.runCommit(selected));
253
+ this._updates++;
254
+ this._lastUpdated = selected;
255
+ }
256
+ /**
257
+ * Saves the volume: its maps, probe data and windows, to restore with
258
+ * {@link ProbeVolume.importState} on a volume built with the same settings.
259
+ * Resolves once the GPU has handed the maps back.
260
+ */ async exportState() {
261
+ if (!this.ready || !this._irradiance || !this._visibility || !this._data) {
262
+ throw new Error('ProbeVolume.exportState(): nothing to save yet');
263
+ }
264
+ const irr = new Uint16Array(new ArrayBuffer(this._irradiance.width * this._irradiance.height * 8));
265
+ const visTexels = this._visibility.width * this._visibility.height;
266
+ const visChannels = this._visibilityFormat === 'rg16f' ? 2 : 4;
267
+ const visRead = new Uint16Array(new ArrayBuffer(visTexels * visChannels * 2));
268
+ const data = new Float32Array(new ArrayBuffer(this._count * 16));
269
+ // Issued together so the three readbacks wait for the GPU once, not three times
270
+ await Promise.all([
271
+ this._irradiance.readPixels(0, 0, this._irradiance.width, this._irradiance.height, 0, 0, irr),
272
+ this._visibility.readPixels(0, 0, this._visibility.width, this._visibility.height, 0, 0, visRead),
273
+ this._data.getBufferSubData(new Uint8Array(data.buffer), 0, data.byteLength)
274
+ ]);
275
+ // Saved as rgba16f whatever the device keeps, so a state moves between devices
276
+ const vis = visChannels === 4 ? visRead : expandVisibility(visRead, visTexels);
277
+ const header = JSON.stringify({
278
+ grid: this._options.grid,
279
+ spacing: this._options.spacing,
280
+ levels: this._levels.length,
281
+ levelScale: this._options.levelScale,
282
+ origins: this._levels.map((l)=>l.origin),
283
+ irradiance: [
284
+ this._irradiance.width,
285
+ this._irradiance.height
286
+ ],
287
+ visibility: [
288
+ this._visibility.width,
289
+ this._visibility.height
290
+ ]
291
+ });
292
+ const headerBytes = new TextEncoder().encode(header);
293
+ const headerLength = headerBytes.byteLength + 3 & -4;
294
+ const out = new Uint8Array(12 + headerLength + irr.byteLength + vis.byteLength + data.byteLength);
295
+ const view = new DataView(out.buffer);
296
+ view.setUint32(0, STATE_MAGIC, true);
297
+ view.setUint32(4, STATE_VERSION, true);
298
+ view.setUint32(8, headerLength, true);
299
+ out.fill(0x20, 12, 12 + headerLength);
300
+ out.set(headerBytes, 12);
301
+ let offset = 12 + headerLength;
302
+ out.set(new Uint8Array(irr.buffer), offset);
303
+ offset += irr.byteLength;
304
+ out.set(new Uint8Array(vis.buffer), offset);
305
+ offset += vis.byteLength;
306
+ out.set(new Uint8Array(data.buffer), offset);
307
+ return out.buffer;
308
+ }
309
+ /**
310
+ * Reads back up to `limit` probes of a level inside a world box, nearest the
311
+ * centre first, for inspection: position, update/inactive state, mean
312
+ * irradiance and sky visibility, and min/mean visibility hit distance.
313
+ */ async queryProbes(level, min, max, limit = 512) {
314
+ const l = this._levels[level];
315
+ if (!l || !l.origin || !this._irradiance || !this._visibility || !this._data) {
316
+ return null;
317
+ }
318
+ const s = l.spacing;
319
+ const lo = [
320
+ Math.ceil(min.x / s),
321
+ Math.ceil(min.y / s),
322
+ Math.ceil(min.z / s)
323
+ ];
324
+ const hi = [
325
+ Math.floor(max.x / s),
326
+ Math.floor(max.y / s),
327
+ Math.floor(max.z / s)
328
+ ];
329
+ const centre = [
330
+ (min.x + max.x) / 2,
331
+ (min.y + max.y) / 2,
332
+ (min.z + max.z) / 2
333
+ ];
334
+ const wrap = (v, m)=>(v % m + m) % m;
335
+ const picked = [];
336
+ for(let z = lo[2]; z <= hi[2]; z++){
337
+ for(let y = lo[1]; y <= hi[1]; y++){
338
+ for(let x = lo[0]; x <= hi[0]; x++){
339
+ const w = [
340
+ x,
341
+ y,
342
+ z
343
+ ];
344
+ if (w.some((v, a)=>v < l.origin[a] || v >= l.origin[a] + l.grid[a])) {
345
+ continue;
346
+ }
347
+ const local = wrap(x, l.grid[0]) + l.grid[0] * (wrap(y, l.grid[1]) + l.grid[1] * wrap(z, l.grid[2]));
348
+ picked.push({
349
+ world: w,
350
+ index: local
351
+ });
352
+ }
353
+ }
354
+ }
355
+ const dist2 = (w)=>w.reduce((acc, v, a)=>acc + (v * s - centre[a]) ** 2, 0);
356
+ picked.sort((a, b)=>dist2(a.world) - dist2(b.world));
357
+ picked.length = Math.min(picked.length, limit);
358
+ const irr = new Uint16Array(new ArrayBuffer(this._irradiance.width * this._irradiance.height * 8));
359
+ const visChannels = this._visibilityFormat === 'rg16f' ? 2 : 4;
360
+ const vis = new Uint16Array(new ArrayBuffer(this._visibility.width * this._visibility.height * visChannels * 2));
361
+ const data = new Float32Array(new ArrayBuffer(this._count * 16));
362
+ await Promise.all([
363
+ this._irradiance.readPixels(0, 0, this._irradiance.width, this._irradiance.height, 0, 0, irr),
364
+ this._visibility.readPixels(0, 0, this._visibility.width, this._visibility.height, 0, 0, vis),
365
+ this._data.getBufferSubData(new Uint8Array(data.buffer), 0, data.byteLength)
366
+ ]);
367
+ const tileMean = (texels, width, channels, raw, index, reduce)=>{
368
+ const ox = index % this._tileCols * texels;
369
+ const oy = Math.floor(index / this._tileCols) * texels;
370
+ // Interior texels only; the border repeats them
371
+ for(let y = 1; y < texels - 1; y++){
372
+ for(let x = 1; x < texels - 1; x++){
373
+ const o = ((oy + y) * width + ox + x) * channels;
374
+ const t = [];
375
+ for(let c = 0; c < channels; c++){
376
+ t.push(half2float(raw[o + c]));
377
+ }
378
+ reduce(t);
379
+ }
380
+ }
381
+ };
382
+ const probes = picked.map(({ world, index })=>{
383
+ const tile = l.tileBase + index;
384
+ const d = data.subarray(tile * 4, tile * 4 + 4);
385
+ const irrSum = [
386
+ 0,
387
+ 0,
388
+ 0,
389
+ 0
390
+ ];
391
+ let n = 0;
392
+ tileMean(IRRADIANCE_TEXELS, this._irradiance.width, 4, irr, tile, (t)=>{
393
+ for(let c = 0; c < 4; c++){
394
+ irrSum[c] += t[c];
395
+ }
396
+ n++;
397
+ });
398
+ let visMin = Infinity;
399
+ let visSum = 0;
400
+ let m = 0;
401
+ tileMean(VISIBILITY_TEXELS, this._visibility.width, visChannels, vis, tile, (t)=>{
402
+ visMin = Math.min(visMin, t[0]);
403
+ visSum += t[0];
404
+ m++;
405
+ });
406
+ const r = (v)=>Math.round(v * 1000) / 1000;
407
+ return {
408
+ world,
409
+ position: [
410
+ world[0] * s + d[0],
411
+ world[1] * s + d[1],
412
+ world[2] * s + d[2]
413
+ ].map(r),
414
+ offset: [
415
+ d[0],
416
+ d[1],
417
+ d[2]
418
+ ].map(r),
419
+ updated: d[3] > 0.5,
420
+ inactive: d[3] > PROBE_STATE_INACTIVE - 0.5,
421
+ irradiance: irrSum.slice(0, 3).map((v)=>r(v / n)),
422
+ sky: r(irrSum[3] / n),
423
+ hitMin: r(visMin),
424
+ hitMean: r(visSum / m)
425
+ };
426
+ });
427
+ return {
428
+ level,
429
+ spacing: s,
430
+ origin: [
431
+ ...l.origin
432
+ ],
433
+ grid: [
434
+ ...l.grid
435
+ ],
436
+ probes
437
+ };
438
+ }
439
+ /**
440
+ * Restores a state saved by {@link ProbeVolume.exportState} with matching
441
+ * settings; freeze the lighting afterwards or the windows follow the camera.
442
+ */ importState(buffer) {
443
+ const view = new DataView(buffer);
444
+ if (view.getUint32(0, true) !== STATE_MAGIC || view.getUint32(4, true) !== STATE_VERSION) {
445
+ throw new Error('ProbeVolume.importState(): not a probe state');
446
+ }
447
+ const headerLength = view.getUint32(8, true);
448
+ const header = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer, 12, headerLength)));
449
+ if (header.levels !== this._levels.length || header.spacing !== this._options.spacing || header.levelScale !== this._options.levelScale || header.grid.some((v, i)=>v !== this._options.grid[i])) {
450
+ throw new Error('ProbeVolume.importState(): the state was saved with other settings');
451
+ }
452
+ this.ensureResources();
453
+ const irrSize = this._irradiance.width * this._irradiance.height * 4;
454
+ const visSize = this._visibility.width * this._visibility.height * 4;
455
+ let offset = 12 + headerLength;
456
+ const irr = new Uint16Array(buffer, offset, irrSize);
457
+ offset += irr.byteLength;
458
+ const vis = new Uint16Array(buffer, offset, visSize);
459
+ offset += vis.byteLength;
460
+ const visTexels = this._visibility.width * this._visibility.height;
461
+ const data = new Float32Array(buffer, offset, this._count * 4);
462
+ this._irradiance.update(irr, 0, 0, this._irradiance.width, this._irradiance.height);
463
+ this._visibility.update(this._visibilityFormat === 'rg16f' ? compactVisibility(vis, visTexels) : vis, 0, 0, this._visibility.width, this._visibility.height);
464
+ this._data.bufferSubData(0, data);
465
+ this._levels.forEach((level, l)=>{
466
+ level.origin = header.origins[l];
467
+ for(let i = 0; i < level.count; i++){
468
+ level.state[i] = data[(level.tileBase + i) * 4 + 3] > 0.5 ? 1 : 0;
469
+ }
470
+ });
471
+ this.updateParams();
472
+ this._updates = Math.max(this._updates, 1);
473
+ }
474
+ dispose() {
475
+ for (const tex of [
476
+ this._irradiance,
477
+ this._irradianceNext,
478
+ this._irradianceSeed,
479
+ this._visibility,
480
+ this._visibilityNext,
481
+ this._rayTexture
482
+ ]){
483
+ tex?.dispose();
484
+ }
485
+ this._irradiance = this._irradianceNext = this._irradianceSeed = this._visibility = this._visibilityNext = this._rayTexture = null;
486
+ this._rayFramebuffer?.dispose();
487
+ this._rayFramebuffer = null;
488
+ this._data?.dispose();
489
+ this._selected?.dispose();
490
+ this._data = this._selected = null;
491
+ for (const buffer of this._scratch.values()){
492
+ buffer.dispose();
493
+ }
494
+ this._scratch.clear();
495
+ for (const bg of [
496
+ this._relocateBindGroup,
497
+ this._seedBindGroup,
498
+ this._traceBindGroup,
499
+ this._blendIrrBindGroup,
500
+ this._blendVisBindGroup,
501
+ this._commitBindGroup
502
+ ]){
503
+ bg?.dispose();
504
+ }
505
+ for (const prog of [
506
+ this._relocateProgram,
507
+ this._seedProgram,
508
+ this._traceProgram,
509
+ this._blendIrrProgram,
510
+ this._blendVisProgram,
511
+ this._commitProgram
512
+ ]){
513
+ prog?.dispose();
514
+ }
515
+ this._relocateBindGroup = this._traceBindGroup = this._blendIrrBindGroup = this._blendVisBindGroup = null;
516
+ this._relocateProgram = this._traceProgram = this._blendIrrProgram = this._blendVisProgram = null;
517
+ this._seedProgram = this._seedBindGroup = null;
518
+ this._commitProgram = this._commitBindGroup = null;
519
+ for (const level of this._levels){
520
+ level.origin = null;
521
+ }
522
+ this._updates = 0;
523
+ }
524
+ /** @internal */ ensureResources() {
525
+ if (this._irradiance) {
526
+ return;
527
+ }
528
+ const device = getDevice();
529
+ const rows = Math.ceil(this._count / this._tileCols);
530
+ const make = (texels, name, cols = this._tileCols, r = rows, format = 'rgba16f')=>{
531
+ const tex = device.createTexture2D(format, cols * texels, r * texels, {
532
+ mipmapping: false,
533
+ writable: true
534
+ });
535
+ tex.name = name;
536
+ return tex;
537
+ };
538
+ this._irradiance = make(IRRADIANCE_TEXELS, '@ProbeIrradiance');
539
+ // The blend outputs hold one update's tiles, committed to the maps after
540
+ const stagingRows = Math.ceil(this._budget / STAGING_COLS);
541
+ this._irradianceNext = make(IRRADIANCE_TEXELS, '@ProbeIrradianceNext', STAGING_COLS, stagingRows);
542
+ this._irradianceSeed = make(IRRADIANCE_TEXELS, '@ProbeIrradianceSeed', STAGING_COLS, stagingRows);
543
+ this._visibility = make(VISIBILITY_TEXELS, '@ProbeVisibility', this._tileCols, rows, this._visibilityFormat);
544
+ this._visibilityNext = make(VISIBILITY_TEXELS, '@ProbeVisibilityNext', STAGING_COLS, stagingRows, this._visibilityFormat);
545
+ this._rayTexture = device.createTexture2D('rgba16f', this._rays, this._budget, {
546
+ mipmapping: false
547
+ });
548
+ this._rayTexture.name = '@ProbeRays';
549
+ this._rayFramebuffer = device.createFrameBuffer([
550
+ this._rayTexture
551
+ ], null);
552
+ this._data = device.createBuffer(this._count * 16, {
553
+ usage: 'uniform',
554
+ storage: true
555
+ });
556
+ this._data.bufferSubData(0, new Float32Array(new ArrayBuffer(this._count * 16)));
557
+ this._selected = device.createBuffer(this._budget * 4, {
558
+ usage: 'uniform',
559
+ storage: true
560
+ });
561
+ for (const level of this._levels){
562
+ level.state.fill(0);
563
+ }
564
+ }
565
+ /** Re-centres a level's window on the camera, a spacing at a time; probes that entered start over. @internal */ moveWindow(level, cameraPos) {
566
+ const s = level.spacing;
567
+ const n = level.grid;
568
+ const next = [
569
+ Math.floor(cameraPos.x / s) - (n[0] >> 1),
570
+ Math.floor(cameraPos.y / s) - (n[1] >> 1),
571
+ Math.floor(cameraPos.z / s) - (n[2] >> 1)
572
+ ];
573
+ const prev = level.origin;
574
+ if (prev && prev[0] === next[0] && prev[1] === next[1] && prev[2] === next[2]) {
575
+ return;
576
+ }
577
+ level.origin = next;
578
+ const wrap = (v, m)=>(v % m + m) % m;
579
+ for(let z = 0; z < n[2]; z++){
580
+ for(let y = 0; y < n[1]; y++){
581
+ for(let x = 0; x < n[0]; x++){
582
+ const wx = next[0] + x;
583
+ const wy = next[1] + y;
584
+ const wz = next[2] + z;
585
+ const inPrev = !!prev && wx >= prev[0] && wy >= prev[1] && wz >= prev[2] && wx < prev[0] + n[0] && wy < prev[1] + n[1] && wz < prev[2] + n[2];
586
+ if (!inPrev) {
587
+ const index = wrap(wx, n[0]) + n[0] * (wrap(wy, n[1]) + n[1] * wrap(wz, n[2]));
588
+ level.state[index] = 0;
589
+ level.lastRelocated[index] = -1e3;
590
+ this._data.bufferSubData((level.tileBase + index) * 16, this._dataReset);
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ /**
597
+ * Picks the probes to update: never-updated ones first (a quarter of the
598
+ * budget to the coarsest level, the rest finest first), then each level by
599
+ * its share. At most once per frame per probe, since two updates would race
600
+ * on the same staged tile.
601
+ * @internal
602
+ */ select() {
603
+ let count = 0;
604
+ const budget = this._budget;
605
+ const frame = this._frame;
606
+ const takeNew = (l, limit)=>{
607
+ const level = this._levels[l];
608
+ for(let i = 0; i < level.count && count < limit; i++){
609
+ if (!level.state[i]) {
610
+ this._selection[count++] = (level.tileBase + i | l << SELECT_LEVEL_SHIFT | SELECT_FIRST_BIT) >>> 0;
611
+ level.state[i] = 1;
612
+ level.lastSelected[i] = frame;
613
+ }
614
+ }
615
+ };
616
+ if (this._levels.length > 1) {
617
+ takeNew(this._levels.length - 1, budget >> 2);
618
+ }
619
+ for(let l = 0; l < this._levels.length && count < budget; l++){
620
+ takeNew(l, budget);
621
+ }
622
+ const remaining = budget - count;
623
+ for(let l = 0; l < this._levels.length && count < budget; l++){
624
+ const level = this._levels[l];
625
+ let share = Math.max(1, Math.round(remaining * level.count / this._count));
626
+ let scanned = 0;
627
+ while(share > 0 && count < budget && scanned < level.count){
628
+ const i = level.cursor;
629
+ level.cursor = (level.cursor + 1) % level.count;
630
+ scanned++;
631
+ if (level.state[i] && level.lastSelected[i] !== frame) {
632
+ this._selection[count++] = (level.tileBase + i | l << SELECT_LEVEL_SHIFT) >>> 0;
633
+ level.lastSelected[i] = frame;
634
+ share--;
635
+ }
636
+ }
637
+ }
638
+ for(let i = count; i < budget; i++){
639
+ this._selection[i] = 0xffffffff;
640
+ }
641
+ return count;
642
+ }
643
+ /** @internal */ randomRotation() {
644
+ // Uniform random unit quaternion (Shoemake)
645
+ const u1 = this._prng.get();
646
+ const u2 = this._prng.get() * 2 * Math.PI;
647
+ const u3 = this._prng.get() * 2 * Math.PI;
648
+ const a = Math.sqrt(1 - u1);
649
+ const b = Math.sqrt(u1);
650
+ this._rotation.setXYZW(a * Math.sin(u2), a * Math.cos(u2), b * Math.sin(u3), b * Math.cos(u3));
651
+ }
652
+ /** @internal */ updateParams() {
653
+ const o = this._options;
654
+ const p = this._params;
655
+ p.set([
656
+ this._levels.length,
657
+ this._tileCols,
658
+ IRRADIANCE_TEXELS,
659
+ VISIBILITY_TEXELS
660
+ ], 0);
661
+ p.set([
662
+ this._irradiance.width,
663
+ this._irradiance.height,
664
+ this._visibility.width,
665
+ this._visibility.height
666
+ ], 4);
667
+ p.set([
668
+ o.intensity * this._exposureScale,
669
+ o.normalBias,
670
+ o.viewBias,
671
+ o.maxRayDistance
672
+ ], 8);
673
+ this._levels.forEach((level, l)=>{
674
+ const origin = level.origin ?? [
675
+ 0,
676
+ 0,
677
+ 0
678
+ ];
679
+ p.set([
680
+ origin[0],
681
+ origin[1],
682
+ origin[2],
683
+ level.spacing
684
+ ], 12 + l * 8);
685
+ p.set([
686
+ level.grid[0],
687
+ level.grid[1],
688
+ level.grid[2],
689
+ level.tileBase
690
+ ], 16 + l * 8);
691
+ });
692
+ }
693
+ /** @internal */ bindCommon(bg, global) {
694
+ global.applyBindGroup(bg);
695
+ bg.setBuffer(PROBE_DATA, this._data);
696
+ bg.setValue(PROBE_PARAMS, this._params);
697
+ bg.setBuffer('selected', this._selected);
698
+ bg.setValue('rotation', this._rotation);
699
+ bg.setValue('rayInfo', new Vector4(this._rays, this._budget, this._frame, this._options.hysteresis));
700
+ }
701
+ /** @internal */ scratchBuffer(name, bytes) {
702
+ let buffer = this._scratch.get(name);
703
+ if (!buffer || buffer.byteLength < bytes) {
704
+ buffer?.dispose();
705
+ buffer = getDevice().createBuffer(Math.max(16, bytes), {
706
+ usage: 'uniform',
707
+ storage: true
708
+ });
709
+ this._scratch.set(name, buffer);
710
+ }
711
+ return buffer;
712
+ }
713
+ /**
714
+ * Pushes selected probes out of nearby geometry along the SDF gradient,
715
+ * never through a surface; on first update and every few frames after.
716
+ * @internal
717
+ */ runRelocate(global, selected) {
718
+ const device = getDevice();
719
+ const levels = global.levels;
720
+ if (!this._relocateProgram) {
721
+ this._relocateProgram = buildSDFProgram({
722
+ label: 'ProbeRelocate',
723
+ workgroupSize: [
724
+ 64,
725
+ 1,
726
+ 1
727
+ ],
728
+ compute (pb) {
729
+ declareGlobalSDFUniforms(this, 0, levels);
730
+ declareDynamicSDFUniforms(this, 0);
731
+ this[PROBE_DATA] = pb.vec4[0]().storageBuffer(0);
732
+ this[PROBE_PARAMS] = pb.vec4[PROBE_PARAM_VEC4S]().uniform(0);
733
+ this.selected = pb.uint[0]().storageBufferReadonly(0);
734
+ this.rotation = pb.vec4().uniform(0);
735
+ // (rays per probe, probes selected, frame, hysteresis)
736
+ this.rayInfo = pb.vec4().uniform(0);
737
+ this.relocate = pb.uint[0]().storageBufferReadonly(0);
738
+ pb.main(function() {
739
+ this.$l.slot = this.$builtins.globalInvocationId.x;
740
+ this.$if(pb.greaterThanEqual(this.slot, pb.uint(this.rayInfo.y)), function() {
741
+ this.$return();
742
+ });
743
+ this.$if(pb.equal(this.relocate.at(this.slot), 0), function() {
744
+ this.$return();
745
+ });
746
+ this.$l.sel = this.selected.at(this.slot);
747
+ this.$l.index = pb.int(pb.compAnd(this.sel, pb.uint(SELECT_INDEX_MASK)));
748
+ this.$l.level = pb.int(pb.compAnd(pb.sar(this.sel, SELECT_LEVEL_SHIFT), pb.uint(0x7f)));
749
+ this.$l.texel = probeTexelFromIndex(this, this.level, this.index);
750
+ this.$l.world = probeWorldFromTexel(this, this.level, this.texel);
751
+ this.$l.data = this[PROBE_DATA].at(this.index);
752
+ this.$l.spacing = probeLevelOrigin(this, this.level).w;
753
+ this.$l.grid = pb.mul(pb.vec3(this.world), this.spacing);
754
+ // Keep at least this far from surfaces, moving at most this far from the grid point
755
+ this.$l.keep = pb.mul(this.spacing, 0.3);
756
+ this.$l.limit = pb.mul(this.spacing, 0.45);
757
+ // Central differences at least a finest field voxel apart, so sub-voxel walls give a gradient
758
+ this.$l.h = pb.max(pb.mul(this.spacing, 0.05), this[GLOBAL_SDF_LEVELS].at(0).w);
759
+ // Crossing check every quarter voxel: thin walls dip only slightly below their face distance
760
+ this.$l.probeStep = pb.mul(this[GLOBAL_SDF_LEVELS].at(0).w, 0.25);
761
+ // Always from the grid point, so the result depends on the field alone and never drifts
762
+ this.$l.pos = this.grid;
763
+ this.$for(pb.int('iter'), 0, 3, function() {
764
+ this.$l.d = sampleSceneSDF(this, this.pos, levels);
765
+ this.$if(pb.greaterThanEqual(this.d, this.keep), function() {
766
+ this.$break();
767
+ });
768
+ this.$l.g = pb.vec3(pb.sub(sampleSceneSDF(this, pb.add(this.pos, pb.vec3(this.h, 0, 0)), levels), sampleSceneSDF(this, pb.sub(this.pos, pb.vec3(this.h, 0, 0)), levels)), pb.sub(sampleSceneSDF(this, pb.add(this.pos, pb.vec3(0, this.h, 0)), levels), sampleSceneSDF(this, pb.sub(this.pos, pb.vec3(0, this.h, 0)), levels)), pb.sub(sampleSceneSDF(this, pb.add(this.pos, pb.vec3(0, 0, this.h)), levels), sampleSceneSDF(this, pb.sub(this.pos, pb.vec3(0, 0, this.h)), levels)));
769
+ this.$l.n = pb.div(this.g, pb.max(pb.length(this.g), 1e-6));
770
+ this.$l.next = pb.sub(pb.add(this.pos, pb.mul(this.n, pb.sub(this.keep, this.d))), this.grid);
771
+ this.$l.len = pb.length(this.next);
772
+ this.$if(pb.greaterThan(this.len, this.limit), function() {
773
+ this.next = pb.mul(this.next, pb.div(this.limit, this.len));
774
+ });
775
+ this.next = pb.add(this.grid, this.next);
776
+ // A distance dip along the move means it passes through geometry: stop here
777
+ this.$l.moveLen = pb.length(pb.sub(this.next, this.pos));
778
+ this.$l.samples = pb.int(pb.min(pb.ceil(pb.div(this.moveLen, this.probeStep)), 64));
779
+ this.$l.through = false;
780
+ this.$for(pb.int('k'), 1, this.samples, function() {
781
+ this.$l.q = pb.mix(this.pos, this.next, pb.div(pb.float(this.k), pb.float(this.samples)));
782
+ this.$if(pb.lessThan(sampleSceneSDF(this, this.q, levels), pb.sub(this.d, this.probeStep)), function() {
783
+ this.through = true;
784
+ this.$break();
785
+ });
786
+ });
787
+ this.$if(this.through, function() {
788
+ this.$break();
789
+ });
790
+ this.pos = this.next;
791
+ });
792
+ this.$l.offset = pb.sub(this.pos, this.grid);
793
+ // Classification (Majercik et al. 2021, §5): a probe inside geometry is
794
+ // inactive; one out again starts over. Static field only, so dynamic instances turn none off
795
+ this.$l.probeState = this.data.w;
796
+ this.$if(pb.lessThan(sampleGlobalSDF(this, this.pos, levels), 0), function() {
797
+ this.probeState = pb.float(PROBE_STATE_INACTIVE);
798
+ }).$elseif(pb.greaterThan(this.probeState, PROBE_STATE_INACTIVE - 0.5), function() {
799
+ this.probeState = pb.float(0);
800
+ });
801
+ this[PROBE_DATA].setAt(this.index, pb.vec4(this.offset, this.probeState));
802
+ });
803
+ }
804
+ });
805
+ this._relocateBindGroup = device.createBindGroup(this._relocateProgram.bindGroupLayouts[0]);
806
+ }
807
+ // Which selected probes relocate this frame
808
+ const flags = new Uint32Array(new ArrayBuffer(this._budget * 4));
809
+ for(let i = 0; i < selected; i++){
810
+ const sel = this._selection[i];
811
+ const level = this._levels[sel >>> SELECT_LEVEL_SHIFT & 0x7f];
812
+ const index = (sel & SELECT_INDEX_MASK) - level.tileBase;
813
+ if (this._frame - level.lastRelocated[index] >= RELOCATE_EVERY) {
814
+ flags[i] = 1;
815
+ level.lastRelocated[index] = this._frame;
816
+ }
817
+ }
818
+ const relocateBuffer = this.scratchBuffer('relocate', flags.byteLength);
819
+ relocateBuffer.bufferSubData(0, flags);
820
+ const bg = this._relocateBindGroup;
821
+ this.bindCommon(bg, global);
822
+ bg.setBuffer('relocate', relocateBuffer);
823
+ device.setProgram(this._relocateProgram);
824
+ device.setBindGroup(0, bg);
825
+ device.compute(Math.ceil(selected / 64), 1, 1);
826
+ }
827
+ /**
828
+ * Seeds the irradiance of probes starting over by trilinearly blending the
829
+ * eight surrounding active probes of the level above; the first update then
830
+ * blends into the seed with hysteresis. Without a seed, alpha is -1. One
831
+ * workgroup per selected probe, written by selection slot.
832
+ * @internal
833
+ */ runSeed(selected) {
834
+ const device = getDevice();
835
+ if (!this._seedProgram) {
836
+ this._seedProgram = buildSDFProgram({
837
+ label: 'ProbeSeed',
838
+ workgroupSize: [
839
+ IRRADIANCE_TEXELS,
840
+ IRRADIANCE_TEXELS,
841
+ 1
842
+ ],
843
+ compute (pb) {
844
+ this[PROBE_DATA] = pb.vec4[0]().storageBufferReadonly(0);
845
+ this[PROBE_PARAMS] = pb.vec4[PROBE_PARAM_VEC4S]().uniform(0);
846
+ this.selected = pb.uint[0]().storageBufferReadonly(0);
847
+ this.irradiance = pb.tex2D().sampleType('unfilterable-float').uniform(0);
848
+ this.seedOut = pb.texStorage2D.rgba16float().storage(0);
849
+ pb.main(function() {
850
+ this.$l.slot = this.$builtins.workGroupId.x;
851
+ this.$l.sel = this.selected.at(this.slot);
852
+ this.$l.index = pb.int(pb.compAnd(this.sel, pb.uint(SELECT_INDEX_MASK)));
853
+ this.$l.level = pb.int(pb.compAnd(pb.sar(this.sel, SELECT_LEVEL_SHIFT), pb.uint(0x7f)));
854
+ this.$l.uv = pb.ivec2(this.$builtins.localInvocationId.xy);
855
+ this.$l.data = this[PROBE_DATA].at(this.index);
856
+ // Starting over as the blend tells it
857
+ this.$l.first = pb.or(pb.greaterThan(pb.compAnd(this.sel, pb.uint(SELECT_FIRST_BIT)), pb.uint(0)), pb.lessThan(this.data.w, 0.5));
858
+ this.$l.parent = pb.add(this.level, 1);
859
+ this.$l.sum = pb.vec4(0);
860
+ this.$l.wsum = pb.float(0);
861
+ this.$if(pb.and(this.first, pb.lessThan(this.parent, pb.int(this[PROBE_PARAMS].at(0).x))), function() {
862
+ this.$l.texel = probeTexelFromIndex(this, this.level, this.index);
863
+ this.$l.pos = probePosition(this, this.level, probeWorldFromTexel(this, this.level, this.texel), this.data);
864
+ this.$l.lo = probeLevelOrigin(this, this.parent);
865
+ this.$l.n3 = pb.vec3(probeLevelGrid(this, this.parent).xyz);
866
+ // In probes of the level above, its grid points at integers
867
+ this.$l.g = pb.sub(pb.div(this.pos, this.lo.w), this.lo.xyz);
868
+ this.$if(pb.and(pb.all(pb.greaterThanEqual(this.g, pb.vec3(0))), pb.all(pb.lessThanEqual(this.g, pb.sub(this.n3, pb.vec3(1))))), function() {
869
+ this.$l.base = pb.clamp(pb.floor(this.g), pb.vec3(0), pb.sub(this.n3, pb.vec3(2)));
870
+ this.$l.alpha = pb.clamp(pb.sub(this.g, this.base), pb.vec3(0), pb.vec3(1));
871
+ for(let i = 0; i < 8; i++){
872
+ const off = [
873
+ i & 1,
874
+ i >> 1 & 1,
875
+ i >> 2 & 1
876
+ ];
877
+ this.$l[`pw${i}`] = pb.add(pb.ivec3(this.lo.xyz), pb.ivec3(this.base), pb.ivec3(off[0], off[1], off[2]));
878
+ this.$l[`pi${i}`] = probeTileIndex(this, this.parent, probeTexelFromWorld(this, this.parent, this[`pw${i}`]));
879
+ this.$if(probeActive(this, this[PROBE_DATA].at(this[`pi${i}`])), function() {
880
+ this.$l.tri = pb.mul(off[0] ? this.alpha.x : pb.sub(1, this.alpha.x), off[1] ? this.alpha.y : pb.sub(1, this.alpha.y), off[2] ? this.alpha.z : pb.sub(1, this.alpha.z));
881
+ this.sum = pb.add(this.sum, pb.mul(pb.textureLoad(this.irradiance, pb.add(probeTileOrigin(this, this[`pi${i}`], pb.float(IRRADIANCE_TEXELS)), this.uv), 0), this.tri));
882
+ this.wsum = pb.add(this.wsum, this.tri);
883
+ });
884
+ }
885
+ });
886
+ });
887
+ pb.textureStore(this.seedOut, pb.uvec2(pb.add(stagingOrigin(this, this.slot, IRRADIANCE_TEXELS), this.uv)), this.$choice(pb.greaterThan(this.wsum, 1e-4), pb.div(this.sum, pb.max(this.wsum, 1e-4)), pb.vec4(0, 0, 0, -1)));
888
+ });
889
+ }
890
+ });
891
+ this._seedBindGroup = device.createBindGroup(this._seedProgram.bindGroupLayouts[0]);
892
+ }
893
+ const bg = this._seedBindGroup;
894
+ bg.setBuffer(PROBE_DATA, this._data);
895
+ bg.setValue(PROBE_PARAMS, this._params);
896
+ bg.setBuffer('selected', this._selected);
897
+ bg.setTexture('irradiance', this._irradiance, fetchSampler('clamp_nearest_nomip'));
898
+ bg.setTexture('seedOut', this._irradianceSeed);
899
+ device.setProgram(this._seedProgram);
900
+ device.setBindGroup(0, bg);
901
+ device.compute(selected, 1, 1);
902
+ }
903
+ /**
904
+ * Traces the rays of the selected probes: one fragment per ray, the hit's
905
+ * voxel radiance or the sky, and the hit distance (negative when the ray
906
+ * started inside geometry, {@link SKY_ESCAPE_DISTANCE} when it escaped to
907
+ * the sky), into the ray texture.
908
+ * @internal
909
+ */ runTrace(global, voxels, camera, selected) {
910
+ const device = getDevice();
911
+ const scene = this._scene;
912
+ const envLight = scene.env.light.envLight;
913
+ // Voxel radiance and sky bake share the cache exposure; the sky takes the strength alone
914
+ const envStrength = scene.env.light.strength ?? 0;
915
+ // No sky without an environment light (its stand-in carries only the probes)
916
+ const skyMode = scene.env.light.type === 'none' ? 'none' : envLight.hasRadiance() ? 'radiance' : envLight.hasIrradiance() ? 'irradiance' : 'none';
917
+ const envVariant = skyMode !== 'none' ? `${envLight.getType()}:${envLight.irradianceSHFB ? 'fb' : envLight.irradianceSH ? 'buf' : ''}` : '';
918
+ const key = `${global.levels}:${skyMode}:${envVariant}`;
919
+ if (!this._traceProgram || this._traceKey !== key) {
920
+ this._traceBindGroup?.dispose();
921
+ this._traceProgram?.dispose();
922
+ const levels = global.levels;
923
+ this._traceProgram = buildSDFProgram({
924
+ label: 'ProbeTrace',
925
+ vertex (pb) {
926
+ this.$inputs.pos = pb.vec2().attrib('position');
927
+ pb.main(function() {
928
+ this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
929
+ });
930
+ },
931
+ fragment (pb) {
932
+ this.$outputs.color = pb.vec4();
933
+ declareGlobalSDFUniforms(this, 0, levels);
934
+ declareDynamicSDFUniforms(this, 0);
935
+ declareVoxelSceneUniforms(this, 0);
936
+ this[PROBE_DATA] = pb.vec4[0]().storageBufferReadonly(0);
937
+ this[PROBE_PARAMS] = pb.vec4[PROBE_PARAM_VEC4S]().uniform(0);
938
+ this.selected = pb.uint[0]().storageBufferReadonly(0);
939
+ this.rotation = pb.vec4().uniform(0);
940
+ this.rayInfo = pb.vec4().uniform(0);
941
+ // (environment strength, 0, 0, 0)
942
+ this.skyInfo = pb.vec4().uniform(0);
943
+ if (skyMode !== 'none') {
944
+ envLight.initShaderBindings(pb);
945
+ }
946
+ pb.main(function() {
947
+ this.$l.fc = pb.ivec2(pb.floor(this.$builtins.fragCoord.xy));
948
+ this.$l.slot = pb.uint(this.fc.y);
949
+ this.$l.sel = this.selected.at(this.slot);
950
+ this.$l.index = pb.int(pb.compAnd(this.sel, pb.uint(SELECT_INDEX_MASK)));
951
+ this.$l.level = pb.int(pb.compAnd(pb.sar(this.sel, SELECT_LEVEL_SHIFT), pb.uint(0x7f)));
952
+ this.$l.texel = probeTexelFromIndex(this, this.level, this.index);
953
+ this.$l.world = probeWorldFromTexel(this, this.level, this.texel);
954
+ this.$l.data = this[PROBE_DATA].at(this.index);
955
+ this.$l.pos = probePosition(this, this.level, this.world, this.data);
956
+ this.$l.dir = quatRotate(this, this.rotation, sphericalFibonacci(this, pb.float(this.fc.x), this.rayInfo.x));
957
+ // Coarser levels look proportionally farther
958
+ this.$l.spacing = probeLevelOrigin(this, this.level).w;
959
+ this.$l.maxDist = pb.mul(this[PROBE_PARAMS].at(2).w, pb.div(this.spacing, probeLevelOrigin(this, 0).w));
960
+ // Past the visibility range, start at the field level matching the probe level;
961
+ // within it read every level, since coarse
962
+ // levels close thin gaps and would shut probes in
963
+ this.$l.fieldScale = pb.div(this[GLOBAL_SDF_LEVELS].at(0).w, probeLevelOrigin(this, 0).w);
964
+ this.$l.farLevel = pb.int(0);
965
+ for(let i = 1; i < levels; i++){
966
+ this.$if(pb.lessThanEqual(this[GLOBAL_SDF_LEVELS].at(i).w, pb.mul(this.spacing, this.fieldScale, 1.01)), function() {
967
+ this.farLevel = pb.int(i);
968
+ });
969
+ }
970
+ this.$l.fineRange = pb.mul(this.spacing, VISIBILITY_RANGE);
971
+ this.$l.radiance = pb.vec3(0);
972
+ this.$l.dist = this.maxDist;
973
+ this.$l.d0 = sampleSceneSDF(this, this.pos, levels);
974
+ this.$if(pb.lessThan(this.d0, 0), function() {
975
+ // Started inside geometry: a backface hit at no distance
976
+ this.dist = pb.float(-0.01);
977
+ }).$else(function() {
978
+ this.$l.t = pb.float(0);
979
+ this.$l.hit = false;
980
+ this.$l.prevD = pb.float(0);
981
+ // Distance read at the hit
982
+ this.$l.hitD = pb.float(0);
983
+ // Voxel size of the level that answered at the hit, 0 for a dynamic instance
984
+ this.$l.hitVoxel = pb.float(0);
985
+ // Finest field level the step that hit could read
986
+ this.$l.hitMin = pb.int(0);
987
+ this.$for(pb.int('i'), 0, TRACE_STEPS, function() {
988
+ this.$l.sp = pb.add(this.pos, pb.mul(this.dir, this.t));
989
+ this.$l.stepMin = this.$choice(pb.lessThan(this.t, this.fineRange), pb.int(0), this.farLevel);
990
+ this.$l.s = sampleSceneSDFHinted(this, this.sp, this.prevD, levels, this.stepMin);
991
+ this.$l.d = this.s.x;
992
+ this.prevD = this.d;
993
+ this.$l.expand = sdfSurfaceExpand(this, this.sp, this.s, this.t, levels);
994
+ this.$if(pb.lessThan(this.d, this.expand), function() {
995
+ // Swollen surface decides the hit so sub-voxel walls stop the ray
996
+ this.hitD = this.d;
997
+ this.hit = true;
998
+ this.hitVoxel = this.s.y;
999
+ this.hitMin = this.stepMin;
1000
+ this.$break();
1001
+ });
1002
+ this.t = pb.add(this.t, pb.max(this.d, 0.02));
1003
+ this.$if(pb.greaterThan(this.t, this.maxDist), function() {
1004
+ this.$break();
1005
+ });
1006
+ });
1007
+ this.$if(this.hit, function() {
1008
+ // Keep the distance to the unswollen surface, else concave corners read as hidden
1009
+ this.dist = sdfRefineHit(this, this.pos, this.dir, this.t, this.hitD, this.hitVoxel, this.hitMin, levels);
1010
+ this.$l.hp = pb.add(this.pos, pb.mul(this.dir, this.t));
1011
+ // Hit normal from the SDF gradient
1012
+ this.$l.hg = pb.vec3(0);
1013
+ this.$if(pb.greaterThan(this.hitVoxel, 0), function() {
1014
+ // Central differences over half a voxel of the answering level
1015
+ // (ComputeGlobalDistanceFieldNormal); level blends take the nearer level
1016
+ this.$l.hl = this.hitMin;
1017
+ for(let i = 1; i < levels; i++){
1018
+ this.$if(pb.and(pb.greaterThanEqual(pb.int(i), this.hitMin), pb.greaterThan(this.hitVoxel, pb.mul(this[GLOBAL_SDF_LEVELS].at(i - 1).w, Math.SQRT2))), function() {
1019
+ this.hl = pb.int(i);
1020
+ });
1021
+ }
1022
+ this.$l.he = pb.mul(this[GLOBAL_SDF_LEVELS].at(this.hl).w, 0.5);
1023
+ this.hg = pb.vec3(pb.sub(sampleGlobalSDFLevel(this, pb.add(this.hp, pb.vec3(this.he, 0, 0)), this.hl, levels), sampleGlobalSDFLevel(this, pb.sub(this.hp, pb.vec3(this.he, 0, 0)), this.hl, levels)), pb.sub(sampleGlobalSDFLevel(this, pb.add(this.hp, pb.vec3(0, this.he, 0)), this.hl, levels), sampleGlobalSDFLevel(this, pb.sub(this.hp, pb.vec3(0, this.he, 0)), this.hl, levels)), pb.sub(sampleGlobalSDFLevel(this, pb.add(this.hp, pb.vec3(0, 0, this.he)), this.hl, levels), sampleGlobalSDFLevel(this, pb.sub(this.hp, pb.vec3(0, 0, this.he)), this.hl, levels)));
1024
+ }).$else(function() {
1025
+ // Dynamic instance: forward differences of the merged field over the finest voxel
1026
+ this.$l.he = this[GLOBAL_SDF_LEVELS].at(0).w;
1027
+ this.$l.hd = sampleSceneSDF(this, this.hp, levels);
1028
+ this.hg = pb.vec3(pb.sub(sampleSceneSDF(this, pb.add(this.hp, pb.vec3(this.he, 0, 0)), levels), this.hd), pb.sub(sampleSceneSDF(this, pb.add(this.hp, pb.vec3(0, this.he, 0)), levels), this.hd), pb.sub(sampleSceneSDF(this, pb.add(this.hp, pb.vec3(0, 0, this.he)), levels), this.hd));
1029
+ });
1030
+ this.$l.hn = this.$choice(pb.greaterThan(pb.dot(this.hg, this.hg), 1e-12), pb.normalize(this.hg), pb.neg(this.dir));
1031
+ this.radiance = sampleVoxelSceneRadiance(this, this.hp, this.hn, levels);
1032
+ }).$else(function() {
1033
+ this.dist = pb.float(SKY_ESCAPE_DISTANCE);
1034
+ if (skyMode === 'radiance') {
1035
+ this.radiance = pb.mul(envLight.getRadiance(this, this.dir, pb.float(0)).rgb, this.skyInfo.x);
1036
+ } else if (skyMode === 'irradiance') {
1037
+ this.radiance = pb.mul(envLight.getIrradiance(this, this.dir).rgb, this.skyInfo.x);
1038
+ }
1039
+ });
1040
+ });
1041
+ this.$outputs.color = pb.vec4(this.radiance, this.dist);
1042
+ });
1043
+ }
1044
+ });
1045
+ this._traceKey = key;
1046
+ this._traceBindGroup = device.createBindGroup(this._traceProgram.bindGroupLayouts[0]);
1047
+ }
1048
+ const bg = this._traceBindGroup;
1049
+ this.bindCommon(bg, global);
1050
+ bg.setTexture(VOXEL_SCENE_ALBEDO, voxels.albedo, fetchSampler('clamp_nearest_nomip'));
1051
+ bg.setTexture(VOXEL_SCENE_EMISSIVE, voxels.emissive, fetchSampler('clamp_nearest_nomip'));
1052
+ bg.setTexture(VOXEL_SCENE_RADIANCE, voxels.radiance, fetchSampler('clamp_nearest_nomip'));
1053
+ bg.setTexture(VOXEL_SCENE_ANISO0, voxels.radianceAniso0, fetchSampler('clamp_nearest_nomip'));
1054
+ bg.setTexture(VOXEL_SCENE_ANISO1, voxels.radianceAniso1, fetchSampler('clamp_nearest_nomip'));
1055
+ bg.setValue('skyInfo', new Vector4(envStrength, 0, 0, 0));
1056
+ if (skyMode !== 'none') {
1057
+ envLight.updateBindGroup(bg);
1058
+ }
1059
+ if (!this._renderStates) {
1060
+ this._renderStates = device.createRenderStateSet();
1061
+ this._renderStates.useDepthState().enableTest(false).enableWrite(false);
1062
+ this._renderStates.useRasterizerState().setCullMode('none');
1063
+ }
1064
+ device.pushDeviceStates();
1065
+ try {
1066
+ device.setFramebuffer(this._rayFramebuffer);
1067
+ device.setViewport([
1068
+ 0,
1069
+ 0,
1070
+ this._rays,
1071
+ selected
1072
+ ]);
1073
+ device.setScissor([
1074
+ 0,
1075
+ 0,
1076
+ this._rays,
1077
+ selected
1078
+ ]);
1079
+ device.setProgram(this._traceProgram);
1080
+ device.setBindGroup(0, bg);
1081
+ drawFullscreenQuad(this._renderStates);
1082
+ } finally{
1083
+ device.popDeviceStates();
1084
+ }
1085
+ }
1086
+ /**
1087
+ * Integrates the traced rays into the selected probes' maps, one thread per
1088
+ * texel; border texels use the direction of the interior texel they mirror.
1089
+ * @internal
1090
+ */ runBlend(selected) {
1091
+ const device = getDevice();
1092
+ if (!this._blendIrrProgram) {
1093
+ this._blendIrrProgram = this.createBlendProgram(IRRADIANCE_TEXELS, false);
1094
+ this._blendIrrBindGroup = device.createBindGroup(this._blendIrrProgram.bindGroupLayouts[0]);
1095
+ this._blendVisProgram = this.createBlendProgram(VISIBILITY_TEXELS, true);
1096
+ this._blendVisBindGroup = device.createBindGroup(this._blendVisProgram.bindGroupLayouts[0]);
1097
+ }
1098
+ // Irradiance last: it marks probes updated, and both passes read the prior state
1099
+ const passes = [
1100
+ [
1101
+ 'ProbeBlendVisibility',
1102
+ this._blendVisProgram,
1103
+ this._blendVisBindGroup,
1104
+ this._visibility,
1105
+ this._visibilityNext,
1106
+ null
1107
+ ],
1108
+ [
1109
+ 'ProbeBlendIrradiance',
1110
+ this._blendIrrProgram,
1111
+ this._blendIrrBindGroup,
1112
+ this._irradiance,
1113
+ this._irradianceNext,
1114
+ this._irradianceSeed
1115
+ ]
1116
+ ];
1117
+ for (const [scope, program, bg, current, next, seed] of passes){
1118
+ if (seed) {
1119
+ bg.setTexture('seedTex', seed, fetchSampler('clamp_nearest_nomip'));
1120
+ }
1121
+ bg.setBuffer(PROBE_DATA, this._data);
1122
+ bg.setValue(PROBE_PARAMS, this._params);
1123
+ bg.setBuffer('selected', this._selected);
1124
+ bg.setValue('rotation', this._rotation);
1125
+ bg.setValue('rayInfo', new Vector4(this._rays, this._budget, this._frame, this._options.hysteresis));
1126
+ bg.setTexture('rays', this._rayTexture, fetchSampler('clamp_nearest_nomip'));
1127
+ bg.setTexture('current', current, fetchSampler('clamp_nearest_nomip'));
1128
+ bg.setTexture('next', next);
1129
+ RenderGraphExecutor.profileScope(scope, ()=>{
1130
+ device.setProgram(program);
1131
+ device.setBindGroup(0, bg);
1132
+ device.compute(selected, 1, 1);
1133
+ });
1134
+ }
1135
+ }
1136
+ /**
1137
+ * Copies the staged tiles of {@link ProbeVolume.runBlend} into the maps, one
1138
+ * workgroup per updated probe, leaving other tiles untouched.
1139
+ * @internal
1140
+ */ runCommit(selected) {
1141
+ const device = getDevice();
1142
+ const twoChannel = this._visibilityFormat === 'rg16f';
1143
+ if (!this._commitProgram) {
1144
+ this._commitProgram = buildSDFProgram({
1145
+ label: 'ProbeCommit',
1146
+ workgroupSize: [
1147
+ VISIBILITY_TEXELS,
1148
+ VISIBILITY_TEXELS,
1149
+ 1
1150
+ ],
1151
+ compute (pb) {
1152
+ this[PROBE_PARAMS] = pb.vec4[PROBE_PARAM_VEC4S]().uniform(0);
1153
+ this.selected = pb.uint[0]().storageBufferReadonly(0);
1154
+ this.stagedIrradiance = pb.tex2D().sampleType('unfilterable-float').uniform(0);
1155
+ this.stagedVisibility = pb.tex2D().sampleType('unfilterable-float').uniform(0);
1156
+ this.irradiance = pb.texStorage2D.rgba16float().storage(0);
1157
+ this.visibility = twoChannel ? pb.texStorage2D.rg16float().storage(0) : pb.texStorage2D.rgba16float().storage(0);
1158
+ pb.main(function() {
1159
+ this.$l.slot = this.$builtins.workGroupId.x;
1160
+ this.$l.index = pb.int(pb.compAnd(this.selected.at(this.slot), pb.uint(SELECT_INDEX_MASK)));
1161
+ this.$l.uv = pb.ivec2(this.$builtins.localInvocationId.xy);
1162
+ pb.textureStore(this.visibility, pb.uvec2(pb.add(probeTileOrigin(this, this.index, pb.float(VISIBILITY_TEXELS)), this.uv)), pb.textureLoad(this.stagedVisibility, pb.add(stagingOrigin(this, this.slot, VISIBILITY_TEXELS), this.uv), 0));
1163
+ this.$if(pb.all(pb.lessThan(this.uv, pb.ivec2(IRRADIANCE_TEXELS))), function() {
1164
+ pb.textureStore(this.irradiance, pb.uvec2(pb.add(probeTileOrigin(this, this.index, pb.float(IRRADIANCE_TEXELS)), this.uv)), pb.textureLoad(this.stagedIrradiance, pb.add(stagingOrigin(this, this.slot, IRRADIANCE_TEXELS), this.uv), 0));
1165
+ });
1166
+ });
1167
+ }
1168
+ });
1169
+ this._commitBindGroup = device.createBindGroup(this._commitProgram.bindGroupLayouts[0]);
1170
+ }
1171
+ const bg = this._commitBindGroup;
1172
+ bg.setValue(PROBE_PARAMS, this._params);
1173
+ bg.setBuffer('selected', this._selected);
1174
+ bg.setTexture('stagedIrradiance', this._irradianceNext, fetchSampler('clamp_nearest_nomip'));
1175
+ bg.setTexture('stagedVisibility', this._visibilityNext, fetchSampler('clamp_nearest_nomip'));
1176
+ bg.setTexture('irradiance', this._irradiance);
1177
+ bg.setTexture('visibility', this._visibility);
1178
+ device.setProgram(this._commitProgram);
1179
+ device.setBindGroup(0, bg);
1180
+ device.compute(selected, 1, 1);
1181
+ }
1182
+ /** @internal */ createBlendProgram(texels, visibility) {
1183
+ const rays = this._rays;
1184
+ const twoChannel = this._visibilityFormat === 'rg16f';
1185
+ return buildSDFProgram({
1186
+ label: visibility ? 'ProbeBlendVisibility' : 'ProbeBlendIrradiance',
1187
+ workgroupSize: [
1188
+ texels,
1189
+ texels,
1190
+ 1
1191
+ ],
1192
+ compute (pb) {
1193
+ this[PROBE_DATA] = pb.vec4[0]().storageBuffer(0);
1194
+ this[PROBE_PARAMS] = pb.vec4[PROBE_PARAM_VEC4S]().uniform(0);
1195
+ this.selected = pb.uint[0]().storageBufferReadonly(0);
1196
+ this.rotation = pb.vec4().uniform(0);
1197
+ this.rayInfo = pb.vec4().uniform(0);
1198
+ this.rays = pb.tex2D().sampleType('unfilterable-float').uniform(0);
1199
+ this.current = pb.tex2D().sampleType('unfilterable-float').uniform(0);
1200
+ if (!visibility) {
1201
+ // Seeds of the probes starting over, by selection slot (runSeed)
1202
+ this.seedTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
1203
+ }
1204
+ this.next = visibility && twoChannel ? pb.texStorage2D.rg16float().storage(0) : pb.texStorage2D.rgba16float().storage(0);
1205
+ // A batch of the probe's rays, direction and traced result, read once per workgroup
1206
+ this.batchDir = pb.vec4[BLEND_BATCH]().workgroup();
1207
+ this.batchRay = pb.vec4[BLEND_BATCH]().workgroup();
1208
+ pb.main(function() {
1209
+ this.$l.slot = this.$builtins.workGroupId.x;
1210
+ this.$l.sel = this.selected.at(this.slot);
1211
+ this.$l.index = pb.int(pb.compAnd(this.sel, pb.uint(SELECT_INDEX_MASK)));
1212
+ this.$l.level = pb.int(pb.compAnd(pb.sar(this.sel, SELECT_LEVEL_SHIFT), pb.uint(0x7f)));
1213
+ // First update: new to its window, or state 0 (e.g. reactivated by relocation)
1214
+ this.$l.probeState = this[PROBE_DATA].at(this.index).w;
1215
+ this.$l.first = pb.or(pb.greaterThan(pb.compAnd(this.sel, pb.uint(SELECT_FIRST_BIT)), pb.uint(0)), pb.lessThan(this.probeState, 0.5));
1216
+ this.$l.uv = pb.ivec2(this.$builtins.localInvocationId.xy);
1217
+ // Border texels mirror the interior: take the direction of the texel they copy
1218
+ const s = texels;
1219
+ this.$l.src = this.uv;
1220
+ this.$l.onX = pb.or(pb.equal(this.uv.x, 0), pb.equal(this.uv.x, s - 1));
1221
+ this.$l.onY = pb.or(pb.equal(this.uv.y, 0), pb.equal(this.uv.y, s - 1));
1222
+ this.$if(pb.and(this.onX, this.onY), function() {
1223
+ this.src = pb.ivec2(this.$choice(pb.equal(this.uv.x, 0), pb.int(s - 2), pb.int(1)), this.$choice(pb.equal(this.uv.y, 0), pb.int(s - 2), pb.int(1)));
1224
+ }).$elseif(this.onX, function() {
1225
+ this.src = pb.ivec2(this.$choice(pb.equal(this.uv.x, 0), pb.int(1), pb.int(s - 2)), pb.sub(s - 1, this.uv.y));
1226
+ }).$elseif(this.onY, function() {
1227
+ this.src = pb.ivec2(pb.sub(s - 1, this.uv.x), this.$choice(pb.equal(this.uv.y, 0), pb.int(1), pb.int(s - 2)));
1228
+ });
1229
+ this.$l.oct = pb.sub(pb.mul(pb.div(pb.add(pb.vec2(this.src), pb.vec2(-1 + 0.5)), s - 2), 2), pb.vec2(1));
1230
+ this.$l.dir = octDecode(this, this.oct);
1231
+ this.$l.sum = pb.vec4(0);
1232
+ this.$l.wsum = pb.float(0);
1233
+ this.$l.maxDist = pb.mul(probeLevelOrigin(this, this.level).w, VISIBILITY_RANGE);
1234
+ // One ray, `ray` its traced result and `c` the cosine to the texel's direction
1235
+ const accumulate = (scope)=>{
1236
+ if (visibility) {
1237
+ // Sharpened (cos^50); below cos 0.8 the weight is under 2e-5, so skip
1238
+ scope.$if(pb.greaterThan(scope.c, 0.8), function() {
1239
+ this.$l.w = pb.pow(this.c, 50);
1240
+ this.$l.hd = pb.abs(this.ray.a);
1241
+ this.$if(pb.lessThan(this.ray.a, 0), function() {
1242
+ this.hd = pb.mul(this.hd, 0.2);
1243
+ });
1244
+ this.hd = pb.min(this.hd, this.maxDist);
1245
+ this.sum = pb.add(this.sum, pb.mul(pb.vec4(this.hd, pb.mul(this.hd, this.hd), 0, 0), this.w));
1246
+ this.wsum = pb.add(this.wsum, this.w);
1247
+ });
1248
+ } else {
1249
+ // Cosine-weighted sky escape share in alpha: sky visibility, as DDGI [Majercik19]
1250
+ scope.$if(pb.greaterThanEqual(scope.ray.a, 0), function() {
1251
+ this.$l.escaped = pb.float(pb.greaterThanEqual(this.ray.a, SKY_ESCAPE_DISTANCE * 0.5));
1252
+ this.sum = pb.add(this.sum, pb.mul(pb.vec4(this.ray.rgb, this.escaped), this.c));
1253
+ this.wsum = pb.add(this.wsum, this.c);
1254
+ });
1255
+ }
1256
+ };
1257
+ this.$l.thread = pb.int(pb.add(pb.mul(this.$builtins.localInvocationId.y, texels), this.$builtins.localInvocationId.x));
1258
+ for(let b = 0; b < rays; b += BLEND_BATCH){
1259
+ const count = Math.min(BLEND_BATCH, rays - b);
1260
+ // Each thread stages every (texels²)-th ray of the batch
1261
+ this.$for(pb.int('stageRound'), 0, Math.ceil(count / (texels * texels)), function() {
1262
+ this.$l.j = pb.add(this.thread, pb.mul(this.stageRound, texels * texels));
1263
+ this.$if(pb.greaterThanEqual(this.j, count), function() {
1264
+ this.$break();
1265
+ });
1266
+ this.$l.r = pb.add(this.j, b);
1267
+ this.batchRay.setAt(this.j, pb.textureLoad(this.rays, pb.ivec2(this.r, pb.int(this.slot)), 0));
1268
+ this.batchDir.setAt(this.j, pb.vec4(quatRotate(this, this.rotation, sphericalFibonacci(this, pb.float(this.r), pb.float(rays))), 0));
1269
+ });
1270
+ pb.workgroupBarrier();
1271
+ this.$for(pb.int('k'), 0, count, function() {
1272
+ this.$l.ray = this.batchRay.at(this.k);
1273
+ this.$l.c = pb.max(pb.dot(this.dir, this.batchDir.at(this.k).xyz), 0);
1274
+ accumulate(this);
1275
+ });
1276
+ // The next batch overwrites the one still being read
1277
+ pb.workgroupBarrier();
1278
+ }
1279
+ this.$l.result = this.$choice(pb.greaterThan(this.wsum, 1e-5), pb.div(this.sum, this.wsum), visibility ? pb.vec4(this.maxDist, pb.mul(this.maxDist, this.maxDist), 0, 0) : pb.vec4(0));
1280
+ this.$l.origin = probeTileOrigin(this, this.index, pb.float(s));
1281
+ this.$l.texel = pb.add(this.origin, this.uv);
1282
+ this.$l.old = pb.textureLoad(this.current, this.texel, 0);
1283
+ this.$l.h = this.$choice(this.first, pb.float(0), this.rayInfo.w);
1284
+ if (!visibility) {
1285
+ // A probe starting over blends into its seed, if any (alpha -1 = none)
1286
+ this.$if(this.first, function() {
1287
+ this.$l.seedValue = pb.textureLoad(this.seedTex, pb.add(stagingOrigin(this, this.slot, s), this.uv), 0);
1288
+ this.$if(pb.greaterThanEqual(this.seedValue.a, 0), function() {
1289
+ this.old = this.seedValue;
1290
+ this.h = this.rayInfo.w;
1291
+ });
1292
+ });
1293
+ }
1294
+ this.$l.value = pb.mix(this.result, this.old, this.h);
1295
+ // Staged by selection slot; runCommit copies the tile into the map
1296
+ pb.textureStore(this.next, pb.uvec2(pb.add(stagingOrigin(this, this.slot, s), this.uv)), visibility ? pb.vec4(this.value.xyz, 1) : this.value);
1297
+ if (!visibility) {
1298
+ // Mark the probe updated; an inactive one stays inactive
1299
+ this.$if(pb.equal(this.$builtins.localInvocationId.xy, pb.uvec2(0)), function() {
1300
+ this.$l.data = this[PROBE_DATA].at(this.index);
1301
+ this[PROBE_DATA].setAt(this.index, pb.vec4(this.data.xyz, this.$choice(pb.greaterThan(this.data.w, PROBE_STATE_INACTIVE - 0.5), pb.float(PROBE_STATE_INACTIVE), pb.float(1))));
1302
+ });
1303
+ }
1304
+ });
1305
+ }
1306
+ });
1307
+ }
1308
+ }
1309
+ /** First texel of a selection slot's tile in a blend output, {@link STAGING_COLS} tiles a row. */ function stagingOrigin(scope, slot, texels) {
1310
+ const pb = scope.$builder;
1311
+ const s = pb.int(slot);
1312
+ return pb.mul(pb.ivec2(pb.mod(s, STAGING_COLS), pb.div(s, STAGING_COLS)), texels);
1313
+ }
1314
+ /** Visibility texels as saved, rgba16f: the two moments, then 0 and 1. */ function expandVisibility(rg, texels) {
1315
+ const out = new Uint16Array(new ArrayBuffer(texels * 8));
1316
+ for(let i = 0; i < texels; i++){
1317
+ out[i * 4] = rg[i * 2];
1318
+ out[i * 4 + 1] = rg[i * 2 + 1];
1319
+ // Half 1.0, which the blend writes to alpha
1320
+ out[i * 4 + 3] = 0x3c00;
1321
+ }
1322
+ return out;
1323
+ }
1324
+ /** The two moments of saved rgba16f visibility texels, for an rg16f map. */ function compactVisibility(rgba, texels) {
1325
+ const out = new Uint16Array(new ArrayBuffer(texels * 4));
1326
+ for(let i = 0; i < texels; i++){
1327
+ out[i * 2] = rgba[i * 4];
1328
+ out[i * 2 + 1] = rgba[i * 4 + 1];
1329
+ }
1330
+ return out;
1331
+ }
1332
+
1333
+ export { ProbeVolume, getActiveProbeVolume, getProbeVolumeForDraw };
1334
+ //# sourceMappingURL=probe_volume.js.map