@zephyr3d/scene 0.9.26 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/README.md +60 -27
  2. package/dist/animation/animation_bank.js +634 -0
  3. package/dist/animation/animation_bank.js.map +1 -0
  4. package/dist/animation/animation_bank_gpu.js +256 -0
  5. package/dist/animation/animation_bank_gpu.js.map +1 -0
  6. package/dist/animation/animationset.js +146 -30
  7. package/dist/animation/animationset.js.map +1 -1
  8. package/dist/animation/cloth/gpu_cloth_component.js +406 -0
  9. package/dist/animation/cloth/gpu_cloth_component.js.map +1 -0
  10. package/dist/animation/cloth/gpu_cloth_system.js +332 -202
  11. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
  12. package/dist/animation/gpu_animation_driver.js +289 -0
  13. package/dist/animation/gpu_animation_driver.js.map +1 -0
  14. package/dist/animation/joint_dynamics/collision.js +112 -1
  15. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  16. package/dist/animation/joint_dynamics/controller.js +6 -0
  17. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js +11 -0
  19. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  20. package/dist/animation/joint_dynamics/solver.js +50 -12
  21. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  22. package/dist/animation/joint_dynamics/types.js.map +1 -1
  23. package/dist/animation/skeleton.js +300 -56
  24. package/dist/animation/skeleton.js.map +1 -1
  25. package/dist/animation/skin_palette_atlas.js +252 -0
  26. package/dist/animation/skin_palette_atlas.js.map +1 -0
  27. package/dist/animation/spring/multi_chain_spring_system.js +298 -38
  28. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  29. package/dist/animation/spring/spring_chain.js +2 -0
  30. package/dist/animation/spring/spring_chain.js.map +1 -1
  31. package/dist/animation/spring/spring_collider.js +159 -21
  32. package/dist/animation/spring/spring_collider.js.map +1 -1
  33. package/dist/animation/spring/spring_particle.js +1 -0
  34. package/dist/animation/spring/spring_particle.js.map +1 -1
  35. package/dist/animation/spring/spring_solver.js +173 -0
  36. package/dist/animation/spring/spring_solver.js.map +1 -0
  37. package/dist/animation/spring/spring_system.js +249 -24
  38. package/dist/animation/spring/spring_system.js.map +1 -1
  39. package/dist/animation/spring_modifier.js +32 -1
  40. package/dist/animation/spring_modifier.js.map +1 -1
  41. package/dist/app/app.js +26 -0
  42. package/dist/app/app.js.map +1 -1
  43. package/dist/app/engine.js +86 -15
  44. package/dist/app/engine.js.map +1 -1
  45. package/dist/app/gamepad.js +191 -0
  46. package/dist/app/gamepad.js.map +1 -0
  47. package/dist/app/inputactions.js +271 -0
  48. package/dist/app/inputactions.js.map +1 -0
  49. package/dist/app/inputmgr.js +331 -22
  50. package/dist/app/inputmgr.js.map +1 -1
  51. package/dist/app/loading.js +138 -0
  52. package/dist/app/loading.js.map +1 -0
  53. package/dist/app/pipelinecache.js +209 -0
  54. package/dist/app/pipelinecache.js.map +1 -0
  55. package/dist/app/runtimescript.js +25 -0
  56. package/dist/app/runtimescript.js.map +1 -1
  57. package/dist/app/scriptingsystem.js +54 -0
  58. package/dist/app/scriptingsystem.js.map +1 -1
  59. package/dist/app/scriptregistry.js +7 -2
  60. package/dist/app/scriptregistry.js.map +1 -1
  61. package/dist/asset/asset_manifest.js +59 -0
  62. package/dist/asset/asset_manifest.js.map +1 -0
  63. package/dist/asset/asset_preload.js +258 -0
  64. package/dist/asset/asset_preload.js.map +1 -0
  65. package/dist/asset/assetmanager.js +117 -65
  66. package/dist/asset/assetmanager.js.map +1 -1
  67. package/dist/asset/loaders/ktx2/basis/LICENSE +201 -0
  68. package/dist/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  69. package/dist/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  70. package/dist/asset/loaders/ktx2/basis_location.js +17 -0
  71. package/dist/asset/loaders/ktx2/basis_location.js.map +1 -0
  72. package/dist/asset/loaders/ktx2/ktx2_loader.js +88 -0
  73. package/dist/asset/loaders/ktx2/ktx2_loader.js.map +1 -0
  74. package/dist/asset/loaders/ktx2/ktx2_targets.js +118 -0
  75. package/dist/asset/loaders/ktx2/ktx2_targets.js.map +1 -0
  76. package/dist/asset/loaders/ktx2/transcoder_pool.js +259 -0
  77. package/dist/asset/loaders/ktx2/transcoder_pool.js.map +1 -0
  78. package/dist/asset/mesh_lod_builder.js +104 -0
  79. package/dist/asset/mesh_lod_builder.js.map +1 -0
  80. package/dist/asset/mesh_settings.js +111 -0
  81. package/dist/asset/mesh_settings.js.map +1 -0
  82. package/dist/asset/meshopt_decoder.js +18 -0
  83. package/dist/asset/meshopt_decoder.js.map +1 -0
  84. package/dist/asset/meshopt_simplifier.js +26 -0
  85. package/dist/asset/meshopt_simplifier.js.map +1 -0
  86. package/dist/asset/model.js +177 -3
  87. package/dist/asset/model.js.map +1 -1
  88. package/dist/asset/texture_settings.js +231 -0
  89. package/dist/asset/texture_settings.js.map +1 -0
  90. package/dist/asset/zmsh_binary.js +131 -0
  91. package/dist/asset/zmsh_binary.js.map +1 -0
  92. package/dist/asset/zsdf_binary.js +111 -0
  93. package/dist/asset/zsdf_binary.js.map +1 -0
  94. package/dist/blitter/bilateralblur.js +1 -2
  95. package/dist/blitter/bilateralblur.js.map +1 -1
  96. package/dist/blitter/gaussianblur.js +1 -6
  97. package/dist/blitter/gaussianblur.js.map +1 -1
  98. package/dist/camera/base.js +31 -10
  99. package/dist/camera/base.js.map +1 -1
  100. package/dist/camera/camera.js +178 -200
  101. package/dist/camera/camera.js.map +1 -1
  102. package/dist/camera/follow.js +342 -0
  103. package/dist/camera/follow.js.map +1 -0
  104. package/dist/camera/fps.js +9 -7
  105. package/dist/camera/fps.js.map +1 -1
  106. package/dist/camera/orbit.js +46 -19
  107. package/dist/camera/orbit.js.map +1 -1
  108. package/dist/index.d.ts +30475 -19865
  109. package/dist/index.js +67 -12
  110. package/dist/index.js.map +1 -1
  111. package/dist/material/blinn.js +3 -0
  112. package/dist/material/blinn.js.map +1 -1
  113. package/dist/material/eye.js +1 -1
  114. package/dist/material/eye.js.map +1 -1
  115. package/dist/material/hair.js +2 -3
  116. package/dist/material/hair.js.map +1 -1
  117. package/dist/material/hairstrand.js +235 -235
  118. package/dist/material/hairstrand.js.map +1 -1
  119. package/dist/material/lambert.js +4 -1
  120. package/dist/material/lambert.js.map +1 -1
  121. package/dist/material/material.js +362 -300
  122. package/dist/material/material.js.map +1 -1
  123. package/dist/material/meshmaterial.js +118 -45
  124. package/dist/material/meshmaterial.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  126. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  128. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  130. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  132. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  133. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  134. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  135. package/dist/material/mixins/lit.js +223 -83
  136. package/dist/material/mixins/lit.js.map +1 -1
  137. package/dist/material/mixins/pbr/common.js +31 -206
  138. package/dist/material/mixins/pbr/common.js.map +1 -1
  139. package/dist/material/mtoon.js +1 -1
  140. package/dist/material/mtoon.js.map +1 -1
  141. package/dist/material/pbrmr.js +3 -0
  142. package/dist/material/pbrmr.js.map +1 -1
  143. package/dist/material/pbrsg.js +3 -0
  144. package/dist/material/pbrsg.js.map +1 -1
  145. package/dist/material/sdf_debug.js +78 -0
  146. package/dist/material/sdf_debug.js.map +1 -0
  147. package/dist/material/shader/helper.js +733 -190
  148. package/dist/material/shader/helper.js.map +1 -1
  149. package/dist/material/shader/terrain_blend.js +204 -43
  150. package/dist/material/shader/terrain_blend.js.map +1 -1
  151. package/dist/material/skin.js +331 -297
  152. package/dist/material/skin.js.map +1 -1
  153. package/dist/material/skinprofile.js +1058 -0
  154. package/dist/material/skinprofile.js.map +1 -0
  155. package/dist/material/sss.js +353 -0
  156. package/dist/material/sss.js.map +1 -0
  157. package/dist/material/sss_kernel.js +265 -0
  158. package/dist/material/sss_kernel.js.map +1 -0
  159. package/dist/material/sssprofile.js +879 -0
  160. package/dist/material/sssprofile.js.map +1 -0
  161. package/dist/material/subsurfaceprofile.js +4 -8
  162. package/dist/material/subsurfaceprofile.js.map +1 -1
  163. package/dist/material/terrain-cm.js +464 -188
  164. package/dist/material/terrain-cm.js.map +1 -1
  165. package/dist/material/unlit.js +3 -0
  166. package/dist/material/unlit.js.map +1 -1
  167. package/dist/material/water.js +2009 -201
  168. package/dist/material/water.js.map +1 -1
  169. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  172. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  173. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  174. package/dist/physics/character.js +226 -0
  175. package/dist/physics/character.js.map +1 -0
  176. package/dist/physics/collider.js +209 -0
  177. package/dist/physics/collider.js.map +1 -0
  178. package/dist/physics/events.js +79 -0
  179. package/dist/physics/events.js.map +1 -0
  180. package/dist/physics/joint.js +272 -0
  181. package/dist/physics/joint.js.map +1 -0
  182. package/dist/physics/node_physics.js +234 -0
  183. package/dist/physics/node_physics.js.map +1 -0
  184. package/dist/physics/part.js +91 -0
  185. package/dist/physics/part.js.map +1 -0
  186. package/dist/physics/rigid_body.js +379 -0
  187. package/dist/physics/rigid_body.js.map +1 -0
  188. package/dist/physics/vehicle.js +335 -0
  189. package/dist/physics/vehicle.js.map +1 -0
  190. package/dist/physics/world.js +25 -0
  191. package/dist/physics/world.js.map +1 -0
  192. package/dist/posteffect/bloom.js +49 -14
  193. package/dist/posteffect/bloom.js.map +1 -1
  194. package/dist/posteffect/motionblur.js +668 -63
  195. package/dist/posteffect/motionblur.js.map +1 -1
  196. package/dist/posteffect/posteffect.js +12 -1
  197. package/dist/posteffect/posteffect.js.map +1 -1
  198. package/dist/posteffect/postsss.js +572 -0
  199. package/dist/posteffect/postsss.js.map +1 -0
  200. package/dist/posteffect/sao.js +48 -50
  201. package/dist/posteffect/sao.js.map +1 -1
  202. package/dist/posteffect/skinsss.js +605 -355
  203. package/dist/posteffect/skinsss.js.map +1 -1
  204. package/dist/posteffect/ssgi.js +91 -162
  205. package/dist/posteffect/ssgi.js.map +1 -1
  206. package/dist/posteffect/ssr.js +3 -11
  207. package/dist/posteffect/ssr.js.map +1 -1
  208. package/dist/posteffect/sss.js +34 -15
  209. package/dist/posteffect/sss.js.map +1 -1
  210. package/dist/posteffect/taa.js +1 -0
  211. package/dist/posteffect/taa.js.map +1 -1
  212. package/dist/render/abuffer_oit.js +40 -39
  213. package/dist/render/abuffer_oit.js.map +1 -1
  214. package/dist/render/clipmap.js +328 -62
  215. package/dist/render/clipmap.js.map +1 -1
  216. package/dist/render/cluster_light.js +958 -224
  217. package/dist/render/cluster_light.js.map +1 -1
  218. package/dist/render/cull_visitor.js +26 -5
  219. package/dist/render/cull_visitor.js.map +1 -1
  220. package/dist/render/debug_views.js +37 -0
  221. package/dist/render/debug_views.js.map +1 -0
  222. package/dist/render/depthpass.js +5 -1
  223. package/dist/render/depthpass.js.map +1 -1
  224. package/dist/render/drawable_mixin.js +53 -6
  225. package/dist/render/drawable_mixin.js.map +1 -1
  226. package/dist/render/dualdepthpeeling_oit.js +53 -53
  227. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  228. package/dist/render/envlight.js +196 -113
  229. package/dist/render/envlight.js.map +1 -1
  230. package/dist/render/fbm_wavegenerator.js +8 -13
  231. package/dist/render/fbm_wavegenerator.js.map +1 -1
  232. package/dist/render/fft_wavegenerator.js +268 -136
  233. package/dist/render/fft_wavegenerator.js.map +1 -1
  234. package/dist/render/gerstner_wavegenerator.js +113 -28
  235. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  236. package/dist/render/gi/probe_volume.js +1334 -0
  237. package/dist/render/gi/probe_volume.js.map +1 -0
  238. package/dist/render/gi/scene_gi.js +282 -0
  239. package/dist/render/gi/scene_gi.js.map +1 -0
  240. package/dist/render/gi/voxel_atlas.js +198 -0
  241. package/dist/render/gi/voxel_atlas.js.map +1 -0
  242. package/dist/render/gi/voxel_lighting.js +652 -0
  243. package/dist/render/gi/voxel_lighting.js.map +1 -0
  244. package/dist/render/gi/voxel_scene.js +642 -0
  245. package/dist/render/gi/voxel_scene.js.map +1 -0
  246. package/dist/render/gi/voxelize_pass.js +223 -0
  247. package/dist/render/gi/voxelize_pass.js.map +1 -0
  248. package/dist/render/globalbindgroup_allocator.js +3 -2
  249. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  250. package/dist/render/gpu_picking.js +9 -36
  251. package/dist/render/gpu_picking.js.map +1 -1
  252. package/dist/render/hzb.js +80 -22
  253. package/dist/render/hzb.js.map +1 -1
  254. package/dist/render/instance_culling.js +499 -0
  255. package/dist/render/instance_culling.js.map +1 -0
  256. package/dist/render/lightpass.js +23 -9
  257. package/dist/render/lightpass.js.map +1 -1
  258. package/dist/render/lod.js +46 -0
  259. package/dist/render/lod.js.map +1 -0
  260. package/dist/render/oit.js +11 -11
  261. package/dist/render/oit.js.map +1 -1
  262. package/dist/render/primitive.js +301 -9
  263. package/dist/render/primitive.js.map +1 -1
  264. package/dist/render/primitive_readback.js +209 -0
  265. package/dist/render/primitive_readback.js.map +1 -0
  266. package/dist/render/render_queue.js +198 -11
  267. package/dist/render/render_queue.js.map +1 -1
  268. package/dist/render/renderer.js +14 -7
  269. package/dist/render/renderer.js.map +1 -1
  270. package/dist/render/rendergraph/blackboard.js +15 -1
  271. package/dist/render/rendergraph/blackboard.js.map +1 -1
  272. package/dist/render/rendergraph/executor.js +136 -0
  273. package/dist/render/rendergraph/executor.js.map +1 -1
  274. package/dist/render/rendergraph/forward_plus_builder.js +585 -59
  275. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  276. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  277. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  278. package/dist/render/rendergraph/history_resources.js +8 -1
  279. package/dist/render/rendergraph/history_resources.js.map +1 -1
  280. package/dist/render/rendergraph/scene_render_context.js +31 -2
  281. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  282. package/dist/render/rendergraph/types.js.map +1 -1
  283. package/dist/render/renderpass.js +28 -5
  284. package/dist/render/renderpass.js.map +1 -1
  285. package/dist/render/sdf/box_allocator.js +98 -0
  286. package/dist/render/sdf/box_allocator.js.map +1 -0
  287. package/dist/render/sdf/global_sdf.js +865 -0
  288. package/dist/render/sdf/global_sdf.js.map +1 -0
  289. package/dist/render/sdf/scene_sdf.js +562 -0
  290. package/dist/render/sdf/scene_sdf.js.map +1 -0
  291. package/dist/render/sdf/sdf_atlas.js +227 -0
  292. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  293. package/dist/render/sdf/sdf_programs.js +42 -0
  294. package/dist/render/sdf/sdf_programs.js.map +1 -0
  295. package/dist/render/sdf/sdf_view.js +199 -0
  296. package/dist/render/sdf/sdf_view.js.map +1 -0
  297. package/dist/render/sdf/waterline.js +610 -0
  298. package/dist/render/sdf/waterline.js.map +1 -0
  299. package/dist/render/shadow_mask_pass.js +142 -25
  300. package/dist/render/shadow_mask_pass.js.map +1 -1
  301. package/dist/render/shore_wave_generator.js +1193 -0
  302. package/dist/render/shore_wave_generator.js.map +1 -0
  303. package/dist/render/sky.js +516 -86
  304. package/dist/render/sky.js.map +1 -1
  305. package/dist/render/transmission_thickness_pass.js +477 -0
  306. package/dist/render/transmission_thickness_pass.js.map +1 -0
  307. package/dist/render/underwater.js +524 -0
  308. package/dist/render/underwater.js.map +1 -0
  309. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  310. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  311. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  312. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  313. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  314. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  315. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  316. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  317. package/dist/render/volumetric_cloud/resources.js +380 -0
  318. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  319. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  320. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  321. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  322. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  323. package/dist/render/water_caustics.js +1195 -0
  324. package/dist/render/water_caustics.js.map +1 -0
  325. package/dist/render/water_foam.js +627 -0
  326. package/dist/render/water_foam.js.map +1 -0
  327. package/dist/render/water_interaction.js +1795 -0
  328. package/dist/render/water_interaction.js.map +1 -0
  329. package/dist/render/weightedblended_oit.js +36 -36
  330. package/dist/render/weightedblended_oit.js.map +1 -1
  331. package/dist/scene/basesprite.js +9 -4
  332. package/dist/scene/basesprite.js.map +1 -1
  333. package/dist/scene/batchgroup.js +108 -12
  334. package/dist/scene/batchgroup.js.map +1 -1
  335. package/dist/scene/environment.js +28 -7
  336. package/dist/scene/environment.js.map +1 -1
  337. package/dist/scene/foliage.js +1020 -0
  338. package/dist/scene/foliage.js.map +1 -0
  339. package/dist/scene/foliage_buckets.js +147 -0
  340. package/dist/scene/foliage_buckets.js.map +1 -0
  341. package/dist/scene/foliage_mask.js +358 -0
  342. package/dist/scene/foliage_mask.js.map +1 -0
  343. package/dist/scene/hair.js +15 -4
  344. package/dist/scene/hair.js.map +1 -1
  345. package/dist/scene/light.js +179 -13
  346. package/dist/scene/light.js.map +1 -1
  347. package/dist/scene/mesh.js +327 -45
  348. package/dist/scene/mesh.js.map +1 -1
  349. package/dist/scene/meshdrawable.js +6 -0
  350. package/dist/scene/meshdrawable.js.map +1 -1
  351. package/dist/scene/msdftext.js +9 -3
  352. package/dist/scene/msdftext.js.map +1 -1
  353. package/dist/scene/msdftextsprite.js +9 -3
  354. package/dist/scene/msdftextsprite.js.map +1 -1
  355. package/dist/scene/particlesys.js +8 -2
  356. package/dist/scene/particlesys.js.map +1 -1
  357. package/dist/scene/physics_settings.js +157 -0
  358. package/dist/scene/physics_settings.js.map +1 -0
  359. package/dist/scene/scene.js +190 -44
  360. package/dist/scene/scene.js.map +1 -1
  361. package/dist/scene/scene_node.js +147 -6
  362. package/dist/scene/scene_node.js.map +1 -1
  363. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  364. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  365. package/dist/scene/terrain-cm/grass.js +881 -42
  366. package/dist/scene/terrain-cm/grass.js.map +1 -1
  367. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  368. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  369. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  370. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  371. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  372. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  373. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  374. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  375. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  376. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  377. package/dist/scene/update_queue.js +54 -0
  378. package/dist/scene/update_queue.js.map +1 -0
  379. package/dist/scene/water.js +997 -96
  380. package/dist/scene/water.js.map +1 -1
  381. package/dist/scene/water_surface_sampler.js +324 -0
  382. package/dist/scene/water_surface_sampler.js.map +1 -0
  383. package/dist/scene/wind.js +317 -0
  384. package/dist/scene/wind.js.map +1 -0
  385. package/dist/shaders/atmosphere.js +388 -166
  386. package/dist/shaders/atmosphere.js.map +1 -1
  387. package/dist/shaders/fog.js +100 -32
  388. package/dist/shaders/fog.js.map +1 -1
  389. package/dist/shaders/gi.js +365 -0
  390. package/dist/shaders/gi.js.map +1 -0
  391. package/dist/shaders/ltc_rect.js +202 -0
  392. package/dist/shaders/ltc_rect.js.map +1 -0
  393. package/dist/shaders/misc.js +1 -10
  394. package/dist/shaders/misc.js.map +1 -1
  395. package/dist/shaders/noise.js +5 -1
  396. package/dist/shaders/noise.js.map +1 -1
  397. package/dist/shaders/probes.js +309 -0
  398. package/dist/shaders/probes.js.map +1 -0
  399. package/dist/shaders/sdf.js +537 -0
  400. package/dist/shaders/sdf.js.map +1 -0
  401. package/dist/shaders/shadow.js +104 -70
  402. package/dist/shaders/shadow.js.map +1 -1
  403. package/dist/shaders/skin_brdf.js +243 -0
  404. package/dist/shaders/skin_brdf.js.map +1 -0
  405. package/dist/shaders/ssr.js +6 -159
  406. package/dist/shaders/ssr.js.map +1 -1
  407. package/dist/shaders/temporal.js +38 -27
  408. package/dist/shaders/temporal.js.map +1 -1
  409. package/dist/shaders/water.js +40 -87
  410. package/dist/shaders/water.js.map +1 -1
  411. package/dist/shaders/water_medium.js +42 -0
  412. package/dist/shaders/water_medium.js.map +1 -0
  413. package/dist/shadow/dom.js +110 -19
  414. package/dist/shadow/dom.js.map +1 -1
  415. package/dist/shadow/esm.js +3 -3
  416. package/dist/shadow/esm.js.map +1 -1
  417. package/dist/shadow/pcf_opt.js +15 -48
  418. package/dist/shadow/pcf_opt.js.map +1 -1
  419. package/dist/shadow/pcf_pd.js +15 -53
  420. package/dist/shadow/pcf_pd.js.map +1 -1
  421. package/dist/shadow/pcss.js +34 -9
  422. package/dist/shadow/pcss.js.map +1 -1
  423. package/dist/shadow/shadow_impl.js +3 -8
  424. package/dist/shadow/shadow_impl.js.map +1 -1
  425. package/dist/shadow/shadow_region.js +87 -49
  426. package/dist/shadow/shadow_region.js.map +1 -1
  427. package/dist/shadow/shadowmapper.js +93 -53
  428. package/dist/shadow/shadowmapper.js.map +1 -1
  429. package/dist/shadow/ssm.js +18 -72
  430. package/dist/shadow/ssm.js.map +1 -1
  431. package/dist/shadow/vsm.js +3 -3
  432. package/dist/shadow/vsm.js.map +1 -1
  433. package/dist/shapes/shape.js +44 -1
  434. package/dist/shapes/shape.js.map +1 -1
  435. package/dist/utility/buoyancy.js +287 -0
  436. package/dist/utility/buoyancy.js.map +1 -0
  437. package/dist/utility/physical.js +10 -1
  438. package/dist/utility/physical.js.map +1 -1
  439. package/dist/utility/pmrem.js +17 -43
  440. package/dist/utility/pmrem.js.map +1 -1
  441. package/dist/utility/sdf/bake.js +435 -0
  442. package/dist/utility/sdf/bake.js.map +1 -0
  443. package/dist/utility/sdf/bvh.js +576 -0
  444. package/dist/utility/sdf/bvh.js.map +1 -0
  445. package/dist/utility/serialization/manager.js +36 -6
  446. package/dist/utility/serialization/manager.js.map +1 -1
  447. package/dist/utility/serialization/scene/animation.js +77 -7
  448. package/dist/utility/serialization/scene/animation.js.map +1 -1
  449. package/dist/utility/serialization/scene/batch.js +11 -4
  450. package/dist/utility/serialization/scene/batch.js.map +1 -1
  451. package/dist/utility/serialization/scene/camera.js +140 -279
  452. package/dist/utility/serialization/scene/camera.js.map +1 -1
  453. package/dist/utility/serialization/scene/cloth.js +25 -0
  454. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  455. package/dist/utility/serialization/scene/common.js +14 -6
  456. package/dist/utility/serialization/scene/common.js.map +1 -1
  457. package/dist/utility/serialization/scene/foliage.js +821 -0
  458. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  459. package/dist/utility/serialization/scene/hair.js +11 -4
  460. package/dist/utility/serialization/scene/hair.js.map +1 -1
  461. package/dist/utility/serialization/scene/light.js +115 -7
  462. package/dist/utility/serialization/scene/light.js.map +1 -1
  463. package/dist/utility/serialization/scene/material.js +388 -220
  464. package/dist/utility/serialization/scene/material.js.map +1 -1
  465. package/dist/utility/serialization/scene/mesh.js +69 -4
  466. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  467. package/dist/utility/serialization/scene/node.js +140 -5
  468. package/dist/utility/serialization/scene/node.js.map +1 -1
  469. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  470. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  471. package/dist/utility/serialization/scene/particle.js +11 -4
  472. package/dist/utility/serialization/scene/particle.js.map +1 -1
  473. package/dist/utility/serialization/scene/physics.js +120 -0
  474. package/dist/utility/serialization/scene/physics.js.map +1 -0
  475. package/dist/utility/serialization/scene/primitive.js +124 -10
  476. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  477. package/dist/utility/serialization/scene/scene.js +1687 -51
  478. package/dist/utility/serialization/scene/scene.js.map +1 -1
  479. package/dist/utility/serialization/scene/sprite.js +11 -4
  480. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  481. package/dist/utility/serialization/scene/terrain.js +163 -33
  482. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  483. package/dist/utility/serialization/scene/text.js +11 -4
  484. package/dist/utility/serialization/scene/text.js.map +1 -1
  485. package/dist/utility/serialization/scene/water.js +2473 -114
  486. package/dist/utility/serialization/scene/water.js.map +1 -1
  487. package/dist/utility/serialization/types.js.map +1 -1
  488. package/dist/utility/shprojector.js +97 -109
  489. package/dist/utility/shprojector.js.map +1 -1
  490. package/dist/utility/textures/foam.js +170 -0
  491. package/dist/utility/textures/foam.js.map +1 -0
  492. package/dist/utility/textures/ggxlut.js +17 -43
  493. package/dist/utility/textures/ggxlut.js.map +1 -1
  494. package/dist/utility/textures/ltcdata.js +4 -4
  495. package/dist/utility/textures/ltcdata.js.map +1 -1
  496. package/dist/utility/textures/ltclut.js +16 -6
  497. package/dist/utility/textures/ltclut.js.map +1 -1
  498. package/dist/utility/unsupported.js +24 -0
  499. package/dist/utility/unsupported.js.map +1 -0
  500. package/dist/values.js +13 -1
  501. package/dist/values.js.map +1 -1
  502. package/package.json +25 -12
  503. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  504. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  505. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  506. package/src/asset/meshoptimizer/LICENSE +21 -0
@@ -0,0 +1,929 @@
1
+ import { Disposable, Vector3, Vector4, DRef, DEBUG_VIEWS, nextPowerOf2 } from '@zephyr3d/base';
2
+ import '../../values.js';
3
+ import { getDevice } from '../../app/api.js';
4
+ import '../../render/render_queue.js';
5
+ import { Primitive } from '../../render/primitive.js';
6
+ import '@zephyr3d/device';
7
+ import '../../material/shader/helper.js';
8
+ import '../../material/lambert.js';
9
+ import '../../material/blinn.js';
10
+ import '../../material/unlit.js';
11
+ import '../../material/meshmaterial.js';
12
+ import '../../material/particle.js';
13
+ import '../../material/subsurfaceprofile.js';
14
+ import '../../material/sssprofile.js';
15
+ import '../../material/sss.js';
16
+ import '../../material/eye.js';
17
+ import '../../material/hair.js';
18
+ import '../../material/hairstrand.js';
19
+ import '../../material/grassmaterial.js';
20
+ import '../../material/terrain-cm.js';
21
+ import '../../material/pbrmr.js';
22
+ import '../../material/pbrsg.js';
23
+ import '../../material/mtoon.js';
24
+ import '../../material/pbrblueprint.js';
25
+ import '../../material/sprite.js';
26
+ import '../../material/sprite_std.js';
27
+ import '../../utility/blueprint/material/texture.js';
28
+ import { fetchSampler } from '../../utility/misc.js';
29
+ import '../../render/rendergraph/forward_plus_builder.js';
30
+ import '../../render/lightpass.js';
31
+ import '../../render/sky.js';
32
+ import '../../render/clipmap.js';
33
+ import '../../render/envlight.js';
34
+ import '../../render/lod.js';
35
+ import '../../render/dualdepthpeeling_oit.js';
36
+ import '../../shaders/atmosphere.js';
37
+ import '../../render/water_interaction.js';
38
+ import '../../render/rendergraph/scene_render_context.js';
39
+ import '../../render/sdf/scene_sdf.js';
40
+ import '../../render/sdf/global_sdf.js';
41
+ import '../../render/gi/voxelize_pass.js';
42
+ import { WindField } from '../wind.js';
43
+
44
+ const WORKGROUP_SIZE = 8;
45
+ /**
46
+ * Largest placement window side, in cells. Bounds the per-frame work and the instance buffer
47
+ * when the draw distance is unlimited or the cells are tiny.
48
+ */ const MAX_WINDOW_CELLS = 2048;
49
+ /**
50
+ * Width of the band, in density LOD hash units, over which a blade shrinks before the density
51
+ * LOD drops it. Hashes are scaled into [0, 1 - band) so that full density keeps every blade whole.
52
+ * @internal
53
+ */ const GRASS_LOD_FADE_BAND = 0.05;
54
+ /** Fraction of the draw distance at which the density starts to fall */ const GRASS_LOD_START = 0.25;
55
+ /**
56
+ * Density LOD parameters shared by the grass material and the GPU placement: (distance at which
57
+ * the density starts to fall, fraction of blades kept at the draw distance). A fraction of 1
58
+ * disables it.
59
+ * @internal
60
+ */ function grassDensityLod(drawDistance, farDensity) {
61
+ if (drawDistance <= 0 || farDensity >= 1) {
62
+ return [
63
+ 0,
64
+ 1
65
+ ];
66
+ }
67
+ return [
68
+ drawDistance * GRASS_LOD_START,
69
+ Math.max(0.01, farDensity)
70
+ ];
71
+ }
72
+ /** Vec4 per instance written by the placement pass for each layer kind */ const INSTANCE_VEC4 = {
73
+ card: 1,
74
+ blade: 4
75
+ };
76
+ /** Seed offset of the procedural blade hashes, clear of the card hashes of every layer */ const BLADE_SEED_BASE = 65536;
77
+ /**
78
+ * Fraction of the low detail distance over which the near blades make way for the far ones:
79
+ * three in four shrink away, the fourth widens and morphs into the low detail shape. The same
80
+ * last-quarter band as the draw distance fade.
81
+ * @internal
82
+ */ const GRASS_BLADE_LOD_BAND = 0.25;
83
+ /** Stand-in for an unlimited distance in shader uniforms */ const NO_LIMIT = 1e30;
84
+ /**
85
+ * Distance at which procedural blades switch to the low detail level, or NO_LIMIT when they never
86
+ * do: zero disables the low detail level, and so does a distance past the draw distance.
87
+ * @internal
88
+ */ function grassBladeLodDistance(lodDistance, drawDistance) {
89
+ return lodDistance > 0 && (drawDistance <= 0 || lodDistance < drawDistance) ? lodDistance : NO_LIMIT;
90
+ }
91
+ /**
92
+ * Number of culling planes of the placement pass: the six of a view frustum, or up to the twelve
93
+ * of a shadow caster culling volume (see GrassRenderer). Unused ones are planes everything is
94
+ * inside of.
95
+ * @internal
96
+ */ const GRASS_CULL_PLANES = 12;
97
+ /** Samples along the line of sight of the terrain occlusion test, one per workgroup thread */ const OCCLUSION_SAMPLES = WORKGROUP_SIZE * WORKGROUP_SIZE;
98
+ /**
99
+ * Terrain occlusion culling mode of the GPU grass placement
100
+ * @internal
101
+ */ const GrassOcclusionMode = {
102
+ Off: 0,
103
+ Cull: 1,
104
+ /** Keep occluded blades but flag them, see ClipmapGrassMaterial.occlusionDebug */ Debug: 2
105
+ };
106
+ /**
107
+ * Grass blade placement hash, shared by the CPU and GPU paths.
108
+ *
109
+ * Returns a value in [0, 1) with 24 bits of precision: that is all a float32 can hold, so
110
+ * keeping the CPU side to the same bits makes both paths place exactly the same blades.
111
+ *
112
+ * @internal
113
+ */ function grassHash(x, z, seed) {
114
+ let h = Math.imul(x, 0x27d4eb2d) ^ Math.imul(z, 0x165667b1) ^ Math.imul(seed, 0x9e3779b9) | 0;
115
+ h = Math.imul(h ^ h >>> 15, 0x85ebca6b);
116
+ h = Math.imul(h ^ h >>> 13, 0xc2b2ae35);
117
+ h ^= h >>> 16;
118
+ return (h >>> 8) / 16777216;
119
+ }
120
+ /**
121
+ * GPU placement and culling of the blades of one grass layer (WebGPU with indirect draw only).
122
+ *
123
+ * Blade placement is procedural - a hash per placement cell and a bilinear density lookup - so
124
+ * instead of keeping instances for the whole terrain, a compute pass evaluates the cells around
125
+ * the camera every frame, keeps the blades inside the frustum and the draw distance, and appends
126
+ * them to an instance buffer whose count lands in an indirect draw argument buffer.
127
+ *
128
+ * The output uses the instance format of the CPU path (u, v, facing angle, density LOD hash), so the grass material
129
+ * is shared. Each dispatched thread emits at most one blade and the instance buffer holds one
130
+ * per thread, so the output can never overflow.
131
+ *
132
+ * Procedural blades have two detail levels, after "Procedural Grass in Ghost of Tsushima"
133
+ * (Wohllaib, GDC 2021): low detail blades are spaced twice as far apart, and the high detail
134
+ * blades thin out by three quarters before the switch so the two meet seamlessly. Here the
135
+ * dispatch has two layers: z = 0 places a blade per cell around the camera, up to the low detail
136
+ * distance, into the high detail list; z = 1 places one blade per 2x2 block of cells beyond it
137
+ * into the low detail list. The blade kept for a block is one of its four cells picked by a
138
+ * hash, placed exactly as the near layer places it, so a blade moving from one list to the other
139
+ * stays where it is. Both lists share the instance buffer, the low one from `lowListBase` on,
140
+ * and each has its own indirect draw arguments.
141
+ *
142
+ * Placements go to numbered targets, each with its own lists: target 0 holds the blades seen
143
+ * from the camera, the others the blades casting shadows into a shadow map. A shadow target is
144
+ * placed around the same camera, so it picks the same blades at the same detail levels, but
145
+ * culls them by the volume the shadow casters can be in instead of the view frustum.
146
+ *
147
+ * @internal
148
+ */ class GrassGpuPlacement extends Disposable {
149
+ static _programs = {};
150
+ static _cameraPos = new Vector3();
151
+ static _planes = new Float32Array(GRASS_CULL_PLANES * 4);
152
+ _densityTexture;
153
+ _kind;
154
+ _targets;
155
+ _baseVertexBuffer;
156
+ _shape0;
157
+ _shape1;
158
+ _clump0;
159
+ _clump1;
160
+ static _calmWind = new Float32Array(32);
161
+ _indexBuffer;
162
+ _args;
163
+ /** Index count of each detail level; they follow each other in the index buffer */ _lodIndexCounts;
164
+ _window;
165
+ /** Far window, in 2x2 blocks of cells (procedural blades only) */ _window2;
166
+ /** (low detail distance, first instance of the low detail list, shadow fade start, 1 / fade length) */ _lod;
167
+ /** Camera forward axis and offset, see generate() */ _viewDepth;
168
+ static _forward = new Vector3();
169
+ _cells;
170
+ _params;
171
+ _posScale;
172
+ _camera;
173
+ _occlusion;
174
+ _heightInfo;
175
+ _densityLod;
176
+ /**
177
+ * Whether the current device can run the GPU path
178
+ */ static isSupported() {
179
+ const device = getDevice();
180
+ return device.type === 'webgpu' && device.getDeviceCaps().miscCaps.supportDrawIndirect;
181
+ }
182
+ /**
183
+ * @param kind - Kind of blades placed
184
+ * @param baseVertexBuffer - Vertex buffer of the drawn blade. Procedural blades read nothing
185
+ * from it; a draw still needs a vertex layout.
186
+ * @param indexBuffer - Index buffer of the drawn blade
187
+ * @param lodIndexCounts - Index count of each detail level, which follow each other in the index
188
+ * buffer. Procedural blades have two; defaults to the whole buffer as one level.
189
+ */ constructor(kind, baseVertexBuffer, indexBuffer, lodIndexCounts){
190
+ super();
191
+ this._kind = kind;
192
+ this._lodIndexCounts = lodIndexCounts ?? [
193
+ indexBuffer.length
194
+ ];
195
+ this._shape0 = new Vector4();
196
+ this._shape1 = new Vector4();
197
+ this._clump0 = new Vector4();
198
+ this._clump1 = new Vector4();
199
+ this._densityTexture = new DRef();
200
+ this._targets = [];
201
+ this._baseVertexBuffer = new DRef(baseVertexBuffer);
202
+ this._indexBuffer = new DRef(indexBuffer);
203
+ // Five draw arguments per detail level, then with debug views the same for the wireframe edges
204
+ this._args = new Uint32Array(this._lodIndexCounts.length * 5 * (DEBUG_VIEWS ? 2 : 1));
205
+ this._window = new Vector4();
206
+ this._window2 = new Vector4();
207
+ this._lod = new Vector4();
208
+ this._viewDepth = new Vector4();
209
+ this._cells = new Vector4();
210
+ this._params = new Vector4();
211
+ this._posScale = new Vector4();
212
+ this._camera = new Vector4();
213
+ this._occlusion = new Vector4();
214
+ this._heightInfo = new Vector4();
215
+ this._densityLod = new Vector4();
216
+ }
217
+ setBaseVertexBuffer(baseVertexBuffer) {
218
+ if (baseVertexBuffer !== this._baseVertexBuffer.get()) {
219
+ this._baseVertexBuffer.set(baseVertexBuffer);
220
+ for (const target of this._targets){
221
+ target.primitive.dispose();
222
+ }
223
+ }
224
+ }
225
+ /**
226
+ * Uploads the whole density map, recreating the texture when its size changed
227
+ * @param width - Density map width in texels
228
+ * @param height - Density map height in texels
229
+ * @param data - Density data, one byte per texel
230
+ */ setDensity(width, height, data) {
231
+ let tex = this._densityTexture.get();
232
+ if (!tex || tex.width !== width || tex.height !== height) {
233
+ tex = getDevice().createTexture2D('r8unorm', width, height, {
234
+ mipmapping: false
235
+ });
236
+ tex.name = 'GrassDensity';
237
+ this._densityTexture.set(tex);
238
+ for (const target of this._targets){
239
+ target.bindGroup.dispose();
240
+ }
241
+ }
242
+ // Texture uploads take views of plain array buffers only
243
+ const bytes = data.buffer instanceof ArrayBuffer ? data : new Uint8Array(data);
244
+ tex.update(bytes, 0, 0, width, height);
245
+ }
246
+ /**
247
+ * Uploads a rectangle of the density map
248
+ * @param data - The whole density map, one byte per texel
249
+ * @param width - Density map width in texels
250
+ * @param x0 - Minimum x texel (inclusive)
251
+ * @param z0 - Minimum z texel (inclusive)
252
+ * @param x1 - Maximum x texel (exclusive)
253
+ * @param z1 - Maximum z texel (exclusive)
254
+ */ updateDensityRegion(data, width, x0, z0, x1, z1) {
255
+ const tex = this._densityTexture.get();
256
+ if (!tex || x1 <= x0 || z1 <= z0) {
257
+ return;
258
+ }
259
+ const w = x1 - x0;
260
+ const h = z1 - z0;
261
+ const rect = new Uint8Array(w * h);
262
+ for(let z = 0; z < h; z++){
263
+ rect.set(data.subarray((z0 + z) * width + x0, (z0 + z) * width + x1), z * w);
264
+ }
265
+ tex.update(rect, x0, z0, w, h);
266
+ }
267
+ /**
268
+ * Places and culls the blades seen from a camera. Runs a compute pass, so it must be called
269
+ * outside of render passes, before the draws that consume the result.
270
+ * @param target - Target the blades are placed into, see draw()
271
+ * @param cullPlanes - GRASS_CULL_PLANES planes (a, b, c, d) the blades are culled by instead of
272
+ * the camera's frustum, whose normals point inward
273
+ * @param shadowFade - Procedural blades only: (start, end) view depth over which the blades thin
274
+ * out to none, each leaving at its own depth by a hash. Lets the blades casting shadows fade
275
+ * out before the last cascade they are drawn into instead of stopping at its far split.
276
+ */ generate(camera, terrain, seed, cellsPerTexel, drawDistance, bladeWidth, bladeHeight, occlusionMode, farDensity, bladeShape, windParams, target = 0, cullPlanes, shadowFade) {
277
+ const placementTarget = this.getTarget(target);
278
+ const density = this._densityTexture.get();
279
+ const heightMap = terrain.heightMap;
280
+ // Draw arguments of each detail level, whose indices follow each other: (index count,
281
+ // instance count, first index, base vertex, first instance)
282
+ const levels = this._lodIndexCounts.length;
283
+ let firstIndex = 0;
284
+ for(let i = 0; i < levels; i++){
285
+ this._args.set([
286
+ this._lodIndexCounts[i],
287
+ 0,
288
+ firstIndex,
289
+ 0,
290
+ 0
291
+ ], i * 5);
292
+ if (DEBUG_VIEWS) {
293
+ // Edge index buffers hold 6 indices per triangle, twice the triangle indices
294
+ this._args.set([
295
+ this._lodIndexCounts[i] * 2,
296
+ 0,
297
+ firstIndex * 2,
298
+ 0,
299
+ 0
300
+ ], (levels + i) * 5);
301
+ }
302
+ firstIndex += this._lodIndexCounts[i];
303
+ }
304
+ placementTarget.argsBuffer.get().bufferSubData(0, this._args.subarray(0, levels * 5));
305
+ placementTarget.edgeArgsBuffer.get()?.bufferSubData(0, this._args.subarray(levels * 5));
306
+ if (!density || !heightMap) {
307
+ return;
308
+ }
309
+ const blade = this._kind === 'blade' && !!bladeShape;
310
+ const region = terrain.worldRegion;
311
+ const cellsW = density.width * cellsPerTexel;
312
+ const cellsH = density.height * cellsPerTexel;
313
+ const cellSizeX = (region.z - region.x) / cellsW;
314
+ const cellSizeZ = (region.w - region.y) / cellsH;
315
+ const cameraPos = camera.getWorldPosition(GrassGpuPlacement._cameraPos);
316
+ const centerX = (cameraPos.x - region.x) / cellSizeX;
317
+ const centerZ = (cameraPos.z - region.y) / cellSizeZ;
318
+ const drawRadius = drawDistance > 0 ? drawDistance : Infinity;
319
+ // Near window: cells within the draw distance, or within the low detail distance of blades
320
+ // that have one; far window: 2x2 blocks of cells from there on to the draw distance
321
+ const lodDistance = blade ? grassBladeLodDistance(bladeShape[15], drawDistance) : NO_LIMIT;
322
+ const nearRadius = Math.min(drawRadius, lodDistance);
323
+ const near = GrassGpuPlacement.cellWindow(centerX, centerZ, nearRadius / cellSizeX, nearRadius / cellSizeZ, cellsW, cellsH, 1);
324
+ const far = lodDistance < NO_LIMIT && lodDistance < drawRadius ? GrassGpuPlacement.cellWindow(centerX, centerZ, drawRadius / cellSizeX, drawRadius / cellSizeZ, cellsW, cellsH, 2) : [
325
+ 0,
326
+ 0,
327
+ 0,
328
+ 0
329
+ ];
330
+ const nearCount = near[2] * near[3];
331
+ const farCount = far[2] * far[3];
332
+ if (nearCount <= 0 && farCount <= 0) {
333
+ return;
334
+ }
335
+ // Each list holds at most one blade per thread of its layer
336
+ this.ensureCapacity(placementTarget, Math.max(nearCount, farCount));
337
+ const bindGroup = this.getBindGroup(placementTarget);
338
+ this._window.setXYZW(near[0], near[1], near[2], near[3]);
339
+ this._window2.setXYZW(far[0], far[1], far[2], far[3]);
340
+ // (fade start, 1 / fade length); a zero length leaves every blade in
341
+ const fadeLength = shadowFade ? shadowFade[1] - shadowFade[0] : 0;
342
+ this._lod.setXYZW(lodDistance, placementTarget.capacity, shadowFade ? shadowFade[0] : 0, fadeLength > 0 ? 1 / fadeLength : 0);
343
+ // View depth of a point: dot(xyz, p) + w, along the camera's forward axis
344
+ const m = camera.worldMatrix;
345
+ const forward = GrassGpuPlacement._forward.setXYZ(-m.m02, -m.m12, -m.m22).inplaceNormalize();
346
+ this._viewDepth.setXYZW(forward.x, forward.y, forward.z, -Vector3.dot(forward, cameraPos));
347
+ this._cells.setXYZW(cellsW, cellsH, density.width, density.height);
348
+ // Bounding sphere of a blade, centred halfway up; the density LOD widens distant blades
349
+ const lod = grassDensityLod(drawDistance, farDensity);
350
+ this._densityLod.setXYZW(lod[0], lod[1], 0, 0);
351
+ if (blade) {
352
+ // A blade reaches at most its tallest height from its base in any direction, plus half its
353
+ // width, which grows fourfold toward the low detail level
354
+ const maxHeight = bladeShape[0] * (1 + Math.abs(bladeShape[1])) * (1 + Math.abs(bladeShape[9])) * (1 + bladeShape[14]);
355
+ const maxWidth = bladeShape[2] * (1 + Math.abs(bladeShape[3])) * 4;
356
+ this._shape0.setXYZW(bladeShape[0], bladeShape[1], bladeShape[2], bladeShape[3]);
357
+ this._shape1.setXYZW(bladeShape[4], bladeShape[5], bladeShape[6], bladeShape[7]);
358
+ this._clump0.setXYZW(1 / Math.max(1e-3, bladeShape[8]), bladeShape[9], bladeShape[10], bladeShape[11]);
359
+ this._clump1.setXYZW(bladeShape[12], bladeShape[13], 0, 0);
360
+ this._params.setXYZW(seed * 4, drawDistance, maxHeight + maxWidth * 0.5, 0);
361
+ bladeHeight = maxHeight;
362
+ } else {
363
+ const maxWidth = bladeWidth / lod[1];
364
+ // The wind turns a card about its base, which takes its top up to 1.12 times its height
365
+ // from the centre of the card
366
+ const radius = windParams && windParams[2] > 0 ? maxWidth * 0.5 + bladeHeight * 1.12 : Math.sqrt(maxWidth * maxWidth * 0.25 + bladeHeight * bladeHeight * 0.25);
367
+ this._params.setXYZW(seed * 4, drawDistance, radius, bladeHeight * 0.5);
368
+ }
369
+ this._posScale.setXYZW(terrain.scale.x, terrain.scale.y, terrain.scale.z, terrain.worldMatrix.m13);
370
+ this._camera.setXYZW(cameraPos.x, cameraPos.y, cameraPos.z, 0);
371
+ // Terrain occlusion needs the height pyramid, and heights that grow upwards
372
+ const pyramid = terrain.heightPyramid;
373
+ const occlusion = pyramid && terrain.scale.y > 0 ? occlusionMode : GrassOcclusionMode.Off;
374
+ const bounds = terrain.getWorldBoundingVolume()?.toAABB();
375
+ // Absorbs half float and interpolation error in the heights
376
+ const epsilon = bounds ? (bounds.maxPoint.y - bounds.minPoint.y) * 1e-3 + 1e-4 : 1e-3;
377
+ this._occlusion.setXYZW(occlusion, terrain.clipmapTileResolution, epsilon, bladeHeight);
378
+ this._heightInfo.setXYZW(heightMap.width, heightMap.height, pyramid?.mipLevelCount ?? 1, Math.max((region.z - region.x) / heightMap.width, (region.w - region.y) / heightMap.height));
379
+ // Planes masked out by the camera become ones everything is inside of
380
+ let planes = GrassGpuPlacement._planes;
381
+ if (cullPlanes) {
382
+ planes = cullPlanes;
383
+ } else {
384
+ const frustumPlanes = camera.frustum.planes;
385
+ const mask = camera.clipMask || 0x3f;
386
+ for(let i = 0; i < GRASS_CULL_PLANES; i++){
387
+ const p = frustumPlanes[i];
388
+ const enabled = i < 6 && !!(mask & 1 << i);
389
+ planes[i * 4 + 0] = enabled ? p.a : 0;
390
+ planes[i * 4 + 1] = enabled ? p.b : 0;
391
+ planes[i * 4 + 2] = enabled ? p.c : 0;
392
+ planes[i * 4 + 3] = enabled ? p.d : 1e30;
393
+ }
394
+ }
395
+ bindGroup.setValue('window', this._window);
396
+ bindGroup.setValue('cells', this._cells);
397
+ bindGroup.setValue('params', this._params);
398
+ bindGroup.setValue('region', region);
399
+ bindGroup.setValue('posScale', this._posScale);
400
+ bindGroup.setValue('cameraPos', this._camera);
401
+ bindGroup.setValue('planes', planes);
402
+ bindGroup.setValue('occlusion', this._occlusion);
403
+ bindGroup.setValue('densityLod', this._densityLod);
404
+ bindGroup.setValue('heightInfo', this._heightInfo);
405
+ if (this._kind === 'blade') {
406
+ bindGroup.setValue('window2', this._window2);
407
+ bindGroup.setValue('lod', this._lod);
408
+ bindGroup.setValue('viewDepth', this._viewDepth);
409
+ bindGroup.setValue('shape0', this._shape0);
410
+ bindGroup.setValue('shape1', this._shape1);
411
+ bindGroup.setValue('clump0', this._clump0);
412
+ bindGroup.setValue('clump1', this._clump1);
413
+ bindGroup.setValue('wind', windParams ?? GrassGpuPlacement._calmWind);
414
+ }
415
+ // Any texture of the right sample type does when occlusion is off. Declared unfilterable
416
+ // (read with textureLoad only), so the sampler bound with it must be a non-filtering one
417
+ // whatever the texture's default is: rg32f counts as filterable where the device has
418
+ // float32-filterable.
419
+ bindGroup.setTexture('heightPyramid', pyramid ?? GrassGpuPlacement.getDummyPyramid(), fetchSampler('clamp_nearest_nomip'));
420
+ bindGroup.setTexture('density', density);
421
+ bindGroup.setTexture('heightMap', heightMap, fetchSampler('clamp_linear_nomip'));
422
+ const device = getDevice();
423
+ device.setProgram(GrassGpuPlacement.getProgram(this._kind));
424
+ device.setBindGroup(0, bindGroup);
425
+ // The far layer, when there is one, runs as z = 1 of the same dispatch
426
+ device.compute(Math.ceil(Math.max(near[2], far[2]) / WORKGROUP_SIZE), Math.ceil(Math.max(near[3], far[3]) / WORKGROUP_SIZE), farCount > 0 ? 2 : 1);
427
+ // The instance counts placement wrote, for drawing the edges in a wireframe view
428
+ const edgeArgs = placementTarget.edgeArgsBuffer.get();
429
+ if (edgeArgs) {
430
+ for(let i = 0; i < this._lodIndexCounts.length; i++){
431
+ device.copyBuffer(placementTarget.argsBuffer.get(), edgeArgs, (i * 5 + 1) * 4, (i * 5 + 1) * 4, 4);
432
+ }
433
+ }
434
+ }
435
+ /**
436
+ * Window of placement units within a radius of a point, clipped to the grid and to
437
+ * MAX_WINDOW_CELLS units a side: (first x, first z, units wide, units high)
438
+ * @param centerX - Point x, in cells
439
+ * @param centerZ - Point z, in cells
440
+ * @param radiusX - Radius along x, in cells
441
+ * @param radiusZ - Radius along z, in cells
442
+ * @param cellsW - Grid width, in cells
443
+ * @param cellsH - Grid height, in cells
444
+ * @param unit - Placement unit side, in cells
445
+ */ static cellWindow(centerX, centerZ, radiusX, radiusZ, cellsW, cellsH, unit) {
446
+ const halfMax = MAX_WINDOW_CELLS / 2;
447
+ const cx = centerX / unit;
448
+ const cz = centerZ / unit;
449
+ const rx = Math.min(radiusX / unit, halfMax);
450
+ const rz = Math.min(radiusZ / unit, halfMax);
451
+ const x0 = Math.max(0, Math.floor(cx - rx));
452
+ const x1 = Math.min(Math.ceil(cellsW / unit), Math.ceil(cx + rx));
453
+ const z0 = Math.max(0, Math.floor(cz - rz));
454
+ const z1 = Math.min(Math.ceil(cellsH / unit), Math.ceil(cz + rz));
455
+ return [
456
+ x0,
457
+ z0,
458
+ Math.max(0, x1 - x0),
459
+ Math.max(0, z1 - z0)
460
+ ];
461
+ }
462
+ /**
463
+ * Instance buffer written by the last generate() call into a target
464
+ * @param target - The target
465
+ */ getInstanceBuffer(target = 0) {
466
+ return this._targets[target]?.instanceBuffer.get() ?? null;
467
+ }
468
+ /**
469
+ * First instance of the low detail list in the instance buffer of a target (procedural blades
470
+ * only); the high detail list starts at 0
471
+ * @param target - The target
472
+ */ getLowListBase(target = 0) {
473
+ return this._targets[target]?.capacity ?? 0;
474
+ }
475
+ /**
476
+ * Draws the blades of the last generate() call into a target; the material must be bound
477
+ * @param target - The target
478
+ */ draw(target = 0) {
479
+ const placementTarget = this._targets[target];
480
+ if (!placementTarget) {
481
+ return;
482
+ }
483
+ let primitive = placementTarget.primitive.get();
484
+ if (!primitive) {
485
+ const instanceBuffer = placementTarget.instanceBuffer.get();
486
+ if (!instanceBuffer) {
487
+ return;
488
+ }
489
+ primitive = new Primitive();
490
+ primitive.setVertexBuffer(this._baseVertexBuffer.get());
491
+ if (this._kind === 'card') {
492
+ primitive.setVertexBuffer(instanceBuffer, 'instance');
493
+ }
494
+ primitive.setIndexBuffer(this._indexBuffer.get());
495
+ primitive.primitiveType = 'triangle-list';
496
+ primitive.indexStart = 0;
497
+ primitive.indexCount = this._indexBuffer.get().length;
498
+ placementTarget.primitive.set(primitive);
499
+ }
500
+ // One draw per detail level; the arguments pick its range of the index buffer
501
+ const edgeArgs = placementTarget.edgeArgsBuffer.get();
502
+ for(let i = 0; i < this._lodIndexCounts.length; i++){
503
+ primitive.drawIndirect(placementTarget.argsBuffer.get(), i * 20, edgeArgs ? i * 20 : -1, edgeArgs ?? undefined);
504
+ }
505
+ }
506
+ /**
507
+ * Releases the targets from a number on, which are no longer placed into
508
+ * @param count - Number of targets kept
509
+ */ trimTargets(count) {
510
+ while(this._targets.length > Math.max(1, count)){
511
+ GrassGpuPlacement.disposeTarget(this._targets.pop());
512
+ }
513
+ }
514
+ getTarget(index) {
515
+ while(this._targets.length <= index){
516
+ this._targets.push({
517
+ instanceBuffer: new DRef(),
518
+ argsBuffer: new DRef(// Dynamic: the arguments are reset every frame, and a dynamic buffer reuses its upload
519
+ // staging buffers instead of creating and destroying one per upload
520
+ getDevice().createBuffer(this._lodIndexCounts.length * 20, {
521
+ usage: 'indirect',
522
+ storage: true,
523
+ dynamic: true
524
+ })),
525
+ edgeArgsBuffer: new DRef(DEBUG_VIEWS ? getDevice().createBuffer(this._lodIndexCounts.length * 20, {
526
+ usage: 'indirect',
527
+ dynamic: true
528
+ }) : null),
529
+ bindGroup: new DRef(),
530
+ primitive: new DRef(),
531
+ capacity: 0
532
+ });
533
+ }
534
+ return this._targets[index];
535
+ }
536
+ static disposeTarget(target) {
537
+ target.instanceBuffer.dispose();
538
+ target.argsBuffer.dispose();
539
+ target.edgeArgsBuffer.dispose();
540
+ target.bindGroup.dispose();
541
+ target.primitive.dispose();
542
+ }
543
+ ensureCapacity(target, numInstances) {
544
+ if (numInstances > target.capacity) {
545
+ target.capacity = nextPowerOf2(numInstances);
546
+ const device = getDevice();
547
+ const buffer = this._kind === 'card' ? device.createVertexBuffer('tex1_f32x4', new Float32Array(target.capacity * 4), {
548
+ storage: true
549
+ }) : device.createBuffer(target.capacity * 2 * INSTANCE_VEC4.blade * 16, {
550
+ usage: 'uniform',
551
+ storage: true,
552
+ dynamic: false,
553
+ managed: false
554
+ });
555
+ target.instanceBuffer.set(buffer);
556
+ target.primitive.dispose();
557
+ target.bindGroup.dispose();
558
+ }
559
+ }
560
+ getBindGroup(target) {
561
+ let bindGroup = target.bindGroup.get();
562
+ if (!bindGroup) {
563
+ bindGroup = getDevice().createBindGroup(GrassGpuPlacement.getProgram(this._kind).bindGroupLayouts[0]);
564
+ bindGroup.setBuffer('instances', target.instanceBuffer.get());
565
+ bindGroup.setBuffer('args', target.argsBuffer.get());
566
+ target.bindGroup.set(bindGroup);
567
+ }
568
+ return bindGroup;
569
+ }
570
+ static _dummyPyramid = null;
571
+ static getDummyPyramid() {
572
+ if (!this._dummyPyramid) {
573
+ this._dummyPyramid = getDevice().createTexture2D('rg32f', 1, 1, {
574
+ mipmapping: false
575
+ });
576
+ this._dummyPyramid.update(new Float32Array(2), 0, 0, 1, 1);
577
+ }
578
+ return this._dummyPyramid;
579
+ }
580
+ static getProgram(kind) {
581
+ let program = this._programs[kind];
582
+ if (!program) {
583
+ const blade = kind === 'blade';
584
+ program = getDevice().buildComputeProgram({
585
+ label: blade ? 'GrassBladePlacement' : 'GrassPlacement',
586
+ workgroupSize: [
587
+ WORKGROUP_SIZE,
588
+ WORKGROUP_SIZE,
589
+ 1
590
+ ],
591
+ compute (pb) {
592
+ // (first cell x, first cell z, cells wide, cells high)
593
+ this.window = pb.vec4().uniform(0);
594
+ // (cells x, cells z, density width, density height)
595
+ this.cells = pb.vec4().uniform(0);
596
+ // (seed, draw distance, blade bounding radius, blade centre height)
597
+ this.params = pb.vec4().uniform(0);
598
+ this.region = pb.vec4().uniform(0);
599
+ // (terrain scale xyz, terrain origin y)
600
+ this.posScale = pb.vec4().uniform(0);
601
+ this.cameraPos = pb.vec4().uniform(0);
602
+ this.planes = pb.vec4[GRASS_CULL_PLANES]().uniform(0);
603
+ // (mode, clipmap tile resolution, height epsilon, blade height)
604
+ this.occlusion = pb.vec4().uniform(0);
605
+ // (density falloff start distance, density at the draw distance, unused, unused)
606
+ this.densityLod = pb.vec4().uniform(0);
607
+ // (height map width, height map height, pyramid mip count, grid cell size)
608
+ this.heightInfo = pb.vec4().uniform(0);
609
+ if (blade) {
610
+ // Far window: (first block x, first block z, blocks wide, blocks high), 2x2 cells a block
611
+ this.window2 = pb.vec4().uniform(0);
612
+ // (low detail distance, first instance of the low detail list, shadow fade start depth,
613
+ // 1 / shadow fade length or 0 for no fade)
614
+ this.lod = pb.vec4().uniform(0);
615
+ // (camera forward, -dot(forward, camera position)): view depth of a point
616
+ this.viewDepth = pb.vec4().uniform(0);
617
+ // See GrassBladeShape
618
+ this.shape0 = pb.vec4().uniform(0);
619
+ this.shape1 = pb.vec4().uniform(0);
620
+ // (1 / clump size, height variation, pull to the clump point, same direction)
621
+ this.clump0 = pb.vec4().uniform(0);
622
+ // (face away from the clump point, wind facing, unused, unused)
623
+ this.clump1 = pb.vec4().uniform(0);
624
+ // Scene wind, current frame then previous frame, see WindField.shaderParams
625
+ this.wind = pb.vec4[8]().uniform(0);
626
+ }
627
+ this.heightPyramid = pb.tex2D().sampleType('unfilterable-float').uniform(0);
628
+ this.occludedFlag = pb.atomic_uint().workgroup();
629
+ this.density = pb.tex2D().uniform(0);
630
+ this.heightMap = pb.tex2D().uniform(0);
631
+ this.instances = pb.vec4[0]().storageBuffer(0);
632
+ this.args = pb.atomic_uint[0]().storageBuffer(0);
633
+ // See grassHash(); unsigned integer arithmetic wraps like Math.imul
634
+ pb.func('grassHash', [
635
+ pb.uint('x'),
636
+ pb.uint('z'),
637
+ pb.uint('s')
638
+ ], function() {
639
+ this.$l.h = pb.compXor(pb.compXor(pb.mul(this.x, pb.uint(0x27d4eb2d)), pb.mul(this.z, pb.uint(0x165667b1))), pb.mul(this.s, pb.uint(0x9e3779b9)));
640
+ this.h = pb.mul(pb.compXor(this.h, pb.sar(this.h, 15)), pb.uint(0x85ebca6b));
641
+ this.h = pb.mul(pb.compXor(this.h, pb.sar(this.h, 13)), pb.uint(0xc2b2ae35));
642
+ this.h = pb.compXor(this.h, pb.sar(this.h, 16));
643
+ this.$return(pb.mul(pb.float(pb.sar(this.h, 8)), 1 / 16777216));
644
+ });
645
+ // Bilinear density, the same filtering as GrassLayer.sampleDensity()
646
+ pb.func('sampleDensity', [
647
+ pb.vec2('uv')
648
+ ], function() {
649
+ this.$l.size = this.cells.zw;
650
+ this.$l.p = pb.sub(pb.mul(this.uv, this.size), pb.vec2(0.5));
651
+ this.$l.i = pb.floor(this.p);
652
+ this.$l.f = pb.sub(this.p, this.i);
653
+ this.$l.maxIndex = pb.sub(this.size, pb.vec2(1));
654
+ this.$l.i0 = pb.ivec2(pb.clamp(this.i, pb.vec2(0), this.maxIndex));
655
+ this.$l.i1 = pb.ivec2(pb.clamp(pb.add(this.i, pb.vec2(1)), pb.vec2(0), this.maxIndex));
656
+ const texel = (scope, x, z)=>pb.textureLoad(scope.density, pb.ivec2(scope[x].x, scope[z].y), 0).r;
657
+ this.$l.a = pb.mix(texel(this, 'i0', 'i0'), texel(this, 'i1', 'i0'), this.f.x);
658
+ this.$l.b = pb.mix(texel(this, 'i0', 'i1'), texel(this, 'i1', 'i1'), this.f.x);
659
+ this.$return(pb.mix(this.a, this.b, this.f.y));
660
+ });
661
+ // (max, min) height over a rectangle of height map texels [lo, hi], read from the
662
+ // pyramid level where the rectangle spans at most 2x2 texels
663
+ pb.func('pyramidRange', [
664
+ pb.vec2('lo'),
665
+ pb.vec2('hi')
666
+ ], function() {
667
+ this.$l.extent = pb.max(pb.sub(this.hi.x, this.lo.x), pb.sub(this.hi.y, this.lo.y));
668
+ this.$l.level = pb.clamp(pb.ceil(pb.log2(pb.max(this.extent, 1))), 0, pb.sub(this.heightInfo.z, 1));
669
+ this.$l.texelSize = pb.exp2(this.level);
670
+ this.$l.mip = pb.int(this.level);
671
+ this.$l.maxIndex = pb.sub(pb.vec2(pb.textureDimensions(this.heightPyramid, this.mip)), pb.vec2(1));
672
+ this.$l.i0 = pb.ivec2(pb.clamp(pb.floor(pb.div(this.lo, this.texelSize)), pb.vec2(0), this.maxIndex));
673
+ this.$l.i1 = pb.ivec2(pb.clamp(pb.floor(pb.div(this.hi, this.texelSize)), pb.vec2(0), this.maxIndex));
674
+ this.$l.a = pb.textureLoad(this.heightPyramid, this.i0, this.mip).rg;
675
+ this.$l.b = pb.textureLoad(this.heightPyramid, pb.ivec2(this.i1.x, this.i0.y), this.mip).rg;
676
+ this.$l.c = pb.textureLoad(this.heightPyramid, pb.ivec2(this.i0.x, this.i1.y), this.mip).rg;
677
+ this.$l.d = pb.textureLoad(this.heightPyramid, this.i1, this.mip).rg;
678
+ this.$return(pb.vec2(pb.max(pb.max(this.a.x, this.b.x), pb.max(this.c.x, this.d.x)), pb.min(pb.min(this.a.y, this.b.y), pb.min(this.c.y, this.d.y))));
679
+ });
680
+ pb.main(function() {
681
+ // Placement units of this thread's layer: cells of the near window, or 2x2 blocks of
682
+ // cells of the far window (z = 1, procedural blades only)
683
+ this.$l.far = pb.notEqual(this.$builtins.globalInvocationId.z, pb.uint(0));
684
+ this.$l.win = this.window;
685
+ this.$l.unit = pb.float(1);
686
+ if (blade) {
687
+ this.$if(this.far, function() {
688
+ this.win = this.window2;
689
+ this.unit = pb.float(2);
690
+ });
691
+ }
692
+ // Terrain occlusion, one test per workgroup: the group's blades are hidden if every
693
+ // line of sight to them dips below the terrain somewhere in front of them. Each
694
+ // thread checks one point along the way.
695
+ this.$l.occluded = pb.uint(0);
696
+ this.$if(pb.greaterThan(this.occlusion.x, 0.5), function() {
697
+ this.$l.cellSize = pb.div(pb.sub(this.region.zw, this.region.xy), this.cells.xy);
698
+ this.$l.toTexel = pb.div(this.heightInfo.xy, pb.sub(this.region.zw, this.region.xy));
699
+ this.$l.g0 = pb.mul(pb.add(this.win.xy, pb.mul(pb.vec2(this.$builtins.workGroupId.xy), WORKGROUP_SIZE)), this.unit);
700
+ this.$l.g1 = pb.min(pb.add(this.g0, pb.vec2(pb.mul(this.unit, WORKGROUP_SIZE))), pb.mul(pb.add(this.win.xy, this.win.zw), this.unit));
701
+ this.$l.fmin = pb.add(this.region.xy, pb.mul(this.g0, this.cellSize));
702
+ this.$l.fmax = pb.add(this.region.xy, pb.mul(this.g1, this.cellSize));
703
+ this.$l.cam = this.cameraPos.xz;
704
+ this.$l.camY = this.cameraPos.y;
705
+ // Highest blade tip of the group; one texel of margin for the bilinear heights
706
+ this.$l.groupRange = this.pyramidRange(pb.sub(pb.mul(pb.sub(this.fmin, this.region.xy), this.toTexel), pb.vec2(1)), pb.add(pb.mul(pb.sub(this.fmax, this.region.xy), this.toTexel), pb.vec2(1)));
707
+ this.$l.topY = pb.add(pb.add(pb.mul(this.groupRange.x, this.posScale.y), this.posScale.w), this.occlusion.w);
708
+ this.$l.dNear = pb.length(pb.max(pb.max(pb.sub(this.fmin, this.cam), pb.sub(this.cam, this.fmax)), pb.vec2(0)));
709
+ this.$l.dFar = pb.length(pb.max(pb.abs(pb.sub(this.cam, this.fmin)), pb.abs(pb.sub(this.cam, this.fmax))));
710
+ this.$l.center = pb.mul(pb.add(this.fmin, this.fmax), 0.5);
711
+ this.$l.groupRadius = pb.mul(pb.length(pb.sub(this.fmax, this.fmin)), 0.5);
712
+ this.$l.sMin = pb.mul(this.heightInfo.w, 2);
713
+ this.$l.sEnd = pb.sub(this.dNear, this.groupRadius);
714
+ // A camera below the terrain would see everything as occluded
715
+ this.$l.camTexel = pb.mul(pb.sub(this.cam, this.region.xy), this.toTexel);
716
+ this.$l.camGround = pb.add(pb.mul(this.pyramidRange(this.camTexel, this.camTexel).y, this.posScale.y), this.posScale.w);
717
+ this.$if(pb.and(pb.greaterThan(this.sEnd, this.sMin), pb.greaterThanEqual(this.camY, this.camGround)), function() {
718
+ // Steepest line of sight to the group: to the nearest edge when looking up at
719
+ // its highest tip, to the farthest one when looking down
720
+ this.$l.slope = pb.div(pb.sub(this.topY, this.camY), pb.mix(this.dFar, this.dNear, pb.step(this.camY, this.topY)));
721
+ this.$l.dir = pb.normalize(pb.sub(this.center, this.cam));
722
+ this.$l.k = pb.float(pb.add(pb.mul(this.$builtins.localInvocationId.y, WORKGROUP_SIZE), this.$builtins.localInvocationId.x));
723
+ this.$l.s = pb.add(this.sMin, pb.mul(pb.sub(this.sEnd, this.sMin), pb.div(pb.add(this.k, 0.5), OCCLUSION_SAMPLES)));
724
+ this.$l.p = pb.add(this.cam, pb.mul(this.dir, this.s));
725
+ // Lateral spread of the lines of sight at this distance, plus how far below the
726
+ // samples the rendered surface can reach: a clipmap level of 2^L cells averages
727
+ // heights within 4 * 2^L cells, and 2^L <= s / (T * cell) there, doubled for safety
728
+ this.$l.spread = pb.div(pb.mul(this.groupRadius, this.s), this.dNear);
729
+ this.$l.lodReach = pb.mul(pb.mul(pb.max(1, pb.div(pb.mul(this.s, 2), pb.mul(this.occlusion.y, this.heightInfo.w))), this.heightInfo.w), 4);
730
+ this.$l.halfSize = pb.mul(pb.vec2(pb.add(this.spread, this.lodReach)), this.toTexel);
731
+ this.$l.pc = pb.mul(pb.sub(this.p, this.region.xy), this.toTexel);
732
+ this.$l.lo = pb.sub(this.pc, this.halfSize);
733
+ this.$l.hi = pb.add(this.pc, this.halfSize);
734
+ // Beyond the terrain there is nothing to hide behind
735
+ this.$if(pb.and(pb.and(pb.greaterThanEqual(this.lo.x, 0), pb.greaterThanEqual(this.lo.y, 0)), pb.and(pb.lessThanEqual(this.hi.x, this.heightInfo.x), pb.lessThanEqual(this.hi.y, this.heightInfo.y))), function() {
736
+ this.$l.ground = pb.sub(pb.add(pb.mul(this.pyramidRange(this.lo, this.hi).y, this.posScale.y), this.posScale.w), this.occlusion.z);
737
+ this.$if(pb.lessThan(pb.add(this.camY, pb.mul(this.slope, this.s)), this.ground), function() {
738
+ pb.atomicOr(this.occludedFlag, 1);
739
+ });
740
+ });
741
+ });
742
+ pb.workgroupBarrier();
743
+ this.occluded = pb.atomicOr(this.occludedFlag, 0);
744
+ });
745
+ this.$l.id = this.$builtins.globalInvocationId.xy;
746
+ this.$if(pb.and(pb.and(pb.lessThan(this.id.x, pb.uint(this.win.z)), pb.lessThan(this.id.y, pb.uint(this.win.w))), pb.or(pb.equal(this.occluded, 0), pb.greaterThan(this.occlusion.x, 1.5))), function() {
747
+ this.$l.cx = pb.add(pb.uint(this.win.x), this.id.x);
748
+ this.$l.cz = pb.add(pb.uint(this.win.y), this.id.y);
749
+ this.$l.seed = pb.uint(this.params.x);
750
+ // Blades that give way to the low detail level: 1 for the three cells of each
751
+ // 2x2 block that the far layer does not keep
752
+ this.$l.extra = pb.float(0);
753
+ if (blade) {
754
+ this.$l.bseed = pb.add(pb.mul(this.seed, 16), pb.uint(BLADE_SEED_BASE));
755
+ // The cell of a 2x2 block the far layer keeps, picked by a hash of the block
756
+ this.$l.block = this.$choice(this.far, pb.uvec2(this.cx, this.cz), pb.uvec2(pb.sar(this.cx, 1), pb.sar(this.cz, 1)));
757
+ this.$l.kept = pb.min(pb.uint(pb.mul(this.grassHash(this.block.x, this.block.y, pb.add(this.bseed, 5)), 4)), pb.uint(3));
758
+ this.$if(this.far, function() {
759
+ this.cx = pb.add(pb.mul(this.block.x, 2), pb.compAnd(this.kept, pb.uint(1)));
760
+ this.cz = pb.add(pb.mul(this.block.y, 2), pb.sar(this.kept, 1));
761
+ }).$else(function() {
762
+ this.extra = pb.float(pb.notEqual(pb.add(pb.compAnd(this.cx, pb.uint(1)), pb.mul(pb.compAnd(this.cz, pb.uint(1)), 2)), this.kept));
763
+ });
764
+ }
765
+ this.$l.uv = pb.div(pb.add(pb.vec2(pb.float(this.cx), pb.float(this.cz)), pb.vec2(this.grassHash(this.cx, this.cz, this.seed), this.grassHash(this.cx, this.cz, pb.add(this.seed, 1)))), this.cells.xy);
766
+ this.$if(pb.and(pb.greaterThan(this.sampleDensity(this.uv), this.grassHash(this.cx, this.cz, pb.add(this.seed, 2))), // The cell a block of the far layer keeps can fall past the grid's last row
767
+ pb.and(pb.lessThan(this.cx, pb.uint(this.cells.x)), pb.lessThan(this.cz, pb.uint(this.cells.y)))), function() {
768
+ this.$l.regionSize = pb.sub(this.region.zw, this.region.xy);
769
+ if (blade) {
770
+ // Clumps, after "Procedural Grass in Ghost of Tsushima": a procedural
771
+ // Voronoi over the nearest 3x3 points of a grid, each jittered by a hash;
772
+ // the blade belongs to the clump of the nearest point
773
+ this.$l.cp = pb.mul(pb.add(this.region.xy, pb.mul(this.uv, this.regionSize)), this.clump0.x);
774
+ this.$l.cellBase = pb.floor(this.cp);
775
+ this.$l.bestDist = pb.float(1e9);
776
+ this.$l.clumpPoint = this.cp;
777
+ this.$l.clumpCell = pb.uvec2(0);
778
+ this.$l.cellF = pb.vec2(0);
779
+ this.$l.cellU = pb.uvec2(0);
780
+ this.$l.point = pb.vec2(0);
781
+ this.$l.pointDist = pb.float(0);
782
+ for(let dz = -1; dz <= 1; dz++){
783
+ for(let dx = -1; dx <= 1; dx++){
784
+ this.cellF = pb.add(this.cellBase, pb.vec2(dx, dz));
785
+ // Negative cells wrap to large unsigned values, all the hash needs
786
+ this.cellU = pb.uvec2(pb.ivec2(this.cellF));
787
+ this.point = pb.add(this.cellF, pb.vec2(this.grassHash(this.cellU.x, this.cellU.y, pb.add(this.bseed, 8)), this.grassHash(this.cellU.x, this.cellU.y, pb.add(this.bseed, 9))));
788
+ this.pointDist = pb.dot(pb.sub(this.point, this.cp), pb.sub(this.point, this.cp));
789
+ this.$if(pb.lessThan(this.pointDist, this.bestDist), function() {
790
+ this.bestDist = this.pointDist;
791
+ this.clumpPoint = this.point;
792
+ this.clumpCell = this.cellU;
793
+ });
794
+ }
795
+ }
796
+ this.$l.clumpCenter = pb.div(this.clumpPoint, this.clump0.x);
797
+ this.$l.clumpAngle = pb.mul(this.grassHash(this.clumpCell.x, this.clumpCell.y, pb.add(this.bseed, 10)), Math.PI * 2);
798
+ this.$l.clumpHeight = pb.sub(pb.mul(this.grassHash(this.clumpCell.x, this.clumpCell.y, pb.add(this.bseed, 11)), 2), 1);
799
+ this.$l.clumpColor = this.grassHash(this.clumpCell.x, this.clumpCell.y, pb.add(this.bseed, 12));
800
+ // Pull the blade toward its clump point before it is placed on the terrain
801
+ this.uv = pb.mix(this.uv, pb.div(pb.sub(this.clumpCenter, this.region.xy), this.regionSize), this.clump0.z);
802
+ }
803
+ this.$l.xz = pb.add(this.region.xy, pb.mul(this.uv, this.regionSize));
804
+ this.$l.height = pb.add(pb.mul(pb.textureSampleLevel(this.heightMap, this.uv, 0).r, this.posScale.y), this.posScale.w);
805
+ this.$l.base = pb.vec3(this.xz.x, this.height, this.xz.y);
806
+ this.$l.dist = pb.distance(this.base, this.cameraPos.xyz);
807
+ this.$l.visible = pb.or(pb.lessThanEqual(this.params.y, 0), pb.lessThanEqual(this.dist, this.params.y));
808
+ this.$l.lodHash = this.grassHash(this.cx, this.cz, pb.add(this.seed, 4));
809
+ if (blade) {
810
+ // Detail level: each blade belongs to the near layer up to the low detail
811
+ // distance and to the far layer from there on, measured the same way in both
812
+ this.visible = pb.and(this.visible, pb.equal(this.far, pb.greaterThanEqual(this.dist, this.lod.x)));
813
+ } else {
814
+ // Density LOD, see ClipmapGrassMaterial: drop the blades the material would
815
+ // shrink to nothing
816
+ this.$if(pb.greaterThan(this.params.y, 0), function() {
817
+ this.$l.keep = pb.mix(1, this.densityLod.y, pb.smoothStep(this.densityLod.x, this.params.y, this.dist));
818
+ this.visible = pb.and(this.visible, pb.lessThan(pb.mul(this.lodHash, 1 - GRASS_LOD_FADE_BAND), this.keep));
819
+ });
820
+ }
821
+ if (blade) {
822
+ // Shadow caster fade: thin out to none over a band of view depth, each blade
823
+ // leaving at its own depth like the density LOD
824
+ this.$if(pb.greaterThan(this.lod.w, 0), function() {
825
+ this.$l.viewZ = pb.add(pb.dot(this.viewDepth.xyz, this.base), this.viewDepth.w);
826
+ this.$l.fadeKeep = pb.sub(1, pb.clamp(pb.mul(pb.sub(this.viewZ, this.lod.z), this.lod.w), 0, 1));
827
+ this.visible = pb.and(this.visible, pb.lessThan(this.grassHash(this.cx, this.cz, pb.add(this.bseed, 13)), this.fadeKeep));
828
+ });
829
+ }
830
+ this.$l.center = pb.add(this.base, pb.vec3(0, this.params.w, 0));
831
+ for(let i = 0; i < GRASS_CULL_PLANES; i++){
832
+ this.visible = pb.and(this.visible, pb.greaterThanEqual(pb.add(pb.dot(this.planes.at(i).xyz, this.center), this.planes.at(i).w), pb.neg(this.params.z)));
833
+ }
834
+ this.$if(this.visible, function() {
835
+ this.$l.angle = pb.mul(this.grassHash(this.cx, this.cz, pb.add(this.seed, 3)), Math.PI * 2);
836
+ // Occluded blades kept for debugging are flagged by adding 2 to the hash; blade
837
+ // instances carry the extra flag in its place, their density LOD is the
838
+ // detail levels
839
+ this.$l.flaggedHash = pb.add(blade ? this.extra : this.lodHash, pb.mul(pb.float(pb.notEqual(this.occluded, 0)), 2));
840
+ if (blade) {
841
+ // Near blades append to the high detail list, far ones to the low detail
842
+ // list, whose draw arguments follow and whose instances start at lod.y
843
+ this.$l.slot = pb.uint(0);
844
+ this.$if(this.far, function() {
845
+ this.slot = pb.add(pb.atomicAdd(this.args.at(6), 1), pb.uint(this.lod.y));
846
+ }).$else(function() {
847
+ this.slot = pb.atomicAdd(this.args.at(1), 1);
848
+ });
849
+ } else {
850
+ this.$l.slot = pb.atomicAdd(this.args.at(1), 1);
851
+ }
852
+ if (!blade) {
853
+ this.instances.setAt(this.slot, pb.vec4(this.uv, this.angle, this.flaggedHash));
854
+ } else {
855
+ // Layout: see ClipmapBladeGrassMaterial
856
+ const signedHash = (scope, k)=>pb.sub(pb.mul(scope.grassHash(scope.cx, scope.cz, pb.add(scope.bseed, k)), 2), 1);
857
+ this.$l.bladeHeight = pb.mul(this.shape0.x, pb.max(0, pb.add(1, pb.mul(this.shape0.y, signedHash(this, 0)))), pb.max(0, pb.add(1, pb.mul(this.clump0.y, this.clumpHeight))));
858
+ this.$l.bladeWidth = pb.mul(this.shape0.z, pb.max(0, pb.add(1, pb.mul(this.shape0.w, signedHash(this, 1)))));
859
+ this.$l.tilt = pb.clamp(pb.add(this.shape1.x, pb.mul(this.shape1.y, signedHash(this, 2))), 0, 1);
860
+ this.$l.bend = pb.add(this.shape1.z, pb.mul(this.shape1.w, signedHash(this, 3)));
861
+ this.$l.bladeHash = this.grassHash(this.cx, this.cz, pb.add(this.bseed, 4));
862
+ // Facing: toward the clump's shared direction, then away from its point
863
+ this.$l.clumpDir = pb.vec2(pb.cos(this.clumpAngle), pb.sin(this.clumpAngle));
864
+ this.$l.facing = pb.mix(pb.vec2(pb.cos(this.angle), pb.sin(this.angle)), this.clumpDir, this.clump0.w);
865
+ this.$l.away = pb.sub(this.xz, this.clumpCenter);
866
+ this.$l.awayLen = pb.length(this.away);
867
+ this.$if(pb.greaterThan(this.awayLen, 1e-5), function() {
868
+ this.facing = pb.mix(this.facing, pb.div(this.away, this.awayLen), this.clump1.x);
869
+ });
870
+ this.$if(pb.lessThan(pb.dot(this.facing, this.facing), 1e-8), function() {
871
+ this.facing = this.clumpDir;
872
+ });
873
+ this.$l.facingAngle = pb.atan2(this.facing.y, this.facing.x);
874
+ // Wind: sample the push for this frame and the last one, and turn the facing
875
+ // downwind by it; the vertex shader rebuilds both frames for motion vectors
876
+ this.$l.windPush = WindField.shaderPush(this, this.xz, this.wind.at(0), this.wind.at(1), this.wind.at(2), this.wind.at(3));
877
+ this.$l.windPushPrev = WindField.shaderPush(this, this.xz, this.wind.at(4), this.wind.at(5), this.wind.at(6), this.wind.at(7));
878
+ this.$l.facingNow = this.facingAngle;
879
+ this.$l.facingPrev = this.facingAngle;
880
+ this.$l.bladeDir = pb.vec2(pb.cos(this.facingAngle), pb.sin(this.facingAngle));
881
+ const turn = (scope, out, push, dir)=>{
882
+ scope.$if(pb.and(pb.greaterThan(scope[push], 0), pb.greaterThan(pb.dot(dir, dir), 1e-6)), function() {
883
+ this[out] = pb.add(this.facingAngle, pb.mul(pb.atan2(pb.sub(pb.mul(this.bladeDir.x, dir.y), pb.mul(this.bladeDir.y, dir.x)), pb.dot(this.bladeDir, dir)), pb.clamp(pb.mul(this[push], this.clump1.y), 0, 1)));
884
+ });
885
+ };
886
+ turn(this, 'facingNow', 'windPush', this.wind.at(0).xy);
887
+ turn(this, 'facingPrev', 'windPushPrev', this.wind.at(4).xy);
888
+ // Terrain normal under the blade (central differences of the height map, as
889
+ // in ClipmapGrassMaterial), x and z quantized to 12 bits each and packed into
890
+ // an integer a float holds exactly. Distant blades shade toward it.
891
+ this.$l.texel = pb.div(pb.vec2(1), this.heightInfo.xy);
892
+ this.$l.sampleDist = pb.mul(this.regionSize, pb.mul(this.texel, 2));
893
+ this.$l.hL = pb.textureSampleLevel(this.heightMap, pb.sub(this.uv, pb.vec2(this.texel.x, 0)), 0).r;
894
+ this.$l.hR = pb.textureSampleLevel(this.heightMap, pb.add(this.uv, pb.vec2(this.texel.x, 0)), 0).r;
895
+ this.$l.hU = pb.textureSampleLevel(this.heightMap, pb.sub(this.uv, pb.vec2(0, this.texel.y)), 0).r;
896
+ this.$l.hD = pb.textureSampleLevel(this.heightMap, pb.add(this.uv, pb.vec2(0, this.texel.y)), 0).r;
897
+ this.$l.terrainNormal = pb.normalize(pb.vec3(pb.neg(pb.div(pb.mul(pb.sub(this.hR, this.hL), this.posScale.y), this.sampleDist.x)), 1, pb.neg(pb.div(pb.mul(pb.sub(this.hD, this.hU), this.posScale.y), this.sampleDist.y))));
898
+ this.$l.quantized = pb.floor(pb.add(pb.mul(pb.add(pb.mul(this.terrainNormal.xz, 0.5), pb.vec2(0.5)), 4095), pb.vec2(0.5)));
899
+ this.$l.packedNormal = pb.add(pb.mul(this.quantized.x, 4096), this.quantized.y);
900
+ this.$l.first = pb.mul(this.slot, INSTANCE_VEC4.blade);
901
+ this.instances.setAt(this.first, pb.vec4(this.base, this.flaggedHash));
902
+ this.instances.setAt(pb.add(this.first, 1), pb.vec4(this.facingNow, this.facingPrev, this.windPush, this.windPushPrev));
903
+ this.instances.setAt(pb.add(this.first, 2), pb.vec4(this.clumpAngle, this.clumpColor, this.packedNormal, this.bladeHash));
904
+ this.instances.setAt(pb.add(this.first, 3), pb.vec4(this.bladeHeight, this.bladeWidth, this.tilt, this.bend));
905
+ }
906
+ });
907
+ });
908
+ });
909
+ });
910
+ }
911
+ });
912
+ this._programs[kind] = program;
913
+ }
914
+ return program;
915
+ }
916
+ onDispose() {
917
+ super.onDispose();
918
+ this._densityTexture.dispose();
919
+ for (const target of this._targets){
920
+ GrassGpuPlacement.disposeTarget(target);
921
+ }
922
+ this._targets.length = 0;
923
+ this._baseVertexBuffer.dispose();
924
+ this._indexBuffer.dispose();
925
+ }
926
+ }
927
+
928
+ export { GRASS_BLADE_LOD_BAND, GRASS_CULL_PLANES, GRASS_LOD_FADE_BAND, GrassGpuPlacement, GrassOcclusionMode, grassBladeLodDistance, grassDensityLod, grassHash };
929
+ //# sourceMappingURL=grass_gpu.js.map