@zephyr3d/scene 0.9.27 → 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 (502) 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 +54 -0
  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 +279 -55
  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 +171 -1
  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 +104 -178
  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 +25436 -15990
  109. package/dist/index.js +65 -11
  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/lambert.js +4 -1
  118. package/dist/material/lambert.js.map +1 -1
  119. package/dist/material/material.js +66 -4
  120. package/dist/material/material.js.map +1 -1
  121. package/dist/material/meshmaterial.js +118 -45
  122. package/dist/material/meshmaterial.js.map +1 -1
  123. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  124. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  126. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  128. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  130. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  132. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  133. package/dist/material/mixins/lit.js +191 -83
  134. package/dist/material/mixins/lit.js.map +1 -1
  135. package/dist/material/mixins/pbr/common.js +31 -206
  136. package/dist/material/mixins/pbr/common.js.map +1 -1
  137. package/dist/material/mtoon.js +1 -1
  138. package/dist/material/mtoon.js.map +1 -1
  139. package/dist/material/pbrmr.js +3 -0
  140. package/dist/material/pbrmr.js.map +1 -1
  141. package/dist/material/pbrsg.js +3 -0
  142. package/dist/material/pbrsg.js.map +1 -1
  143. package/dist/material/sdf_debug.js +78 -0
  144. package/dist/material/sdf_debug.js.map +1 -0
  145. package/dist/material/shader/helper.js +333 -191
  146. package/dist/material/shader/helper.js.map +1 -1
  147. package/dist/material/shader/terrain_blend.js +204 -43
  148. package/dist/material/shader/terrain_blend.js.map +1 -1
  149. package/dist/material/skin.js +331 -297
  150. package/dist/material/skin.js.map +1 -1
  151. package/dist/material/skinprofile.js +1058 -0
  152. package/dist/material/skinprofile.js.map +1 -0
  153. package/dist/material/sss.js +353 -0
  154. package/dist/material/sss.js.map +1 -0
  155. package/dist/material/sss_kernel.js +265 -0
  156. package/dist/material/sss_kernel.js.map +1 -0
  157. package/dist/material/sssprofile.js +879 -0
  158. package/dist/material/sssprofile.js.map +1 -0
  159. package/dist/material/subsurfaceprofile.js +4 -8
  160. package/dist/material/subsurfaceprofile.js.map +1 -1
  161. package/dist/material/terrain-cm.js +464 -188
  162. package/dist/material/terrain-cm.js.map +1 -1
  163. package/dist/material/unlit.js +3 -0
  164. package/dist/material/unlit.js.map +1 -1
  165. package/dist/material/water.js +1261 -1438
  166. package/dist/material/water.js.map +1 -1
  167. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  168. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  169. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  172. package/dist/physics/character.js +226 -0
  173. package/dist/physics/character.js.map +1 -0
  174. package/dist/physics/collider.js +209 -0
  175. package/dist/physics/collider.js.map +1 -0
  176. package/dist/physics/events.js +79 -0
  177. package/dist/physics/events.js.map +1 -0
  178. package/dist/physics/joint.js +272 -0
  179. package/dist/physics/joint.js.map +1 -0
  180. package/dist/physics/node_physics.js +234 -0
  181. package/dist/physics/node_physics.js.map +1 -0
  182. package/dist/physics/part.js +91 -0
  183. package/dist/physics/part.js.map +1 -0
  184. package/dist/physics/rigid_body.js +379 -0
  185. package/dist/physics/rigid_body.js.map +1 -0
  186. package/dist/physics/vehicle.js +335 -0
  187. package/dist/physics/vehicle.js.map +1 -0
  188. package/dist/physics/world.js +25 -0
  189. package/dist/physics/world.js.map +1 -0
  190. package/dist/posteffect/bloom.js +49 -14
  191. package/dist/posteffect/bloom.js.map +1 -1
  192. package/dist/posteffect/motionblur.js +106 -106
  193. package/dist/posteffect/motionblur.js.map +1 -1
  194. package/dist/posteffect/postsss.js +572 -0
  195. package/dist/posteffect/postsss.js.map +1 -0
  196. package/dist/posteffect/sao.js +2 -6
  197. package/dist/posteffect/sao.js.map +1 -1
  198. package/dist/posteffect/skinsss.js +605 -355
  199. package/dist/posteffect/skinsss.js.map +1 -1
  200. package/dist/posteffect/ssgi.js +91 -162
  201. package/dist/posteffect/ssgi.js.map +1 -1
  202. package/dist/posteffect/ssr.js +3 -11
  203. package/dist/posteffect/ssr.js.map +1 -1
  204. package/dist/posteffect/sss.js +4 -13
  205. package/dist/posteffect/sss.js.map +1 -1
  206. package/dist/posteffect/taa.js +1 -0
  207. package/dist/posteffect/taa.js.map +1 -1
  208. package/dist/render/abuffer_oit.js +3 -2
  209. package/dist/render/abuffer_oit.js.map +1 -1
  210. package/dist/render/clipmap.js +179 -64
  211. package/dist/render/clipmap.js.map +1 -1
  212. package/dist/render/cluster_light.js +958 -224
  213. package/dist/render/cluster_light.js.map +1 -1
  214. package/dist/render/cull_visitor.js +25 -5
  215. package/dist/render/cull_visitor.js.map +1 -1
  216. package/dist/render/debug_views.js +37 -0
  217. package/dist/render/debug_views.js.map +1 -0
  218. package/dist/render/depthpass.js +5 -1
  219. package/dist/render/depthpass.js.map +1 -1
  220. package/dist/render/drawable_mixin.js +53 -6
  221. package/dist/render/drawable_mixin.js.map +1 -1
  222. package/dist/render/dualdepthpeeling_oit.js +5 -5
  223. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  224. package/dist/render/envlight.js +196 -113
  225. package/dist/render/envlight.js.map +1 -1
  226. package/dist/render/fbm_wavegenerator.js +5 -10
  227. package/dist/render/fbm_wavegenerator.js.map +1 -1
  228. package/dist/render/fft_wavegenerator.js +247 -157
  229. package/dist/render/fft_wavegenerator.js.map +1 -1
  230. package/dist/render/gerstner_wavegenerator.js +16 -38
  231. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  232. package/dist/render/gi/probe_volume.js +1334 -0
  233. package/dist/render/gi/probe_volume.js.map +1 -0
  234. package/dist/render/gi/scene_gi.js +282 -0
  235. package/dist/render/gi/scene_gi.js.map +1 -0
  236. package/dist/render/gi/voxel_atlas.js +198 -0
  237. package/dist/render/gi/voxel_atlas.js.map +1 -0
  238. package/dist/render/gi/voxel_lighting.js +652 -0
  239. package/dist/render/gi/voxel_lighting.js.map +1 -0
  240. package/dist/render/gi/voxel_scene.js +642 -0
  241. package/dist/render/gi/voxel_scene.js.map +1 -0
  242. package/dist/render/gi/voxelize_pass.js +223 -0
  243. package/dist/render/gi/voxelize_pass.js.map +1 -0
  244. package/dist/render/globalbindgroup_allocator.js +3 -2
  245. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  246. package/dist/render/gpu_picking.js +9 -36
  247. package/dist/render/gpu_picking.js.map +1 -1
  248. package/dist/render/instance_culling.js +499 -0
  249. package/dist/render/instance_culling.js.map +1 -0
  250. package/dist/render/lightpass.js +19 -8
  251. package/dist/render/lightpass.js.map +1 -1
  252. package/dist/render/lod.js +46 -0
  253. package/dist/render/lod.js.map +1 -0
  254. package/dist/render/primitive.js +301 -9
  255. package/dist/render/primitive.js.map +1 -1
  256. package/dist/render/primitive_readback.js +209 -0
  257. package/dist/render/primitive_readback.js.map +1 -0
  258. package/dist/render/render_queue.js +162 -11
  259. package/dist/render/render_queue.js.map +1 -1
  260. package/dist/render/renderer.js +10 -7
  261. package/dist/render/renderer.js.map +1 -1
  262. package/dist/render/rendergraph/blackboard.js +14 -1
  263. package/dist/render/rendergraph/blackboard.js.map +1 -1
  264. package/dist/render/rendergraph/executor.js +136 -0
  265. package/dist/render/rendergraph/executor.js.map +1 -1
  266. package/dist/render/rendergraph/forward_plus_builder.js +272 -55
  267. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  268. package/dist/render/rendergraph/frame_graph_context.js +41 -0
  269. package/dist/render/rendergraph/frame_graph_context.js.map +1 -0
  270. package/dist/render/rendergraph/scene_render_context.js +31 -2
  271. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  272. package/dist/render/rendergraph/types.js.map +1 -1
  273. package/dist/render/renderpass.js +28 -5
  274. package/dist/render/renderpass.js.map +1 -1
  275. package/dist/render/sdf/box_allocator.js +98 -0
  276. package/dist/render/sdf/box_allocator.js.map +1 -0
  277. package/dist/render/sdf/global_sdf.js +865 -0
  278. package/dist/render/sdf/global_sdf.js.map +1 -0
  279. package/dist/render/sdf/scene_sdf.js +562 -0
  280. package/dist/render/sdf/scene_sdf.js.map +1 -0
  281. package/dist/render/sdf/sdf_atlas.js +227 -0
  282. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  283. package/dist/render/sdf/sdf_programs.js +42 -0
  284. package/dist/render/sdf/sdf_programs.js.map +1 -0
  285. package/dist/render/sdf/sdf_view.js +199 -0
  286. package/dist/render/sdf/sdf_view.js.map +1 -0
  287. package/dist/render/sdf/waterline.js +610 -0
  288. package/dist/render/sdf/waterline.js.map +1 -0
  289. package/dist/render/shadow_mask_pass.js +142 -25
  290. package/dist/render/shadow_mask_pass.js.map +1 -1
  291. package/dist/render/shore_wave_generator.js +1193 -0
  292. package/dist/render/shore_wave_generator.js.map +1 -0
  293. package/dist/render/sky.js +516 -86
  294. package/dist/render/sky.js.map +1 -1
  295. package/dist/render/transmission_thickness_pass.js +477 -0
  296. package/dist/render/transmission_thickness_pass.js.map +1 -0
  297. package/dist/render/underwater.js +58 -158
  298. package/dist/render/underwater.js.map +1 -1
  299. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  300. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  301. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  302. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  303. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  304. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  305. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  306. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  307. package/dist/render/volumetric_cloud/resources.js +380 -0
  308. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  309. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  310. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  311. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  312. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  313. package/dist/render/water_caustics.js +134 -319
  314. package/dist/render/water_caustics.js.map +1 -1
  315. package/dist/render/water_foam.js +627 -0
  316. package/dist/render/water_foam.js.map +1 -0
  317. package/dist/render/water_interaction.js +1795 -0
  318. package/dist/render/water_interaction.js.map +1 -0
  319. package/dist/render/weightedblended_oit.js +3 -3
  320. package/dist/render/weightedblended_oit.js.map +1 -1
  321. package/dist/scene/basesprite.js +9 -4
  322. package/dist/scene/basesprite.js.map +1 -1
  323. package/dist/scene/batchgroup.js +108 -12
  324. package/dist/scene/batchgroup.js.map +1 -1
  325. package/dist/scene/environment.js +28 -7
  326. package/dist/scene/environment.js.map +1 -1
  327. package/dist/scene/foliage.js +1020 -0
  328. package/dist/scene/foliage.js.map +1 -0
  329. package/dist/scene/foliage_buckets.js +147 -0
  330. package/dist/scene/foliage_buckets.js.map +1 -0
  331. package/dist/scene/foliage_mask.js +358 -0
  332. package/dist/scene/foliage_mask.js.map +1 -0
  333. package/dist/scene/hair.js +15 -4
  334. package/dist/scene/hair.js.map +1 -1
  335. package/dist/scene/light.js +179 -13
  336. package/dist/scene/light.js.map +1 -1
  337. package/dist/scene/mesh.js +327 -45
  338. package/dist/scene/mesh.js.map +1 -1
  339. package/dist/scene/meshdrawable.js +6 -0
  340. package/dist/scene/meshdrawable.js.map +1 -1
  341. package/dist/scene/msdftext.js +9 -3
  342. package/dist/scene/msdftext.js.map +1 -1
  343. package/dist/scene/msdftextsprite.js +9 -3
  344. package/dist/scene/msdftextsprite.js.map +1 -1
  345. package/dist/scene/particlesys.js +8 -2
  346. package/dist/scene/particlesys.js.map +1 -1
  347. package/dist/scene/physics_settings.js +157 -0
  348. package/dist/scene/physics_settings.js.map +1 -0
  349. package/dist/scene/scene.js +190 -44
  350. package/dist/scene/scene.js.map +1 -1
  351. package/dist/scene/scene_node.js +147 -6
  352. package/dist/scene/scene_node.js.map +1 -1
  353. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  354. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  355. package/dist/scene/terrain-cm/grass.js +881 -42
  356. package/dist/scene/terrain-cm/grass.js.map +1 -1
  357. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  358. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  359. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  360. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  361. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  362. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  363. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  364. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  365. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  366. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  367. package/dist/scene/update_queue.js +54 -0
  368. package/dist/scene/update_queue.js.map +1 -0
  369. package/dist/scene/water.js +633 -268
  370. package/dist/scene/water.js.map +1 -1
  371. package/dist/scene/water_surface_sampler.js +324 -0
  372. package/dist/scene/water_surface_sampler.js.map +1 -0
  373. package/dist/scene/wind.js +317 -0
  374. package/dist/scene/wind.js.map +1 -0
  375. package/dist/shaders/atmosphere.js +388 -166
  376. package/dist/shaders/atmosphere.js.map +1 -1
  377. package/dist/shaders/fog.js +100 -32
  378. package/dist/shaders/fog.js.map +1 -1
  379. package/dist/shaders/gi.js +365 -0
  380. package/dist/shaders/gi.js.map +1 -0
  381. package/dist/shaders/ltc_rect.js +202 -0
  382. package/dist/shaders/ltc_rect.js.map +1 -0
  383. package/dist/shaders/misc.js +1 -10
  384. package/dist/shaders/misc.js.map +1 -1
  385. package/dist/shaders/noise.js +5 -1
  386. package/dist/shaders/noise.js.map +1 -1
  387. package/dist/shaders/probes.js +309 -0
  388. package/dist/shaders/probes.js.map +1 -0
  389. package/dist/shaders/sdf.js +537 -0
  390. package/dist/shaders/sdf.js.map +1 -0
  391. package/dist/shaders/shadow.js +104 -70
  392. package/dist/shaders/shadow.js.map +1 -1
  393. package/dist/shaders/skin_brdf.js +243 -0
  394. package/dist/shaders/skin_brdf.js.map +1 -0
  395. package/dist/shaders/ssgi.js +63 -0
  396. package/dist/shaders/ssgi.js.map +1 -0
  397. package/dist/shaders/ssgi_reprojection.js +118 -0
  398. package/dist/shaders/ssgi_reprojection.js.map +1 -0
  399. package/dist/shaders/ssr.js +6 -159
  400. package/dist/shaders/ssr.js.map +1 -1
  401. package/dist/shaders/temporal.js +38 -27
  402. package/dist/shaders/temporal.js.map +1 -1
  403. package/dist/shaders/water.js +32 -82
  404. package/dist/shaders/water.js.map +1 -1
  405. package/dist/shaders/water_medium.js +7 -21
  406. package/dist/shaders/water_medium.js.map +1 -1
  407. package/dist/shadow/dom.js +110 -19
  408. package/dist/shadow/dom.js.map +1 -1
  409. package/dist/shadow/esm.js +3 -3
  410. package/dist/shadow/esm.js.map +1 -1
  411. package/dist/shadow/pcf_opt.js +15 -48
  412. package/dist/shadow/pcf_opt.js.map +1 -1
  413. package/dist/shadow/pcf_pd.js +15 -53
  414. package/dist/shadow/pcf_pd.js.map +1 -1
  415. package/dist/shadow/pcss.js +34 -9
  416. package/dist/shadow/pcss.js.map +1 -1
  417. package/dist/shadow/shadow_impl.js +3 -8
  418. package/dist/shadow/shadow_impl.js.map +1 -1
  419. package/dist/shadow/shadow_region.js +40 -2
  420. package/dist/shadow/shadow_region.js.map +1 -1
  421. package/dist/shadow/shadowmapper.js +93 -53
  422. package/dist/shadow/shadowmapper.js.map +1 -1
  423. package/dist/shadow/ssm.js +18 -72
  424. package/dist/shadow/ssm.js.map +1 -1
  425. package/dist/shadow/vsm.js +3 -3
  426. package/dist/shadow/vsm.js.map +1 -1
  427. package/dist/shapes/shape.js +44 -1
  428. package/dist/shapes/shape.js.map +1 -1
  429. package/dist/utility/buoyancy.js +287 -0
  430. package/dist/utility/buoyancy.js.map +1 -0
  431. package/dist/utility/physical.js +10 -1
  432. package/dist/utility/physical.js.map +1 -1
  433. package/dist/utility/pmrem.js +17 -43
  434. package/dist/utility/pmrem.js.map +1 -1
  435. package/dist/utility/sdf/bake.js +435 -0
  436. package/dist/utility/sdf/bake.js.map +1 -0
  437. package/dist/utility/sdf/bvh.js +576 -0
  438. package/dist/utility/sdf/bvh.js.map +1 -0
  439. package/dist/utility/serialization/manager.js +34 -6
  440. package/dist/utility/serialization/manager.js.map +1 -1
  441. package/dist/utility/serialization/scene/animation.js +46 -5
  442. package/dist/utility/serialization/scene/animation.js.map +1 -1
  443. package/dist/utility/serialization/scene/batch.js +11 -4
  444. package/dist/utility/serialization/scene/batch.js.map +1 -1
  445. package/dist/utility/serialization/scene/camera.js +87 -254
  446. package/dist/utility/serialization/scene/camera.js.map +1 -1
  447. package/dist/utility/serialization/scene/cloth.js +25 -0
  448. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  449. package/dist/utility/serialization/scene/common.js +14 -6
  450. package/dist/utility/serialization/scene/common.js.map +1 -1
  451. package/dist/utility/serialization/scene/foliage.js +821 -0
  452. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  453. package/dist/utility/serialization/scene/hair.js +11 -4
  454. package/dist/utility/serialization/scene/hair.js.map +1 -1
  455. package/dist/utility/serialization/scene/light.js +115 -7
  456. package/dist/utility/serialization/scene/light.js.map +1 -1
  457. package/dist/utility/serialization/scene/material.js +388 -220
  458. package/dist/utility/serialization/scene/material.js.map +1 -1
  459. package/dist/utility/serialization/scene/mesh.js +69 -4
  460. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  461. package/dist/utility/serialization/scene/node.js +140 -5
  462. package/dist/utility/serialization/scene/node.js.map +1 -1
  463. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  464. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  465. package/dist/utility/serialization/scene/particle.js +11 -4
  466. package/dist/utility/serialization/scene/particle.js.map +1 -1
  467. package/dist/utility/serialization/scene/physics.js +120 -0
  468. package/dist/utility/serialization/scene/physics.js.map +1 -0
  469. package/dist/utility/serialization/scene/primitive.js +124 -10
  470. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  471. package/dist/utility/serialization/scene/scene.js +1687 -51
  472. package/dist/utility/serialization/scene/scene.js.map +1 -1
  473. package/dist/utility/serialization/scene/sprite.js +11 -4
  474. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  475. package/dist/utility/serialization/scene/terrain.js +163 -33
  476. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  477. package/dist/utility/serialization/scene/text.js +11 -4
  478. package/dist/utility/serialization/scene/text.js.map +1 -1
  479. package/dist/utility/serialization/scene/water.js +1543 -264
  480. package/dist/utility/serialization/scene/water.js.map +1 -1
  481. package/dist/utility/serialization/types.js.map +1 -1
  482. package/dist/utility/shprojector.js +10 -22
  483. package/dist/utility/shprojector.js.map +1 -1
  484. package/dist/utility/textures/foam.js +170 -0
  485. package/dist/utility/textures/foam.js.map +1 -0
  486. package/dist/utility/textures/ggxlut.js +17 -43
  487. package/dist/utility/textures/ggxlut.js.map +1 -1
  488. package/dist/utility/textures/ltcdata.js +4 -4
  489. package/dist/utility/textures/ltcdata.js.map +1 -1
  490. package/dist/utility/textures/ltclut.js +16 -6
  491. package/dist/utility/textures/ltclut.js.map +1 -1
  492. package/dist/utility/unsupported.js +24 -0
  493. package/dist/utility/unsupported.js.map +1 -0
  494. package/dist/values.js +13 -1
  495. package/dist/values.js.map +1 -1
  496. package/package.json +25 -12
  497. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  498. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  499. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  500. package/src/asset/meshoptimizer/LICENSE +21 -0
  501. package/dist/animation/joint_dynamics/convex_collider.js +0 -320
  502. package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
@@ -9,6 +9,7 @@ import '../render/primitive.js';
9
9
  import '@zephyr3d/device';
10
10
  import { getDevice } from '../app/api.js';
11
11
  import { CopyBlitter } from '../blitter/copy.js';
12
+ import { blendTerrainDetailNormal, terrainSplatMaskCount, blendTerrainRoughness, blendTerrainAlbedo } from './shader/terrain_blend.js';
12
13
 
13
14
  const MAX_DETAIL_MAPS = 8;
14
15
  /**
@@ -17,6 +18,8 @@ const MAX_DETAIL_MAPS = 8;
17
18
  */ class ClipmapTerrainMaterial extends applyMaterialMixins(MeshMaterial, mixinLight, mixinPBRMetallicRoughness) {
18
19
  static FEATURE_DETAIL_MAP = this.defineFeature();
19
20
  static FEATURE_DEBUG_MODE = this.defineFeature();
21
+ static FEATURE_VIRTUAL_TEXTURE = this.defineFeature();
22
+ /** Bit i: detail layer i uses hex tiling */ static FEATURE_HEX_TILING = this.defineFeature();
20
23
  static _defaultDetailMap = new DRef();
21
24
  static _defaultNormalMap = new DRef();
22
25
  _region;
@@ -28,6 +31,8 @@ const MAX_DETAIL_MAPS = 8;
28
31
  _splatMapSize;
29
32
  _heightMapSize;
30
33
  _levelDataBuffer;
34
+ _virtualTexture;
35
+ _contentVersion;
31
36
  constructor(heightMap){
32
37
  super();
33
38
  this.metallic = 0;
@@ -47,6 +52,10 @@ const MAX_DETAIL_MAPS = 8;
47
52
  this._heightMapSize = new Vector4(this.heightMap.width, this.heightMap.height, 1 / this.heightMap.width, 1 / this.heightMap.height);
48
53
  this.useFeature(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP, 0);
49
54
  this.useFeature(ClipmapTerrainMaterial.FEATURE_DEBUG_MODE, 'none');
55
+ this.useFeature(ClipmapTerrainMaterial.FEATURE_VIRTUAL_TEXTURE, '');
56
+ this.useFeature(ClipmapTerrainMaterial.FEATURE_HEX_TILING, 0);
57
+ this._virtualTexture = null;
58
+ this._contentVersion = 0;
50
59
  }
51
60
  static get MAX_DETAIL_MAP_COUNT() {
52
61
  return MAX_DETAIL_MAPS;
@@ -57,6 +66,111 @@ const MAX_DETAIL_MAPS = 8;
57
66
  set debugMode(mode) {
58
67
  this.useFeature(ClipmapTerrainMaterial.FEATURE_DEBUG_MODE, mode);
59
68
  }
69
+ /**
70
+ * Bumped whenever a splat or detail texture is replaced. The detail parameters, the layer
71
+ * count and the roughness are uniforms, compared directly by the virtual texture owner.
72
+ * @internal
73
+ */ get contentVersion() {
74
+ return this._contentVersion;
75
+ }
76
+ /**
77
+ * The runtime virtual texture holding the blended layers, null to blend them per pixel.
78
+ * Only used on WebGPU with at least one detail layer.
79
+ * @internal
80
+ */ get virtualTexture() {
81
+ return this._virtualTexture;
82
+ }
83
+ set virtualTexture(vt) {
84
+ this._virtualTexture = vt;
85
+ this.updateVirtualTextureFeature();
86
+ }
87
+ /** Whether shading reads the runtime virtual texture instead of blending the layers @internal */ get virtualTextureUsed() {
88
+ return !!this.featureUsed(ClipmapTerrainMaterial.FEATURE_VIRTUAL_TEXTURE);
89
+ }
90
+ /**
91
+ * The feature value is the layout of the virtual texture, which the shader bakes in (level
92
+ * count and sizes, page and atlas geometry), so a rebuilt texture gets its own program.
93
+ */ updateVirtualTextureFeature() {
94
+ const vt = this._virtualTexture;
95
+ const used = !!vt && getDevice().type === 'webgpu' && this._detailMapInfo.numDetailMaps > 0;
96
+ this.useFeature(ClipmapTerrainMaterial.FEATURE_VIRTUAL_TEXTURE, used ? `${vt.levels[0].pagesX}x${vt.levels[0].pagesY}:${vt.pageSize}:${vt.border}:${vt.atlasSize}` : '');
97
+ }
98
+ /** @internal */ get detailMapParams() {
99
+ return this._detailMapInfo.detailMapParams;
100
+ }
101
+ /** @internal */ get detailHexParams() {
102
+ return this._detailMapInfo.detailHexParams;
103
+ }
104
+ /** Bit i set: detail layer i is hex tiled. @internal */ get hexTilingMask() {
105
+ return this.featureUsed(ClipmapTerrainMaterial.FEATURE_HEX_TILING);
106
+ }
107
+ updateHexTilingFeature() {
108
+ let mask = 0;
109
+ for(let i = 0; i < this._detailMapInfo.numDetailMaps; i++){
110
+ if (this._detailMapInfo.detailHexParams[i * 4 + 3] !== 0) {
111
+ mask |= 1 << i;
112
+ }
113
+ }
114
+ this.useFeature(ClipmapTerrainMaterial.FEATURE_HEX_TILING, mask);
115
+ }
116
+ /**
117
+ * Whether a detail layer is hex tiled to break up its repetition (Mikkelsen, Practical
118
+ * Real-Time Hex-Tiling, 2022). Costs three texture samples per layer instead of one, only
119
+ * when the layers are blended: once per page with the runtime virtual texture.
120
+ */ getDetailMapHexTiling(index) {
121
+ if (index >= this._detailMapInfo.numDetailMaps || index < 0 || !Number.isInteger(index)) {
122
+ console.error('Invalid detail map index');
123
+ return false;
124
+ }
125
+ return this._detailMapInfo.detailHexParams[index * 4 + 3] !== 0;
126
+ }
127
+ setDetailMapHexTiling(index, enabled) {
128
+ if (index >= this._detailMapInfo.numDetailMaps || index < 0 || !Number.isInteger(index)) {
129
+ console.error('Invalid detail map index');
130
+ return;
131
+ }
132
+ this._detailMapInfo.detailHexParams[index * 4 + 3] = enabled ? 1 : 0;
133
+ this.updateHexTilingFeature();
134
+ }
135
+ /**
136
+ * Hex tiling parameters of a detail layer as [rotation, scale, contrast], MaterialX
137
+ * hextiledimage inputs:
138
+ * - rotation: 0 to 1, how much each tile is turned at random, 1 for any angle
139
+ * - scale: 0 to 1, how much each tile is resized at random, 1 for 0.5 to 2 times
140
+ * - contrast: 0 to 1, how much brighter tiles win at the seams between tiles
141
+ */ getDetailMapHexParams(index) {
142
+ if (index >= this._detailMapInfo.numDetailMaps || index < 0 || !Number.isInteger(index)) {
143
+ console.error('Invalid detail map index');
144
+ return [
145
+ 1,
146
+ 1,
147
+ 0.5
148
+ ];
149
+ }
150
+ const p = this._detailMapInfo.detailHexParams;
151
+ return [
152
+ p[index * 4],
153
+ p[index * 4 + 1],
154
+ p[index * 4 + 2]
155
+ ];
156
+ }
157
+ setDetailMapHexParams(index, rotation, scale, contrast) {
158
+ if (index >= this._detailMapInfo.numDetailMaps || index < 0 || !Number.isInteger(index)) {
159
+ console.error('Invalid detail map index');
160
+ return;
161
+ }
162
+ const p = this._detailMapInfo.detailHexParams;
163
+ p[index * 4] = rotation;
164
+ p[index * 4 + 1] = scale;
165
+ p[index * 4 + 2] = contrast;
166
+ this.uniformChanged();
167
+ }
168
+ /** @internal */ get detailMapArray() {
169
+ return this._detailMapInfo.detailMap.get();
170
+ }
171
+ /** @internal */ get detailNormalMapArray() {
172
+ return this._detailMapInfo.detailNormalMap?.get() ?? null;
173
+ }
60
174
  /** @internal */ setLevelData(data, length) {
61
175
  this._levelDataBuffer.get().bufferSubData(0, data, 0, length);
62
176
  }
@@ -92,10 +206,6 @@ const MAX_DETAIL_MAPS = 8;
92
206
  console.error('Invalid number of detail maps');
93
207
  return;
94
208
  }
95
- if (getDevice().type === 'webgl' && val > 4) {
96
- console.error('Only 4 detail map layers is supported for WebGL1');
97
- return;
98
- }
99
209
  const n = this._detailMapInfo.numDetailMaps;
100
210
  const defaultDetailMap = ClipmapTerrainMaterial.getDefaultDetailMap();
101
211
  const defaultNormalMap = ClipmapTerrainMaterial.getDefaultNormalMap();
@@ -106,6 +216,12 @@ const MAX_DETAIL_MAPS = 8;
106
216
  this.setDetailNormalMap(i, defaultNormalMap);
107
217
  this.setDetailMapUVScale(i, 80);
108
218
  this.setDetailMapRoughness(i, 1);
219
+ this._detailMapInfo.detailHexParams.set([
220
+ 1,
221
+ 1,
222
+ 0.5,
223
+ 0
224
+ ], i * 4);
109
225
  this._detailMapInfo.detailMapList[i].dispose();
110
226
  this._detailMapInfo.detailNormalMapList[i].dispose();
111
227
  }
@@ -115,12 +231,20 @@ const MAX_DETAIL_MAPS = 8;
115
231
  this.setDetailNormalMap(i, defaultNormalMap);
116
232
  this.setDetailMapUVScale(i, 80);
117
233
  this.setDetailMapRoughness(i, 1);
234
+ this._detailMapInfo.detailHexParams.set([
235
+ 1,
236
+ 1,
237
+ 0.5,
238
+ 0
239
+ ], i * 4);
118
240
  this._detailMapInfo.detailMapList[i].dispose();
119
241
  this._detailMapInfo.detailNormalMapList[i].dispose();
120
242
  }
121
243
  this._detailMapInfo.numDetailMaps = val;
122
244
  }
123
245
  this.useFeature(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP, this._detailMapInfo.numDetailMaps);
246
+ this.updateVirtualTextureFeature();
247
+ this.updateHexTilingFeature();
124
248
  }
125
249
  getSplatMap() {
126
250
  return this._detailMapInfo.splatMap?.get() ?? null;
@@ -132,6 +256,7 @@ const MAX_DETAIL_MAPS = 8;
132
256
  return;
133
257
  }
134
258
  this._detailMapInfo.splatMap.set(tex);
259
+ this._contentVersion++;
135
260
  this.uniformChanged();
136
261
  }
137
262
  }
@@ -186,14 +311,13 @@ const MAX_DETAIL_MAPS = 8;
186
311
  this._detailMapInfo.detailMapList[index] = new DRef();
187
312
  }
188
313
  this._detailMapInfo.detailMapList[index].set(albedoMap === ClipmapTerrainMaterial.getDefaultDetailMap() ? null : albedoMap);
189
- if (getDevice().type !== 'webgl') {
190
- const blitter = new CopyBlitter();
191
- const fb = getDevice().createFrameBuffer([
192
- this._detailMapInfo.detailMap.get()
193
- ], null);
194
- blitter.blit(albedoMap, fb, index, albedoMap.width === this._detailMapSize && albedoMap.height === this._detailMapSize ? fetchSampler('clamp_nearest_nomip') : fetchSampler('clamp_linear_nomip'));
195
- fb.dispose();
196
- }
314
+ this._contentVersion++;
315
+ const blitter = new CopyBlitter();
316
+ const fb = getDevice().createFrameBuffer([
317
+ this._detailMapInfo.detailMap.get()
318
+ ], null);
319
+ blitter.blit(albedoMap, fb, index, albedoMap.width === this._detailMapSize && albedoMap.height === this._detailMapSize ? fetchSampler('clamp_nearest_nomip') : fetchSampler('clamp_linear_nomip'));
320
+ fb.dispose();
197
321
  }
198
322
  getDetailNormalMap(index) {
199
323
  if (index >= this._detailMapInfo.numDetailMaps || index < 0 || !Number.isInteger(index)) {
@@ -212,14 +336,13 @@ const MAX_DETAIL_MAPS = 8;
212
336
  this._detailMapInfo.detailNormalMapList[index] = new DRef();
213
337
  }
214
338
  this._detailMapInfo.detailNormalMapList[index].set(normalMap === ClipmapTerrainMaterial.getDefaultNormalMap() ? null : normalMap);
215
- if (getDevice().type !== 'webgl') {
216
- const blitter = new CopyBlitter();
217
- const fb = getDevice().createFrameBuffer([
218
- this._detailMapInfo.detailNormalMap.get()
219
- ], null);
220
- blitter.blit(normalMap, fb, index, normalMap.width === this._detailMapSize && normalMap.height === this._detailMapSize ? fetchSampler('clamp_nearest_nomip') : fetchSampler('clamp_linear_nomip'));
221
- fb.dispose();
222
- }
339
+ this._contentVersion++;
340
+ const blitter = new CopyBlitter();
341
+ const fb = getDevice().createFrameBuffer([
342
+ this._detailMapInfo.detailNormalMap.get()
343
+ ], null);
344
+ blitter.blit(normalMap, fb, index, normalMap.width === this._detailMapSize && normalMap.height === this._detailMapSize ? fetchSampler('clamp_nearest_nomip') : fetchSampler('clamp_linear_nomip'));
345
+ fb.dispose();
223
346
  }
224
347
  /** @internal */ update(region, terrainScale) {
225
348
  if (!region.equalsTo(this._region) || !terrainScale.equalsTo(this._terrainScale)) {
@@ -234,6 +357,7 @@ const MAX_DETAIL_MAPS = 8;
234
357
  set heightMap(val) {
235
358
  if (val !== this._heightMap.get()) {
236
359
  this._heightMap.set(val);
360
+ this._contentVersion++;
237
361
  this._heightMapSize.setXYZW(this.heightMap.width, this.heightMap.height, 1 / this.heightMap.width, 1 / this.heightMap.height);
238
362
  this.uniformChanged();
239
363
  }
@@ -253,41 +377,104 @@ const MAX_DETAIL_MAPS = 8;
253
377
  getMetallicRoughnessTexCoord = function(scope) {
254
378
  return scope.$inputs.uv;
255
379
  };
256
- sampleDetailNormalMap(scope, index, texCoord) {
380
+ /** Raw rgb of a detail normal map, see blendTerrainDetailNormal */ sampleDetailNormalMap(scope, index, texCoord, ddx = null, ddy = null) {
257
381
  const pb = scope.$builder;
258
- const sample = this.drawContext.device.type === 'webgl' ? pb.textureSample(scope[`detailNormalMap${index}`], texCoord).rgb : pb.textureArraySample(scope.detailNormalMap, texCoord, index).rgb;
259
- const normal = pb.sub(pb.mul(sample, 2), pb.vec3(1));
260
- return pb.normalize(normal);
382
+ return ddx && ddy ? pb.textureArraySampleGrad(scope.detailNormalMap, texCoord, index, ddx, ddy).rgb : pb.textureArraySample(scope.detailNormalMap, texCoord, index).rgb;
383
+ }
384
+ /**
385
+ * Gradients of the terrain uv and the hex tiled layers, for the blend functions; null when no
386
+ * layer is hex tiled, so the others keep sampling with implicit derivatives.
387
+ */ hexTilingInputs(scope) {
388
+ const mask = this.hexTilingMask;
389
+ if (!mask) {
390
+ return [
391
+ null,
392
+ null
393
+ ];
394
+ }
395
+ const pb = scope.$builder;
396
+ return [
397
+ {
398
+ ddx: pb.dpdx(scope.$inputs.uv),
399
+ ddy: pb.dpdy(scope.$inputs.uv)
400
+ },
401
+ {
402
+ mask,
403
+ params: scope.detailHexParams
404
+ }
405
+ ];
261
406
  }
262
407
  calculateDetailNormal(scope, TBN) {
263
408
  const that = this;
264
409
  const pb = scope.$builder;
265
410
  const funcName = 'getTerrainNormal';
411
+ const numDetailMaps = that.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP);
266
412
  pb.func(funcName, [
267
- pb.mat3('TBN')
413
+ pb.mat3('TBN'),
414
+ ...this.splatMaskParams(scope, numDetailMaps)
268
415
  ], function() {
269
- const numDetailMaps = that.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP);
270
- this.$l.detailNormal = pb.vec3(0);
271
- for(let i = 0; i < numDetailMaps + 3 >> 2; i++){
272
- this.$l[`mask${i}`] = that.drawContext.device.type === 'webgl' ? pb.textureSample(this.splatMap, this.$inputs.uv) : pb.textureArraySample(this.splatMap, this.$inputs.uv, i);
273
- }
274
- for(let i = 0; i < numDetailMaps; i++){
275
- const uv = pb.mul(this.$inputs.uv, this.detailParams[i].x);
276
- this.detailNormal = pb.add(this.detailNormal, pb.mul(that.sampleDetailNormalMap(this, i, uv), this[`mask${i >> 2}`][i & 3]));
277
- }
416
+ this.$l.detailNormal = blendTerrainDetailNormal(this, numDetailMaps, that.splatMaskLocals(this, numDetailMaps), this.$inputs.uv, this.detailParams, (scope, i, uv, ddx, ddy)=>that.sampleDetailNormalMap(scope, i, uv, ddx, ddy), ...that.hexTilingInputs(this));
278
417
  this.$return(pb.normalize(pb.mul(this.TBN, this.detailNormal)));
279
418
  });
280
- return pb.getGlobalScope()[funcName](TBN);
419
+ return pb.getGlobalScope()[funcName](TBN, ...this.splatMaskArgs(scope, numDetailMaps));
420
+ }
421
+ /** Parameter declarations for passing the splat masks sampled in the main scope */ splatMaskParams(scope, numDetailMaps) {
422
+ const pb = scope.$builder;
423
+ return Array.from({
424
+ length: terrainSplatMaskCount(numDetailMaps)
425
+ }, (_, i)=>pb.vec4(`mask${i}`));
426
+ }
427
+ /** The splat mask parameters inside a function declared with splatMaskParams() */ splatMaskLocals(scope, numDetailMaps) {
428
+ return Array.from({
429
+ length: terrainSplatMaskCount(numDetailMaps)
430
+ }, (_, i)=>scope[`mask${i}`]);
431
+ }
432
+ /** The splat masks sampled once in the main scope, see fragmentShader() */ splatMaskArgs(scope, numDetailMaps) {
433
+ return Array.from({
434
+ length: terrainSplatMaskCount(numDetailMaps)
435
+ }, (_, i)=>scope[`terrainSplatMask${i}`]);
281
436
  }
282
437
  getNormalTexCoord = function(scope) {
283
438
  return scope.$inputs.uv;
284
439
  };
440
+ /** Samples the splat weights of detail layers [4 * index, 4 * index + 3] */ sampleSplatMask(scope, index) {
441
+ const pb = scope.$builder;
442
+ return pb.textureArraySample(scope.splatMap, scope.$inputs.uv, index);
443
+ }
444
+ calculateRoughness(scope, albedo, normal) {
445
+ const base = super.calculateRoughness(scope, albedo, normal);
446
+ const numDetailMaps = this.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP);
447
+ if (numDetailMaps === 0) {
448
+ return base;
449
+ }
450
+ if (this.virtualTextureUsed) {
451
+ // This runs inside PBRLight's own function, so the roughness read from the virtual texture
452
+ // comes in through albedo.a (see sampleVirtualTexture); nothing reads the terrain albedo
453
+ // alpha otherwise (opaque, no subsurface outputs). The page fill already blended in the
454
+ // material roughness.
455
+ return albedo.a;
456
+ }
457
+ const that = this;
458
+ const pb = scope.$builder;
459
+ const funcName = 'getTerrainRoughness';
460
+ pb.func(funcName, [
461
+ pb.float('base')
462
+ ], function() {
463
+ const masks = [];
464
+ for(let i = 0; i < terrainSplatMaskCount(numDetailMaps); i++){
465
+ this.$l[`mask${i}`] = that.sampleSplatMask(this, i);
466
+ masks.push(this[`mask${i}`]);
467
+ }
468
+ this.$return(blendTerrainRoughness(this, numDetailMaps, masks, this.detailParams, this.base));
469
+ });
470
+ return pb.getGlobalScope()[funcName](base);
471
+ }
285
472
  calculateAlbedoColor(scope) {
286
473
  const that = this;
287
474
  const pb = scope.$builder;
288
475
  const funcName = 'getTerrainAlbedo';
289
- pb.func(funcName, [], function() {
290
- const numDetailMaps = that.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP);
476
+ const numDetailMaps = that.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP);
477
+ pb.func(funcName, this.splatMaskParams(scope, numDetailMaps), function() {
291
478
  if (numDetailMaps === 0) {
292
479
  this.$l.checkerPos = pb.mul(this.$inputs.uv, pb.sub(this.region.zw, this.region.xy));
293
480
  this.$l.ddx = pb.dpdx(this.checkerPos);
@@ -302,74 +489,129 @@ const MAX_DETAIL_MAPS = 8;
302
489
  this.$l.checkerColor = pb.mix(pb.vec3(0.4), pb.vec3(1), pb.vec3(this.checker));
303
490
  this.$return(pb.vec4(this.checkerColor, 1));
304
491
  } else {
305
- this.$l.color = pb.vec3(0);
306
- for(let i = 0; i < numDetailMaps + 3 >> 2; i++){
307
- this.$l[`mask${i}`] = that.drawContext.device.type === 'webgl' ? pb.textureSample(this.splatMap, this.$inputs.uv) : pb.textureArraySample(this.splatMap, this.$inputs.uv, i);
308
- }
309
- for(let i = 0; i < numDetailMaps; i++){
310
- const uv = pb.mul(this.$inputs.uv, this.detailParams[i].x);
311
- const sample = that.drawContext.device.type === 'webgl' ? pb.textureSample(this[`detailAlbedoMap${i}`], uv).rgb : pb.textureArraySample(this.detailAlbedoMap, uv, i).rgb;
312
- this.color = pb.add(this.color, pb.mul(sample, this[`mask${i >> 2}`][i & 3]));
313
- }
492
+ this.$l.color = blendTerrainAlbedo(this, numDetailMaps, that.splatMaskLocals(this, numDetailMaps), this.$inputs.uv, this.detailParams, (scope, i, uv, ddx, ddy)=>ddx && ddy ? pb.textureArraySampleGrad(scope.detailAlbedoMap, uv, i, ddx, ddy) : pb.textureArraySample(scope.detailAlbedoMap, uv, i), ...that.hexTilingInputs(this));
314
493
  this.$return(pb.vec4(this.color, 1));
315
494
  }
316
495
  });
317
- return pb.getGlobalScope()[funcName]();
496
+ return pb.getGlobalScope()[funcName](...this.splatMaskArgs(scope, numDetailMaps));
318
497
  }
319
- sampleHeightMap(scope, uv, pos, levelStart, levelDiff) {
498
+ /**
499
+ * Reads the blended layers from the runtime virtual texture: (linear albedo, roughness) is
500
+ * returned, the world space normal written to `normal`.
501
+ */ sampleVirtualTexture(scope, normal) {
502
+ const pb = scope.$builder;
503
+ const vt = this._virtualTexture;
504
+ const maxLevel = vt.levels.length - 1;
505
+ pb.func('zTerrainSampleRVT', [
506
+ pb.vec3('normal').out()
507
+ ], function() {
508
+ this.$l.uv = this.$inputs.uv;
509
+ this.$l.level = vt.computeLevel(this, this.uv);
510
+ this.$l.level0 = pb.floor(this.level);
511
+ this.$l.level1 = pb.min(pb.add(this.level0, 1), maxLevel);
512
+ this.$l.loc0 = vt.resolve(this, pb.uint(this.level0), this.uv);
513
+ this.$l.loc1 = vt.resolve(this, pb.uint(this.level1), this.uv);
514
+ this.$l.t = pb.select(pb.float(0), pb.sub(this.level, this.level0), pb.equal(this.loc1.z, this.level1));
515
+ this.$l.baseColor = pb.mix(pb.textureSampleLevel(this.zTerrainRVT0, this.loc0.xy, 0), pb.textureSampleLevel(this.zTerrainRVT0, this.loc1.xy, 0), this.t);
516
+ this.$l.normalRoughness = pb.mix(pb.textureSampleLevel(this.zTerrainRVT1, this.loc0.xy, 0), pb.textureSampleLevel(this.zTerrainRVT1, this.loc1.xy, 0), this.t);
517
+ // VirtualTextureUnpackNormal
518
+ this.$l.nxz = pb.sub(pb.mul(this.normalRoughness.xz, 255 / 127), pb.vec2(1));
519
+ this.normal = pb.normalize(pb.vec3(this.nxz.x, pb.sqrt(pb.clamp(pb.sub(1, pb.dot(this.nxz, this.nxz)), 0, 1)), this.nxz.y));
520
+ // sRGB decode
521
+ this.$l.c = this.baseColor.rgb;
522
+ this.$l.linear = pb.mix(pb.pow(pb.div(pb.add(this.c, pb.vec3(0.055)), 1.055), pb.vec3(2.4)), pb.div(this.c, 12.92), pb.vec3(pb.lessThanEqual(this.c, pb.vec3(0.04045))));
523
+ this.$return(pb.vec4(this.linear, this.normalRoughness.y));
524
+ });
525
+ return scope.zTerrainSampleRVT(normal);
526
+ }
527
+ /**
528
+ * Height of a clipmap vertex, blended towards the next coarser level across the outer part of
529
+ * its ring (the transition regions of Hoppe's GPU geometry clipmaps).
530
+ *
531
+ * The outer boundary of level L lies on grid lines of level L + 1, so its vertices already sit
532
+ * where the coarser ring's do; only their heights have to agree. hC is the height the coarser
533
+ * mesh has at the vertex: its own height where a coarse vertex coincides, the mean of the two
534
+ * coarse vertices around the edge midpoints, the mean of the four corners at cell centres
535
+ * (never on a boundary, only has to be continuous). With alpha reaching 1 before the boundary,
536
+ * boundary vertices lie exactly on the coarse edges and the rings meet without T-junction
537
+ * cracks. The inner boundary needs no blend: it is the finer level's outer one.
538
+ *
539
+ * @param uv - Height map coordinate of the vertex
540
+ * @param gridCoord - Global grid coordinate of the vertex, in grid cells
541
+ * @param pos - World XZ of the vertex
542
+ */ sampleHeightMap(scope, uv, gridCoord, pos, levelStart, levelDiff) {
320
543
  const pb = scope.$builder;
321
- const morphRange = 0.5;
544
+ // Blend over the outer 40% of the ring, fully coarse over its last 5% so boundary vertices
545
+ // are unaffected by rounding in the ring ratio.
546
+ const transitionStart = 0.6;
547
+ const transitionEnd = 0.95;
322
548
  pb.func('sampleHeightMap', [
323
549
  pb.vec2('uv'),
550
+ pb.vec2('gridCoord'),
324
551
  pb.vec2('pos'),
325
552
  pb.vec4('levelStart'),
326
553
  pb.vec4('levelDiff')
327
554
  ], function() {
328
- this.$l.h1 = pb.textureSampleLevel(this.heightMap, this.uv, this.$inputs.miplevel).r;
555
+ this.$l.h = pb.textureSampleLevel(this.heightMap, this.uv, this.$inputs.miplevel).r;
329
556
  this.$l.ratio = pb.mul(pb.sub(this.pos.xyxy, this.levelStart), this.levelDiff);
330
- this.$l.maxVal = pb.clamp(pb.max(pb.max(this.ratio.x, this.ratio.y), pb.max(this.ratio.z, this.ratio.w)), 0, 1);
331
- this.$l.morphFactor = pb.float(0);
332
- this.$if(pb.greaterThan(this.maxVal, 1 - morphRange), function() {
333
- this.$l.h2 = pb.textureSampleLevel(this.heightMap, this.uv, pb.add(this.$inputs.miplevel, 1)).r;
334
- this.morphFactor = pb.div(pb.sub(this.maxVal, 1 - morphRange), morphRange * 2);
335
- this.h1 = pb.mix(this.h1, this.h2, this.morphFactor);
557
+ this.$l.maxVal = pb.max(pb.max(this.ratio.x, this.ratio.y), pb.max(this.ratio.z, this.ratio.w));
558
+ this.$l.alpha = pb.clamp(pb.div(pb.sub(this.maxVal, transitionStart), transitionEnd - transitionStart), 0, 1);
559
+ this.$if(pb.greaterThan(this.alpha, 0), function() {
560
+ this.$l.levelScale = pb.exp2(this.$inputs.miplevel);
561
+ // Vertex index in this level; the coarser level's vertices are its even ones
562
+ this.$l.index = pb.div(this.gridCoord, this.levelScale);
563
+ this.$l.odd = pb.sub(this.index, pb.mul(pb.floor(pb.mul(this.index, 0.5)), 2));
564
+ // Height map distance to the neighbouring coarse vertices along the odd axes
565
+ this.$l.d = pb.div(pb.mul(this.odd, pb.mul(this.levelScale, this.clipmapGridInfo.x)), pb.sub(this.region.zw, this.region.xy));
566
+ this.$l.coarseLevel = pb.add(this.$inputs.miplevel, 1);
567
+ this.$l.hc = pb.mul(pb.add(pb.add(pb.textureSampleLevel(this.heightMap, pb.sub(this.uv, this.d), this.coarseLevel).r, pb.textureSampleLevel(this.heightMap, pb.add(this.uv, this.d), this.coarseLevel).r), pb.add(pb.textureSampleLevel(this.heightMap, pb.add(this.uv, pb.vec2(this.d.x, pb.neg(this.d.y))), this.coarseLevel).r, pb.textureSampleLevel(this.heightMap, pb.add(this.uv, pb.vec2(pb.neg(this.d.x), this.d.y)), this.coarseLevel).r)), 0.25);
568
+ this.h = pb.mix(this.h, this.hc, this.alpha);
336
569
  });
337
- this.$if(pb.lessThan(this.maxVal, morphRange), function() {
338
- this.$l.h2 = pb.textureSampleLevel(this.heightMap, this.uv, pb.max(pb.sub(this.$inputs.miplevel, 1), 0)).r;
339
- this.morphFactor = pb.mul(pb.add(pb.div(this.maxVal, morphRange), 1), 0.5);
340
- this.h1 = pb.mix(this.h1, this.h2, this.morphFactor);
341
- });
342
- this.$return(this.h1);
570
+ this.$return(this.h);
343
571
  });
344
- return scope.sampleHeightMap(uv, pos, levelStart, levelDiff);
345
- }
346
- calculateTerrainTBN(scope, clipmapPos, uv, texSize, scale, levelStart, levelDiff, tangent, bitangent, normal) {
347
- const that = this;
572
+ return scope.sampleHeightMap(uv, gridCoord, pos, levelStart, levelDiff);
573
+ }
574
+ /**
575
+ * Terrain tangent frame at the fragment, differentiated from the height map itself.
576
+ *
577
+ * Per-vertex normals only see the mesh resolution and blur slopes: a one-texel peak gets an
578
+ * upward normal at its tip and half the real slope around it. Differentiating the bilinear
579
+ * height field per pixel gives the slopes the geometry actually has, and keeps height map
580
+ * detail on the coarse outer rings.
581
+ */ calculateTerrainTBN(scope, tangent, bitangent, normal) {
348
582
  const pb = scope.$builder;
583
+ const textureLod = this.drawContext.device.getDeviceCaps().shaderCaps.supportShaderTextureLod;
349
584
  pb.func('calcTerrainTBN', [
350
- pb.vec2('clipmapPos'),
351
- pb.vec2('uv'),
352
- pb.vec4('texSize'),
353
- pb.vec3('scale'),
354
- pb.vec4('levelStart'),
355
- pb.vec4('levelDiff'),
356
585
  pb.vec3('t').out(),
357
586
  pb.vec3('b').out(),
358
587
  pb.vec3('n').out()
359
588
  ], function() {
360
- this.$l.texelSize = pb.mul(this.texSize.zw, pb.exp2(this.$inputs.miplevel));
361
- this.$l.delta = pb.exp2(pb.add(this.$inputs.miplevel, 1));
362
- this.$l.hL = that.sampleHeightMap(this, pb.sub(this.uv, pb.vec2(this.texelSize.x, 0)), this.clipmapPos, this.levelStart, this.levelDiff);
363
- this.$l.hR = that.sampleHeightMap(this, pb.add(this.uv, pb.vec2(this.texelSize.x, 0)), this.clipmapPos, this.levelStart, this.levelDiff);
364
- this.$l.hD = that.sampleHeightMap(this, pb.add(this.uv, pb.vec2(0, this.texelSize.y)), this.clipmapPos, this.levelStart, this.levelDiff);
365
- this.$l.hU = that.sampleHeightMap(this, pb.sub(this.uv, pb.vec2(0, this.texelSize.y)), this.clipmapPos, this.levelStart, this.levelDiff);
366
- this.$l.dHdU = pb.div(pb.mul(pb.sub(this.hR, this.hL), this.scale.y), pb.mul(this.scale.x, this.delta));
367
- this.$l.dHdV = pb.div(pb.mul(pb.sub(this.hD, this.hU), this.scale.y), pb.mul(this.scale.z, this.delta));
589
+ if (textureLod) {
590
+ // Differentiate on the mip whose texels match the pixel footprint, so distant terrain
591
+ // does not alias.
592
+ this.$l.texCoord = pb.mul(this.$inputs.uv, this.heightMapSize.xy);
593
+ this.$l.footprint = pb.max(pb.length(pb.dpdx(this.texCoord)), pb.length(pb.dpdy(this.texCoord)));
594
+ this.$l.lod = pb.max(pb.log2(pb.max(this.footprint, 0.0001)), 0);
595
+ } else {
596
+ this.$l.lod = pb.float(0);
597
+ }
598
+ // Half a texel either way: on a bilinear field that yields the full slope of each cell
599
+ // everywhere but within half a texel of a crest or foot.
600
+ this.$l.offset = pb.mul(this.heightMapSize.zw, pb.mul(pb.exp2(this.lod), 0.5));
601
+ const sample = (scope, uv)=>textureLod ? pb.textureSampleLevel(scope.heightMap, uv, scope.lod).r : pb.textureSample(scope.heightMap, uv).r;
602
+ this.$l.hL = sample(this, pb.sub(this.$inputs.uv, pb.vec2(this.offset.x, 0)));
603
+ this.$l.hR = sample(this, pb.add(this.$inputs.uv, pb.vec2(this.offset.x, 0)));
604
+ this.$l.hU = sample(this, pb.sub(this.$inputs.uv, pb.vec2(0, this.offset.y)));
605
+ this.$l.hD = sample(this, pb.add(this.$inputs.uv, pb.vec2(0, this.offset.y)));
606
+ // World-space distance between the two samples of each difference
607
+ this.$l.sampleDist = pb.mul(pb.sub(this.region.zw, this.region.xy), pb.mul(this.offset, 2));
608
+ this.$l.dHdU = pb.div(pb.mul(pb.sub(this.hR, this.hL), this.terrainScale.y), this.sampleDist.x);
609
+ this.$l.dHdV = pb.div(pb.mul(pb.sub(this.hD, this.hU), this.terrainScale.y), this.sampleDist.y);
368
610
  this.t = pb.normalize(pb.vec3(1, this.dHdU, 0));
369
611
  this.b = pb.normalize(pb.vec3(0, this.dHdV, 1));
370
612
  this.n = pb.normalize(pb.cross(this.b, this.t));
371
613
  });
372
- return scope.calcTerrainTBN(clipmapPos, uv, texSize, scale, levelStart, levelDiff, tangent, bitangent, normal);
614
+ return scope.calcTerrainTBN(tangent, bitangent, normal);
373
615
  }
374
616
  vertexShader(scope) {
375
617
  super.vertexShader(scope);
@@ -387,34 +629,45 @@ const MAX_DETAIL_MAPS = 8;
387
629
  scope.$l.c = pb.cos(scope.$inputs.clipmapInfo.x);
388
630
  scope.$l.scale2 = pb.mul(scope.$inputs.clipmapInfo.y, scope.clipmapGridInfo.x);
389
631
  scope.$l.clipmapMatrix = pb.mat4(pb.mul(scope.c, scope.scale2), pb.mul(scope.s, scope.scale2), 0, 0, pb.neg(pb.mul(scope.s, scope.scale2)), pb.mul(scope.c, scope.scale2), 0, 0, 0, 0, 1, 0, pb.sub(pb.mul(scope.$inputs.clipmapInfo.z, scope.clipmapGridInfo.x), scope.clipmapGridInfo.y), pb.sub(pb.mul(scope.$inputs.clipmapInfo.w, scope.clipmapGridInfo.x), scope.clipmapGridInfo.z), 0, 1);
390
- scope.$l.clipmapPos = pb.mul(scope.clipmapMatrix, pb.vec4(scope.$inputs.position, 1)).xy;
632
+ scope.$l.localPos = scope.$inputs.position.xy;
633
+ // position.z = -side flags a seam midpoint (see Clipmap.generateSeamMesh). When the seam edge
634
+ // lies outside the region, clamping would line its flaps up along the border as vertical
635
+ // curtains, so the midpoint takes its neighbour's local position instead: both then run the
636
+ // exact same math below, land on the same point, and the flap degenerates.
637
+ scope.$l.seamSide = pb.neg(scope.$inputs.position.z);
638
+ scope.$if(pb.greaterThan(scope.seamSide, 0.5), function() {
639
+ this.$l.probeLocal = pb.mul(this.clipmapMatrix, pb.vec4(this.localPos, 0, 1)).xy;
640
+ this.$l.probe = pb.mul(ShaderHelper.getWorldMatrix(this), pb.vec4(this.probeLocal.x, 0, this.probeLocal.y, 1)).xz;
641
+ // Sides 1 and 3 run along local x (world x, seams are never rotated), so the border they
642
+ // can lie beyond is a z one; sides 2 and 4 run along z.
643
+ this.$l.alongX = pb.greaterThan(pb.mod(this.seamSide, 2), 0.5);
644
+ this.$l.outside = pb.or(pb.and(this.alongX, pb.or(pb.lessThan(this.probe.y, this.region.y), pb.greaterThan(this.probe.y, this.region.w))), pb.and(pb.not(this.alongX), pb.or(pb.lessThan(this.probe.x, this.region.x), pb.greaterThan(this.probe.x, this.region.z))));
645
+ this.$if(this.outside, function() {
646
+ // The previous seam vertex sits one unit back along the side: -x, -y, +x, +y for sides 1..4
647
+ this.$l.dir = pb.sub(pb.mul(pb.float(pb.greaterThan(this.seamSide, 2.5)), 2), 1);
648
+ this.localPos = pb.add(this.localPos, pb.mul(pb.vec2(pb.float(this.alongX), pb.float(pb.not(this.alongX))), this.dir));
649
+ });
650
+ });
651
+ scope.$l.clipmapPos = pb.mul(scope.clipmapMatrix, pb.vec4(scope.localPos, 0, 1)).xy;
391
652
  scope.$l.clipmapWorldPos = pb.mul(ShaderHelper.getWorldMatrix(scope), pb.vec4(scope.clipmapPos.x, 0, scope.clipmapPos.y, 1)).xyz;
653
+ // Heights are evaluated at the unclamped grid point: every level computes the same function
654
+ // of its own grid, which keeps the rings matching. Beyond the region the sampler clamps to
655
+ // the edge texels, the same heights the clamped position would get.
656
+ scope.$l.gridPos = scope.clipmapWorldPos.xz;
657
+ scope.$l.gridUV = pb.div(pb.sub(scope.gridPos, scope.region.xy), pb.sub(scope.region.zw, scope.region.xy));
658
+ // Global grid coordinate in cells, see ClipmapGatherContext.gridOriginX
659
+ scope.$l.gridCoord = pb.floor(pb.add(pb.div(pb.add(scope.clipmapPos, scope.clipmapGridInfo.yz), scope.clipmapGridInfo.x), 0.5));
660
+ // Keep the grid inside the terrain region instead of discarding fragments outside of it,
661
+ // which would cost early-Z on the largest occluder in the scene. The clipmap grid is
662
+ // axis-aligned, so clamping collapses triangles lying outside into zero-area ones and snaps
663
+ // the ones crossing the border exactly onto it.
664
+ scope.clipmapWorldPos = pb.vec3(pb.clamp(scope.clipmapWorldPos.x, scope.region.x, scope.region.z), scope.clipmapWorldPos.y, pb.clamp(scope.clipmapWorldPos.z, scope.region.y, scope.region.w));
392
665
  scope.$outputs.uv = pb.div(pb.sub(scope.clipmapWorldPos.xz, scope.region.xy), pb.sub(scope.region.zw, scope.region.xy));
393
- if (this.drawContext.device.type === 'webgl') {
394
- scope.$l.levelStart = pb.vec4();
395
- scope.$l.levelDiff = pb.vec4();
396
- scope.$l.index = pb.mul(pb.int(scope.$inputs.miplevel), 2);
397
- scope.$for(pb.int('i'), 0, 32, function() {
398
- this.$if(pb.equal(this.i, this.index), function() {
399
- this.levelStart = this.levelData.at(this.i);
400
- this.levelDiff = this.levelData.at(pb.add(this.i, 1));
401
- this.$break();
402
- });
403
- });
404
- } else {
405
- scope.$l.levelStart = scope.levelData.at(pb.mul(pb.int(scope.$inputs.miplevel), 2));
406
- scope.$l.levelDiff = scope.levelData.at(pb.add(pb.mul(pb.int(scope.$inputs.miplevel), 2), 1));
407
- }
408
- scope.$l.height = this.sampleHeightMap(scope, scope.$outputs.uv, scope.clipmapWorldPos.xz, scope.levelStart, scope.levelDiff);
666
+ scope.$l.levelStart = scope.levelData.at(pb.mul(pb.int(scope.$inputs.miplevel), 2));
667
+ scope.$l.levelDiff = scope.levelData.at(pb.add(pb.mul(pb.int(scope.$inputs.miplevel), 2), 1));
668
+ scope.$l.height = this.sampleHeightMap(scope, scope.gridUV, scope.gridCoord, scope.gridPos, scope.levelStart, scope.levelDiff);
409
669
  scope.$outputs.worldPos = pb.add(scope.clipmapWorldPos, pb.vec3(0, pb.mul(scope.height, scope.terrainScale.y), 0));
410
- scope.$l.t = pb.vec3();
411
- scope.$l.b = pb.vec3();
412
- scope.$l.n = pb.vec3();
413
- this.calculateTerrainTBN(scope, scope.clipmapWorldPos.xz, scope.$outputs.uv, scope.heightMapSize, scope.terrainScale, scope.levelStart, scope.levelDiff, scope.t, scope.b, scope.n);
414
670
  scope.$outputs.clipmapPos = scope.clipmapWorldPos;
415
- scope.$outputs.worldTangent = scope.t;
416
- scope.$outputs.worldBinormal = scope.b;
417
- scope.$outputs.worldNormal = scope.n;
418
671
  ShaderHelper.setClipSpacePosition(scope, pb.mul(ShaderHelper.getViewProjectionMatrix(scope), pb.vec4(scope.$outputs.worldPos, 1)));
419
672
  ShaderHelper.resolveMotionVector(scope, scope.$outputs.worldPos, scope.$outputs.worldPos);
420
673
  }
@@ -422,120 +675,142 @@ const MAX_DETAIL_MAPS = 8;
422
675
  super.fragmentShader(scope);
423
676
  const pb = scope.$builder;
424
677
  scope.region = pb.vec4().uniform(2);
425
- scope.$l.discardable = pb.or(pb.any(pb.lessThan(scope.$inputs.uv, pb.vec2(0))), pb.any(pb.greaterThan(scope.$inputs.uv, pb.vec2(1))));
426
- scope.$if(scope.discardable, function() {
427
- pb.discard();
428
- });
429
- if (this.needFragmentColor()) {
678
+ if (this.needFragmentColor() && this.virtualTextureUsed) {
679
+ const globals = pb.getGlobalScope();
680
+ this._virtualTexture.declareBindings(globals, 2, false);
681
+ globals.zTerrainRVT0 = pb.tex2D().uniform(2);
682
+ globals.zTerrainRVT1 = pb.tex2D().uniform(2);
683
+ scope.$l.terrainN = pb.vec3();
684
+ scope.$l.albedo = this.sampleVirtualTexture(scope, scope.terrainN);
685
+ // The normal read back already carries the height map slopes and the detail layers; the
686
+ // frame around it only orients anisotropic and clear coat terms
687
+ scope.$l.terrainT = pb.normalize(pb.sub(pb.vec3(1, 0, 0), pb.mul(scope.terrainN, scope.terrainN.x)));
688
+ scope.$l.terrainB = pb.cross(scope.terrainT, scope.terrainN);
689
+ scope.$l.normalInfo = this.calculateNormalAndTBN(scope, scope.$inputs.worldPos, scope.terrainN, scope.terrainT, scope.terrainB);
690
+ this.shadeTerrain(scope);
691
+ } else if (this.needFragmentColor()) {
430
692
  scope.heightMap = pb.tex2D().uniform(2);
693
+ scope.heightMapSize = pb.vec4().uniform(2);
694
+ scope.terrainScale = pb.vec3().uniform(2);
431
695
  const numDetailMaps = this.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP);
432
696
  if (numDetailMaps > 0) {
433
697
  scope.detailParams = pb.vec4[numDetailMaps]().uniform(2);
434
- if (this.drawContext.device.type === 'webgl') {
435
- scope.splatMap = pb.tex2D().uniform(2);
436
- for(let i = 0; i < numDetailMaps; i++){
437
- scope[`detailAlbedoMap${i}`] = pb.tex2D().uniform(2);
438
- scope[`detailNormalMap${i}`] = pb.tex2D().uniform(2);
439
- }
440
- } else {
441
- scope.splatMap = pb.tex2DArray().uniform(2);
442
- scope.detailAlbedoMap = pb.tex2DArray().uniform(2);
443
- scope.detailNormalMap = pb.tex2DArray().uniform(2);
698
+ if (this.hexTilingMask) {
699
+ scope.detailHexParams = pb.vec4[numDetailMaps]().uniform(2);
444
700
  }
701
+ scope.splatMap = pb.tex2DArray().uniform(2);
702
+ scope.detailAlbedoMap = pb.tex2DArray().uniform(2);
703
+ scope.detailNormalMap = pb.tex2DArray().uniform(2);
704
+ }
705
+ // Sampled once here and shared by the albedo and detail normal blends
706
+ for(let i = 0; i < numDetailMaps + 3 >> 2; i++){
707
+ scope.$l[`terrainSplatMask${i}`] = this.sampleSplatMask(scope, i);
445
708
  }
446
709
  scope.$l.albedo = this.calculateAlbedoColor(scope);
447
- scope.$l.worldNormal = pb.normalize(scope.$inputs.worldNormal);
448
- scope.$l.normalInfo = this.calculateNormalAndTBN(scope, scope.$inputs.worldPos, scope.$inputs.worldNormal, scope.$inputs.worldTangent, scope.$inputs.worldBinormal);
710
+ scope.$l.terrainT = pb.vec3();
711
+ scope.$l.terrainB = pb.vec3();
712
+ scope.$l.terrainN = pb.vec3();
713
+ this.calculateTerrainTBN(scope, scope.terrainT, scope.terrainB, scope.terrainN);
714
+ scope.$l.normalInfo = this.calculateNormalAndTBN(scope, scope.$inputs.worldPos, scope.terrainN, scope.terrainT, scope.terrainB);
449
715
  if (this.featureUsed(ClipmapTerrainMaterial.FEATURE_DETAIL_MAP) > 0) {
450
716
  scope.normalInfo.normal = this.calculateDetailNormal(scope, scope.normalInfo.TBN);
451
717
  }
452
- scope.$l.viewVec = this.calculateViewVector(scope, scope.$inputs.worldPos);
453
- scope.$l.litColor = this.PBRLight(scope, scope.$inputs.worldPos, scope.normalInfo.normal, scope.viewVec, scope.albedo, scope.normalInfo.TBN);
454
- switch(this.featureUsed(ClipmapTerrainMaterial.FEATURE_DEBUG_MODE)){
455
- case 'albedo':
456
- scope.$l.outColor = scope.albedo;
457
- break;
458
- case 'vertex_normal':
459
- scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.TBN[2], 0.5), pb.vec3(0.5)), 1);
460
- break;
461
- case 'detail_normal':
462
- scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.normal, 0.5), pb.vec3(0.5)), 1);
463
- break;
464
- case 'tangent':
465
- scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.TBN[0], 0.5), pb.vec3(0.5)), 1);
466
- break;
467
- case 'bitangent':
468
- scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.TBN[1], 0.5), pb.vec3(0.5)), 1);
469
- break;
470
- case 'uv':
471
- scope.$l.outColor = pb.vec4(scope.$inputs.uv, 0, 1);
472
- break;
473
- default:
474
- scope.$l.outColor = pb.vec4(scope.litColor, 1);
475
- break;
476
- }
477
- //scope.$l.outColor = pb.vec4(scope.litColor, 1);
478
- if (this.drawContext.materialFlags & (MaterialVaryingFlags.SCENE_STORE_ROUGHNESS | MaterialVaryingFlags.SCENE_STORE_NORMAL)) {
479
- scope.$l.outRoughness = pb.vec4(0, 0, 0, 0);
480
- this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.outColor, scope.outRoughness, scope.outColor);
481
- } else {
482
- this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.outColor);
483
- }
718
+ this.shadeTerrain(scope);
484
719
  } else {
485
720
  this.outputFragmentColor(scope, scope.$inputs.worldPos, null);
486
721
  }
487
722
  }
723
+ /** Lights the terrain from the albedo and normalInfo locals of the fragment shader */ shadeTerrain(scope) {
724
+ const pb = scope.$builder;
725
+ scope.$l.viewVec = this.calculateViewVector(scope, scope.$inputs.worldPos);
726
+ const storeSceneData = !!(this.drawContext.materialFlags & (MaterialVaryingFlags.SCENE_STORE_ROUGHNESS | MaterialVaryingFlags.SCENE_STORE_NORMAL));
727
+ if (storeSceneData) {
728
+ // Filled by PBRLight with (specular color, roughness) for SSR/SSGI
729
+ scope.$l.outRoughness = pb.vec4();
730
+ }
731
+ scope.$l.litColor = this.PBRLight(scope, scope.$inputs.worldPos, scope.normalInfo.normal, scope.viewVec, scope.albedo, scope.normalInfo.TBN, storeSceneData ? scope.outRoughness : undefined);
732
+ switch(this.featureUsed(ClipmapTerrainMaterial.FEATURE_DEBUG_MODE)){
733
+ case 'albedo':
734
+ scope.$l.outColor = pb.vec4(scope.albedo.rgb, 1);
735
+ break;
736
+ case 'vertex_normal':
737
+ scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.TBN[2], 0.5), pb.vec3(0.5)), 1);
738
+ break;
739
+ case 'detail_normal':
740
+ scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.normal, 0.5), pb.vec3(0.5)), 1);
741
+ break;
742
+ case 'tangent':
743
+ scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.TBN[0], 0.5), pb.vec3(0.5)), 1);
744
+ break;
745
+ case 'bitangent':
746
+ scope.$l.outColor = pb.vec4(pb.add(pb.mul(scope.normalInfo.TBN[1], 0.5), pb.vec3(0.5)), 1);
747
+ break;
748
+ case 'uv':
749
+ scope.$l.outColor = pb.vec4(scope.$inputs.uv, 0, 1);
750
+ break;
751
+ default:
752
+ scope.$l.outColor = pb.vec4(scope.litColor, 1);
753
+ break;
754
+ }
755
+ if (storeSceneData) {
756
+ this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.outColor, scope.outRoughness, pb.vec4(pb.add(pb.mul(scope.normalInfo.normal, 0.5), pb.vec3(0.5)), 1));
757
+ } else {
758
+ this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.outColor);
759
+ }
760
+ }
488
761
  applyUniformValues(bindGroup, ctx, pass) {
489
762
  super.applyUniformValues(bindGroup, ctx, pass);
490
763
  bindGroup.setValue('clipmapGridInfo', this._clipmapGridInfo);
491
764
  bindGroup.setValue('region', this._region);
492
765
  bindGroup.setValue('terrainScale', this._terrainScale);
493
- bindGroup.setTexture('heightMap', this._heightMap.get(), fetchSampler('clamp_linear_nomip'));
766
+ const heightMap = this._heightMap.get();
767
+ // The vertex shader samples explicit mip levels per clipmap ring. WebGL ignores the LOD of
768
+ // textureLod() under a non-mipmapped min filter, so the sampler has to be mipmapped whenever
769
+ // the texture has a mip chain.
770
+ bindGroup.setTexture('heightMap', heightMap, fetchSampler(heightMap.mipLevelCount > 1 ? 'clamp_linear' : 'clamp_linear_nomip'));
494
771
  bindGroup.setValue('heightMapSize', this._heightMapSize);
495
772
  bindGroup.setBuffer('levelData', this._levelDataBuffer.get());
496
- if (this.needFragmentColor(ctx)) {
773
+ if (this.needFragmentColor(ctx) && this.virtualTextureUsed) {
774
+ const vt = this._virtualTexture;
775
+ vt.applyBindings(bindGroup, false);
776
+ bindGroup.setTexture('zTerrainRVT0', vt.getPlaneTexture('baseColor'), fetchSampler('clamp_linear_nomip'));
777
+ bindGroup.setTexture('zTerrainRVT1', vt.getPlaneTexture('normalRoughness'), fetchSampler('clamp_linear_nomip'));
778
+ } else if (this.needFragmentColor(ctx)) {
497
779
  if (this._detailMapInfo.numDetailMaps > 0) {
498
780
  bindGroup.setTexture('splatMap', this._detailMapInfo.splatMap.get());
499
- if (ctx.device.type === 'webgl') {
500
- for(let i = 0; i < this._detailMapInfo.numDetailMaps; i++){
501
- bindGroup.setTexture(`detailAlbedoMap${i}`, this._detailMapInfo.detailMapList[i]?.get() ?? ClipmapTerrainMaterial.getDefaultDetailMap(), fetchSampler('repeat_linear'));
502
- bindGroup.setTexture(`detailNormalMap${i}`, this._detailMapInfo.detailNormalMapList[i]?.get() ?? ClipmapTerrainMaterial.getDefaultNormalMap(), fetchSampler('repeat_linear'));
503
- }
504
- } else {
505
- bindGroup.setTexture('detailAlbedoMap', this._detailMapInfo.detailMap.get(), fetchSampler('repeat_linear'));
506
- bindGroup.setTexture('detailNormalMap', this._detailMapInfo.detailNormalMap.get(), fetchSampler('repeat_linear'));
507
- }
781
+ bindGroup.setTexture('detailAlbedoMap', this._detailMapInfo.detailMap.get(), fetchSampler('repeat_linear'));
782
+ bindGroup.setTexture('detailNormalMap', this._detailMapInfo.detailNormalMap.get(), fetchSampler('repeat_linear'));
508
783
  bindGroup.setValue('detailParams', this._detailMapInfo.detailMapParams);
784
+ if (this.hexTilingMask) {
785
+ bindGroup.setValue('detailHexParams', this._detailMapInfo.detailHexParams);
786
+ }
509
787
  }
510
788
  }
511
789
  }
512
790
  createDetailMapInfo() {
513
791
  const device = getDevice();
514
- const isWebGL1 = device.type === 'webgl';
515
- const detailMap = isWebGL1 ? null : device.createTexture2DArray('rgba8unorm', this._detailMapSize, this._detailMapSize, MAX_DETAIL_MAPS);
516
- const detailNormalMap = isWebGL1 ? null : device.createTexture2DArray('rgba8unorm', this._detailMapSize, this._detailMapSize, MAX_DETAIL_MAPS);
517
- const splatMap = isWebGL1 ? device.createTexture2D('rgba8unorm', this._splatMapSize, this._splatMapSize) : device.createTexture2DArray('rgba8unorm', this._splatMapSize, this._splatMapSize, MAX_DETAIL_MAPS >> 2);
792
+ const detailMap = device.createTexture2DArray('rgba8unorm', this._detailMapSize, this._detailMapSize, MAX_DETAIL_MAPS);
793
+ const detailNormalMap = device.createTexture2DArray('rgba8unorm', this._detailMapSize, this._detailMapSize, MAX_DETAIL_MAPS);
794
+ const splatMap = device.createTexture2DArray('rgba8unorm', this._splatMapSize, this._splatMapSize, MAX_DETAIL_MAPS >> 2);
518
795
  device.pushDeviceStates();
519
- if (!isWebGL1) {
520
- const fbDetail = device.createFrameBuffer([
521
- detailMap
522
- ], null);
523
- device.setFramebuffer(fbDetail);
524
- for(let i = 0; i < detailMap.depth; i++){
525
- fbDetail.setColorAttachmentLayer(0, i);
526
- device.clearFrameBuffer(Vector4.zero(), DEPTH_CLEAR_VALUE, 0);
527
- }
528
- fbDetail.dispose();
529
- const fbNormal = device.createFrameBuffer([
530
- detailNormalMap
531
- ], null);
532
- device.setFramebuffer(fbNormal);
533
- for(let i = 0; i < detailNormalMap.depth; i++){
534
- fbNormal.setColorAttachmentLayer(0, i);
535
- device.clearFrameBuffer(new Vector4(0.5, 0.5, 1, 1), DEPTH_CLEAR_VALUE, 0);
536
- }
537
- fbNormal.dispose();
796
+ const fbDetail = device.createFrameBuffer([
797
+ detailMap
798
+ ], null);
799
+ device.setFramebuffer(fbDetail);
800
+ for(let i = 0; i < detailMap.depth; i++){
801
+ fbDetail.setColorAttachmentLayer(0, i);
802
+ device.clearFrameBuffer(Vector4.zero(), DEPTH_CLEAR_VALUE, 0);
803
+ }
804
+ fbDetail.dispose();
805
+ const fbNormal = device.createFrameBuffer([
806
+ detailNormalMap
807
+ ], null);
808
+ device.setFramebuffer(fbNormal);
809
+ for(let i = 0; i < detailNormalMap.depth; i++){
810
+ fbNormal.setColorAttachmentLayer(0, i);
811
+ device.clearFrameBuffer(new Vector4(0.5, 0.5, 1, 1), DEPTH_CLEAR_VALUE, 0);
538
812
  }
813
+ fbNormal.dispose();
539
814
  const fbSplat = device.createFrameBuffer([
540
815
  splatMap
541
816
  ], null);
@@ -553,6 +828,7 @@ const MAX_DETAIL_MAPS = 8;
553
828
  detailNormalMapList: [],
554
829
  splatMap: new DRef(splatMap),
555
830
  detailMapParams: new Float32Array(MAX_DETAIL_MAPS * 4),
831
+ detailHexParams: new Float32Array(MAX_DETAIL_MAPS * 4),
556
832
  numDetailMaps: 0
557
833
  };
558
834
  }