@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
@@ -1,22 +1,51 @@
1
1
  import { Vector3, Vector4, Vector2, REVERSE_Z, DEPTH_FARTHEST, DEPTH_REDUCE_CLOSER, DEPTH_REDUCE_FARTHER } from '@zephyr3d/base';
2
- import { RENDER_PASS_TYPE_DEPTH, RENDER_PASS_TYPE_SHADOWMAP, RENDER_PASS_TYPE_OBJECT_COLOR, RENDER_PASS_TYPE_LIGHT, MaterialVaryingFlags, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, MORPH_WEIGHTS_VECTOR_COUNT, MORPH_TARGET_POSITION, MORPH_TARGET_NORMAL, MORPH_TARGET_TANGENT, MORPH_ATTRIBUTE_VECTOR_COUNT, LIGHT_TYPE_DIRECTIONAL } from '../../values.js';
2
+ import { RENDER_PASS_TYPE_DEPTH, RENDER_PASS_TYPE_SHADOWMAP, RENDER_PASS_TYPE_OBJECT_COLOR, RENDER_PASS_TYPE_VOXELIZE, RENDER_PASS_TYPE_LIGHT, MaterialVaryingFlags, MORPH_WEIGHTS_VECTOR_COUNT, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, MORPH_TARGET_POSITION, MORPH_TARGET_NORMAL, MORPH_TARGET_TANGENT, MORPH_ATTRIBUTE_VECTOR_COUNT, LIGHT_TYPE_DIRECTIONAL } from '../../values.js';
3
3
  import { ProgramBuilder } from '@zephyr3d/device';
4
- import { decodeNormalizedFloatFromRGBA, linearToGamma } from '../../shaders/misc.js';
4
+ import { linearToGamma } from '../../shaders/misc.js';
5
+ import { declareVoxelizeUniforms } from '../../shaders/gi.js';
5
6
  import { getSamplerOptions, fetchSampler } from '../../utility/misc.js';
6
- import { PHYSICAL_BAKE_EXPOSURE } from '../../utility/physical.js';
7
+ import { getGICacheExposure, PHYSICAL_BAKE_EXPOSURE } from '../../utility/physical.js';
7
8
  import { getDevice } from '../../app/api.js';
8
9
  import { getDefaultAtmosphereParams, getAtmosphereParamsStruct } from '../../shaders/atmosphere.js';
9
10
  import { getDefaultHeightFogParams, getHeightFogParamsStruct, calculateFog } from '../../shaders/fog.js';
10
11
  import { getShadowReceiverNoL, getShadowReceiverBiasFactor } from '../../shadow/receiver_bias.js';
12
+ import { declareGlobalSDFUniforms, declareDynamicSDFUniforms, GLOBAL_SDF_TEXTURE, sampleSceneSDF } from '../../shaders/sdf.js';
11
13
 
14
+ /** Clipmap levels of the scene field declared in a program, by program builder. */ const sceneSDFLevels = new WeakMap();
12
15
  const UNIFORM_NAME_LIGHT_BUFFER = 'Z_UniformLightBuffer';
13
16
  const UNIFORM_NAME_LIGHT_INDEX_TEXTURE = 'Z_UniformLightIndexTex';
17
+ const UNIFORM_NAME_CLUSTER_GRID = 'Z_ClusterGrid';
18
+ const UNIFORM_NAME_CLUSTER_LIGHT_LIST = 'Z_ClusterLightList';
14
19
  const UNIFORM_NAME_BAKED_SKY_MAP = 'Z_UniformBakedSky';
15
20
  const UNIFORM_NAME_AERIALPERSPECTIVE_LUT = 'Z_UniformAerialPerspectiveLUT';
16
21
  const UNIFORM_NAME_SKYDISTANTLIGHT_LUT = 'Z_UniformSkyDistantLightLUT';
22
+ const UNIFORM_NAME_FOG_SKYLIGHT_CUBEMAP = 'Z_UniformFogSkyLightCubemap';
17
23
  const UNIFORM_NAME_SHADOW_MAP = 'Z_UniformShadowMap';
18
24
  const UNIFORM_NAME_SHADOW_MASK = 'Z_UniformShadowMask';
19
25
  const UNIFORM_NAME_SHADOW_MASK_MODE = 'Z_UniformShadowMaskMode';
26
+ const UNIFORM_NAME_TRANSMISSION_THICKNESS = 'Z_UniformTransmissionThickness';
27
+ const UNIFORM_NAME_CAUSTIC_MAP = 'Z_UniformCausticMap';
28
+ const UNIFORM_NAME_CAUSTIC_HEIGHT_MAP = 'Z_UniformCausticHeightMap';
29
+ /**
30
+ * Offset added to a surface height before it is stored in the caustic height
31
+ * map, so that a cleared texel - no surface on that ray - is distinguishable
32
+ * from a surface at the rest level. A ray with no water on it must read as
33
+ * dry, not as submerged under a flat surface: on the down-sun side of a
34
+ * footprint edge, a receiver below the rest level but above a trough would
35
+ * otherwise be lit through water that is not there. Eight keeps troughs down
36
+ * to seven metres positive and leaves half floats millimetre precision.
37
+ * @internal
38
+ */ const CAUSTIC_HEIGHT_BIAS = 8;
39
+ /**
40
+ * Water bodies that can cast caustics into one map.
41
+ *
42
+ * They share the map and the slice, so this is not a limit on how much water a
43
+ * scene may hold - only on how many separate surfaces the receiver will test
44
+ * itself against, which it does per fragment. Four covers a lake with a couple
45
+ * of pools beside it; beyond that the renderer keeps the largest four, which
46
+ * are the ones a viewer is most likely to be standing over.
47
+ */ const MAX_CAUSTIC_WATERS = 4;
48
+ const UNIFORM_NAME_CAUSTIC_PARAMS = 'Z_UniformCausticParams';
20
49
  const UNIFORM_NAME_LINEAR_DEPTH_MAP = 'Z_UniformLinearDepth';
21
50
  const UNIFORM_NAME_LINEAR_DEPTH_MAP_SIZE = 'Z_UniformLinearDepthSize';
22
51
  const UNIFORM_NAME_SCENE_COLOR_MAP = 'Z_UniformSceneColor';
@@ -33,6 +62,7 @@ const UNIFORM_NAME_INSTANCE_DATA_OFFSET = 'Z_UniformInstanceDataOffset';
33
62
  const UNIFORM_NAME_BONE_MATRICES = 'Z_UniformBoneMatrices';
34
63
  const UNIFORM_NAME_BONE_TEXTURE_SIZE = 'Z_UniformBoneTexSize';
35
64
  const UNIFORM_NAME_BONE_INV_BIND_MATRIX = 'Z_UniformBoneInvBindMatrix';
65
+ const UNIFORM_NAME_SKIN_PALETTE_BASE = 'Z_UniformSkinPaletteBase';
36
66
  const UNIFORM_NAME_SKIN_INFLUENCE_DATA = 'Z_UniformSkinInfluenceData';
37
67
  const UNIFORM_NAME_SKIN_INFLUENCE_INFO = 'Z_UniformSkinInfluenceInfo';
38
68
  const UNIFORM_NAME_MORPH_DATA = 'Z_UniformMorphData';
@@ -46,6 +76,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
46
76
  static MATERIAL_INSTANCE_DATA_OFFSET = 9;
47
77
  /** @internal */ static defaultSunDir = Vector3.one().inplaceNormalize();
48
78
  /** @internal 1x1x1 fallback bound to the shadow-mask uniform when no mask exists this frame. */ static _dummyShadowMask = null;
79
+ static _dummyTransmissionThickness = null;
49
80
  /** @internal */ static SKIN_MATRIX_NAME = 'Z_SkinMatrix';
50
81
  static SKIN_PREV_MATRIX_NAME = 'Z_PrevSkinMatrix';
51
82
  static SKIN_BONE_OFFSET = 'Z_boneOffset';
@@ -105,6 +136,9 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
105
136
  static getBoneInvBindMatrixUniformName() {
106
137
  return UNIFORM_NAME_BONE_INV_BIND_MATRIX;
107
138
  }
139
+ static getSkinPaletteBaseUniformName() {
140
+ return UNIFORM_NAME_SKIN_PALETTE_BASE;
141
+ }
108
142
  static getSkinInfluenceDataUniformName() {
109
143
  return UNIFORM_NAME_SKIN_INFLUENCE_DATA;
110
144
  }
@@ -209,8 +243,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
209
243
  if (casterParams) {
210
244
  casterParams.impl?.declareCasterUniforms(scope, casterParams);
211
245
  }
212
- } else if (ctx.renderPass.type === RENDER_PASS_TYPE_DEPTH || ctx.renderPass.type === RENDER_PASS_TYPE_OBJECT_COLOR) {
246
+ } else if (ctx.renderPass.type === RENDER_PASS_TYPE_DEPTH || ctx.renderPass.type === RENDER_PASS_TYPE_OBJECT_COLOR || ctx.renderPass.type === RENDER_PASS_TYPE_VOXELIZE) {
213
247
  scope.camera = cameraStruct().uniform(0);
248
+ // Write-only storage textures are fragment-only in WebGPU
249
+ if (ctx.renderPass.type === RENDER_PASS_TYPE_VOXELIZE && pb.shaderKind === 'fragment') {
250
+ declareVoxelizeUniforms(scope, 0);
251
+ }
214
252
  } else if (ctx.renderPass.type === RENDER_PASS_TYPE_LIGHT) {
215
253
  const useClusteredLighting = !ctx.currentShadowLight;
216
254
  if (ctx.materialFlags & MaterialVaryingFlags.APPLY_FOG) {
@@ -226,6 +264,8 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
226
264
  // Static samplers: LUTs are rgba16f without mipmaps
227
265
  scope[UNIFORM_NAME_AERIALPERSPECTIVE_LUT] = pb.tex2D().uniform(0).withSampler(getSamplerOptions('clamp_linear_nomip'));
228
266
  scope[UNIFORM_NAME_SKYDISTANTLIGHT_LUT] = pb.tex2D().uniform(0).withSampler(getSamplerOptions('clamp_linear_nomip'));
267
+ // Mipmapped radiance map, sampled at a roughness-selected LOD
268
+ scope[UNIFORM_NAME_FOG_SKYLIGHT_CUBEMAP] = pb.texCube().uniform(0).withSampler(getSamplerOptions('clamp_linear'));
229
269
  }
230
270
  const lightStruct = ctx.currentShadowLight ? pb.defineStruct([
231
271
  pb.vec3('sunDir'),
@@ -249,7 +289,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
249
289
  pb.float('envLightSpecularStrength'),
250
290
  pb.vec4('clusterParams'),
251
291
  pb.ivec4('countParams'),
292
+ // Buffer indices of the lights shaded outside the clusters, zero-terminated.
293
+ pb.ivec4('globalLights'),
252
294
  pb.ivec2('lightIndexTexSize'),
295
+ // Nonzero when the clusters were sliced for an orthographic camera
296
+ // (linear depth slices instead of exponential).
297
+ pb.int('clusterOrtho'),
253
298
  // Number of shadow-casting lights at the head of the clustered light
254
299
  // buffer (indices 1..N). Only present on the screen-space shadow mask
255
300
  // path (part of the global bind group hash), so the declared and bound
@@ -261,9 +306,17 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
261
306
  scope.camera = cameraStruct().uniform(0);
262
307
  scope.light = lightStruct().uniform(0);
263
308
  if (useClusteredLighting) {
264
- scope[UNIFORM_NAME_LIGHT_BUFFER] = pb.vec4[(this.getMaxClusterLights() + 1) * 4]().uniformBuffer(0);
265
- // Non-WebGL1 devices fetch the light index texture with textureLoad
266
- scope[UNIFORM_NAME_LIGHT_INDEX_TEXTURE] = (pb.getDevice().type === 'webgl' ? pb.tex2D() : pb.utex2D().noSampler()).uniform(0);
309
+ if (this.usesClusterLightLists()) {
310
+ // Storage buffers sized to the frame's lights: no cap on the lights in total or
311
+ // per cluster. See ClusteredLight for the layout of the grid and the lists.
312
+ scope[UNIFORM_NAME_LIGHT_BUFFER] = pb.vec4[0]().storageBufferReadonly(0);
313
+ scope[UNIFORM_NAME_CLUSTER_GRID] = pb.uint[0]().storageBufferReadonly(0);
314
+ scope[UNIFORM_NAME_CLUSTER_LIGHT_LIST] = pb.uint[0]().storageBufferReadonly(0);
315
+ } else {
316
+ scope[UNIFORM_NAME_LIGHT_BUFFER] = pb.vec4[(this.getMaxClusterLights() + 1) * 4]().uniformBuffer(0);
317
+ // The light index texture is fetched with textureLoad
318
+ scope[UNIFORM_NAME_LIGHT_INDEX_TEXTURE] = pb.utex2D().noSampler().uniform(0);
319
+ }
267
320
  // Screen-space shadow mask array (rgba8unorm, 4 shadow lights per layer).
268
321
  // Sampled by clustered lights whose buffer index is <= numShadowLights.
269
322
  // Presence is keyed into the global bind group hash, so declared and bound
@@ -276,6 +329,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
276
329
  // e.g. OIT hair, where the opaque-depth mask would be incorrect).
277
330
  scope[UNIFORM_NAME_SHADOW_MASK_MODE] = pb.int().uniform(0);
278
331
  }
332
+ // Light-space thickness for subsurface transmission, packed exactly like
333
+ // the shadow mask. Presence is keyed into the bind group hash through
334
+ // ctx.transmissionThickness, so declared and bound layouts always agree.
335
+ if (ctx.transmissionThickness) {
336
+ scope[UNIFORM_NAME_TRANSMISSION_THICKNESS] = pb.tex2DArray().uniform(0);
337
+ }
279
338
  }
280
339
  // Baked sky cube: rgba16f without mipmaps
281
340
  scope[UNIFORM_NAME_BAKED_SKY_MAP] = pb.texCube().uniform(0).withSampler(getSamplerOptions('clamp_linear_nomip'));
@@ -294,6 +353,9 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
294
353
  }
295
354
  scope[UNIFORM_NAME_SHADOW_MAP] = tex.uniform(0);
296
355
  }
356
+ if (this.usesWaterCaustics(ctx)) {
357
+ this.declareWaterCausticUniforms(pb);
358
+ }
297
359
  if (ctx.drawEnvLight) {
298
360
  ctx.env.light.envLight.initShaderBindings(pb, ctx);
299
361
  }
@@ -304,8 +366,18 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
304
366
  scope[UNIFORM_NAME_LINEAR_DEPTH_MAP_SIZE] = pb.vec2().uniform(0);
305
367
  }
306
368
  if (ctx.sceneColorTexture) {
307
- // Scene color copy: single mip level
308
- scope[UNIFORM_NAME_SCENE_COLOR_MAP] = pb.tex2D().uniform(0).withSampler(getSamplerOptions('clamp_linear_nomip'));
369
+ // Mip-filtered, because the refraction background is read at a LOD and
370
+ // not only at mip 0: the water picks one from how deep and how turbid
371
+ // the column it is looking through is. A `nomip` sampler pins mip 0
372
+ // whatever LOD the shader passes, so this and the copy's level count
373
+ // have to change together - and each backend only honours one of them.
374
+ // WebGPU binds the sampler declared here as a static binding the runtime
375
+ // cannot overwrite; WebGL falls back to the texture's own sampler, whose
376
+ // mip filter follows its level count.
377
+ //
378
+ // Every other reader of this texture passes an explicit LOD 0 (SSR hits,
379
+ // the sky blend), so none of them changes behaviour.
380
+ scope[UNIFORM_NAME_SCENE_COLOR_MAP] = pb.tex2D().uniform(0).withSampler(getSamplerOptions('clamp_linear'));
309
381
  scope[UNIFORM_NAME_SCENE_COLOR_MAP_SIZE] = pb.vec2().uniform(0);
310
382
  }
311
383
  if (ctx.HiZTexture) {
@@ -343,75 +415,13 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
343
415
  if (!this.hasSkinning(scope)) {
344
416
  return null;
345
417
  }
346
- const pb = scope.$builder;
347
- const isWebGL = pb.getDevice().type === 'webgl';
348
- const supportsTextureLoad = !isWebGL;
349
- const funcNameGetBoneMatrixFromTexture = 'Z_getBoneMatrixFromTexture';
350
- pb.func(funcNameGetBoneMatrixFromTexture, [
351
- pb.int('boneIndex')
352
- ], function() {
353
- const boneTexture = this[UNIFORM_NAME_BONE_MATRICES];
354
- this.$l.uvOffsets = pb.textureSampleLevel(boneTexture, pb.div(pb.vec2(0.5), this[UNIFORM_NAME_BONE_TEXTURE_SIZE]), 0);
355
- this.$l.currentOffset = pb.int(this.uvOffsets.x);
356
- this.$l.w = this[UNIFORM_NAME_BONE_TEXTURE_SIZE].x;
357
- this.$l.pixelIndex = pb.float(pb.mul(pb.add(this.boneIndex, this.currentOffset), 4));
358
- this.$l.xIndex = pb.mod(this.pixelIndex, this.w);
359
- this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.w));
360
- this.$l.u1 = pb.div(pb.add(this.xIndex, 0.5), this.w);
361
- this.$l.u2 = pb.div(pb.add(this.xIndex, 1.5), this.w);
362
- this.$l.u3 = pb.div(pb.add(this.xIndex, 2.5), this.w);
363
- this.$l.u4 = pb.div(pb.add(this.xIndex, 3.5), this.w);
364
- this.$l.v = pb.div(pb.add(this.yIndex, 0.5), this.w);
365
- this.$l.row1 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u1, this.v), 0);
366
- this.$l.row2 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u2, this.v), 0);
367
- this.$l.row3 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u3, this.v), 0);
368
- this.$l.row4 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u4, this.v), 0);
369
- this.$return(pb.mat4(this.row1, this.row2, this.row3, this.row4));
370
- });
371
- const funcNameGetSkinningMatrix = 'Z_getSkinningMatrix';
372
- pb.func(funcNameGetSkinningMatrix, [], function() {
373
- const invBindMatrix = this[UNIFORM_NAME_BONE_INV_BIND_MATRIX];
374
- const blendIndices = scope.$getVertexAttrib('blendIndices');
375
- const blendWeights = scope.$getVertexAttrib('blendWeights');
376
- this.$l.m0 = scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(blendIndices[0]));
377
- this.$l.m1 = scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(blendIndices[1]));
378
- this.$l.m2 = scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(blendIndices[2]));
379
- this.$l.m3 = scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(blendIndices[3]));
380
- this.$l.m = pb.add(pb.mul(this.m0, blendWeights.x), pb.mul(this.m1, blendWeights.y), pb.mul(this.m2, blendWeights.z), pb.mul(this.m3, blendWeights.w));
381
- this.$l.skinInfo = this[UNIFORM_NAME_SKIN_INFLUENCE_INFO];
382
- this.$if(pb.greaterThan(this.skinInfo.z, 4), function() {
383
- this.$l.vertexIndex = isWebGL ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
384
- this.$l.texWidth = pb.int(this.skinInfo.x);
385
- this.$l.pairCount = pb.int(this.skinInfo.w);
386
- this.$for(pb.int('pairIndex'), 0, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, function() {
387
- this.$if(pb.greaterThanEqual(this.pairIndex, this.pairCount), function() {
388
- this.$break();
389
- });
390
- this.$l.pixelIndex = pb.add(pb.mul(this.vertexIndex, this.pairCount), this.pairIndex);
391
- this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
392
- this.$l.yIndex = pb.div(this.pixelIndex, this.texWidth);
393
- if (supportsTextureLoad) {
394
- this.$l.extra = pb.textureLoad(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.ivec2(this.xIndex, this.yIndex), 0);
395
- } else {
396
- this.$l.u = pb.div(pb.add(pb.float(this.xIndex), 0.5), this.skinInfo.x);
397
- this.$l.v = pb.div(pb.add(pb.float(this.yIndex), 0.5), this.skinInfo.y);
398
- this.$l.extra = pb.textureSampleLevel(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.vec2(this.u, this.v), 0);
399
- }
400
- this.$if(pb.greaterThan(this.extra.y, 0), function() {
401
- this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(this.extra.x)), this.extra.y));
402
- });
403
- this.$if(pb.greaterThan(this.extra.w, 0), function() {
404
- this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(this.extra.z)), this.extra.w));
405
- });
406
- });
407
- });
408
- this.$return(pb.mul(invBindMatrix, this.m));
409
- });
410
- return scope.$g[funcNameGetSkinningMatrix]();
418
+ if (!scope[this.SKIN_MATRIX_NAME]) {
419
+ this.prepareSkinAnimation(scope);
420
+ }
421
+ return scope[this.SKIN_MATRIX_NAME];
411
422
  }
412
423
  static calculateMorphDelta(scope, attrib) {
413
424
  const pb = scope.$builder;
414
- const isWebGL1 = pb.getDevice().type === 'webgl';
415
425
  if (pb.shaderKind !== 'vertex') {
416
426
  throw new Error(`ShaderHelper.calculateMorphDelta(): must be called at vertex stage`);
417
427
  }
@@ -423,7 +433,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
423
433
  this.$if(pb.lessThan(this.offset, 0), function() {
424
434
  this.$return(pb.vec4(0));
425
435
  });
426
- this.$l.vertexIndex = isWebGL1 ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
436
+ this.$l.vertexIndex = pb.int(scope.$builtins.vertexIndex);
427
437
  const morphInfo = scope[that.getMorphInfoUniformName()];
428
438
  this.$l.metaData = pb.ivec4(morphInfo[0]);
429
439
  this.$l.texWidth = pb.float(this.metaData.x);
@@ -431,43 +441,21 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
431
441
  this.$l.numVertices = this.metaData.z;
432
442
  this.$l.numTargets = this.metaData.w;
433
443
  this.$l.value = pb.vec4(0);
434
- if (isWebGL1) {
435
- this.$for(pb.int('i'), 0, MORPH_WEIGHTS_VECTOR_COUNT, function() {
436
- this.$for(pb.int('j'), 0, 4, function() {
437
- this.$l.index = pb.add(pb.mul(this.i, 4), this.j);
438
- this.$if(pb.greaterThanEqual(this.index, this.numTargets), function() {
439
- this.$return(this.value);
440
- });
441
- this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
442
- this.$if(pb.notEqual(this.weight, 0), function() {
443
- this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
444
- this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.targetIndex, this.numVertices), this.vertexIndex));
445
- this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
446
- this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.texWidth));
447
- this.$l.u = pb.div(pb.add(this.xIndex, 0.5), this.texWidth);
448
- this.$l.v = pb.div(pb.add(this.yIndex, 0.5), this.texHeight);
449
- this.$l.morphValue = pb.textureSampleLevel(this[that.getMorphDataUniformName()], pb.vec2(this.u, this.v), 0);
450
- this.value = pb.add(this.value, pb.mul(this.morphValue, this.weight));
451
- });
452
- });
453
- });
454
- } else {
455
- this.$for(pb.int('t'), 0, this.numTargets, function() {
456
- this.$l.i = pb.sar(this.t, 2);
457
- this.$l.j = pb.compAnd(this.t, 3);
458
- this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
459
- this.$if(pb.notEqual(this.weight, 0), function() {
460
- this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
461
- this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.targetIndex, this.numVertices), this.vertexIndex));
462
- this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
463
- this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.texWidth));
464
- this.$l.u = pb.div(pb.add(this.xIndex, 0.5), this.texWidth);
465
- this.$l.v = pb.div(pb.add(this.yIndex, 0.5), this.texHeight);
466
- this.$l.morphValue = pb.textureSampleLevel(this[that.getMorphDataUniformName()], pb.vec2(this.u, this.v), 0);
467
- this.value = pb.add(this.value, pb.mul(this.morphValue, this.weight));
468
- });
444
+ this.$for(pb.int('t'), 0, this.numTargets, function() {
445
+ this.$l.i = pb.sar(this.t, 2);
446
+ this.$l.j = pb.compAnd(this.t, 3);
447
+ this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
448
+ this.$if(pb.notEqual(this.weight, 0), function() {
449
+ this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
450
+ this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.targetIndex, this.numVertices), this.vertexIndex));
451
+ this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
452
+ this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.texWidth));
453
+ this.$l.u = pb.div(pb.add(this.xIndex, 0.5), this.texWidth);
454
+ this.$l.v = pb.div(pb.add(this.yIndex, 0.5), this.texHeight);
455
+ this.$l.morphValue = pb.textureSampleLevel(this[that.getMorphDataUniformName()], pb.vec2(this.u, this.v), 0);
456
+ this.value = pb.add(this.value, pb.mul(this.morphValue, this.weight));
469
457
  });
470
- }
458
+ });
471
459
  this.$return(this.value);
472
460
  });
473
461
  const pos = 1 + MORPH_WEIGHTS_VECTOR_COUNT * 2 + (attrib >> 2);
@@ -514,7 +502,6 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
514
502
  * @internal
515
503
  */ static calculateMorphDirectionByDisplacement(scope, base, attrib) {
516
504
  const pb = scope.$builder;
517
- const isWebGL1 = pb.getDevice().type === 'webgl';
518
505
  if (pb.shaderKind !== 'vertex') {
519
506
  throw new Error(`ShaderHelper.calculateMorphDirectionByDisplacement(): must be called at vertex stage`);
520
507
  }
@@ -526,7 +513,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
526
513
  pb.int('directionOffset'),
527
514
  pb.int('positionOffset')
528
515
  ], function() {
529
- this.$l.vertexIndex = isWebGL1 ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
516
+ this.$l.vertexIndex = pb.int(scope.$builtins.vertexIndex);
530
517
  const morphInfo = scope[that.getMorphInfoUniformName()];
531
518
  this.$l.metaData = pb.ivec4(morphInfo[0]);
532
519
  this.$l.texWidth = pb.float(this.metaData.x);
@@ -564,27 +551,13 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
564
551
  this.weightSum = pb.add(this.weightSum, this.displacementWeight);
565
552
  });
566
553
  };
567
- if (isWebGL1) {
568
- this.$for(pb.int('i'), 0, MORPH_WEIGHTS_VECTOR_COUNT, function() {
569
- this.$for(pb.int('j'), 0, 4, function() {
570
- this.$l.index = pb.add(pb.mul(this.i, 4), this.j);
571
- this.$if(pb.greaterThanEqual(this.index, this.numTargets), function() {
572
- this.$break();
573
- });
574
- this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
575
- this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
576
- accumulateTarget(this);
577
- });
578
- });
579
- } else {
580
- this.$for(pb.int('t'), 0, this.numTargets, function() {
581
- this.$l.i = pb.sar(this.t, 2);
582
- this.$l.j = pb.compAnd(this.t, 3);
583
- this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
584
- this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
585
- accumulateTarget(this);
586
- });
587
- }
554
+ this.$for(pb.int('t'), 0, this.numTargets, function() {
555
+ this.$l.i = pb.sar(this.t, 2);
556
+ this.$l.j = pb.compAnd(this.t, 3);
557
+ this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
558
+ this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
559
+ accumulateTarget(this);
560
+ });
588
561
  this.$l.fallback = scope.$g[normalizeName](this.base, this.directionDelta);
589
562
  this.$if(pb.lessThan(this.weightSum, 1e-6), function() {
590
563
  this.$return(this.fallback);
@@ -628,8 +601,6 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
628
601
  }
629
602
  const that = this;
630
603
  const pb = scope.$builder;
631
- const isWebGL = pb.getDevice().type === 'webgl';
632
- const supportsTextureLoad = !isWebGL;
633
604
  const funcNameGetBoneMatrixFromTexture = 'Z_getBoneMatrixFromTexture';
634
605
  pb.func(funcNameGetBoneMatrixFromTexture, [
635
606
  pb.float('boneIndex'),
@@ -637,6 +608,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
637
608
  ], function() {
638
609
  const boneTexture = this[UNIFORM_NAME_BONE_MATRICES];
639
610
  this.$l.w = this[UNIFORM_NAME_BONE_TEXTURE_SIZE].x;
611
+ this.$l.h = this[UNIFORM_NAME_BONE_TEXTURE_SIZE].y;
640
612
  this.$l.pixelIndex = pb.mul(pb.add(this.boneIndex, this.boneOffset), 4);
641
613
  this.$l.xIndex = pb.mod(this.pixelIndex, this.w);
642
614
  this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.w));
@@ -644,7 +616,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
644
616
  this.$l.u2 = pb.div(pb.add(this.xIndex, 1.5), this.w);
645
617
  this.$l.u3 = pb.div(pb.add(this.xIndex, 2.5), this.w);
646
618
  this.$l.u4 = pb.div(pb.add(this.xIndex, 3.5), this.w);
647
- this.$l.v = pb.div(pb.add(this.yIndex, 0.5), this.w);
619
+ this.$l.v = pb.div(pb.add(this.yIndex, 0.5), this.h);
648
620
  this.$l.row1 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u1, this.v), 0);
649
621
  this.$l.row2 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u2, this.v), 0);
650
622
  this.$l.row3 = pb.textureSampleLevel(boneTexture, pb.vec2(this.u3, this.v), 0);
@@ -665,7 +637,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
665
637
  this.$l.m = pb.add(pb.mul(this.m0, blendWeights.x), pb.mul(this.m1, blendWeights.y), pb.mul(this.m2, blendWeights.z), pb.mul(this.m3, blendWeights.w));
666
638
  this.$l.skinInfo = this[UNIFORM_NAME_SKIN_INFLUENCE_INFO];
667
639
  this.$if(pb.greaterThan(this.skinInfo.z, 4), function() {
668
- this.$l.vertexIndex = isWebGL ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
640
+ this.$l.vertexIndex = pb.int(scope.$builtins.vertexIndex);
669
641
  this.$l.texWidth = pb.int(this.skinInfo.x);
670
642
  this.$l.pairCount = pb.int(this.skinInfo.w);
671
643
  this.$for(pb.int('pairIndex'), 0, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, function() {
@@ -675,13 +647,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
675
647
  this.$l.pixelIndex = pb.add(pb.mul(this.vertexIndex, this.pairCount), this.pairIndex);
676
648
  this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
677
649
  this.$l.yIndex = pb.div(this.pixelIndex, this.texWidth);
678
- if (supportsTextureLoad) {
679
- this.$l.extra = pb.textureLoad(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.ivec2(this.xIndex, this.yIndex), 0);
680
- } else {
681
- this.$l.u = pb.div(pb.add(pb.float(this.xIndex), 0.5), this.skinInfo.x);
682
- this.$l.v = pb.div(pb.add(pb.float(this.yIndex), 0.5), this.skinInfo.y);
683
- this.$l.extra = pb.textureSampleLevel(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.vec2(this.u, this.v), 0);
684
- }
650
+ this.$l.extra = pb.textureLoad(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.ivec2(this.xIndex, this.yIndex), 0);
685
651
  this.$if(pb.greaterThan(this.extra.y, 0), function() {
686
652
  this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](this.extra.x, this.boneOffset), this.extra.y));
687
653
  });
@@ -693,8 +659,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
693
659
  this.$return(pb.mul(invBindMatrix, this.m));
694
660
  });
695
661
  const motionVector = !!this.getUnjitteredViewProjectionMatrix(scope);
662
+ // The palette header slot holds the slot indices of the current and previous matrix sets
696
663
  const boneTexture = scope[UNIFORM_NAME_BONE_MATRICES];
697
- scope.$l[that.SKIN_BONE_OFFSET] = pb.textureSampleLevel(boneTexture, pb.div(pb.vec2(0.5), scope[UNIFORM_NAME_BONE_TEXTURE_SIZE]), 0).xy;
664
+ const paletteBase = scope[UNIFORM_NAME_SKIN_PALETTE_BASE] ?? this.getInstancedUniform(scope, 4).z;
665
+ const boneTextureSize = scope[UNIFORM_NAME_BONE_TEXTURE_SIZE];
666
+ scope.$l.Z_skinHeaderTexel = pb.mul(paletteBase, 4);
667
+ scope.$l[that.SKIN_BONE_OFFSET] = pb.textureSampleLevel(boneTexture, pb.div(pb.add(pb.vec2(pb.mod(scope.Z_skinHeaderTexel, boneTextureSize.x), pb.floor(pb.div(scope.Z_skinHeaderTexel, boneTextureSize.x))), pb.vec2(0.5)), boneTextureSize), 0).xy;
698
668
  scope.$l[that.SKIN_MATRIX_NAME] = scope[funcNameGetSkinningMatrix](scope[that.SKIN_BONE_OFFSET].x);
699
669
  if (motionVector) {
700
670
  scope.$l[that.SKIN_PREV_MATRIX_NAME] = scope[funcNameGetSkinningMatrix](scope[that.SKIN_BONE_OFFSET].y);
@@ -929,6 +899,9 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
929
899
  scope[UNIFORM_NAME_BONE_MATRICES] = pb.tex2D().uniform(1).sampleType('unfilterable-float');
930
900
  scope[UNIFORM_NAME_BONE_INV_BIND_MATRIX] = pb.mat4().uniform(1);
931
901
  scope[UNIFORM_NAME_BONE_TEXTURE_SIZE] = pb.vec2().uniform(1);
902
+ if (!instanced) {
903
+ scope[UNIFORM_NAME_SKIN_PALETTE_BASE] = pb.float().uniform(1);
904
+ }
932
905
  scope[UNIFORM_NAME_SKIN_INFLUENCE_DATA] = pb.tex2D().uniform(1).sampleType('unfilterable-float');
933
906
  scope[UNIFORM_NAME_SKIN_INFLUENCE_INFO] = pb.vec4().uniform(1);
934
907
  }
@@ -942,7 +915,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
942
915
  }
943
916
  /** @internal */ static setCameraUniforms(bindGroup, ctx, camera, linear) {
944
917
  const pos = camera.getWorldPosition();
945
- const useJitter = ctx.motionVectors && ctx.renderPass.type !== RENDER_PASS_TYPE_SHADOWMAP && ctx.renderPass.type !== RENDER_PASS_TYPE_OBJECT_COLOR;
918
+ const useJitter = ctx.motionVectors && ctx.renderPass.type !== RENDER_PASS_TYPE_SHADOWMAP && ctx.renderPass.type !== RENDER_PASS_TYPE_OBJECT_COLOR && ctx.renderPass.type !== RENDER_PASS_TYPE_VOXELIZE;
946
919
  const cameraStruct = {
947
920
  position: new Vector4(pos.x, pos.y, pos.z, 0),
948
921
  renderSize: new Vector2(ctx.renderWidth, ctx.renderHeight),
@@ -991,12 +964,14 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
991
964
  viewMatrix: light.viewMatrix,
992
965
  depthBias: shadowMapParams.depthBiasValues[0],
993
966
  implParams: shadowMapParams.impl.getParams(this._lightUniformShadow.implParams),
994
- lightType: light.lightType
967
+ // The shadow projection, not the light's own type: a rect light renders a point light's
968
+ // cube, so its casters must write the point light's radial depth.
969
+ lightType: shadowMapParams.lightType
995
970
  });
996
971
  shadowMapParams.impl?.applyCasterUniforms(bindGroup, shadowMapParams);
997
972
  }
998
973
  }
999
- /** @internal */ static setFogUniforms(bindGroup, withAerialPerspective, fogType, additive, atmosphereParams, heightFogParams, aerialPerspectiveLUT, skyDistantLightLUT) {
974
+ /** @internal */ static setFogUniforms(bindGroup, withAerialPerspective, fogType, additive, atmosphereParams, heightFogParams, aerialPerspectiveLUT, skyDistantLightLUT, skyLightCubemap) {
1000
975
  this._fogUniforms.withAerialPerspective = withAerialPerspective;
1001
976
  this._fogUniforms.fogType = fogType;
1002
977
  this._fogUniforms.additive = additive;
@@ -1005,6 +980,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1005
980
  bindGroup.setValue('fog', this._fogUniforms);
1006
981
  bindGroup.setTexture(UNIFORM_NAME_AERIALPERSPECTIVE_LUT, aerialPerspectiveLUT);
1007
982
  bindGroup.setTexture(UNIFORM_NAME_SKYDISTANTLIGHT_LUT, skyDistantLightLUT);
983
+ bindGroup.setTexture(UNIFORM_NAME_FOG_SKYLIGHT_CUBEMAP, skyLightCubemap);
1008
984
  }
1009
985
  /**
1010
986
  * @internal
@@ -1018,6 +994,10 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1018
994
  * quantity on the CPU so the HDR target stays near 1.0 and downstream passes need no unit
1019
995
  * awareness. Legacy returns 1 so its uploads stay byte-identical.
1020
996
  */ static getPreExposure(ctx) {
997
+ // The voxelize pass writes the global illumination's caches, kept at a fixed exposure
998
+ if (ctx.renderPass?.type === RENDER_PASS_TYPE_VOXELIZE) {
999
+ return getGICacheExposure(ctx.scene.lightingMode);
1000
+ }
1021
1001
  return ctx.scene.lightingMode === 'physical' ? ctx.camera.exposure : 1;
1022
1002
  }
1023
1003
  /**
@@ -1065,24 +1045,29 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1065
1045
  const strength = env.light.strength ?? 0;
1066
1046
  return ctx.scene.lightingMode === 'physical' ? this.getPreExposure(ctx) / PHYSICAL_BAKE_EXPOSURE * strength : strength;
1067
1047
  }
1068
- /** @internal */ static setLightUniforms(bindGroup, ctx, clusterParams, countParams, lightBuffer, lightIndexTexture) {
1048
+ /** @internal */ static setLightUniforms(bindGroup, ctx, clusteredLight) {
1069
1049
  const envLightStrength = this.getEnvLightLuminance(ctx);
1050
+ const lightIndexTexture = clusteredLight.lightIndexTexture;
1070
1051
  bindGroup.setValue('light', {
1071
1052
  sunDir: ctx.sunLight ? ctx.sunLight.directionAndCutoff.xyz().scaleBy(-1) : this.defaultSunDir,
1072
- clusterParams: clusterParams,
1073
- countParams: countParams,
1053
+ clusterParams: clusteredLight.clusterParam,
1054
+ countParams: clusteredLight.countParam,
1055
+ globalLights: clusteredLight.globalLights,
1074
1056
  envLightStrength,
1075
1057
  envLightSpecularStrength: ctx.env.light.specularStrength ?? 1,
1076
- lightIndexTexSize: new Int32Array([
1077
- lightIndexTexture.width,
1078
- lightIndexTexture.height
1079
- ]),
1058
+ lightIndexTexSize: clusteredLight.lightIndexTexSize,
1059
+ clusterOrtho: clusteredLight.orthographic ? 1 : 0,
1080
1060
  ...ctx.screenSpaceShadowMask ? {
1081
- numShadowLights: ctx.clusteredLight?.numShadowLights ?? 0
1061
+ numShadowLights: clusteredLight.numShadowLights
1082
1062
  } : {}
1083
1063
  });
1084
- bindGroup.setBuffer(UNIFORM_NAME_LIGHT_BUFFER, lightBuffer);
1085
- bindGroup.setTexture(UNIFORM_NAME_LIGHT_INDEX_TEXTURE, lightIndexTexture);
1064
+ bindGroup.setBuffer(UNIFORM_NAME_LIGHT_BUFFER, clusteredLight.lightBuffer);
1065
+ if (this.usesClusterLightLists()) {
1066
+ bindGroup.setBuffer(UNIFORM_NAME_CLUSTER_GRID, clusteredLight.clusterGridBuffer);
1067
+ bindGroup.setBuffer(UNIFORM_NAME_CLUSTER_LIGHT_LIST, clusteredLight.lightListBuffer);
1068
+ } else {
1069
+ bindGroup.setTexture(UNIFORM_NAME_LIGHT_INDEX_TEXTURE, lightIndexTexture);
1070
+ }
1086
1071
  if (ctx.screenSpaceShadowMask) {
1087
1072
  // The mask array is declared for the clustered light pass whenever the flag is
1088
1073
  // on (keyed into the global bind group hash). When there are no shadow lights
@@ -1090,11 +1075,396 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1090
1075
  // be bound: fall back to a 1x1 dummy.
1091
1076
  bindGroup.setTexture(UNIFORM_NAME_SHADOW_MASK, ctx.shadowMaskTexture ?? this.getDummyShadowMask(ctx.device), fetchSampler('clamp_nearest'));
1092
1077
  }
1078
+ if (ctx.transmissionThickness) {
1079
+ // Falls back to a white dummy, not the shadow mask's zero one: 0 encodes
1080
+ // maximum optical depth here, which would read as full transmission.
1081
+ bindGroup.setTexture(UNIFORM_NAME_TRANSMISSION_THICKNESS, ctx.transmissionThicknessTexture ?? this.getDummyTransmissionThickness(ctx.device), fetchSampler('clamp_nearest'));
1082
+ }
1093
1083
  bindGroup.setTexture(UNIFORM_NAME_BAKED_SKY_MAP, ctx.scene.env.sky.getBakedSkyTexture(ctx));
1084
+ if (this.usesWaterCaustics(ctx)) {
1085
+ this.setWaterCausticUniforms(bindGroup, ctx);
1086
+ }
1094
1087
  if (ctx.drawEnvLight) {
1095
1088
  ctx.env.light.envLight.updateBindGroup(bindGroup, ctx);
1096
1089
  }
1097
1090
  }
1091
+ /**
1092
+ * Whether the pass being built modulates its light with the water caustic map.
1093
+ *
1094
+ * True for every light pass of a frame that produced a map; which light the
1095
+ * caustics actually attach to is decided per-fragment by
1096
+ * {@link ShaderHelper.calculateWaterCaustic}. Both the shader variant hash and
1097
+ * the global bind group hash fold this in, so a program that declares the
1098
+ * caustic uniforms is always paired with a bind group that supplies them.
1099
+ *
1100
+ * Deliberately a function of the draw context alone, with no material input.
1101
+ * The caustic uniforms sit in the global bind group, whose layout is built
1102
+ * once per {@link DrawContext.renderPassHash} and shared by every material in
1103
+ * the pass, so the declarations have to be uniform across the pass. A material
1104
+ * that must not receive caustics - water itself - opts out of the sampling
1105
+ * instead, through `receivesWaterCaustics()`; the uniforms stay declared and
1106
+ * simply go unread.
1107
+ *
1108
+ * @param ctx - Current draw context.
1109
+ * @returns True when the caustic uniforms take part in this pass.
1110
+ * @internal
1111
+ */ /**
1112
+ * The scene's signed distance field as light-pass materials see it, or null
1113
+ * when it is off or cannot be built (WebGL2). Part of the light pass's global
1114
+ * bind group and shader variant hashes.
1115
+ * @internal
1116
+ */ static getSceneSDF(ctx) {
1117
+ if (ctx.renderPass?.type !== RENDER_PASS_TYPE_LIGHT || !ctx.scene?.sdf.enabled) {
1118
+ return null;
1119
+ }
1120
+ return ctx.scene.sdf.global;
1121
+ }
1122
+ /**
1123
+ * Declares the scene signed distance field in a material's bind group, when
1124
+ * the scene has one for this draw ({@link ShaderHelper.getSceneSDF}).
1125
+ * Opt-in per material, so the textures it binds only count against the
1126
+ * shaders that read them. Bind it with {@link ShaderHelper.applySceneSDF}.
1127
+ *
1128
+ * @returns Whether it was declared; without it, fall back to something else.
1129
+ */ static declareSceneSDF(scope, ctx, group) {
1130
+ const sdf = this.getSceneSDF(ctx);
1131
+ if (!sdf) {
1132
+ return false;
1133
+ }
1134
+ declareGlobalSDFUniforms(scope, group, sdf.levels);
1135
+ declareDynamicSDFUniforms(scope, group);
1136
+ sceneSDFLevels.set(scope.$builder, sdf.levels);
1137
+ return true;
1138
+ }
1139
+ /**
1140
+ * Binds what {@link ShaderHelper.declareSceneSDF} declared. The values change
1141
+ * every frame (the clipmap follows the camera, dynamic meshes move), so call
1142
+ * it from `applyUniforms` on every draw, not from `applyUniformValues`, which
1143
+ * only runs when the material itself changed.
1144
+ */ static applySceneSDF(bindGroup, ctx) {
1145
+ this.getSceneSDF(ctx)?.applyBindGroup(bindGroup);
1146
+ }
1147
+ /** Whether {@link ShaderHelper.declareSceneSDF} declared the field in this program. */ static hasSceneSDF(scope) {
1148
+ return !!scope[GLOBAL_SDF_TEXTURE];
1149
+ }
1150
+ /**
1151
+ * Signed distance in world units from a world position to the scene's
1152
+ * geometry: static meshes and terrain from the global clipmap, merged with
1153
+ * the nearest dynamic meshes at their current transforms. Negative inside.
1154
+ * Only valid when {@link ShaderHelper.hasSceneSDF} is true.
1155
+ *
1156
+ * Near surfaces the value is accurate to a fraction of the finest voxel
1157
+ * (0.25 m by default); farther away it is a lower bound clamped to a few
1158
+ * voxels of the level covering the point, safe for sphere tracing.
1159
+ */ static sampleSceneSDF(scope, worldPos) {
1160
+ const levels = sceneSDFLevels.get(scope.$builder);
1161
+ if (!levels) {
1162
+ throw new Error('ShaderHelper.sampleSceneSDF(): the scene SDF is not bound in this program');
1163
+ }
1164
+ return sampleSceneSDF(scope, worldPos, levels);
1165
+ }
1166
+ static usesWaterCaustics(ctx) {
1167
+ return !!ctx.waterCaustics && !!ctx.waterCausticLight && ctx.renderPass.type === RENDER_PASS_TYPE_LIGHT;
1168
+ }
1169
+ /**
1170
+ * Declares the global uniforms {@link ShaderHelper.calculateWaterCaustic} reads.
1171
+ *
1172
+ * Split out of the global uniform setup so a test can build a program that
1173
+ * exercises the sampling code without standing up a whole light pass.
1174
+ *
1175
+ * @param pb - Program builder whose global scope receives the uniforms.
1176
+ * @internal
1177
+ */ /**
1178
+ * Warps a linear caustic-map coordinate so texels concentrate near the centre.
1179
+ *
1180
+ * The map covers a fixed footprint, so its world texel size is the range
1181
+ * divided by the resolution and a range large enough to light the scene is
1182
+ * already too coarse to resolve a caustic cell. This trades the edge of the
1183
+ * map, which is far from the camera, for the middle, which is not:
1184
+ *
1185
+ * ```
1186
+ * warp(u) = u (1 + k) / (1 + k |u|)
1187
+ * unwarp(w) = w / ((1 + k) - k |w|)
1188
+ * ```
1189
+ *
1190
+ * Texel density is `1 + k` times uniform at the centre and `1 / (1 + k)` times
1191
+ * it at the border, and `k = 0` is exactly the identity, so the pass can apply
1192
+ * this unconditionally. Both ends of `[-1, 1]` are fixed points, which is what
1193
+ * lets the warp sit inside the existing parameterisation without moving the
1194
+ * footprint the receiver gates on. The first derivative is continuous at the
1195
+ * centre; the second flips sign there, which is not visible in a pattern this
1196
+ * noisy.
1197
+ *
1198
+ * Applied per axis rather than radially: the photon grid, the map and the blur
1199
+ * are all axis-aligned, so a separable warp keeps photon `(i,j)` on texel
1200
+ * `(i,j)` for calm water and leaves the map's normalisation to 1.0 intact.
1201
+ *
1202
+ * @param scope - Current shader scope.
1203
+ * @param ndc - Linear map coordinate in `[-1, 1]`.
1204
+ * @param strength - Warp strength `k`; 0 is the identity.
1205
+ * @returns The warped coordinate, also in `[-1, 1]`.
1206
+ * @internal
1207
+ */ static warpCausticNDC(scope, ndc, strength) {
1208
+ const pb = scope.$builder;
1209
+ const funcName = 'Z_warpCausticNDC';
1210
+ pb.func(funcName, [
1211
+ pb.vec2('ndc'),
1212
+ pb.float('k')
1213
+ ], function() {
1214
+ this.$return(pb.div(pb.mul(this.ndc, pb.add(this.k, 1)), pb.add(pb.vec2(1), pb.mul(pb.abs(this.ndc), this.k))));
1215
+ });
1216
+ return pb.getGlobalScope()[funcName](ndc, strength);
1217
+ }
1218
+ /**
1219
+ * Inverse of {@link ShaderHelper.warpCausticNDC}.
1220
+ *
1221
+ * The denominator is `1` at the border and `1 + k` at the centre, so it stays
1222
+ * positive for any input the passes feed it - all of which come from `[-1, 1]`.
1223
+ *
1224
+ * @param scope - Current shader scope.
1225
+ * @param warped - Warped map coordinate in `[-1, 1]`.
1226
+ * @param strength - Warp strength `k`; 0 is the identity.
1227
+ * @returns The linear coordinate the warp came from.
1228
+ * @internal
1229
+ */ static unwarpCausticNDC(scope, warped, strength) {
1230
+ const pb = scope.$builder;
1231
+ const funcName = 'Z_unwarpCausticNDC';
1232
+ pb.func(funcName, [
1233
+ pb.vec2('warped'),
1234
+ pb.float('k')
1235
+ ], function() {
1236
+ this.$return(pb.div(this.warped, pb.sub(pb.vec2(pb.add(this.k, 1)), pb.mul(pb.abs(this.warped), this.k))));
1237
+ });
1238
+ return pb.getGlobalScope()[funcName](warped, strength);
1239
+ }
1240
+ /** @internal */ static declareWaterCausticUniforms(pb) {
1241
+ const scope = pb.getGlobalScope();
1242
+ const causticStruct = pb.defineStruct([
1243
+ pb.vec4('frameX'),
1244
+ pb.vec4('frameY'),
1245
+ pb.vec4('center'),
1246
+ pb.vec4('lightDir'),
1247
+ pb.vec4('params'),
1248
+ pb.vec4('extinction'),
1249
+ // Per water body, because the slice is shared but the water is not: each
1250
+ // has its own surface height, footprint and medium, and none of the three
1251
+ // means anything until a receiver has resolved which water is above it.
1252
+ pb.int('waterCount'),
1253
+ pb.vec4[MAX_CAUSTIC_WATERS]('waterRegions'),
1254
+ // (sigma_t.xyz, surface height)
1255
+ pb.vec4[MAX_CAUSTIC_WATERS]('waterMedia')
1256
+ ]);
1257
+ scope[UNIFORM_NAME_CAUSTIC_PARAMS] = causticStruct().uniform(0);
1258
+ // The map is single mip and read bilinearly; clamping keeps receivers
1259
+ // outside the covered area reading the border rather than wrapping.
1260
+ scope[UNIFORM_NAME_CAUSTIC_MAP] = pb.tex2D().uniform(0).withSampler(getSamplerOptions('clamp_linear_nomip'));
1261
+ // Displaced surface height per water slot, on the same slice as the map.
1262
+ // Read with the map's uv, so it shares the map's sampling.
1263
+ scope[UNIFORM_NAME_CAUSTIC_HEIGHT_MAP] = pb.tex2D().uniform(0).withSampler(getSamplerOptions('clamp_linear_nomip'));
1264
+ }
1265
+ /**
1266
+ * Attenuation the water caustics apply to one light at a world position.
1267
+ *
1268
+ * Returns the product of two effects: the caustic pattern focused by the waves,
1269
+ * and the Beer-Lambert transmittance of the water column the light travelled
1270
+ * through to reach the point. The result is 1 for any light that is not the
1271
+ * caustic light, above the surface, and outside the water region, so the caller
1272
+ * can multiply unconditionally.
1273
+ *
1274
+ * The caustic light is identified at runtime by comparing directions rather
1275
+ * than by which pass is running. Shadow-casting lights reach the opaque queue
1276
+ * through the clustered path when the screen-space shadow mask is on (the
1277
+ * default) and through a per-light additive pass when it is off; only a runtime
1278
+ * test covers both. Two directional lights pointing the same way would both
1279
+ * pick up the caustics, but they are physically the same light.
1280
+ *
1281
+ * @param scope - Current shader scope.
1282
+ * @param worldPos - World position of the shaded fragment.
1283
+ * @param lightType - The light's type constant.
1284
+ * @param lightDirection - Direction the light travels, i.e. `directionAndCutoff.xyz`.
1285
+ * @param ctx - Current draw context.
1286
+ * @returns A `vec3` multiplier, or null when this pass has no caustics.
1287
+ */ static calculateWaterCaustic(scope, worldPos, lightType, lightDirection, ctx) {
1288
+ if (!this.usesWaterCaustics(ctx)) {
1289
+ return null;
1290
+ }
1291
+ return this.sampleWaterCaustic(scope, worldPos, lightType, lightDirection);
1292
+ }
1293
+ /**
1294
+ * {@link ShaderHelper.calculateWaterCaustic} without the pass gate.
1295
+ *
1296
+ * The gate asks whether the *light pass* has caustics, which is the right
1297
+ * question for a material and the wrong one for a standalone full-screen pass
1298
+ * that declared the same uniforms itself. Such a pass has already decided it
1299
+ * has a map; this is the sampling half on its own.
1300
+ *
1301
+ * @param scope - Current shader scope.
1302
+ * @param worldPos - World position of the shaded fragment.
1303
+ * @param lightType - The light's type constant.
1304
+ * @param lightDirection - Direction the light travels, i.e. `directionAndCutoff.xyz`.
1305
+ * @param defocus - Whether to fade the pattern's contrast by how far the point
1306
+ * is from the map's focal depth. True for a receiver surface, which is what
1307
+ * that fade describes. False for a point inside the water column: see the
1308
+ * comment on the term itself.
1309
+ * @returns A `vec3` multiplier.
1310
+ * @internal
1311
+ */ static sampleWaterCaustic(scope, worldPos, lightType, lightDirection, defocus = true) {
1312
+ const pb = scope.$builder;
1313
+ const funcName = defocus ? 'Z_calculateWaterCaustic' : 'Z_calculateWaterCausticVolume';
1314
+ pb.func(funcName, [
1315
+ pb.vec3('worldPos'),
1316
+ pb.int('lightType'),
1317
+ pb.vec3('lightDirection')
1318
+ ], function() {
1319
+ this.$l.cu = this[UNIFORM_NAME_CAUSTIC_PARAMS];
1320
+ // Only the one light the map was built for casts caustics.
1321
+ this.$if(pb.or(pb.notEqual(this.lightType, LIGHT_TYPE_DIRECTIONAL), pb.lessThan(pb.dot(this.lightDirection, this.cu.lightDir.xyz), 0.999)), function() {
1322
+ this.$return(pb.vec3(1));
1323
+ });
1324
+ // Project into the map: frameX/frameY are orthonormal and perpendicular to
1325
+ // the light, so the same two dot products the splat pass used invert it.
1326
+ this.$l.rel = pb.sub(this.worldPos, this.cu.center.xyz);
1327
+ // Per-axis inverse extents: the slice is fitted to the water, so it is
1328
+ // only square in texels, not in meters.
1329
+ this.$l.mapNDC = pb.mul(pb.vec2(pb.dot(this.rel, this.cu.frameX.xyz), pb.dot(this.rel, this.cu.frameY.xyz)), pb.vec2(this.cu.frameX.w, this.cu.frameY.w));
1330
+ // Texels are concentrated towards the middle of the map, so the lookup
1331
+ // goes through the same warp the splat laid the photons out under.
1332
+ this.$l.warpedNDC = ShaderHelper.warpCausticNDC(this, this.mapNDC, this.cu.extinction.w);
1333
+ this.$l.uv = pb.add(pb.mul(this.warpedNDC, 0.5), pb.vec2(0.5));
1334
+ // The displaced surface height of each water slot on this fragment's own
1335
+ // sun ray, biased by CAUSTIC_HEIGHT_BIAS. The height map rasterises the
1336
+ // displaced surface into the same light-space slice as the pattern, so
1337
+ // the texel under this uv holds the surface the ray through this fragment
1338
+ // passed through - no entry point has to be estimated. Beyond the map the
1339
+ // sampler would repeat its border, which is some other ray's wave; the
1340
+ // surface is taken as flat there.
1341
+ this.$l.insideMap = pb.all(pb.lessThanEqual(pb.abs(this.mapNDC), pb.vec2(1)));
1342
+ // How far this fragment is towards the map border, and how much the map is
1343
+ // therefore trusted. Hoisted above the water loop because the wave height
1344
+ // is faded by it too, not just the pattern.
1345
+ //
1346
+ // Measured on the linear coordinate, not the warped one. The warp fixes
1347
+ // both ends of [-1, 1], so the border is in the same world place either
1348
+ // way, but the band between them is not: warping it would make its world
1349
+ // width depend on the warp strength, and the CPU sized it in meters.
1350
+ //
1351
+ // Distance is measured under an L4 norm rather than per-axis. A per-axis
1352
+ // fade is a rectangle in light space, and the sun tilts that rectangle, so
1353
+ // its border lands on the sea bed as straight lines - which the eye picks
1354
+ // out of a caustic pattern however wide the gradient is. An L2 circle has
1355
+ // no straight part anywhere but discards the 21% of the map outside its
1356
+ // inscribed disc; L4 gives up 7% instead and still curves everywhere.
1357
+ this.$l.ndcSq = pb.mul(this.mapNDC, this.mapNDC);
1358
+ this.$l.edgeDist = pb.sqrt(pb.sqrt(pb.dot(this.ndcSq, this.ndcSq)));
1359
+ this.$l.edge = pb.sub(1, pb.smoothStep(this.cu.params.w, 1, this.edgeDist));
1360
+ this.$l.waveHeights = pb.textureSampleLevel(this[UNIFORM_NAME_CAUSTIC_HEIGHT_MAP], this.uv, 0);
1361
+ // Find the water above this point. Several can qualify - a pool inside a
1362
+ // lake, water on terraces - and the one that matters is the lowest of
1363
+ // them, because that is the last surface the sun crossed on its way here.
1364
+ this.$l.depth = pb.float(-1);
1365
+ this.$l.medium = pb.vec3(0);
1366
+ this.$for(pb.int('i'), 0, MAX_CAUSTIC_WATERS, function() {
1367
+ this.$if(pb.greaterThanEqual(this.i, this.cu.waterCount), function() {
1368
+ this.$break();
1369
+ });
1370
+ this.$l.slotMedium = this.cu.waterMedia.at(this.i);
1371
+ // Where the sun ray that lights this point crossed that water's rest
1372
+ // plane, for the footprint test. The splat pass clips photons by that
1373
+ // same crossing, so the receiver must ask the same question: a point
1374
+ // under the footprint that the sun reaches from outside it saw no water
1375
+ // at all, and a point beyond the footprint that the sun reaches through
1376
+ // it did. Testing the point's own xz gets both wrong by the offset
1377
+ // depth / tan(elevation). Only the footprint is decided here; the depth
1378
+ // itself comes from the height map, which needs no crossing point.
1379
+ this.$l.slotFlat = pb.sub(this.slotMedium.w, this.worldPos.y);
1380
+ this.$l.slotPath = pb.mul(this.slotFlat, this.cu.lightDir.w);
1381
+ this.$l.slotEntry = pb.sub(this.worldPos.xz, pb.mul(this.cu.lightDir.xz, this.slotPath));
1382
+ this.$l.slotRegion = this.cu.waterRegions.at(this.i);
1383
+ this.$if(pb.and(pb.all(pb.greaterThanEqual(this.slotEntry, this.slotRegion.xy)), pb.all(pb.lessThanEqual(this.slotEntry, this.slotRegion.zw))), function() {
1384
+ // Depth under the *displaced* surface on this ray, which is what
1385
+ // the splat refracted the photons through. Gating on the rest plane
1386
+ // instead cut the caustics off at a flat line across any receiver
1387
+ // that pokes above it under a crest. Slot i lives in channel i.
1388
+ this.$l.slotMask = pb.vec4(pb.float(pb.equal(this.i, 0)), pb.float(pb.equal(this.i, 1)), pb.float(pb.equal(this.i, 2)), pb.float(pb.equal(this.i, 3)));
1389
+ this.$l.slotSample = pb.dot(this.waveHeights, this.slotMask);
1390
+ // A cleared texel inside the map means this ray never meets that
1391
+ // body's surface - it crosses the rest plane beyond the footprint,
1392
+ // or the surface is on the far side of the receiver - and the
1393
+ // receiver is dry as far as this body is concerned. Outside the map
1394
+ // nothing is known and the surface is taken as flat.
1395
+ this.$l.slotCovered = pb.or(pb.not(this.insideMap), pb.greaterThan(this.slotSample, 0.5));
1396
+ this.$if(this.slotCovered, function() {
1397
+ // Faded into the rest plane over the same band the pattern fades
1398
+ // on, rather than switched off at the border. Beyond the map the
1399
+ // surface is taken as flat, and exp(-sigma * depth) is convex, so
1400
+ // a hard switch makes the wavy side systematically brighter than
1401
+ // the flat side - E[exp(-sigma*w)] > 1 - and the step lands as a
1402
+ // mean brightness seam that grows with both the swell and the
1403
+ // turbidity. Ramping the wave to zero puts the two sides on the
1404
+ // same value at the border.
1405
+ this.$l.slotWave = pb.mul(pb.sub(this.slotSample, CAUSTIC_HEIGHT_BIAS), pb.float(this.insideMap), this.edge);
1406
+ this.$l.slotDepth = pb.sub(pb.add(this.slotMedium.w, this.slotWave), this.worldPos.y);
1407
+ this.$if(pb.greaterThan(this.slotDepth, 0), function() {
1408
+ this.$if(pb.or(pb.lessThan(this.depth, 0), pb.lessThan(this.slotDepth, this.depth)), function() {
1409
+ this.depth = this.slotDepth;
1410
+ this.medium = this.slotMedium.xyz;
1411
+ });
1412
+ });
1413
+ });
1414
+ });
1415
+ });
1416
+ this.$if(pb.lessThanEqual(this.depth, 0), function() {
1417
+ this.$return(pb.vec3(1));
1418
+ });
1419
+ this.$l.pathLength = pb.mul(this.depth, this.cu.lightDir.w);
1420
+ this.$l.transmittance = pb.exp(pb.neg(pb.mul(this.medium, this.pathLength)));
1421
+ this.$l.pattern = pb.textureSampleLevel(this[UNIFORM_NAME_CAUSTIC_MAP], this.uv, 0).x;
1422
+ if (defocus) {
1423
+ // Receivers away from the focal plane see a defocused, lower-contrast
1424
+ // pattern rather than a displaced one.
1425
+ this.$l.defocus = pb.div(1, pb.add(1, pb.mul(pb.abs(pb.sub(this.depth, this.cu.params.y)), this.cu.params.z)));
1426
+ } else {
1427
+ // A point inside the water column is not a receiver, and has no depth of
1428
+ // its own to be out of focus at - the volumetric march visits every
1429
+ // depth along the ray. Charging it the receiver's fade makes the light
1430
+ // shafts fade according to where the *sea bed* was told to be sharpest:
1431
+ // the open-ocean preset focuses the map at 26m for its bed, which at the
1432
+ // default defocus leaves a point 2m down at 26% contrast, and the shafts
1433
+ // dissolve into grain. The shafts instead take the pattern as the map
1434
+ // holds it and fade with the edge and the transmittance like everything
1435
+ // else in the column.
1436
+ this.$l.defocus = pb.float(1);
1437
+ }
1438
+ // None in water shallower than the minimum depth, faded in across it:
1439
+ // where the water thins out on a beach the surface is too coarse to
1440
+ // say where the water ends, and the pattern there would come from
1441
+ // photons that never reached it
1442
+ this.$l.minDepth = this.cu.extinction.x;
1443
+ this.$l.shallow = pb.smoothStep(pb.mul(this.minDepth, 0.5), pb.add(pb.mul(this.minDepth, 1.5), 1e-4), this.depth);
1444
+ this.$l.weight = pb.mul(this.cu.params.x, this.defocus, this.edge, this.shallow);
1445
+ this.$l.caustic = pb.add(1, pb.mul(pb.sub(this.pattern, 1), this.weight));
1446
+ // Soften the waterline so the transmittance does not switch on abruptly.
1447
+ this.$l.submerged = pb.smoothStep(0, 0.05, this.depth);
1448
+ this.$return(pb.mix(pb.vec3(1), pb.mul(pb.vec3(this.caustic), this.transmittance), this.submerged));
1449
+ });
1450
+ return pb.getGlobalScope()[funcName](worldPos, lightType, lightDirection);
1451
+ }
1452
+ /** @internal */ static setWaterCausticUniforms(bindGroup, ctx) {
1453
+ const uniforms = ctx.waterCausticUniforms;
1454
+ bindGroup.setValue(UNIFORM_NAME_CAUSTIC_PARAMS, {
1455
+ frameX: uniforms.frameX,
1456
+ frameY: uniforms.frameY,
1457
+ center: uniforms.center,
1458
+ lightDir: uniforms.lightDir,
1459
+ params: uniforms.params,
1460
+ extinction: uniforms.extinction,
1461
+ waterCount: uniforms.waterCount,
1462
+ waterRegions: uniforms.waterRegions,
1463
+ waterMedia: uniforms.waterMedia
1464
+ });
1465
+ bindGroup.setTexture(UNIFORM_NAME_CAUSTIC_MAP, ctx.waterCausticTexture, fetchSampler('clamp_linear_nomip'));
1466
+ bindGroup.setTexture(UNIFORM_NAME_CAUSTIC_HEIGHT_MAP, ctx.waterCausticHeightTexture, fetchSampler('clamp_linear_nomip'));
1467
+ }
1098
1468
  /** @internal */ static setLightUniformsShadow(bindGroup, ctx, light) {
1099
1469
  const shadowMapParams = ctx.shadowMapInfo.get(light);
1100
1470
  this._lightUniformShadow.sunDir = ctx.sunLight ? ctx.sunLight.directionAndCutoff.xyz().scaleBy(-1) : this.defaultSunDir;
@@ -1114,6 +1484,9 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1114
1484
  this._lightUniformShadow.envLightSpecularStrength = ctx.env?.light.specularStrength ?? 1;
1115
1485
  bindGroup.setValue('light', this._lightUniformShadow);
1116
1486
  bindGroup.setTexture(UNIFORM_NAME_SHADOW_MAP, shadowMapParams.shadowMap, shadowMapParams.shadowMapSampler);
1487
+ if (this.usesWaterCaustics(ctx)) {
1488
+ this.setWaterCausticUniforms(bindGroup, ctx);
1489
+ }
1117
1490
  bindGroup.setTexture(UNIFORM_NAME_BAKED_SKY_MAP, ctx.scene.env.sky.getBakedSkyTexture(ctx));
1118
1491
  if (ctx.drawEnvLight) {
1119
1492
  ctx.env.light.envLight.updateBindGroup(bindGroup, ctx);
@@ -1235,6 +1608,51 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1235
1608
  return scope.camera.elapsedTime;
1236
1609
  }
1237
1610
  /**
1611
+ * Declares the one camera field {@link ShaderHelper.getElapsedTime} reads.
1612
+ *
1613
+ * Wave generators animate off `camera.elapsedTime`, which the light and depth
1614
+ * passes provide as part of the full camera struct. A standalone program that
1615
+ * drives a wave generator - the caustics splat pass - has no such struct, so it
1616
+ * declares that single field under the same name. Any generator that starts
1617
+ * reading a second camera field will need it added here.
1618
+ *
1619
+ * The three padding floats round the struct up to 16 bytes. std140 aligns a
1620
+ * struct to a vec4 boundary, so a lone float leaves the GLSL block larger than
1621
+ * the buffer the engine sizes from the struct's own members - WebGL2 then
1622
+ * rejects every draw with "uniform buffer that is too small" and rasterises
1623
+ * nothing, while WebGPU is unaffected.
1624
+ *
1625
+ * @param scope - Global scope of the standalone program.
1626
+ * @param uniformGroup - Bind group the uniform belongs to.
1627
+ * @internal
1628
+ */ static declareStandaloneCameraTime(scope, uniformGroup) {
1629
+ const pb = scope.$builder;
1630
+ scope.camera = pb.defineStruct([
1631
+ pb.float('elapsedTime'),
1632
+ pb.float('_pad0'),
1633
+ pb.float('_pad1'),
1634
+ pb.float('_pad2')
1635
+ ])().uniform(uniformGroup);
1636
+ }
1637
+ /**
1638
+ * Uploads the value declared by {@link ShaderHelper.declareStandaloneCameraTime}.
1639
+ *
1640
+ * Must be the same clock the light pass uses, or a wave generator's caustics
1641
+ * would animate out of step with the surface they are refracted through.
1642
+ *
1643
+ * @param bindGroup - Bind group of the standalone program.
1644
+ * @param ctx - Current draw context; the current device when omitted, for
1645
+ * a program run outside drawing.
1646
+ * @internal
1647
+ */ static setStandaloneCameraTime(bindGroup, ctx) {
1648
+ bindGroup.setValue('camera', {
1649
+ elapsedTime: (ctx?.device ?? getDevice()).frameInfo.elapsedOverall * 0.001,
1650
+ _pad0: 0,
1651
+ _pad1: 0,
1652
+ _pad2: 0
1653
+ });
1654
+ }
1655
+ /**
1238
1656
  * Gets the elapsed time since last frame in seconds
1239
1657
  * @param scope - Current shader scope
1240
1658
  * @returns The elapsed time since last frame in seconds
@@ -1297,6 +1715,20 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1297
1715
  return scope.light.countParams;
1298
1716
  }
1299
1717
  /**
1718
+ * Buffer indices of the clustered lights every fragment shades regardless of its
1719
+ * cluster, zero-terminated.
1720
+ * @internal
1721
+ */ static getGlobalLights(scope) {
1722
+ return scope.light.globalLights;
1723
+ }
1724
+ /**
1725
+ * Nonzero when the clusters were sliced for an orthographic camera: depth slices
1726
+ * are then linear in view depth rather than exponential.
1727
+ * @internal
1728
+ */ static getClusterOrthographic(scope) {
1729
+ return scope.light.clusterOrtho;
1730
+ }
1731
+ /**
1300
1732
  * Number of shadow-casting lights at the head of the clustered light buffer.
1301
1733
  * Only defined on the screen-space shadow mask path (ctx.screenSpaceShadowMask);
1302
1734
  * a clustered light whose buffer index is `<= N` samples the shadow mask.
@@ -1337,10 +1769,65 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1337
1769
  }
1338
1770
  return tex;
1339
1771
  }
1772
+ /**
1773
+ * Lazily-created 1x1x1 rgba8unorm array texture used as the transmission
1774
+ * thickness binding fallback.
1775
+ *
1776
+ * @remarks
1777
+ * Filled with 1, not left at the zero-initialised default. The thickness
1778
+ * channel encodes `1 - opticalDepth / 5`, so 0 is *maximum* optical depth —
1779
+ * the worst possible value to fall back to, and the opposite of what the real
1780
+ * pass clears its unused channels to. {@link ShaderHelper.getDummyShadowMask}
1781
+ * gets away with zeroes because 0 reads as "fully shadowed" there.
1782
+ *
1783
+ * On the default pipeline this is never sampled: `ctx.transmissionThickness`
1784
+ * and the thickness module's `enabled` are computed from the same condition.
1785
+ * A custom `camera.renderPipeline` that omits the module can break that tie,
1786
+ * and this fallback is what keeps that from turning into full-strength
1787
+ * transmission across the whole screen.
1788
+ * @internal
1789
+ */ static getDummyTransmissionThickness(device) {
1790
+ let tex = this._dummyTransmissionThickness;
1791
+ if (!tex || tex.disposed) {
1792
+ tex = device.createTexture2DArray('rgba8unorm', 1, 1, 1, {
1793
+ mipmapping: false
1794
+ });
1795
+ tex.name = 'DummyTransmissionThickness';
1796
+ tex.update(new Uint8Array([
1797
+ 255,
1798
+ 255,
1799
+ 255,
1800
+ 255
1801
+ ]), 0, 0, 0, 1, 1, 1);
1802
+ this._dummyTransmissionThickness = tex;
1803
+ }
1804
+ return tex;
1805
+ }
1340
1806
  /** @internal */ static getClusteredLightIndexTexture(scope) {
1341
1807
  return scope[UNIFORM_NAME_LIGHT_INDEX_TEXTURE];
1342
1808
  }
1343
1809
  /**
1810
+ * Whether clusters hold variable-length light lists in storage buffers (WebGPU),
1811
+ * rather than 16 byte-sized slots in a texture.
1812
+ * @internal
1813
+ */ static usesClusterLightLists() {
1814
+ return getDevice().type === 'webgpu';
1815
+ }
1816
+ /**
1817
+ * Per cluster, the offset of its light list and the number of lights in it, two
1818
+ * uints each. Only on {@link ShaderHelper.usesClusterLightLists}.
1819
+ * @internal
1820
+ */ static getClusterGrid(scope) {
1821
+ return scope[UNIFORM_NAME_CLUSTER_GRID];
1822
+ }
1823
+ /**
1824
+ * Light buffer indices of every cluster's lights, one list after another. Only on
1825
+ * {@link ShaderHelper.usesClusterLightLists}.
1826
+ * @internal
1827
+ */ static getClusterLightList(scope) {
1828
+ return scope[UNIFORM_NAME_CLUSTER_LIGHT_LIST];
1829
+ }
1830
+ /**
1344
1831
  * Clustered shadow factor for a light, in `[0,1]` (1 = fully lit).
1345
1832
  *
1346
1833
  * - Lights whose clustered buffer index is greater than `numShadowLights` are not
@@ -1383,6 +1870,30 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1383
1870
  return pb.getGlobalScope()[funcName](lightIndex);
1384
1871
  }
1385
1872
  /**
1873
+ * Encoded light-space thickness for a light.
1874
+ *
1875
+ * @internal
1876
+ */ static sampleTransmissionThickness(scope, lightIndex) {
1877
+ const pb = scope.$builder;
1878
+ const that = this;
1879
+ const funcName = 'Z_sampleTransmissionThickness';
1880
+ pb.func(funcName, [
1881
+ pb.int('lightIndex')
1882
+ ], function() {
1883
+ this.$if(pb.greaterThan(this.lightIndex, that.getNumShadowLights(this)), function() {
1884
+ this.$return(pb.float(1));
1885
+ });
1886
+ this.$l.ordinal = pb.sub(this.lightIndex, 1);
1887
+ this.$l.layer = pb.div(this.ordinal, 4);
1888
+ this.$l.channel = pb.sub(this.ordinal, pb.mul(this.layer, 4));
1889
+ this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), that.getRenderSize(this));
1890
+ this.$l.texel = pb.textureArraySampleLevel(this[UNIFORM_NAME_TRANSMISSION_THICKNESS], this.uv, this.layer, 0);
1891
+ this.$l.selector = pb.vec4(pb.float(pb.equal(this.channel, 0)), pb.float(pb.equal(this.channel, 1)), pb.float(pb.equal(this.channel, 2)), pb.float(pb.equal(this.channel, 3)));
1892
+ this.$return(pb.dot(this.texel, this.selector));
1893
+ });
1894
+ return pb.getGlobalScope()[funcName](lightIndex);
1895
+ }
1896
+ /**
1386
1897
  * Gets the uniform variable that contains atmosphere parameters
1387
1898
  * @param scope - Current shader scope
1388
1899
  * @returns The atmosphere parameters
@@ -1505,16 +2016,21 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1505
2016
  }
1506
2017
  }
1507
2018
  /** @internal */ static getLightPositionAndRange(scope, lightIndex) {
1508
- return scope[UNIFORM_NAME_LIGHT_BUFFER].at(scope.$builder.mul(lightIndex, 4));
2019
+ return this.getLightVector(scope, lightIndex, 0);
1509
2020
  }
1510
2021
  /** @internal */ static getLightDirectionAndCutoff(scope, lightIndex) {
1511
- return scope[UNIFORM_NAME_LIGHT_BUFFER].at(scope.$builder.add(scope.$builder.mul(lightIndex, 4), 1));
2022
+ return this.getLightVector(scope, lightIndex, 1);
1512
2023
  }
1513
2024
  /** @internal */ static getLightColorAndIntensity(scope, lightIndex) {
1514
- return scope[UNIFORM_NAME_LIGHT_BUFFER].at(scope.$builder.add(scope.$builder.mul(lightIndex, 4), 2));
2025
+ return this.getLightVector(scope, lightIndex, 2);
1515
2026
  }
1516
2027
  /** @internal */ static getLightExtra(scope, lightIndex) {
1517
- return scope[UNIFORM_NAME_LIGHT_BUFFER].at(scope.$builder.add(scope.$builder.mul(lightIndex, 4), 3));
2028
+ return this.getLightVector(scope, lightIndex, 3);
2029
+ }
2030
+ /** Vector `k` of the 4 of a clustered light, read from the light buffer. */ static getLightVector(scope, lightIndex, k) {
2031
+ const pb = scope.$builder;
2032
+ const buffer = scope[UNIFORM_NAME_LIGHT_BUFFER];
2033
+ return buffer.at(pb.add(pb.mul(lightIndex, 4), k));
1518
2034
  }
1519
2035
  /**
1520
2036
  * Sets the clip space position in vertex shader
@@ -1596,6 +2112,36 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1596
2112
  return pb.add(worldPos, pb.mul(unitNormal, offset));
1597
2113
  }
1598
2114
  /**
2115
+ * How far a rect light's shadow lookup can be trusted at a receiver, as a
2116
+ * weight for `mix(1, shadow, weight)`.
2117
+ *
2118
+ * @remarks
2119
+ * A rect light's shadow is a cube map rendered from its centre, but its
2120
+ * lighting integrates the whole panel. Where the surface turns away from the
2121
+ * centre the panel's far edge still lights it, while the centre already sees
2122
+ * the receiver occlude itself - so the centre's shadow cuts a hard, texel
2123
+ * stepped terminator through lit surface. The weight fades that lookup out as
2124
+ * the centre sinks to the receiver's horizon.
2125
+ *
2126
+ * The band is a fixed, narrow cosine range rather than one scaled by the
2127
+ * panel's size: the stepped edge only lives where the centre grazes the
2128
+ * surface, and its width is set by shadow bias and texel footprint, not by
2129
+ * the panel. A band as wide as the panel's angular radius also caught flat
2130
+ * receivers - a floor seen low from a large upright panel - and faded their
2131
+ * cast shadows, which have no terminator to hide.
2132
+ *
2133
+ * @param worldNormal - Geometric normal; the terminator follows the surface,
2134
+ * not its normal map.
2135
+ * @param posRange - Light position (xyz).
2136
+ * @internal
2137
+ */ static getRectLightShadowWeight(scope, worldPos, worldNormal, posRange) {
2138
+ const pb = scope.$builder;
2139
+ const toCenter = pb.sub(posRange.xyz, worldPos);
2140
+ const unitNormal = pb.mul(worldNormal, pb.div(1, pb.max(pb.length(worldNormal), 1e-6)));
2141
+ const cosCenter = pb.div(pb.dot(unitNormal, toCenter), pb.max(pb.length(toCenter), 1e-6));
2142
+ return pb.smoothStep(0, 0.01, cosCenter);
2143
+ }
2144
+ /**
1599
2145
  * Calculates shadow of current fragment
1600
2146
  *
1601
2147
  * @param scope - Shader scope
@@ -1623,17 +2169,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1623
2169
  // would self-occlude it before projecting into shadow space. Scaled per
1624
2170
  // cascade, because each cascade has its own texel world size.
1625
2171
  this.$l.biasedPos = that.applyShadowNormalOffset(shadowMapParams.lightType, this, this.worldPos, this.worldNormal, this.NoL, that.getShadowCascadeBiasScale(this, this.split));
1626
- if (ctx.device.type === 'webgl') {
1627
- this.$l.shadowVertex = pb.vec4();
1628
- this.$for(pb.int('cascade'), 0, 4, function() {
1629
- this.$if(pb.equal(this.cascade, this.split), function() {
1630
- this.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1), this.cascade);
1631
- this.$break();
1632
- });
1633
- });
1634
- } else {
1635
- this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1), this.split);
1636
- }
2172
+ this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1), this.split);
1637
2173
  this.$l.shadow = shadowMapParams.impl.computeShadowCSM(shadowMapParams, this, this.shadowVertex, this.NoL, this.split);
1638
2174
  this.shadow = pb.clamp(this.shadow, 0, 1);
1639
2175
  this.$l.shadowDistance = that.getShadowCameraParams(scope).w;
@@ -1648,7 +2184,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1648
2184
  // Normal offset bias - see the cascaded branch above.
1649
2185
  this.$l.biasedPos = that.applyShadowNormalOffset(shadowMapParams.lightType, this, this.worldPos, this.worldNormal, this.NoL);
1650
2186
  this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1));
1651
- this.$l.shadow = shadowMapParams.impl.computeShadow(shadowMapParams, this, this.shadowVertex, this.NoL);
2187
+ this.$l.shadow = shadowMapParams.impl.computeShadow(shadowMapParams, this, this.shadowVertex, this.NoL, this.worldNormal);
1652
2188
  this.shadow = pb.clamp(this.shadow, 0, 1);
1653
2189
  this.$l.shadowDistance = that.getShadowCameraParams(scope).w;
1654
2190
  this.shadow = pb.mix(this.shadow, 1, pb.smoothStep(pb.mul(this.shadowDistance, 0.8), this.shadowDistance, pb.distance(that.getCameraPosition(this), this.worldPos)));
@@ -1659,24 +2195,22 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1659
2195
  });
1660
2196
  return pb.getGlobalScope()[funcName](worldPos, worldNormal, NoL);
1661
2197
  }
1662
- static applyFog(scope, worldPos, color, ctx) {
2198
+ static applyFog(scope, worldPos, color, ctx, premultiplied = true) {
1663
2199
  const pb = scope.$builder;
1664
2200
  const that = this;
1665
2201
  if (ctx.materialFlags & MaterialVaryingFlags.APPLY_FOG) {
1666
- const funcName = 'Z_applyFog';
2202
+ const funcName = premultiplied ? 'Z_applyFog' : 'Z_applyFogStraight';
1667
2203
  pb.func(funcName, [
1668
2204
  pb.vec3('worldPos'),
1669
2205
  pb.vec4('color').inout()
1670
2206
  ], function() {
1671
- this.$if(pb.notEqual(this.fog.additive, 0), function() {
1672
- this.$return();
1673
- });
1674
2207
  this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), that.getRenderSize(this));
1675
- this.$l.fogging = calculateFog(this, this.fog.withAerialPerspective, this.fog.fogType, this.fog.atmosphereParams, this.fog.heightFogParams, this.uv, false, that.getCameraPosition(this).xyz, this.worldPos, this.fog.additive, this[UNIFORM_NAME_AERIALPERSPECTIVE_LUT], this[UNIFORM_NAME_SKYDISTANTLIGHT_LUT]);
1676
- this.$l.foggingAlpha = pb.sub(1, pb.mul(pb.sub(1, this.fogging.a), this.color.a));
1677
- this.$l.foggingRGB = pb.mul(this.fogging.rgb, this.color.a);
1678
- this.color = pb.vec4(pb.add(pb.mul(this.color.rgb, this.foggingAlpha), this.foggingRGB), this.color.a);
1679
- //this.color = pb.vec4(pb.vec3(pb.mix(this.u0, this.u1, this.factor)), this.color.a);
2208
+ this.$l.fogging = calculateFog(this, this.fog.withAerialPerspective, this.fog.fogType, this.fog.atmosphereParams, this.fog.heightFogParams, this.uv, false, that.getCameraPosition(this).xyz, this.worldPos, this.fog.additive, this[UNIFORM_NAME_AERIALPERSPECTIVE_LUT], this[UNIFORM_NAME_SKYDISTANTLIGHT_LUT], this[UNIFORM_NAME_FOG_SKYLIGHT_CUBEMAP]);
2209
+ // Premultiplied color: attenuate by the fog transmittance and add the in-scattering weighted
2210
+ // by coverage. Additive light passes and additive blending (alpha 0) get attenuation only --
2211
+ // calculateFog already zeroes the in-scattering for the former. Straight (non-premultiplied)
2212
+ // color is scaled by coverage later, so the in-scattering is added unweighted.
2213
+ this.color = pb.vec4(pb.add(pb.mul(this.color.rgb, this.fogging.a), premultiplied ? pb.mul(this.fogging.rgb, this.color.a) : this.fogging.rgb), this.color.a);
1680
2214
  });
1681
2215
  scope[funcName](worldPos, color);
1682
2216
  }
@@ -1809,7 +2343,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1809
2343
  */ static sampleLinearDepth(scope, tex, uv, level) {
1810
2344
  const pb = scope.$builder;
1811
2345
  const depth = pb.textureSampleLevel(tex, uv, level);
1812
- return pb.getDevice().type === 'webgl' ? decodeNormalizedFloatFromRGBA(scope, depth) : depth.r;
2346
+ return depth.r;
1813
2347
  }
1814
2348
  static samplePositionFromDepth(scope, depthTex, uv, mat, cameraNearFar) {
1815
2349
  const pb = scope.$builder;
@@ -1860,10 +2394,19 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1860
2394
  });
1861
2395
  return pb.getGlobalScope()[funcName](outputColor);
1862
2396
  }
1863
- /** @internal */ static getMaxClusterLights() {
1864
- return getDevice().type === 'webgl' ? 64 : 255;
2397
+ /**
2398
+ * Capacity of the clustered light buffer, excluding the unused slot 0.
2399
+ *
2400
+ * @remarks
2401
+ * On WebGPU the buffers are storage buffers grown to fit and light indices are 32
2402
+ * bits, so this only bounds the CPU-side work. On WebGL2 the buffer is 4 vec4 per
2403
+ * light in a uniform block, and 255 is the most a byte-sized cluster slot can address.
2404
+ * @internal
2405
+ */ static getMaxClusterLights() {
2406
+ const device = getDevice();
2407
+ return device.type === 'webgpu' ? 65535 : 255;
1865
2408
  }
1866
2409
  }
1867
2410
 
1868
- export { ShaderHelper };
2411
+ export { CAUSTIC_HEIGHT_BIAS, MAX_CAUSTIC_WATERS, ShaderHelper };
1869
2412
  //# sourceMappingURL=helper.js.map