@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,1195 @@
1
+ import { Vector4, Vector3, Matrix4x4, REVERSE_Z } from '@zephyr3d/base';
2
+ import { drawFullscreenQuad } from './fullscreenquad.js';
3
+ import { fetchSampler } from '../utility/misc.js';
4
+ import { MAX_CAUSTIC_WATERS, ShaderHelper, CAUSTIC_HEIGHT_BIAS } from '../material/shader/helper.js';
5
+ import { warnUnsupported } from '../utility/unsupported.js';
6
+
7
+ /** Index of refraction of water relative to air. */ const WATER_IOR = 1.333;
8
+ /** Air-to-water eta used when refracting the sun ray at the surface. */ const WATER_ETA = 1 / WATER_IOR;
9
+ /**
10
+ * Minimum |sin(sun elevation)| for caustics to be generated; below it the
11
+ * photon sweep degenerates and the pass switches off.
12
+ */ const MIN_SUN_ELEVATION = 0.15;
13
+ /**
14
+ * Fixed-point steps taken to land a photon on the scene rather than on the
15
+ * focal plane. Two suffice for a flat receiver; the third covers a steep slope.
16
+ */ const SCENE_DEPTH_ITERATIONS = 3;
17
+ /**
18
+ * Photons per covered map texel the automatic grid size solves for. Four lands
19
+ * within about 4% of a converged (16x denser) map.
20
+ */ const PHOTONS_PER_TEXEL = 4;
21
+ /** Automatic edge fade width, as a fraction of the map half-extent. */ const AUTO_FADE_FRACTION = 0.2;
22
+ /** Floor in meters under the automatic fade width. */ const AUTO_FADE_MIN_DISTANCE = 3;
23
+ /** Bounds on the share of the map half-extent the fade may consume. */ const MIN_FADE_FRACTION = 0.001;
24
+ const MAX_FADE_FRACTION = 0.9;
25
+ /**
26
+ * Largest ratio between the fitted slice's two half-extents. The map is square,
27
+ * so an anisotropic slice resolves the pattern at different frequencies along
28
+ * each axis.
29
+ */ const MAX_SLICE_ASPECT = 2;
30
+ /**
31
+ * Steps per octave the fitted half-extents are quantised to. Four costs at most
32
+ * 19% of linear resolution and keeps a level change from reading as a pop.
33
+ */ const SLICE_QUANTISE_STEPS = 4;
34
+ /** Smallest half-extent in meters a fitted slice may shrink to. */ const MIN_SLICE_EXTENT = 0.5;
35
+ /**
36
+ * Divisions of the half-extent the slice centre snaps to under a warp. A warped
37
+ * map must be resampled whenever the centre moves, so it snaps coarsely.
38
+ */ const WARPED_CENTER_SNAP_DIVISIONS = 8;
39
+ /**
40
+ * How far past the map border the photon grid is laid out, as a fraction of the
41
+ * half-extent, on the sides where the range - not the water - is what bounds it.
42
+ *
43
+ * Horizontal displacement carries photons across the border both ways, so
44
+ * launching from a margin outside keeps the border density consistent.
45
+ * `_gridFraction` accounts for the larger launch area, so density per texel is
46
+ * unchanged.
47
+ */ const GRID_BORDER_MARGIN = 0.15;
48
+ /**
49
+ * Concentrates a map coordinate towards the centre. CPU twin of
50
+ * `ShaderHelper.warpCausticNDC`.
51
+ *
52
+ * @internal
53
+ */ function warpCausticNDC(ndc, strength) {
54
+ return ndc * (1 + strength) / (1 + strength * Math.abs(ndc));
55
+ }
56
+ /**
57
+ * Rounds a fitted half-extent up onto a discrete ladder below `range`.
58
+ *
59
+ * The ladder is geometric and anchored at `range`, so water that fills the range
60
+ * lands exactly on a level.
61
+ *
62
+ * @internal
63
+ */ function quantiseSliceExtent(half, range) {
64
+ if (!(half > 0) || half >= range) {
65
+ return range;
66
+ }
67
+ const steps = Math.floor(Math.log2(range / half) * SLICE_QUANTISE_STEPS);
68
+ return range / Math.pow(2, steps / SLICE_QUANTISE_STEPS);
69
+ }
70
+ /**
71
+ * Vertices per side of the grid the height map rasterises the surface with, laid
72
+ * out in warped map space over the body's footprint.
73
+ */ const HEIGHT_GRID_SIZE = 256;
74
+ /**
75
+ * Shader for the photon splat pass.
76
+ *
77
+ * One vertex is one photon, emitted where it lands; the fragment stage deposits
78
+ * the weight, which additive blending accumulates.
79
+ *
80
+ * Returned as a descriptor so it can be compiled against a bare
81
+ * {@link ProgramBuilder} in tests, without a device.
82
+ *
83
+ * @param waveGenerator - Supplies the surface displacement and normal.
84
+ * @internal
85
+ */ function createCausticSplatShader(waveGenerator, sceneDepth) {
86
+ const setupUniforms = (scope)=>{
87
+ const pb = scope.$builder;
88
+ scope.causticFrameX = pb.vec4().uniform(0);
89
+ scope.causticFrameY = pb.vec4().uniform(0);
90
+ scope.causticCenter = pb.vec4().uniform(0);
91
+ scope.causticLightDir = pb.vec4().uniform(0);
92
+ scope.causticRegion = pb.vec4().uniform(0);
93
+ /** Map-NDC rectangle the photon grid is laid out over: (minX, minY, maxX, maxY). */ scope.causticGridBounds = pb.vec4().uniform(0);
94
+ scope.causticSplatParams = pb.vec4().uniform(0);
95
+ /** (grid size, 1 / grid size, 0, 0) */ scope.causticPhotonParams = pb.vec4().uniform(0);
96
+ if (sceneDepth) {
97
+ // The sun's shadow cascade, reused as an orthographic light-space depth map.
98
+ scope.causticSceneMatrix = pb.mat4().uniform(0);
99
+ scope.causticSceneMatrixInv = pb.mat4().uniform(0);
100
+ /** (cascade layer, 0, 0, 0) */ scope.causticSceneParams = pb.vec4().uniform(0);
101
+ // Read for its stored depth, not compared.
102
+ scope.causticSceneDepth = pb.tex2DArrayShadow().uniform(0);
103
+ }
104
+ // Wave generators animate from camera.elapsedTime, which this standalone
105
+ // pass supplies itself.
106
+ ShaderHelper.declareStandaloneCameraTime(scope, 0);
107
+ waveGenerator.setupUniforms(scope, 0);
108
+ };
109
+ return {
110
+ vertex (pb) {
111
+ // Photon ordinal; the grid coordinate is derived from it, so the buffer
112
+ // serves any grid size.
113
+ this.$inputs.photonIndex = pb.float().attrib('position');
114
+ this.$outputs.photonWeight = pb.float();
115
+ setupUniforms(this);
116
+ pb.main(function() {
117
+ // Per-axis: the slice's two half-extents are independent.
118
+ this.$l.invRadius = pb.vec2(this.causticFrameX.w, this.causticFrameY.w);
119
+ this.$l.radius = pb.div(pb.vec2(1), this.invRadius);
120
+ this.$l.focalDepth = this.causticSplatParams.y;
121
+ this.$l.eta = this.causticSplatParams.z;
122
+ this.$l.L = this.causticLightDir.xyz;
123
+ this.$l.waterLevel = this.causticCenter.w;
124
+ // Grid position on the orthographic slice through the map centre. The
125
+ // grid spans only the part of the slice the water casts through, and is
126
+ // uniform in warped map space so calm water keeps photon (i,j) on texel
127
+ // (i,j) and the map normalised to 1.0.
128
+ //
129
+ // Ordinal to grid coordinate: row is the quotient, column the remainder.
130
+ this.$l.gridSize = this.causticPhotonParams.x;
131
+ this.$l.invGrid = this.causticPhotonParams.y;
132
+ this.$l.photonRow = pb.floor(pb.mul(this.$inputs.photonIndex, this.invGrid));
133
+ this.$l.photonCol = pb.sub(this.$inputs.photonIndex, pb.mul(this.photonRow, this.gridSize));
134
+ this.$l.photonUV = pb.mul(pb.add(pb.vec2(this.photonCol, this.photonRow), pb.vec2(0.5)), this.invGrid);
135
+ this.$l.warpedNDC = pb.mix(this.causticGridBounds.xy, this.causticGridBounds.zw, this.photonUV);
136
+ this.$l.ndc = ShaderHelper.unwarpCausticNDC(this, this.warpedNDC, this.causticSplatParams.x);
137
+ this.$l.planePos = pb.add(this.causticCenter.xyz, pb.mul(this.causticFrameX.xyz, pb.mul(this.ndc.x, this.radius.x)), pb.mul(this.causticFrameY.xyz, pb.mul(this.ndc.y, this.radius.y)));
138
+ // Sweep along the light direction onto the undisturbed water plane.
139
+ this.$l.sweep = pb.div(pb.sub(this.waterLevel, this.planePos.y), this.L.y);
140
+ this.$l.surfaceXZ = pb.add(this.planePos, pb.mul(this.L, this.sweep));
141
+ this.$l.outside = pb.or(pb.any(pb.lessThan(this.surfaceXZ.xz, this.causticRegion.xy)), pb.any(pb.greaterThan(this.surfaceXZ.xz, this.causticRegion.zw)));
142
+ // Displaced surface point and the detail normal that bends the ray. The
143
+ // wave generators sample with an explicit LOD, legal in a vertex shader.
144
+ //
145
+ // Launching from the rest point over-weights compressed crests by 1/J;
146
+ // inverting the displacement does not converge where the surface folds.
147
+ this.$l.surfacePos = pb.vec3();
148
+ this.$l.coarseNormal = pb.vec3();
149
+ waveGenerator.calcVertexPositionAndNormal(this, this.surfaceXZ, this.surfacePos, this.coarseNormal);
150
+ this.$l.normal = waveGenerator.calcFragmentNormal(this, this.surfaceXZ.xz, this.coarseNormal);
151
+ // Refract, then subtract the deflection a flat surface would have
152
+ // produced. Calm water then leaves the ray parallel to L, which lands
153
+ // photon (i,j) back on texel (i,j) and normalises the map to 1.
154
+ this.$l.refracted = pb.refract(this.L, this.normal, this.eta);
155
+ this.$l.refractedFlat = pb.refract(this.L, pb.vec3(0, 1, 0), this.eta);
156
+ this.$l.dir = pb.normalize(pb.add(pb.sub(this.refracted, this.refractedFlat), this.L));
157
+ // Intersect the horizontal focal plane below the surface; clamped so a
158
+ // grazing ray cannot produce an enormous or negative step.
159
+ this.$l.invDirY = pb.min(this.dir.y, -1e-4);
160
+ this.$l.targetY = pb.sub(this.waterLevel, this.focalDepth);
161
+ this.$l.hitT = pb.div(pb.sub(this.targetY, this.surfacePos.y), this.invDirY);
162
+ this.$l.hitPos = pb.add(this.surfacePos, pb.mul(this.dir, pb.max(this.hitT, 0)));
163
+ if (sceneDepth) {
164
+ // Land the photon on the scene by fixed-point iteration: read the scene
165
+ // height under the current guess, re-intersect the ray with a
166
+ // horizontal plane at that height, repeat. One fetch per step; at a
167
+ // silhouette it lands on one of the two surfaces rather than diverging.
168
+ this.$l.probe = this.hitPos;
169
+ this.$l.layer = pb.int(this.causticSceneParams.x);
170
+ for(let i = 0; i < SCENE_DEPTH_ITERATIONS; i++){
171
+ this.$l[`clip${i}`] = pb.mul(this.causticSceneMatrix, pb.vec4(this.probe, 1));
172
+ this.$l[`ndc${i}`] = pb.div(this[`clip${i}`].xyz, this[`clip${i}`].w);
173
+ this.$l[`uv${i}`] = pb.add(pb.mul(this[`ndc${i}`].xy, 0.5), pb.vec2(0.5));
174
+ // Outside the cascade, keep the previous step's result.
175
+ this.$l[`inside${i}`] = pb.and(pb.all(pb.greaterThanEqual(this[`uv${i}`], pb.vec2(0))), pb.all(pb.lessThanEqual(this[`uv${i}`], pb.vec2(1))));
176
+ // texelFetch, since filtered depths across a silhouette are wrong.
177
+ // WebGPU only - GLSL ES 3.0 forbids texelFetch on a shadow-sampler
178
+ // texture, which gates this whole path.
179
+ this.$l[`depth${i}`] = pb.textureArrayLoad(this.causticSceneDepth, pb.ivec2(pb.mul(this[`uv${i}`], this.causticSceneParams.y)), this.layer, pb.int(0));
180
+ // Back to a world position through the orthographic cascade.
181
+ this.$l[`sceneNDC${i}`] = pb.vec3(this[`ndc${i}`].xy, REVERSE_Z ? this[`depth${i}`] : pb.sub(pb.mul(this[`depth${i}`], 2), 1));
182
+ this.$l[`sceneClip${i}`] = pb.mul(this.causticSceneMatrixInv, pb.vec4(this[`sceneNDC${i}`], 1));
183
+ this.$l[`scenePos${i}`] = pb.div(this[`sceneClip${i}`].xyz, this[`sceneClip${i}`].w);
184
+ // Re-intersect at the height just read, accepted only below the
185
+ // *displaced* surface at this photon's entry (not the rest level).
186
+ this.$l[`t${i}`] = pb.div(pb.sub(this[`scenePos${i}`].y, this.surfacePos.y), this.invDirY);
187
+ this.$l[`next${i}`] = pb.add(this.surfacePos, pb.mul(this.dir, pb.max(this[`t${i}`], 0)));
188
+ // mix rather than select: the builder has no vec3 select.
189
+ this.probe = pb.mix(this.probe, this[`next${i}`], pb.float(pb.and(this[`inside${i}`], pb.lessThan(this[`scenePos${i}`].y, this.surfacePos.y))));
190
+ }
191
+ this.hitPos = this.probe;
192
+ }
193
+ // Project the hit back into the map. frameX/frameY are perpendicular to
194
+ // L and to each other, so this is just two dot products.
195
+ this.$l.rel = pb.sub(this.hitPos, this.causticCenter.xyz);
196
+ this.$l.hitNDC = pb.mul(pb.vec2(pb.dot(this.rel, this.causticFrameX.xyz), pb.dot(this.rel, this.causticFrameY.xyz)), this.invRadius);
197
+ // Into warped space, where the map's texels live.
198
+ this.$l.hitWarped = ShaderHelper.warpCausticNDC(this, this.hitNDC, this.causticSplatParams.x);
199
+ this.$outputs.photonWeight = this.causticSplatParams.w;
200
+ // No per-backend y flip here; the blur and resolve quads account for it.
201
+ this.$builtins.position = pb.vec4(this.hitWarped, 0, 1);
202
+ if (pb.getDevice().type !== 'webgpu') {
203
+ // GLSL rasterises nothing unless gl_PointSize is written; WebGPU points
204
+ // are always one pixel.
205
+ this.$builtins.pointSize = 1;
206
+ }
207
+ this.$if(this.outside, function() {
208
+ // Park the photon outside the clip volume; w stays 1 to avoid NaN.
209
+ this.$builtins.position = pb.vec4(-2, -2, 0, 1);
210
+ });
211
+ });
212
+ },
213
+ fragment (pb) {
214
+ this.$outputs.outColor = pb.vec4();
215
+ pb.main(function() {
216
+ this.$outputs.outColor = pb.vec4(this.$inputs.photonWeight, 0, 0, 1);
217
+ });
218
+ }
219
+ };
220
+ }
221
+ /**
222
+ * Shader for the displaced surface height map.
223
+ *
224
+ * Rasterises the water surface into the light-space slice, one body per draw. A
225
+ * grid over the body's footprint in warped map space is swept along the light
226
+ * onto the rest plane, displaced by the wave generator, and projected back into
227
+ * the map. The fragment writes its height above the rest plane, offset by
228
+ * `CAUSTIC_HEIGHT_BIAS`, into this body's channel via a colour write mask (last
229
+ * triangle wins). A texel the surface never reaches stays zero, which the
230
+ * receiver reads as "no water on this ray".
231
+ *
232
+ * Rasterising handles horizontal displacement without inverting it, and keeps
233
+ * the footprint edge exact. The receiver looks this up with the same uv as the
234
+ * caustic pattern, since the projection is invariant along the light.
235
+ *
236
+ * @param waveGenerator - Supplies the surface displacement of this body.
237
+ * @internal
238
+ */ function createCausticHeightShader(waveGenerator) {
239
+ return {
240
+ vertex (pb) {
241
+ // Vertex ordinal; the grid coordinate is derived from it.
242
+ this.$inputs.vertexIndex = pb.float().attrib('position');
243
+ this.$outputs.height = pb.float();
244
+ this.$outputs.inside = pb.float();
245
+ this.causticFrameX = pb.vec4().uniform(0);
246
+ this.causticFrameY = pb.vec4().uniform(0);
247
+ /** (slice centre.xyz, this body's rest level) */ this.causticCenter = pb.vec4().uniform(0);
248
+ this.causticLightDir = pb.vec4().uniform(0);
249
+ this.causticRegion = pb.vec4().uniform(0);
250
+ /** Map-NDC rectangle the grid is laid out over: (minX, minY, maxX, maxY). */ this.causticGridBounds = pb.vec4().uniform(0);
251
+ /** (warp strength, grid size, 1 / (grid size + 1), 1 / grid size) */ this.causticHeightParams = pb.vec4().uniform(0);
252
+ ShaderHelper.declareStandaloneCameraTime(this, 0);
253
+ waveGenerator.setupUniforms(this, 0);
254
+ pb.main(function() {
255
+ this.$l.invRadius = pb.vec2(this.causticFrameX.w, this.causticFrameY.w);
256
+ this.$l.radius = pb.div(pb.vec2(1), this.invRadius);
257
+ this.$l.L = this.causticLightDir.xyz;
258
+ this.$l.waterLevel = this.causticCenter.w;
259
+ // Ordinal to grid coordinate, (gridSize + 1) vertices per side. The +0.5
260
+ // keeps the inexact reciprocal from rounding into the previous row.
261
+ this.$l.row = pb.floor(pb.mul(pb.add(this.$inputs.vertexIndex, 0.5), this.causticHeightParams.z));
262
+ this.$l.col = pb.sub(this.$inputs.vertexIndex, pb.mul(this.row, pb.add(this.causticHeightParams.y, 1)));
263
+ this.$l.gridUV = pb.mul(pb.vec2(this.col, this.row), this.causticHeightParams.w);
264
+ // Grid point to the slice, through the warp the map's texels live under.
265
+ this.$l.warpedNDC = pb.mix(this.causticGridBounds.xy, this.causticGridBounds.zw, this.gridUV);
266
+ this.$l.ndc = ShaderHelper.unwarpCausticNDC(this, this.warpedNDC, this.causticHeightParams.x);
267
+ this.$l.planePos = pb.add(this.causticCenter.xyz, pb.mul(this.causticFrameX.xyz, pb.mul(this.ndc.x, this.radius.x)), pb.mul(this.causticFrameY.xyz, pb.mul(this.ndc.y, this.radius.y)));
268
+ // Along the light onto the rest plane: the rest point of this vertex.
269
+ this.$l.sweep = pb.div(pb.sub(this.waterLevel, this.planePos.y), this.L.y);
270
+ this.$l.surfaceXZ = pb.add(this.planePos, pb.mul(this.L, this.sweep));
271
+ // Footprint test, interpolated so the edge fades over one grid cell.
272
+ this.$outputs.inside = pb.float(pb.and(pb.all(pb.greaterThanEqual(this.surfaceXZ.xz, this.causticRegion.xy)), pb.all(pb.lessThanEqual(this.surfaceXZ.xz, this.causticRegion.zw))));
273
+ // Where the surface carried this rest point, and how high it is there.
274
+ this.$l.surfacePos = pb.vec3();
275
+ this.$l.surfaceNormal = pb.vec3();
276
+ waveGenerator.calcVertexPositionAndNormal(this, this.surfaceXZ, this.surfacePos, this.surfaceNormal);
277
+ this.$outputs.height = pb.sub(this.surfacePos.y, this.waterLevel);
278
+ // The displaced point back into the map, as the splat projects a hit.
279
+ this.$l.rel = pb.sub(this.surfacePos, this.causticCenter.xyz);
280
+ this.$l.hitNDC = pb.mul(pb.vec2(pb.dot(this.rel, this.causticFrameX.xyz), pb.dot(this.rel, this.causticFrameY.xyz)), this.invRadius);
281
+ this.$l.hitWarped = ShaderHelper.warpCausticNDC(this, this.hitNDC, this.causticHeightParams.x);
282
+ // The receiver reads the map at `v = 0.5 + 0.5 * ndc.y`. On WebGPU clip
283
+ // +1 is the first row, so the surface is drawn flipped. The splat needs
284
+ // no flip because its map passes through the blur and resolve quads.
285
+ this.$builtins.position = pb.getDevice().type === 'webgpu' ? pb.vec4(this.hitWarped.x, pb.neg(this.hitWarped.y), 0, 1) : pb.vec4(this.hitWarped, 0, 1);
286
+ });
287
+ },
288
+ fragment (pb) {
289
+ this.$outputs.outColor = pb.vec4();
290
+ pb.main(function() {
291
+ // Outside the footprint nothing is written, so the texel reads "no surface".
292
+ this.$if(pb.lessThan(this.$inputs.inside, 0.5), function() {
293
+ pb.discard();
294
+ });
295
+ // Every channel; the colour mask keeps all but this body's.
296
+ this.$outputs.outColor = pb.vec4(pb.add(this.$inputs.height, CAUSTIC_HEIGHT_BIAS));
297
+ });
298
+ }
299
+ };
300
+ }
301
+ /**
302
+ * Shader for the caustic map blur.
303
+ *
304
+ * Four bilinear taps at the texel corners average a 2x2 neighbourhood; weights
305
+ * sum to one, preserving the "calm water reads 1.0" normalisation.
306
+ *
307
+ * @internal
308
+ */ function createCausticBlurShader() {
309
+ return {
310
+ vertex (pb) {
311
+ this.$inputs.pos = pb.vec2().attrib('position');
312
+ this.$outputs.uv = pb.vec2();
313
+ pb.main(function() {
314
+ this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
315
+ this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos, 0.5), pb.vec2(0.5));
316
+ });
317
+ },
318
+ fragment (pb) {
319
+ this.causticSrc = pb.tex2D().uniform(0);
320
+ this.causticTexelSize = pb.vec4().uniform(0);
321
+ this.$outputs.outColor = pb.vec4();
322
+ pb.main(function() {
323
+ this.$l.o = pb.mul(this.causticTexelSize.xy, 0.5);
324
+ this.$l.sum = pb.add(pb.textureSampleLevel(this.causticSrc, pb.add(this.$inputs.uv, pb.vec2(this.o.x, this.o.y)), 0), pb.textureSampleLevel(this.causticSrc, pb.add(this.$inputs.uv, pb.vec2(pb.neg(this.o.x), this.o.y)), 0), pb.textureSampleLevel(this.causticSrc, pb.add(this.$inputs.uv, pb.vec2(this.o.x, pb.neg(this.o.y))), 0), pb.textureSampleLevel(this.causticSrc, pb.add(this.$inputs.uv, pb.vec2(pb.neg(this.o.x), pb.neg(this.o.y))), 0));
325
+ this.$outputs.outColor = pb.vec4(pb.mul(this.sum.x, 0.25), 0, 0, 1);
326
+ });
327
+ }
328
+ };
329
+ }
330
+ /**
331
+ * Shader for the temporal resolve.
332
+ *
333
+ * Reprojection is exact and matrix-free: both frames are orthographic slices
334
+ * perpendicular to the light, so a texel maps to the world with two scaled axes
335
+ * and into the previous frame with two dot products.
336
+ *
337
+ * Map NDC relates to texture coordinate by the receiver's `ndc * 0.5 + 0.5`
338
+ * (see `ShaderHelper.calculateWaterCaustic`).
339
+ *
340
+ * @internal
341
+ */ function createCausticResolveShader() {
342
+ return {
343
+ vertex (pb) {
344
+ this.$inputs.pos = pb.vec2().attrib('position');
345
+ this.$outputs.uv = pb.vec2();
346
+ pb.main(function() {
347
+ this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
348
+ this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos, 0.5), pb.vec2(0.5));
349
+ });
350
+ },
351
+ fragment (pb) {
352
+ this.causticCurrent = pb.tex2D().uniform(0);
353
+ this.causticHistory = pb.tex2D().uniform(0);
354
+ this.causticFrameX = pb.vec4().uniform(0);
355
+ this.causticFrameY = pb.vec4().uniform(0);
356
+ this.causticCenter = pb.vec4().uniform(0);
357
+ this.causticPrevFrameX = pb.vec4().uniform(0);
358
+ this.causticPrevFrameY = pb.vec4().uniform(0);
359
+ this.causticPrevCenter = pb.vec4().uniform(0);
360
+ /** (blend weight, texel size, warp, previous frame's warp) */ this.causticResolveParams = pb.vec4().uniform(0);
361
+ this.$outputs.outColor = pb.vec4();
362
+ pb.main(function() {
363
+ this.$l.texel = this.causticResolveParams.y;
364
+ this.$l.current = pb.textureSampleLevel(this.causticCurrent, this.$inputs.uv, 0).x;
365
+ // Neighbourhood range of the current frame; history is clamped into it
366
+ // so a long blend can follow an animated pattern.
367
+ this.$l.mn = this.current;
368
+ this.$l.mx = this.current;
369
+ for(let dy = -1; dy <= 1; dy++){
370
+ for(let dx = -1; dx <= 1; dx++){
371
+ if (dx === 0 && dy === 0) {
372
+ continue;
373
+ }
374
+ this.$l.s = pb.textureSampleLevel(this.causticCurrent, pb.add(this.$inputs.uv, pb.mul(pb.vec2(dx, dy), this.texel)), 0).x;
375
+ this.mn = pb.min(this.mn, this.s);
376
+ this.mx = pb.max(this.mx, this.s);
377
+ }
378
+ }
379
+ // This texel's world point, then its place in the previous frame; the
380
+ // frames may differ in slice and warp strength.
381
+ this.$l.warpedNDC = pb.sub(pb.mul(this.$inputs.uv, 2), pb.vec2(1));
382
+ this.$l.ndc = ShaderHelper.unwarpCausticNDC(this, this.warpedNDC, this.causticResolveParams.z);
383
+ this.$l.radius = pb.div(pb.vec2(1), pb.vec2(this.causticFrameX.w, this.causticFrameY.w));
384
+ this.$l.world = pb.add(this.causticCenter.xyz, pb.mul(this.causticFrameX.xyz, pb.mul(this.ndc.x, this.radius.x)), pb.mul(this.causticFrameY.xyz, pb.mul(this.ndc.y, this.radius.y)));
385
+ this.$l.rel = pb.sub(this.world, this.causticPrevCenter.xyz);
386
+ this.$l.prevNDC = pb.mul(pb.vec2(pb.dot(this.rel, this.causticPrevFrameX.xyz), pb.dot(this.rel, this.causticPrevFrameY.xyz)), pb.vec2(this.causticPrevFrameX.w, this.causticPrevFrameY.w));
387
+ this.$l.prevWarped = ShaderHelper.warpCausticNDC(this, this.prevNDC, this.causticResolveParams.w);
388
+ this.$l.prevUV = pb.add(pb.mul(this.prevWarped, 0.5), pb.vec2(0.5));
389
+ // Ground the camera has just scrolled into has no history at all.
390
+ this.$l.inside = pb.and(pb.all(pb.greaterThanEqual(this.prevUV, pb.vec2(0))), pb.all(pb.lessThanEqual(this.prevUV, pb.vec2(1))));
391
+ this.$l.history = pb.textureSampleLevel(this.causticHistory, this.prevUV, 0).x;
392
+ this.$l.blend = pb.mul(this.causticResolveParams.x, pb.float(this.inside));
393
+ this.$outputs.outColor = pb.vec4(pb.mix(this.current, pb.clamp(this.history, this.mn, this.mx), this.blend), 0, 0, 1);
394
+ });
395
+ }
396
+ };
397
+ }
398
+ /**
399
+ * Renders the water caustic map.
400
+ *
401
+ * One photon is emitted per texel of a uniform grid laid out on the same
402
+ * orthographic light-space slice the map covers. Each photon is refracted at the
403
+ * water surface, intersected with the scene (or a horizontal focal plane) below
404
+ * it, and splatted additively at the hit position. With the flat-surface
405
+ * deflection removed, calm water maps photon `(i,j)` onto texel `(i,j)` and the
406
+ * map converges to a uniform 1.0 - which the receiver treats as "no caustics".
407
+ *
408
+ * @internal
409
+ */ class WaterCausticsRenderer {
410
+ /** Splat programs keyed by the wave generator's shader hash. */ _splatPrograms;
411
+ /** Height-map programs keyed by the wave generator's shader hash. */ _heightPrograms;
412
+ /** One per water slot, differing only in which channel they let through. */ _heightStates;
413
+ _heightParams;
414
+ /** The surface grid the height map is rasterised with; built once. */ _heightLayout;
415
+ _heightIndexCount;
416
+ _blurProgram;
417
+ _blurBindGroup;
418
+ _resolveProgram;
419
+ _resolveBindGroup;
420
+ _resolveStates;
421
+ /** Slice parameters of the last map committed as history, per camera. */ _prevSlices;
422
+ _resolveParams;
423
+ _photonLayout;
424
+ /** Photons the buffer can serve; it grows and is never rebuilt smaller. */ _photonCapacity;
425
+ _photonBuffer;
426
+ _photonParams;
427
+ _photonCount;
428
+ _splatStates;
429
+ _blurStates;
430
+ _uniforms;
431
+ _tmpRight;
432
+ _tmpUp;
433
+ _tmpDir;
434
+ _tmpCenter;
435
+ _splatParams;
436
+ _blurTexelSize;
437
+ /** Map-NDC rectangle the photon grid covers, and the map fraction it is. */ _gridBounds;
438
+ _gridFraction;
439
+ /** Furthest the fitted slice reaches from the camera, for cascade selection. */ _sliceReach;
440
+ /** Slice frame and extents, kept for the per-body grid fit. */ _sliceRight;
441
+ _sliceUp;
442
+ _sliceCenterR;
443
+ _sliceCenterU;
444
+ _sliceHalfR;
445
+ _sliceHalfU;
446
+ _splatCenter;
447
+ _sceneMatrixT;
448
+ _sceneMatrix;
449
+ _sceneMatrixInv;
450
+ _sceneDepthParams;
451
+ /** Warp strength the current slice was built with; 0 when the map is linear. */ _warp;
452
+ constructor(){
453
+ this._splatPrograms = new Map();
454
+ this._heightPrograms = new Map();
455
+ this._heightStates = [];
456
+ this._heightParams = new Vector4();
457
+ this._heightLayout = null;
458
+ this._heightIndexCount = 0;
459
+ this._blurProgram = null;
460
+ this._blurBindGroup = null;
461
+ this._resolveProgram = null;
462
+ this._resolveBindGroup = null;
463
+ this._resolveStates = null;
464
+ this._prevSlices = new WeakMap();
465
+ this._resolveParams = new Vector4();
466
+ this._photonLayout = null;
467
+ this._photonCapacity = 0;
468
+ this._photonBuffer = null;
469
+ this._photonParams = new Vector4();
470
+ this._photonCount = 0;
471
+ this._splatStates = null;
472
+ this._blurStates = null;
473
+ this._uniforms = {
474
+ frameX: new Vector4(),
475
+ frameY: new Vector4(),
476
+ center: new Vector4(),
477
+ lightDir: new Vector4(),
478
+ params: new Vector4(),
479
+ extinction: new Vector4(),
480
+ waterCount: 0,
481
+ waterRegions: new Float32Array(MAX_CAUSTIC_WATERS * 4),
482
+ waterMedia: new Float32Array(MAX_CAUSTIC_WATERS * 4)
483
+ };
484
+ this._tmpRight = new Vector3();
485
+ this._tmpUp = new Vector3();
486
+ this._tmpDir = new Vector3();
487
+ this._tmpCenter = new Vector3();
488
+ this._splatParams = new Vector4();
489
+ this._blurTexelSize = new Vector4();
490
+ this._gridBounds = new Vector4(-1, -1, 1, 1);
491
+ this._gridFraction = 1;
492
+ this._sliceReach = 1;
493
+ this._sliceRight = new Vector3();
494
+ this._sliceUp = new Vector3();
495
+ this._sliceCenterR = 0;
496
+ this._sliceCenterU = 0;
497
+ this._sliceHalfR = 1;
498
+ this._sliceHalfU = 1;
499
+ this._splatCenter = new Vector4();
500
+ this._sceneMatrixT = new Matrix4x4();
501
+ this._sceneMatrix = new Matrix4x4();
502
+ this._sceneMatrixInv = new Matrix4x4();
503
+ this._sceneDepthParams = new Vector4();
504
+ this._warp = 0;
505
+ }
506
+ /** Parameters of the map produced by the last successful {@link render}. */ get uniforms() {
507
+ return this._uniforms;
508
+ }
509
+ /**
510
+ * Storage format for the caustic map. Single channel when the device can both
511
+ * render to and filter it, which the additive accumulation and the bilinear
512
+ * sampling need.
513
+ */ static getMapFormat(device) {
514
+ const info = device.getDeviceCaps().textureCaps.getTextureFormatInfo('r16f');
515
+ return info.renderable && info.filterable ? 'r16f' : 'rgba16f';
516
+ }
517
+ /**
518
+ * Storage format for the displaced surface height map: one channel per water
519
+ * slot.
520
+ */ static getHeightMapFormat() {
521
+ return 'rgba16f';
522
+ }
523
+ /**
524
+ * Whether caustics can be produced for this water surface and light.
525
+ *
526
+ * @param water - Water surface the photons are refracted through.
527
+ * @param light - Directional light the photons come from.
528
+ * @returns True when {@link render} would produce a usable map.
529
+ */ static canRender(water, light) {
530
+ const material = water.material;
531
+ if (!material?.causticsEnabled || !material.waveGenerator || !light.isDirectionLight()) {
532
+ return false;
533
+ }
534
+ // Sun too close to the horizon: the swept photon grid degenerates.
535
+ return -light.directionAndCutoff.y >= MIN_SUN_ELEVATION;
536
+ }
537
+ /**
538
+ * Builds the caustic map for one water surface.
539
+ *
540
+ * @param ctx - Draw context; supplies the device and the camera the map is centred on.
541
+ * @param water - Water surface to refract through.
542
+ * @param light - Directional light acting as the photon source.
543
+ * @param map - Accumulation target; also the result when nothing resolves after it.
544
+ * @param scratch - Ping-pong target for the blur, and the temporal resolve's
545
+ * output. Same size and format as `map`.
546
+ * @param heights - Target for the displaced surface height map, in
547
+ * {@link getHeightMapFormat} and the same size as `map`. Filled by this call
548
+ * and read by the light pass alongside the map.
549
+ * @param history - Previous frame's resolved map, or null when there is none
550
+ * to reproject. Ignored unless the material asks for temporal accumulation.
551
+ * @param createFramebuffer - Wraps a target texture into a framebuffer.
552
+ * @returns The texture holding the finished map, which is `map` or `scratch`
553
+ * depending on whether the temporal resolve ran.
554
+ */ render(ctx, waters, light, map, scratch, heights, history, createFramebuffer) {
555
+ const device = ctx.device;
556
+ this._updateUniforms(ctx, waters, light, map.width);
557
+ const sceneDepth = this._anySceneDepth(waters) ? this._resolveSceneDepth(ctx, light) : null;
558
+ // The height map first; it depends on the slice alone.
559
+ device.pushDeviceStates();
560
+ device.setFramebuffer(createFramebuffer(heights));
561
+ device.setViewport(null);
562
+ device.setScissor(null);
563
+ device.clearFrameBuffer(Vector4.zero(), null, null);
564
+ this._ensureHeightGrid(device);
565
+ for(let i = 0; i < waters.length; i++){
566
+ this._renderHeightsOne(device, ctx, waters[i], i);
567
+ }
568
+ device.popDeviceStates();
569
+ device.pushDeviceStates();
570
+ device.setFramebuffer(createFramebuffer(map));
571
+ // Viewport and scissor survive a framebuffer change; reset them.
572
+ device.setViewport(null);
573
+ device.setScissor(null);
574
+ device.clearFrameBuffer(Vector4.zero(), null, null);
575
+ device.setRenderStates(this._getSplatStates(device));
576
+ // One draw per water body, accumulating into the one shared-slice map.
577
+ for(let i = 0; i < waters.length; i++){
578
+ this._splatOne(device, ctx, waters[i], i, map, sceneDepth);
579
+ }
580
+ device.popDeviceStates();
581
+ return this._postProcess(device, waters[0].material, map, scratch, history, ctx, createFramebuffer);
582
+ }
583
+ /**
584
+ * Writes one body's displaced surface height into its channel of the height
585
+ * map.
586
+ * @internal
587
+ */ _renderHeightsOne(device, ctx, water, index) {
588
+ const material = water.material;
589
+ const waveGenerator = material.waveGenerator;
590
+ // The grid covers this body's footprint.
591
+ this._updateGridBounds(water);
592
+ if (!(this._gridFraction > 0)) {
593
+ return;
594
+ }
595
+ const info = this._getHeightProgram(device, waveGenerator);
596
+ // A bind group per slot; see _getSplatBindGroup.
597
+ const bindGroup = this._getSplatBindGroup(device, info, index);
598
+ bindGroup.setValue('causticFrameX', this._uniforms.frameX);
599
+ bindGroup.setValue('causticFrameY', this._uniforms.frameY);
600
+ this._splatCenter.setXYZW(this._uniforms.center.x, this._uniforms.center.y, this._uniforms.center.z, water.worldMatrix.m13);
601
+ bindGroup.setValue('causticCenter', this._splatCenter);
602
+ bindGroup.setValue('causticLightDir', this._uniforms.lightDir);
603
+ bindGroup.setValue('causticRegion', material.region);
604
+ bindGroup.setValue('causticGridBounds', this._gridBounds);
605
+ this._heightParams.setXYZW(this._warp, HEIGHT_GRID_SIZE, 1 / (HEIGHT_GRID_SIZE + 1), 1 / HEIGHT_GRID_SIZE);
606
+ bindGroup.setValue('causticHeightParams', this._heightParams);
607
+ ShaderHelper.setStandaloneCameraTime(bindGroup, ctx);
608
+ waveGenerator.applyWaterBindGroup(bindGroup);
609
+ // Channel i for slot i, which is how the receiver picks it back out.
610
+ device.setRenderStates(this._getHeightStates(device, index));
611
+ device.setProgram(info.program);
612
+ device.setBindGroup(0, bindGroup);
613
+ device.setVertexLayout(this._heightLayout);
614
+ device.draw('triangle-list', 0, this._heightIndexCount);
615
+ }
616
+ /**
617
+ * Builds the surface grid once: vertex ordinals, and the index list that
618
+ * stitches a (HEIGHT_GRID_SIZE + 1)^2 lattice of them into triangles.
619
+ * @internal
620
+ */ _ensureHeightGrid(device) {
621
+ if (this._heightLayout) {
622
+ return;
623
+ }
624
+ const side = HEIGHT_GRID_SIZE + 1;
625
+ const ordinals = new Float32Array(side * side);
626
+ for(let i = 0; i < ordinals.length; i++){
627
+ ordinals[i] = i;
628
+ }
629
+ const indices = new Uint32Array(HEIGHT_GRID_SIZE * HEIGHT_GRID_SIZE * 6);
630
+ let n = 0;
631
+ for(let row = 0; row < HEIGHT_GRID_SIZE; row++){
632
+ for(let col = 0; col < HEIGHT_GRID_SIZE; col++){
633
+ const a = row * side + col;
634
+ const b = a + 1;
635
+ const c = a + side;
636
+ const d = c + 1;
637
+ indices[n++] = a;
638
+ indices[n++] = b;
639
+ indices[n++] = d;
640
+ indices[n++] = a;
641
+ indices[n++] = d;
642
+ indices[n++] = c;
643
+ }
644
+ }
645
+ const vertexBuffer = device.createVertexBuffer('position_f32', ordinals);
646
+ const indexBuffer = device.createIndexBuffer(indices);
647
+ this._heightLayout = device.createVertexLayout({
648
+ vertexBuffers: [
649
+ {
650
+ buffer: vertexBuffer
651
+ }
652
+ ],
653
+ indexBuffer
654
+ });
655
+ this._heightIndexCount = indices.length;
656
+ }
657
+ /** @internal */ _splatOne(device, ctx, water, index, map, sceneDepth) {
658
+ const material = water.material;
659
+ const waveGenerator = material.waveGenerator;
660
+ // Photons are laid out over this body's own footprint.
661
+ this._updateGridBounds(water);
662
+ if (!(this._gridFraction > 0)) {
663
+ return;
664
+ }
665
+ const photonGrid = this._resolvePhotonGrid(material.causticsPhotonResolution, map.width);
666
+ this._updatePhotonLayout(device, photonGrid);
667
+ const useSceneDepth = !!sceneDepth && material.causticsSceneDepth;
668
+ const splat = this._getSplatProgram(device, waveGenerator, useSceneDepth);
669
+ // A bind group per slot; see _getSplatBindGroup.
670
+ const bindGroup = this._getSplatBindGroup(device, splat, index);
671
+ if (useSceneDepth) {
672
+ bindGroup.setValue('causticSceneMatrix', sceneDepth.matrix);
673
+ bindGroup.setValue('causticSceneMatrixInv', sceneDepth.matrixInv);
674
+ this._sceneDepthParams.setXYZW(sceneDepth.layer, sceneDepth.size, 0, 0);
675
+ bindGroup.setValue('causticSceneParams', this._sceneDepthParams);
676
+ bindGroup.setTexture('causticSceneDepth', sceneDepth.texture, fetchSampler('clamp_nearest_nomip'));
677
+ }
678
+ bindGroup.setValue('causticFrameX', this._uniforms.frameX);
679
+ bindGroup.setValue('causticFrameY', this._uniforms.frameY);
680
+ // The slice centre is shared, but the water level in w is this body's.
681
+ this._splatCenter.setXYZW(this._uniforms.center.x, this._uniforms.center.y, this._uniforms.center.z, water.worldMatrix.m13);
682
+ bindGroup.setValue('causticCenter', this._splatCenter);
683
+ bindGroup.setValue('causticLightDir', this._uniforms.lightDir);
684
+ bindGroup.setValue('causticRegion', material.region);
685
+ bindGroup.setValue('causticGridBounds', this._gridBounds);
686
+ // Scale the deposit by covered texels per photon so a flat surface
687
+ // integrates to exactly 1.
688
+ const photonWeight = this._gridFraction * map.width * map.height / (photonGrid * photonGrid);
689
+ this._splatParams.setXYZW(this._warp, material.causticsDepth, WATER_ETA, photonWeight);
690
+ bindGroup.setValue('causticSplatParams', this._splatParams);
691
+ this._photonParams.setXYZW(photonGrid, 1 / photonGrid, 0, 0);
692
+ bindGroup.setValue('causticPhotonParams', this._photonParams);
693
+ ShaderHelper.setStandaloneCameraTime(bindGroup, ctx);
694
+ waveGenerator.applyWaterBindGroup(bindGroup);
695
+ device.setProgram(splat.program);
696
+ device.setBindGroup(0, bindGroup);
697
+ device.setVertexLayout(this._photonLayout);
698
+ device.draw('point-list', 0, this._photonCount);
699
+ }
700
+ /**
701
+ * Blur and temporal resolve, run once over the finished map. Settings come
702
+ * from the largest body's material.
703
+ * @internal
704
+ */ _postProcess(device, material, map, scratch, history, ctx, createFramebuffer) {
705
+ // Ping-pong between the two targets. The count is rounded up to an even
706
+ // number by the material so the last pass always lands back in `map`.
707
+ const blurPasses = Math.max(0, Math.min(material.causticsBlurPasses, 4));
708
+ for(let i = 0; i < blurPasses; i++){
709
+ const src = i % 2 === 0 ? map : scratch;
710
+ const dst = i % 2 === 0 ? scratch : map;
711
+ this._blur(device, src, createFramebuffer(dst));
712
+ }
713
+ // The blur lands back in `map`, so `scratch` takes the resolve.
714
+ const strength = material.causticsTemporalStrength;
715
+ if (strength <= 0) {
716
+ // No history committed; keep the recorded slice matching the history slot.
717
+ return map;
718
+ }
719
+ const camera = ctx.camera;
720
+ const previous = this._prevSlices.get(camera);
721
+ // Without history the current map stands on its own and becomes the history.
722
+ const resolved = history && previous ? scratch : map;
723
+ if (resolved === scratch) {
724
+ this._resolve(device, map, history, previous, strength, createFramebuffer(scratch));
725
+ }
726
+ this._rememberSlice(camera);
727
+ return resolved;
728
+ }
729
+ /**
730
+ * Shadow cascade to read the scene depth from, or null when this backend or
731
+ * this light cannot supply one.
732
+ * @internal
733
+ */ _resolveSceneDepth(ctx, light) {
734
+ // WebGPU only: GLSL ES 3.0 forbids texelFetch on a shadow-sampler texture.
735
+ if (ctx.device.type !== 'webgpu') {
736
+ warnUnsupported('Water caustics on scene depth (WaterMaterial.causticsSceneDepth)', 'WebGPU', 'caustics are projected onto the flat plane at causticsDepth');
737
+ return null;
738
+ }
739
+ const params = ctx.shadowMapInfo?.get(light);
740
+ const shadowMap = params?.shadowMap;
741
+ // Only cascaded native-depth maps; others fall back to the plane.
742
+ if (!params || !shadowMap?.isTexture2DArray() || !shadowMap.isDepth()) {
743
+ return null;
744
+ }
745
+ // The smallest cascade whose split distance clears the slice's reach, else
746
+ // the last.
747
+ const count = Math.max(1, Math.min(params.numShadowCascades, 4));
748
+ const reach = Math.max(1, this._sliceReach);
749
+ let layer = count - 1;
750
+ for(let i = 0; i < count; i++){
751
+ if (params.cascadeDistances[i] >= reach) {
752
+ layer = i;
753
+ break;
754
+ }
755
+ }
756
+ // shadowMatrices holds each cascade's view-projection transposed.
757
+ this._sceneMatrixT.set(params.shadowMatrices.subarray(layer * 16, layer * 16 + 16));
758
+ Matrix4x4.transpose(this._sceneMatrixT, this._sceneMatrix);
759
+ Matrix4x4.invert(this._sceneMatrix, this._sceneMatrixInv);
760
+ return {
761
+ texture: shadowMap,
762
+ layer,
763
+ size: shadowMap.width,
764
+ matrix: this._sceneMatrix,
765
+ matrixInv: this._sceneMatrixInv
766
+ };
767
+ }
768
+ /**
769
+ * Photon grid edge length for this frame.
770
+ *
771
+ * Auto solves the grid for a fixed density in photons per covered map texel:
772
+ * the grid covers `gridFraction` of the map, so the target is reached at
773
+ * `grid = size * sqrt(target * gridFraction)`.
774
+ *
775
+ * @param requested - Material setting; 0 asks for the automatic size.
776
+ * @param mapSize - Edge length of the caustic map.
777
+ * @internal
778
+ */ _resolvePhotonGrid(requested, mapSize) {
779
+ if (requested > 0) {
780
+ return Math.max(8, Math.min(requested, 4096));
781
+ }
782
+ const grid = Math.round(mapSize * Math.sqrt(PHOTONS_PER_TEXEL * this._gridFraction));
783
+ return Math.max(8, Math.min(grid, 4096));
784
+ }
785
+ /**
786
+ * Records the slice the map just produced was built with, for the next frame's
787
+ * reprojection.
788
+ * @internal
789
+ */ _rememberSlice(camera) {
790
+ let slice = this._prevSlices.get(camera);
791
+ if (!slice) {
792
+ slice = {
793
+ frameX: new Vector4(),
794
+ frameY: new Vector4(),
795
+ center: new Vector4(),
796
+ warp: 0
797
+ };
798
+ this._prevSlices.set(camera, slice);
799
+ }
800
+ slice.frameX.set(this._uniforms.frameX);
801
+ slice.frameY.set(this._uniforms.frameY);
802
+ slice.center.set(this._uniforms.center);
803
+ slice.warp = this._warp;
804
+ }
805
+ /** @internal */ _resolve(device, current, history, previous, strength, dst) {
806
+ const program = this._getResolveProgram(device);
807
+ const bindGroup = this._resolveBindGroup;
808
+ device.pushDeviceStates();
809
+ device.setFramebuffer(dst);
810
+ device.setViewport(null);
811
+ device.setScissor(null);
812
+ device.setProgram(program);
813
+ bindGroup.setTexture('causticCurrent', current, fetchSampler('clamp_linear_nomip'));
814
+ bindGroup.setTexture('causticHistory', history, fetchSampler('clamp_linear_nomip'));
815
+ bindGroup.setValue('causticFrameX', this._uniforms.frameX);
816
+ bindGroup.setValue('causticFrameY', this._uniforms.frameY);
817
+ bindGroup.setValue('causticCenter', this._uniforms.center);
818
+ bindGroup.setValue('causticPrevFrameX', previous.frameX);
819
+ bindGroup.setValue('causticPrevFrameY', previous.frameY);
820
+ bindGroup.setValue('causticPrevCenter', previous.center);
821
+ this._resolveParams.setXYZW(strength, 1 / current.width, this._warp, previous.warp);
822
+ bindGroup.setValue('causticResolveParams', this._resolveParams);
823
+ device.setBindGroup(0, bindGroup);
824
+ drawFullscreenQuad(this._getResolveStates(device));
825
+ device.popDeviceStates();
826
+ }
827
+ /** @internal */ _getResolveProgram(device) {
828
+ if (!this._resolveProgram) {
829
+ this._resolveProgram = device.buildRenderProgram(createCausticResolveShader());
830
+ this._resolveProgram.name = '@Water_CausticResolve';
831
+ this._resolveBindGroup = device.createBindGroup(this._resolveProgram.bindGroupLayouts[0]);
832
+ }
833
+ return this._resolveProgram;
834
+ }
835
+ /** @internal */ _getResolveStates(device) {
836
+ if (!this._resolveStates) {
837
+ this._resolveStates = device.createRenderStateSet();
838
+ this._resolveStates.useRasterizerState().setCullMode('none');
839
+ this._resolveStates.useDepthState().enableTest(false).enableWrite(false);
840
+ }
841
+ return this._resolveStates;
842
+ }
843
+ /** Releases the GPU objects owned by this renderer. */ dispose() {
844
+ for (const info of this._splatPrograms.values()){
845
+ for (const bindGroup of info.bindGroups){
846
+ bindGroup?.dispose();
847
+ }
848
+ info.program.dispose();
849
+ }
850
+ this._splatPrograms.clear();
851
+ for (const info of this._heightPrograms.values()){
852
+ for (const bindGroup of info.bindGroups){
853
+ bindGroup?.dispose();
854
+ }
855
+ info.program.dispose();
856
+ }
857
+ this._heightPrograms.clear();
858
+ this._heightStates.length = 0;
859
+ // The layout does not own its buffers.
860
+ this._heightLayout?.dispose();
861
+ this._heightLayout = null;
862
+ this._heightIndexCount = 0;
863
+ this._blurBindGroup?.dispose();
864
+ this._blurBindGroup = null;
865
+ this._resolveProgram?.dispose();
866
+ this._resolveProgram = null;
867
+ this._resolveBindGroup?.dispose();
868
+ this._resolveBindGroup = null;
869
+ this._resolveStates = null;
870
+ this._blurProgram?.dispose();
871
+ this._blurProgram = null;
872
+ this._photonLayout?.dispose();
873
+ this._photonLayout = null;
874
+ this._photonBuffer?.dispose();
875
+ this._photonBuffer = null;
876
+ this._photonCapacity = 0;
877
+ this._photonCount = 0;
878
+ this._splatStates = null;
879
+ this._blurStates = null;
880
+ }
881
+ /** @internal */ _blur(device, src, dst) {
882
+ const program = this._getBlurProgram(device);
883
+ device.pushDeviceStates();
884
+ device.setFramebuffer(dst);
885
+ device.setProgram(program);
886
+ this._blurBindGroup.setTexture('causticSrc', src, fetchSampler('clamp_linear_nomip'));
887
+ this._blurTexelSize.setXYZW(1 / src.width, 1 / src.height, 0, 0);
888
+ this._blurBindGroup.setValue('causticTexelSize', this._blurTexelSize);
889
+ device.setBindGroup(0, this._blurBindGroup);
890
+ drawFullscreenQuad(this._getBlurStates(device));
891
+ device.popDeviceStates();
892
+ }
893
+ /**
894
+ * Recomputes the orthographic light-space slice the map covers.
895
+ * @internal
896
+ */ _updateUniforms(ctx, waters, light, mapSize) {
897
+ // Slice-wide settings come from the first (largest) body.
898
+ const material = waters[0].material;
899
+ const dir = this._tmpDir;
900
+ dir.set(light.directionAndCutoff.xyz());
901
+ dir.inplaceNormalize();
902
+ // Two axes perpendicular to the light, seeded by a non-parallel world axis.
903
+ const seed = Math.abs(dir.y) > 0.99 ? Vector3.axisPZ() : Vector3.axisPY();
904
+ Vector3.cross(seed, dir, this._tmpRight);
905
+ this._tmpRight.inplaceNormalize();
906
+ Vector3.cross(dir, this._tmpRight, this._tmpUp);
907
+ this._tmpUp.inplaceNormalize();
908
+ const right = this._tmpRight;
909
+ const up = this._tmpUp;
910
+ const waterLevel = waters[0].worldMatrix.m13;
911
+ const range = Math.max(1, material.causticsRange);
912
+ const camera = ctx.camera.getWorldPosition();
913
+ const center = this._tmpCenter.setXYZ(camera.x, waterLevel, camera.z);
914
+ const cameraRight = Vector3.dot(center, right);
915
+ const cameraUp = Vector3.dot(center, up);
916
+ // The component along the light direction is arbitrary: both the projection
917
+ // into the map and the photon sweep are invariant along it.
918
+ const alongDir = Vector3.dot(center, dir);
919
+ // Light-space AABB of all bodies' regions, from their four corners.
920
+ let regionMinR = Infinity;
921
+ let regionMaxR = -Infinity;
922
+ let regionMinU = Infinity;
923
+ let regionMaxU = -Infinity;
924
+ for (const body of waters){
925
+ const region = body.material.region;
926
+ // Each body's own height, since the projection is taken on its surface.
927
+ const level = body.worldMatrix.m13;
928
+ for (const [x, z] of [
929
+ [
930
+ region.x,
931
+ region.y
932
+ ],
933
+ [
934
+ region.z,
935
+ region.y
936
+ ],
937
+ [
938
+ region.x,
939
+ region.w
940
+ ],
941
+ [
942
+ region.z,
943
+ region.w
944
+ ]
945
+ ]){
946
+ const r = x * right.x + level * right.y + z * right.z;
947
+ const v = x * up.x + level * up.y + z * up.z;
948
+ regionMinR = Math.min(regionMinR, r);
949
+ regionMaxR = Math.max(regionMaxR, r);
950
+ regionMinU = Math.min(regionMinU, v);
951
+ regionMaxU = Math.max(regionMaxU, v);
952
+ }
953
+ }
954
+ // Fit the slice to the water; `causticsRange` caps how far from the camera
955
+ // the map reaches.
956
+ let loR = Math.max(regionMinR, cameraRight - range);
957
+ let hiR = Math.min(regionMaxR, cameraRight + range);
958
+ let loU = Math.max(regionMinU, cameraUp - range);
959
+ let hiU = Math.min(regionMaxU, cameraUp + range);
960
+ if (hiR <= loR || hiU <= loU) {
961
+ // No water within range; keep the slice finite so the grid bounds come
962
+ // out empty rather than inverted.
963
+ loR = cameraRight - range;
964
+ hiR = cameraRight + range;
965
+ loU = cameraUp - range;
966
+ hiU = cameraUp + range;
967
+ }
968
+ let halfR = Math.max(MIN_SLICE_EXTENT, (hiR - loR) * 0.5);
969
+ let halfU = Math.max(MIN_SLICE_EXTENT, (hiU - loU) * 0.5);
970
+ // Cap the aspect ratio to bound directional smearing on the square map.
971
+ halfR = Math.max(halfR, halfU / MAX_SLICE_ASPECT);
972
+ halfU = Math.max(halfU, halfR / MAX_SLICE_ASPECT);
973
+ // Quantise so the texel size holds still while the camera moves, which the
974
+ // centre snapping and the temporal history rely on.
975
+ halfR = Math.min(range, quantiseSliceExtent(halfR, range));
976
+ halfU = Math.min(range, quantiseSliceExtent(halfU, range));
977
+ // Warp only when the range, not the water, bounds the map.
978
+ const rangeLimited = regionMinR < cameraRight - range || regionMaxR > cameraRight + range || regionMinU < cameraUp - range || regionMaxU > cameraUp + range;
979
+ const warp = rangeLimited ? material.causticsWarp : 0;
980
+ // Snap the centre so the map does not crawl: whole texels when linear, a
981
+ // coarse ladder under a warp.
982
+ const snapR = warp > 0 ? halfR / WARPED_CENTER_SNAP_DIVISIONS : 2 * halfR / mapSize;
983
+ const snapU = warp > 0 ? halfU / WARPED_CENTER_SNAP_DIVISIONS : 2 * halfU / mapSize;
984
+ const centerR = Math.round((loR + hiR) * 0.5 / snapR) * snapR;
985
+ const centerU = Math.round((loU + hiU) * 0.5 / snapU) * snapU;
986
+ center.setXYZ(right.x * centerR + up.x * centerU + dir.x * alongDir, right.y * centerR + up.y * centerU + dir.y * alongDir, right.z * centerR + up.z * centerU + dir.z * alongDir);
987
+ const u = this._uniforms;
988
+ u.frameX.setXYZW(right.x, right.y, right.z, 1 / halfR);
989
+ u.frameY.setXYZW(up.x, up.y, up.z, 1 / halfU);
990
+ u.center.setXYZW(center.x, center.y, center.z, waterLevel);
991
+ u.lightDir.setXYZW(dir.x, dir.y, dir.z, 1 / Math.max(-dir.y, MIN_SUN_ELEVATION));
992
+ // Edge fade start, in units of the map half-extent. Only a border set by the
993
+ // range is faded; a border fitted to the water coincides with the region.
994
+ const minHalf = Math.min(halfR, halfU);
995
+ const fadeDistance = !rangeLimited ? 0 : material.causticsFadeDistance > 0 ? material.causticsFadeDistance : Math.max(AUTO_FADE_MIN_DISTANCE, AUTO_FADE_FRACTION * minHalf);
996
+ const fadeFraction = Math.max(MIN_FADE_FRACTION, Math.min(MAX_FADE_FRACTION, fadeDistance / minHalf));
997
+ u.params.setXYZW(material.causticsIntensity, material.causticsDepth, material.causticsDefocus, 1 - fadeFraction);
998
+ u.extinction.setXYZW(material.causticsMinDepth, 0, 0, warp);
999
+ this._warp = warp;
1000
+ // Retained for the per-body grid fit.
1001
+ this._sliceRight = right;
1002
+ this._sliceUp = up;
1003
+ this._sliceCenterR = centerR;
1004
+ this._sliceCenterU = centerU;
1005
+ this._sliceHalfR = halfR;
1006
+ this._sliceHalfU = halfU;
1007
+ // Slice reach from the camera, for shadow cascade selection.
1008
+ this._sliceReach = Math.hypot(halfR, halfU);
1009
+ // Per-body table the receiver searches: footprint, medium and surface height.
1010
+ u.waterCount = Math.min(waters.length, MAX_CAUSTIC_WATERS);
1011
+ for(let i = 0; i < u.waterCount; i++){
1012
+ const body = waters[i];
1013
+ const region = body.material.region;
1014
+ u.waterRegions[i * 4 + 0] = region.x;
1015
+ u.waterRegions[i * 4 + 1] = region.y;
1016
+ u.waterRegions[i * 4 + 2] = region.z;
1017
+ u.waterRegions[i * 4 + 3] = region.w;
1018
+ const ext = body.material.extinction;
1019
+ u.waterMedia[i * 4 + 0] = ext.x;
1020
+ u.waterMedia[i * 4 + 1] = ext.y;
1021
+ u.waterMedia[i * 4 + 2] = ext.z;
1022
+ u.waterMedia[i * 4 + 3] = body.worldMatrix.m13;
1023
+ }
1024
+ }
1025
+ /**
1026
+ * Map-NDC bounds of one body's footprint, so its photon grid covers just that.
1027
+ *
1028
+ * The body's light-space bounds carry over directly since the projection is
1029
+ * invariant along the light. Clamped to the map, then warped; the warp is
1030
+ * monotone and separable, so warping the interval's ends is exact.
1031
+ * @internal
1032
+ */ _updateGridBounds(water) {
1033
+ const region = water.material.region;
1034
+ const level = water.worldMatrix.m13;
1035
+ const right = this._sliceRight;
1036
+ const up = this._sliceUp;
1037
+ let minR = Infinity;
1038
+ let maxR = -Infinity;
1039
+ let minU = Infinity;
1040
+ let maxU = -Infinity;
1041
+ for (const [x, z] of [
1042
+ [
1043
+ region.x,
1044
+ region.y
1045
+ ],
1046
+ [
1047
+ region.z,
1048
+ region.y
1049
+ ],
1050
+ [
1051
+ region.x,
1052
+ region.w
1053
+ ],
1054
+ [
1055
+ region.z,
1056
+ region.w
1057
+ ]
1058
+ ]){
1059
+ const r = x * right.x + level * right.y + z * right.z;
1060
+ const v = x * up.x + level * up.y + z * up.z;
1061
+ minR = Math.min(minR, r);
1062
+ maxR = Math.max(maxR, r);
1063
+ minU = Math.min(minU, v);
1064
+ maxU = Math.max(maxU, v);
1065
+ }
1066
+ const warp = this._warp;
1067
+ // Clamped to the map plus a margin (see GRID_BORDER_MARGIN), but never past
1068
+ // the water itself.
1069
+ const m = 1 + GRID_BORDER_MARGIN;
1070
+ const lo = (v)=>Math.max(-1.15, Math.min(m, v));
1071
+ const minX = warpCausticNDC(lo((minR - this._sliceCenterR) / this._sliceHalfR), warp);
1072
+ const maxX = warpCausticNDC(lo((maxR - this._sliceCenterR) / this._sliceHalfR), warp);
1073
+ const minY = warpCausticNDC(lo((minU - this._sliceCenterU) / this._sliceHalfU), warp);
1074
+ const maxY = warpCausticNDC(lo((maxU - this._sliceCenterU) / this._sliceHalfU), warp);
1075
+ this._gridBounds.setXYZW(minX, minY, maxX, maxY);
1076
+ // Share of the map the grid covers; scales the photon weight to keep calm
1077
+ // water at 1.0.
1078
+ this._gridFraction = Math.max(0, (maxX - minX) * (maxY - minY) / 4);
1079
+ }
1080
+ /** @internal */ _updatePhotonLayout(device, gridSize) {
1081
+ this._photonCount = gridSize * gridSize;
1082
+ if (this._photonCapacity >= this._photonCount) {
1083
+ return;
1084
+ }
1085
+ // Grow only. The buffer holds each photon's ordinal.
1086
+ const capacity = Math.max(this._photonCount, this._photonCapacity * 2);
1087
+ const indices = new Float32Array(capacity);
1088
+ for(let i = 0; i < capacity; i++){
1089
+ indices[i] = i;
1090
+ }
1091
+ // WebGL2 refuses to draw without a bound vertex layout, so the ordinal is
1092
+ // materialised as an attribute rather than read from the vertex index.
1093
+ const buffer = device.createVertexBuffer('position_f32', indices);
1094
+ this._photonLayout?.dispose();
1095
+ this._photonBuffer?.dispose();
1096
+ this._photonBuffer = buffer;
1097
+ this._photonLayout = device.createVertexLayout({
1098
+ vertexBuffers: [
1099
+ {
1100
+ buffer
1101
+ }
1102
+ ]
1103
+ });
1104
+ this._photonCapacity = capacity;
1105
+ }
1106
+ /** @internal */ _getSplatStates(device) {
1107
+ if (!this._splatStates) {
1108
+ this._splatStates = device.createRenderStateSet();
1109
+ this._splatStates.useRasterizerState().setCullMode('none');
1110
+ this._splatStates.useDepthState().enableTest(false).enableWrite(false);
1111
+ // Photon accumulation: every splat adds to whatever is already there.
1112
+ this._splatStates.useBlendingState().enable(true).setBlendFunc('one', 'one').setBlendEquation('add', 'add');
1113
+ }
1114
+ return this._splatStates;
1115
+ }
1116
+ /** @internal */ _getHeightStates(device, slot) {
1117
+ let states = this._heightStates[slot];
1118
+ if (!states) {
1119
+ states = device.createRenderStateSet();
1120
+ states.useRasterizerState().setCullMode('none');
1121
+ states.useDepthState().enableTest(false).enableWrite(false);
1122
+ // Plain overwrite into this slot's channel only.
1123
+ states.useColorState().setColorMask(slot === 0, slot === 1, slot === 2, slot === 3);
1124
+ this._heightStates[slot] = states;
1125
+ }
1126
+ return states;
1127
+ }
1128
+ /** @internal */ _getHeightProgram(device, waveGenerator) {
1129
+ const key = waveGenerator.getHash();
1130
+ let info = this._heightPrograms.get(key);
1131
+ if (info) {
1132
+ return info;
1133
+ }
1134
+ const program = device.buildRenderProgram(createCausticHeightShader(waveGenerator));
1135
+ program.name = '@Water_CausticHeight';
1136
+ info = {
1137
+ program,
1138
+ bindGroups: []
1139
+ };
1140
+ this._heightPrograms.set(key, info);
1141
+ return info;
1142
+ }
1143
+ /** @internal */ _getBlurStates(device) {
1144
+ if (!this._blurStates) {
1145
+ this._blurStates = device.createRenderStateSet();
1146
+ this._blurStates.useRasterizerState().setCullMode('none');
1147
+ this._blurStates.useDepthState().enableTest(false).enableWrite(false);
1148
+ }
1149
+ return this._blurStates;
1150
+ }
1151
+ /** @internal */ _getSplatProgram(device, waveGenerator, sceneDepth) {
1152
+ const key = `${waveGenerator.getHash()}:${sceneDepth ? 1 : 0}`;
1153
+ let info = this._splatPrograms.get(key);
1154
+ if (info) {
1155
+ return info;
1156
+ }
1157
+ const program = device.buildRenderProgram(createCausticSplatShader(waveGenerator, sceneDepth));
1158
+ program.name = `@Water_CausticSplat${sceneDepth ? '_SceneDepth' : ''}`;
1159
+ info = {
1160
+ program,
1161
+ bindGroups: []
1162
+ };
1163
+ this._splatPrograms.set(key, info);
1164
+ return info;
1165
+ }
1166
+ /**
1167
+ * The bind group for one water slot of a splat program.
1168
+ * @internal
1169
+ */ _getSplatBindGroup(device, info, slot) {
1170
+ let bindGroup = info.bindGroups[slot];
1171
+ if (!bindGroup) {
1172
+ bindGroup = device.createBindGroup(info.program.bindGroupLayouts[0]);
1173
+ info.bindGroups[slot] = bindGroup;
1174
+ }
1175
+ return bindGroup;
1176
+ }
1177
+ /**
1178
+ * Whether any body asks for the scene-depth intersection, which decides
1179
+ * whether the shadow cascade needs resolving at all.
1180
+ * @internal
1181
+ */ _anySceneDepth(waters) {
1182
+ return waters.some((w)=>w.material.causticsSceneDepth);
1183
+ }
1184
+ /** @internal */ _getBlurProgram(device) {
1185
+ if (!this._blurProgram) {
1186
+ this._blurProgram = device.buildRenderProgram(createCausticBlurShader());
1187
+ this._blurProgram.name = '@Water_CausticBlur';
1188
+ this._blurBindGroup = device.createBindGroup(this._blurProgram.bindGroupLayouts[0]);
1189
+ }
1190
+ return this._blurProgram;
1191
+ }
1192
+ }
1193
+
1194
+ export { WaterCausticsRenderer, createCausticBlurShader, createCausticHeightShader, createCausticResolveShader, createCausticSplatShader };
1195
+ //# sourceMappingURL=water_caustics.js.map