@woosh/meep-engine 3.53.0 → 3.55.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 (249) hide show
  1. package/package.json +1 -1
  2. package/src/core/math/noise/worley_noise_2d.d.ts +14 -0
  3. package/src/core/math/noise/worley_noise_2d.d.ts.map +1 -0
  4. package/src/core/math/noise/worley_noise_2d.js +74 -0
  5. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts +6 -1
  6. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts.map +1 -1
  7. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.js +363 -325
  8. package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts +93 -14
  9. package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts.map +1 -1
  10. package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.js +322 -183
  11. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts +9 -0
  12. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts.map +1 -1
  13. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +110 -83
  14. package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts +42 -0
  15. package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts.map +1 -0
  16. package/src/engine/{graphics3/decal/DecalAtlas.js → graphics/texture/atlas/SpriteAtlas.js} +202 -194
  17. package/src/engine/{graphics3/decal/DecalAtlasFill.d.ts → graphics/texture/atlas/SpriteAtlasFill.d.ts} +4 -4
  18. package/src/engine/graphics/texture/atlas/SpriteAtlasFill.d.ts.map +1 -0
  19. package/src/engine/graphics/texture/atlas/SpriteAtlasFill.js +30 -0
  20. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts +7 -0
  21. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  22. package/src/engine/graphics/texture/atlas/TextureAtlas.js +11 -1
  23. package/src/engine/{graphics3/decal/decal_atlas_border.d.ts → graphics/texture/atlas/sprite_atlas_border.d.ts} +12 -12
  24. package/src/engine/graphics/texture/atlas/sprite_atlas_border.d.ts.map +1 -0
  25. package/src/engine/{graphics3/decal/decal_atlas_border.js → graphics/texture/atlas/sprite_atlas_border.js} +69 -69
  26. package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
  27. package/src/engine/graphics3/DecalSystem.js +447 -450
  28. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +9 -22
  29. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  30. package/src/engine/graphics3/GPUParticleEmitterSystem.js +892 -951
  31. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  32. package/src/generation/foliage/FOLIAGE_DESIGN.md +2 -1
  33. package/src/generation/foliage/config/BarkMaterialConfig.d.ts +4 -3
  34. package/src/generation/foliage/config/BarkMaterialConfig.d.ts.map +1 -1
  35. package/src/generation/foliage/config/BarkMaterialConfig.js +6 -6
  36. package/src/generation/foliage/config/FoliageBarkType.d.ts +11 -0
  37. package/src/generation/foliage/config/FoliageBarkType.d.ts.map +1 -0
  38. package/src/generation/foliage/config/FoliageBarkType.js +16 -0
  39. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts +58 -5
  40. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts.map +1 -1
  41. package/src/generation/foliage/config/FoliageMaterialConfig.js +279 -181
  42. package/src/generation/foliage/index.d.ts +2 -1
  43. package/src/generation/foliage/index.js +44 -43
  44. package/src/generation/foliage/material/make_bark_material.d.ts.map +1 -1
  45. package/src/generation/foliage/material/make_bark_material.js +23 -95
  46. package/src/generation/foliage/material/make_foliage_material.d.ts +29 -3
  47. package/src/generation/foliage/material/make_foliage_material.d.ts.map +1 -1
  48. package/src/generation/foliage/material/make_foliage_material.js +605 -359
  49. package/src/generation/foliage/mesh/branch_collar_project.d.ts.map +1 -1
  50. package/src/generation/foliage/mesh/branch_collar_project.js +205 -203
  51. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts +15 -0
  52. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts.map +1 -1
  53. package/src/generation/foliage/mesh/foliage_collect_attachments.js +569 -502
  54. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
  55. package/src/generation/foliage/mesh/tree_mesh_build_bark.js +1049 -1047
  56. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts +2 -1
  57. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts.map +1 -1
  58. package/src/generation/foliage/mesh/tree_mesh_build_foliage.js +454 -415
  59. package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -1
  60. package/src/generation/foliage/presets/make_tree_species_preset.js +870 -837
  61. package/src/generation/foliage/skeleton/crown_envelope_evaluate.js +121 -121
  62. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts +10 -7
  63. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts.map +1 -1
  64. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.js +206 -179
  65. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -1
  66. package/src/generation/foliage/skeleton/tree_skeleton_grow.js +1871 -1104
  67. package/src/shade/RENDERER_CONTRACT.md +5 -2
  68. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -1
  69. package/src/shade/device/SHADE_DEVICE_FLOOR.js +2 -0
  70. package/src/shade/playground/foliage_wind/foliage_world.d.ts +6 -0
  71. package/src/shade/playground/foliage_wind/foliage_world.d.ts.map +1 -1
  72. package/src/shade/playground/foliage_wind/foliage_world.js +602 -593
  73. package/src/shade/playground/foliage_wind/index.html +260 -254
  74. package/src/shade/playground/foliage_wind/main.js +275 -263
  75. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  76. package/src/shade/renderer/Renderer.js +1 -1
  77. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  78. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  79. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  80. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  81. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  82. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +1 -1
  83. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +3 -3
  84. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  85. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +350 -294
  86. package/src/shade/renderer/hiz/generate_downsampler_code.d.ts +0 -2
  87. package/src/shade/renderer/hiz/generate_downsampler_code.d.ts.map +1 -1
  88. package/src/shade/renderer/hiz/generate_downsampler_code.js +99 -100
  89. package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts +15 -0
  90. package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts.map +1 -0
  91. package/src/shade/renderer/hiz/hzb_pick_alignment.js +148 -0
  92. package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts +28 -0
  93. package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts.map +1 -0
  94. package/src/shade/renderer/hiz/hzb_plan_reduction_steps.js +58 -0
  95. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +5 -5
  96. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  97. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +57 -67
  98. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +3 -3
  99. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +1 -1
  100. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +111 -112
  101. package/src/shade/renderer/hiz/shader_hzb_spd.d.ts +5 -0
  102. package/src/shade/renderer/hiz/shader_hzb_spd.d.ts.map +1 -0
  103. package/src/shade/renderer/hiz/shader_hzb_spd.js +160 -0
  104. package/src/shade/renderer/light/GPULightCollection.d.ts +8 -0
  105. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  106. package/src/shade/renderer/light/GPULightCollection.js +67 -0
  107. package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts +7 -0
  108. package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts.map +1 -0
  109. package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.js +13 -0
  110. package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts +10 -0
  111. package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts.map +1 -0
  112. package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.js +72 -0
  113. package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts +39 -0
  114. package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts.map +1 -0
  115. package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.js +90 -0
  116. package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts +12 -0
  117. package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts.map +1 -0
  118. package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.js +56 -0
  119. package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts +31 -0
  120. package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts.map +1 -0
  121. package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.js +196 -0
  122. package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts +10 -0
  123. package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts.map +1 -0
  124. package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.js +163 -0
  125. package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts +4 -0
  126. package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts.map +1 -0
  127. package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.js +137 -0
  128. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  130. package/src/shade/renderer/particles/GPUParticleSystem.js +4 -3
  131. package/src/shade/renderer/particles/graph_particles_avboit.d.ts +4 -3
  132. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  133. package/src/shade/renderer/particles/graph_particles_avboit.js +28 -11
  134. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +9 -13
  135. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  136. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +25 -20
  137. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
  138. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +21 -38
  139. package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.d.ts.map +1 -1
  140. package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.js +154 -147
  141. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +2 -1
  142. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  143. package/src/shade/renderer/postprocess/gtao/GTAO.js +5 -1
  144. package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts +8 -0
  145. package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts.map +1 -1
  146. package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.js +10 -1
  147. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
  148. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +5 -0
  149. package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts +22 -0
  150. package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts.map +1 -0
  151. package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.js +33 -0
  152. package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts +22 -0
  153. package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts.map +1 -0
  154. package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.js +56 -0
  155. package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts +13 -0
  156. package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts.map +1 -0
  157. package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.js +42 -0
  158. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts +2 -1
  159. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  160. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +382 -357
  161. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
  162. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +414 -410
  163. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  164. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -7
  165. package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.d.ts.map +1 -1
  166. package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.js +62 -62
  167. package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts +6 -0
  168. package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts.map +1 -0
  169. package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.js +19 -0
  170. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.d.ts.map +1 -1
  171. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.js +16 -10
  172. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.d.ts.map +1 -1
  173. package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.js +118 -117
  174. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts +41 -7
  175. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts.map +1 -1
  176. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.js +162 -58
  177. package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.js +1 -1
  178. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +10 -12
  179. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  180. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +583 -606
  181. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +1 -1
  182. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +640 -640
  183. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +113 -0
  184. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -0
  185. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +484 -0
  186. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts +50 -0
  187. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts.map +1 -0
  188. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.js +60 -0
  189. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts +25 -0
  190. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts.map +1 -0
  191. package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.js +47 -0
  192. package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts +74 -0
  193. package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts.map +1 -0
  194. package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.js +173 -0
  195. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +26 -0
  196. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -0
  197. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +83 -0
  198. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts +36 -0
  199. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts.map +1 -0
  200. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.js +129 -0
  201. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts +121 -0
  202. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts.map +1 -0
  203. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.js +370 -0
  204. package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts +16 -0
  205. package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts.map +1 -0
  206. package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.js +191 -0
  207. package/src/shade/renderer/wind/GPUSceneWind.d.ts +1 -1
  208. package/src/shade/renderer/wind/GPUSceneWind.js +1 -1
  209. package/src/engine/graphics3/decal/DecalAtlas.d.ts +0 -43
  210. package/src/engine/graphics3/decal/DecalAtlas.d.ts.map +0 -1
  211. package/src/engine/graphics3/decal/DecalAtlasFill.d.ts.map +0 -1
  212. package/src/engine/graphics3/decal/DecalAtlasFill.js +0 -32
  213. package/src/engine/graphics3/decal/decal_atlas_border.d.ts.map +0 -1
  214. package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts +0 -26
  215. package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts.map +0 -1
  216. package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.js +0 -268
  217. package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts +0 -3
  218. package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts.map +0 -1
  219. package/src/shade/renderer/postprocess/ssr/copy_shader_f32.js +0 -32
  220. package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts +0 -17
  221. package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts.map +0 -1
  222. package/src/shade/renderer/postprocess/ssr/depth_max_shader.js +0 -98
  223. package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts +0 -23
  224. package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts.map +0 -1
  225. package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.js +0 -118
  226. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts +0 -15
  227. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts.map +0 -1
  228. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.js +0 -49
  229. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +0 -17
  230. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts.map +0 -1
  231. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +0 -50
  232. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts +0 -35
  233. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts.map +0 -1
  234. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.js +0 -105
  235. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +0 -35
  236. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts.map +0 -1
  237. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +0 -80
  238. package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts +0 -10
  239. package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts.map +0 -1
  240. package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.js +0 -51
  241. package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts +0 -28
  242. package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts.map +0 -1
  243. package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.js +0 -39
  244. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +0 -48
  245. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +0 -1
  246. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +0 -121
  247. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +0 -28
  248. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +0 -1
  249. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +0 -237
@@ -1,606 +1,583 @@
1
- import { GPUTypedBuffer } from "../buffer/GPUTypedBuffer.js";
2
- import { graph_compute_pass } from "../shader/graph/compute/graph_compute_pass.js";
3
- import { graph_import_buffer } from "../shader/graph/graph_import_buffer.js";
4
- import { graph_import_texture } from "../shader/graph/graph_import_texture.js";
5
- import { GPUTextureContext } from "../texture/GPUTextureContext.js";
6
- import { update_frustum_far_plane } from "../shadow/map/update_frustum_far_plane.js";
7
- import { slice_flat_array } from "../util/slice_flat_array.js";
8
- import { build_light_cluster_parameters } from "../light/cluster/build_light_cluster_parameters.js";
9
- import {
10
- build_volumetrics_sun_shadow_transform,
11
- volumetrics_sun_shadow_cascade_far
12
- } from "./build_volumetrics_sun_shadow_transform.js";
13
- import { ShaderSwitchSet } from "../shader/compiler/ShaderSwitchSet.js";
14
- import { graph_build_froxel_irradiance } from "./graph_build_froxel_irradiance.js";
15
- import { graph_build_volumetrics } from "./graph_build_volumetrics.js";
16
- import {
17
- VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT,
18
- VOLUMETRICS_SUN_SHADOW_CASCADE_RATIO,
19
- VOLUMETRICS_SUN_SHADOW_LIGHT_NONE,
20
- VOLUMETRICS_SUN_SHADOW_RESOLUTION
21
- } from "./VOLUMETRICS_SUN_SHADOW_RESOLUTION.js";
22
- import { shader_volumetrics_taa } from "./taa/shader_volumetrics_taa.js";
23
- import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
24
- import { VOLUMETRICS_RESOLUTION_Z } from "./VOLUMETRICS_RESOLUTION_Z.js";
25
-
26
- /**
27
- * @typedef {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} FrameGraph
28
- * @typedef {import("../view/GPUViewContext.js").GPUViewContext} GPUViewContext
29
- */
30
-
31
- /**
32
- * resolution of a single froxel in XY, in pixels
33
- */
34
- const TILE_SIZE = 16;
35
-
36
- export class GPUViewVolumetrics {
37
-
38
- /**
39
- * @type {GPUViewContext}
40
- */
41
- #view
42
-
43
- /**
44
- * NOTE: Initialized in the constructor
45
- * @type {GPUTextureContext[]}
46
- */
47
- #volumetrics_textures = [];
48
-
49
- /**
50
- * Should we perform temporal integration, or not?
51
- * If set to true - the next update will discard history.
52
- * Useful for significant scene transitions and screen resizing.
53
- * @type {boolean}
54
- */
55
- #reset_history = true;
56
-
57
- /**
58
- * Whether to run the 3D temporal reprojection (volumetric TAA) over the per-froxel scattering
59
- * volume. When disabled, the aerial pass integrates the raw (noisier) scattering LUT directly —
60
- * a safe fallback if temporal artifacts appear.
61
- * @type {boolean}
62
- */
63
- #taa_enabled = true;
64
-
65
- /**
66
- * @returns {boolean}
67
- */
68
- get taa_enabled() {
69
- return this.#taa_enabled;
70
- }
71
-
72
- /**
73
- * @param {boolean} v
74
- */
75
- set taa_enabled(v) {
76
- if (v === this.#taa_enabled) {
77
- return;
78
- }
79
- this.#taa_enabled = v;
80
- // re-enabling must not blend against a stale / never-written history texture
81
- this.#reset_history = true;
82
- }
83
-
84
- /**
85
- * View-space distance at which the froxel grid ends and the aerial pass switches to a direct
86
- * RDR2-style volume ray-march. Clamped to camera.far at upload, so the default (Infinity) means
87
- * "grid covers the whole range" and the far march is a no-op.
88
- * @type {number}
89
- */
90
- #froxel_far = Infinity;
91
-
92
- /**
93
- * The froxel depth curve uploaded last frame. The TAA history is addressed on the curve it was
94
- * accumulated with, so when the curve changes the history is wrong at every slice and must be
95
- * dropped. Comparing the built parameters catches every input to the curve at once -- camera
96
- * near, camera far, the froxel-far override -- rather than watching each separately.
97
- * @type {Float32Array}
98
- */
99
- #froxel_curve_previous = new Float32Array(3);
100
-
101
- /**
102
- * @returns {number}
103
- */
104
- get froxel_far() {
105
- return this.#froxel_far;
106
- }
107
-
108
- /**
109
- * @param {number} v
110
- */
111
- set froxel_far(v) {
112
- this.#froxel_far = v;
113
- }
114
-
115
- /**
116
- * Q-04. World -> volume and back for each cascade, innermost first, rebuilt every frame by
117
- * {@link build_volumetrics_sun_shadow_transform} and uploaded in the metadata.
118
- * @type {Float32Array[]}
119
- */
120
- #sun_shadow_ws_to_uvw = Array.from({ length: VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT }, () => new Float32Array(16));
121
-
122
- /**
123
- * @type {Float32Array[]}
124
- */
125
- #sun_shadow_uvw_to_ws = Array.from({ length: VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT }, () => new Float32Array(16));
126
-
127
- /**
128
- * Voxel counts one sun optical-depth cascade is dispatched at this frame. Collapses to
129
- * `[1, 1, 1]` when the froxels are marching instead, so the passes stay in the frame graph and
130
- * cost nothing — see `graph_build_sun_shadow_volume` for why they stay rather than going.
131
- * @type {number[]}
132
- */
133
- #sun_shadow_resolution = [1, 1, 1];
134
-
135
- /**
136
- * World-space size of one voxel along the sun axis per cascade, i.e. each scan's step, packed
137
- * for the `vec4f` the metadata carries.
138
- * @type {Float32Array}
139
- */
140
- #sun_shadow_step_ws = new Float32Array(4);
141
-
142
- /**
143
- * Which directional light this frame's cascades were built for, or
144
- * {@link VOLUMETRICS_SUN_SHADOW_LIGHT_NONE} when there was nothing to build for.
145
- * @type {number}
146
- */
147
- #sun_shadow_light_index = VOLUMETRICS_SUN_SHADOW_LIGHT_NONE;
148
-
149
- /**
150
- * View depth each cascade covers out to, packed the same way. Trailing lanes repeat the
151
- * outermost so a read past the end of the ladder still lands on a real cascade.
152
- * @type {Float32Array}
153
- */
154
- #sun_shadow_cascade_far = new Float32Array(4);
155
-
156
- /**
157
- * What the fog integrates to through no medium: nothing in-scattered (.rgb 0), everything
158
- * transmitted (.a 1). Handed out in place of the aerial LUT on a frame whose scene has no
159
- * participating-media volume, so every surface that samples the fog keeps doing so and reads
160
- * it as none, for one texel and no pass. Written once, at construction.
161
- * @type {GPUTextureContext}
162
- */
163
- #no_medium;
164
-
165
- /**
166
- * @type {GPUTypedBuffer}
167
- */
168
- #metadata;
169
-
170
- /**
171
- * @type {GPUDevice}
172
- */
173
- #device
174
-
175
- /**
176
- *
177
- * @param {GPUViewContext} view
178
- * @param {GPUDevice} device
179
- */
180
- constructor(view, device) {
181
- this.#device = device;
182
- this.#view = view;
183
-
184
- // initialize textures
185
- for (let i = 0; i < 2; i++) {
186
- const texture = new GPUTextureContext(device);
187
-
188
- texture.descriptor.format = "rgba16float";
189
- texture.descriptor.dimension = "3d";
190
- texture.descriptor.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING;
191
-
192
- this.#volumetrics_textures[i] = texture;
193
- }
194
-
195
- const no_medium = new GPUTextureContext(device);
196
- no_medium.descriptor.label = "volumetrics/no medium";
197
- no_medium.descriptor.format = "rgba16float";
198
- no_medium.descriptor.dimension = "3d";
199
- no_medium.descriptor.size = [1, 1, 1];
200
- no_medium.descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
201
-
202
- device.queue.writeTexture(
203
- { texture: no_medium.gpu_texture },
204
- new Float16Array([0, 0, 0, 1]),
205
- { bytesPerRow: 8, rowsPerImage: 1 },
206
- [1, 1, 1]
207
- );
208
- no_medium.incrementVersion();
209
-
210
- this.#no_medium = no_medium;
211
-
212
- this.#metadata = GPUTypedBuffer.create({
213
- type: VOLUMETRICS_METADATA_STRUCT,
214
- device,
215
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
216
- });
217
-
218
- }
219
-
220
- destroy() {
221
-
222
- const textures = this.#volumetrics_textures;
223
-
224
- for (let i = 0; i < 2; i++) {
225
-
226
- textures[i].destroy();
227
-
228
- textures[i] = null;
229
- }
230
-
231
- this.#no_medium.destroy();
232
-
233
- }
234
-
235
- #update_resolution() {
236
-
237
-
238
- const screen_resolution = this.#view.resolution;
239
-
240
- const output_resolution_x = Math.ceil(screen_resolution[0] / TILE_SIZE);
241
- const output_resolution_y = Math.ceil(screen_resolution[1] / TILE_SIZE);
242
-
243
- const t_first = this.#volumetrics_textures[0];
244
-
245
- if (t_first.width === output_resolution_x
246
- && t_first.height === output_resolution_y
247
- ) {
248
- // already at the right resolution
249
- return;
250
- }
251
-
252
- for (let i = 0; i < 2; i++) {
253
-
254
- this.#volumetrics_textures[i].resize(
255
- output_resolution_x,
256
- output_resolution_y,
257
- VOLUMETRICS_RESOLUTION_Z
258
- );
259
-
260
- }
261
-
262
- // drop history
263
- this.#reset_history = true;
264
- }
265
-
266
- /**
267
- * Place this frame's sun optical-depth cascades, and say which light they cover.
268
- *
269
- * This is how the sun's media self-shadowing is computed — there is no second path to select
270
- * between. The march in `chunk_integrate_optical_depth` remains for the lights the cascades do
271
- * not cover: every point and spot light, whose rays are short enough that there is little to
272
- * amortize, and any directional light past the first, since there is one ladder of boxes and it
273
- * is aligned to one direction. Two situations leave the cascades covering nothing at all, and
274
- * `VOLUMETRICS_SUN_SHADOW_LIGHT_NONE` is how they say so.
275
- *
276
- * The light is the first entry of the `light_directional` table, which is the first
277
- * `DirectionalLight` in scene order — `GPULightCollection.#build` packs them in that order, so
278
- * walking the same list here recovers the same index. In practice a scene has one sun.
279
- *
280
- * Every cascade is placed by the same call with a different `far`, which is what makes the
281
- * stability argument transfer: each box is sized from the bounding sphere of the sub-frustum it
282
- * covers, that sphere is a function of the projection alone, and the origin is snapped to whole
283
- * voxels — so each cascade's world-space lattice is the same set of points from frame to frame
284
- * no matter how the camera moves.
285
- *
286
- * @returns {number} the light index the cascades cover, or the sentinel
287
- */
288
- #update_sun_shadow() {
289
- const camera = this.#view.camera.camera;
290
-
291
- // One voxel per cascade rather than no pass at all: the lighting pass binds these textures
292
- // unconditionally and WebGPU has no optional bindings. Nothing samples them, because the
293
- // sentinel below matches no light index.
294
- this.#sun_shadow_resolution = [1, 1, 1];
295
-
296
- // an orthographic view has no fov to derive a bounding sphere from, and no volumetrics
297
- // camera path that reaches here today — bail rather than invent one
298
- if (typeof camera.fov !== "number") {
299
- return VOLUMETRICS_SUN_SHADOW_LIGHT_NONE;
300
- }
301
-
302
- const lights = this.#view.scene.lights.source.elements;
303
-
304
- let sun = null;
305
-
306
- for (let i = 0; i < lights.length; i++) {
307
- if (lights[i].isDirectionalLight === true) {
308
- sun = lights[i];
309
- break;
310
- }
311
- }
312
-
313
- if (sun === null) {
314
- return VOLUMETRICS_SUN_SHADOW_LIGHT_NONE;
315
- }
316
-
317
- const resolution = VOLUMETRICS_SUN_SHADOW_RESOLUTION;
318
-
319
- // `forward` is the direction light TRAVELS — the same vector light_record_directional
320
- // writes into the table, which the shader then negates to point at the light. Read off the
321
- // CPU copy of the pose: the cascade is built here, on this side.
322
- const direction = sun.transform.forward;
323
-
324
- const view_matrix = camera.view_matrix;
325
- const position = camera.transform;
326
-
327
- // the medium lives in the froxel grid and the grid stops here, so there is nothing to
328
- // integrate past it
329
- const far = Math.min(this.#froxel_far, camera.far);
330
-
331
- const cascade_count = VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT;
332
- const cascade_ratio = VOLUMETRICS_SUN_SHADOW_CASCADE_RATIO;
333
-
334
- // the view matrix's z row, negated: view space looks down -Z, and the row is unit length
335
- // because the linear part is a rotation
336
- const camera_forward = [-view_matrix[2], -view_matrix[6], -view_matrix[10]];
337
-
338
- for (let cascade = 0; cascade < cascade_count; cascade++) {
339
-
340
- const cascade_far = volumetrics_sun_shadow_cascade_far(far, cascade, cascade_count, cascade_ratio);
341
-
342
- this.#sun_shadow_step_ws[cascade] = build_volumetrics_sun_shadow_transform({
343
- ws_to_uvw: this.#sun_shadow_ws_to_uvw[cascade],
344
- uvw_to_ws: this.#sun_shadow_uvw_to_ws[cascade],
345
- light_direction: [direction[0], direction[1], direction[2]],
346
- camera_position: [position.translation_x, position.translation_y, position.translation_z],
347
- camera_forward,
348
- fov_y: camera.fov,
349
- aspect: camera.aspect,
350
- near: camera.near,
351
- far: cascade_far,
352
- resolution,
353
- });
354
-
355
- this.#sun_shadow_cascade_far[cascade] = cascade_far;
356
- }
357
-
358
- // Trailing lanes repeat the outermost cascade rather than holding zero: the read side walks
359
- // the ladder comparing view depth against these, and a zero would read as a cascade that
360
- // ends at the camera.
361
- for (let lane = cascade_count; lane < 4; lane++) {
362
- this.#sun_shadow_cascade_far[lane] = far;
363
- this.#sun_shadow_step_ws[lane] = this.#sun_shadow_step_ws[cascade_count - 1];
364
- }
365
-
366
- this.#sun_shadow_resolution = resolution;
367
-
368
- return 0;
369
- }
370
-
371
- #update_metadata() {
372
- const view = this.#view;
373
- const camera = view.camera;
374
-
375
- const resolution = this.#volumetrics_textures[0].size;
376
-
377
- this.#sun_shadow_light_index = this.#update_sun_shadow();
378
-
379
- const volumetrics_cluster_parameters = new Float32Array(3);
380
-
381
- // The depth-curve `scale` must grow with the slice count. The hard-coded 4.06 was tuned for
382
- // the light-cluster grid (24 slices); reusing it with 64 froxel slices collapses the log
383
- // curve so ~half the slices bunch into the first fraction of a unit of depth, leaving almost
384
- // none for the actual scene. Derive scale for a log-uniform distribution over
385
- // [near + near_offset, far] instead (gives O ~= 0, i.e. depth ~ offset * (far/offset)^(s/(N-1))).
386
- const NEAR_OFFSET = 0.1;
387
- const depth_offset = camera.camera.near + NEAR_OFFSET;
388
- const depth_scale = (VOLUMETRICS_RESOLUTION_Z - 1) / Math.log2(camera.camera.far / depth_offset);
389
-
390
- build_light_cluster_parameters(
391
- volumetrics_cluster_parameters,
392
- 0,
393
- VOLUMETRICS_RESOLUTION_Z,
394
- camera.camera.near,
395
- camera.camera.far,
396
- NEAR_OFFSET,
397
- depth_scale
398
- );
399
-
400
- // Froxel-grid curve: same construction but ending at froxel_far (<= camera.far). The grid's
401
- // slices pack into [near, froxel_far] for extra near detail; beyond froxel_far the aerial
402
- // pass ray-marches the volumes. froxel_far == camera.far -> identical to the LUT curve (no-op).
403
- const froxel_far = Math.min(this.#froxel_far, camera.camera.far);
404
- const froxel_depth_scale = (VOLUMETRICS_RESOLUTION_Z - 1) / Math.log2(froxel_far / depth_offset);
405
- const volumetrics_froxel_cluster_parameters = new Float32Array(3);
406
- build_light_cluster_parameters(
407
- volumetrics_froxel_cluster_parameters,
408
- 0,
409
- VOLUMETRICS_RESOLUTION_Z,
410
- camera.camera.near,
411
- froxel_far,
412
- NEAR_OFFSET,
413
- froxel_depth_scale
414
- );
415
-
416
- // Recomputed deterministically, so a steady camera compares bitwise equal and never resets.
417
- {
418
- const curve = volumetrics_froxel_cluster_parameters;
419
- const previous = this.#froxel_curve_previous;
420
-
421
- if (curve[0] !== previous[0] || curve[1] !== previous[1] || curve[2] !== previous[2]) {
422
- previous.set(curve);
423
- this.#reset_history = true;
424
- }
425
- }
426
-
427
- // The projection is infinite-far reverse-Z, so the camera frustum's far plane is zeroed and
428
- // can't be sliced into froxel slabs. Rebuild a finite far plane at the logical far for the
429
- // participating-media coverage test — exactly as the light-cluster assignment does.
430
- const clipped_frustum = new Float32Array(24);
431
- clipped_frustum.set(camera.camera.frustum);
432
- update_frustum_far_plane(clipped_frustum, camera.camera.view_matrix, camera.camera.far);
433
-
434
- this.#metadata.upload({
435
- cluster_parameters: volumetrics_cluster_parameters,
436
- froxel_cluster_parameters: volumetrics_froxel_cluster_parameters,
437
- resolution,
438
- frustum: slice_flat_array(clipped_frustum, 0, 4, 6),
439
- history_reset: this.#reset_history ? 1 : 0,
440
- froxel_far,
441
- // multiscatter knobs (GUI-tunable via globals); engine defaults 6 / 0.5
442
- ms_octave_count: globalThis.__VOL_MS_OCTAVES ?? 6,
443
- ms_extinction_decay: globalThis.__VOL_MS_EXT_DECAY ?? 0.5,
444
- // Q-04. The sun's media self-shadowing, out of a 16-step per-froxel march and into one
445
- // fetch of the cascades built earlier this frame — see #update_sun_shadow.
446
- sun_shadow_ws_to_uvw: this.#sun_shadow_ws_to_uvw,
447
- sun_shadow_uvw_to_ws: this.#sun_shadow_uvw_to_ws,
448
- sun_shadow_step_ws: this.#sun_shadow_step_ws,
449
- sun_shadow_cascade_far: this.#sun_shadow_cascade_far,
450
- sun_shadow_light_index: this.#sun_shadow_light_index,
451
- }, this.#device.queue);
452
- }
453
-
454
- /**
455
- *
456
- * @param {Object} options
457
- * @param {FrameGraph} options.graph
458
- * @param {number} options.light_cluster_data
459
- * @param {number} options.light_cluster_lookup
460
- * @param {number} options.light_cluster_parameters
461
- * @param {number} options.shadowmap_atlas
462
- * @param {ShaderSwitchSet} [options.shader_switches] the renderer's; the passes that light froxels are built
463
- * for them, the froxel GI that starts the froxel irradiance volume among them
464
- * @param {import("../global_illumination/brick4/Brick4LightMap.js").Brick4LightMap} [options.brick4] the scene's,
465
- * while Brick4 runs
466
- * @param {import("../global_illumination/lpv/GPULightProbeVolume.js").GPULightProbeVolume} [options.lpv] the
467
- * scene's, while LPV runs
468
- * @returns {{resolved: number, metadata: number, irradiance: {Y: number, CoCg: number}}} the
469
- * integrated aerial LUT, the metadata buffer, and the froxel irradiance volume
470
- * (chunk_volumetrics_irradiance_sh) that lit particles are lit by. The two do not depend on
471
- * each other, and a pass building only one of them is culled when nothing reads it. A scene
472
- * with no participating-media volume records no fog at all, and `resolved` is then a constant:
473
- * no in-scatter, full transmittance.
474
- */
475
- graph_update({
476
- graph,
477
- light_cluster_data,
478
- light_cluster_lookup,
479
- light_cluster_parameters,
480
- shadowmap_atlas,
481
- shader_switches = ShaderSwitchSet.EMPTY,
482
- brick4,
483
- lpv,
484
- }) {
485
-
486
- const view = this.#view;
487
- const lights = view.scene.lights;
488
- const camera = view.camera;
489
-
490
- /*
491
- Optional runtime A/B overrides (a GUI checkbox, a console poke) without per-view plumbing.
492
- The setters force a history reset on change, so flipping one back never blends against
493
- stale data.
494
-
495
- Applied only when the global is actually SET. They used to be read with `?? true` and
496
- `?? Infinity` and written back into the fields every frame, so the defaults they supplied
497
- outranked whatever a caller had assigned through the documented `taa_enabled` and
498
- `froxel_far` properties — this subsystem's only two tuning knobs, inoperative from code.
499
- Setting one bought a single frame of effect, then a silent revert, and because both paths
500
- mark the history stale, a TAA history reset with it.
501
- */
502
- const taa_requested = globalThis.__VOL_TAA_ENABLED;
503
- if (taa_requested !== undefined && taa_requested !== this.#taa_enabled) {
504
- this.taa_enabled = taa_requested;
505
- }
506
-
507
- const froxel_far_requested = globalThis.__VOL_FROXEL_FAR;
508
- if (froxel_far_requested !== undefined && froxel_far_requested !== this.#froxel_far) {
509
- this.#froxel_far = froxel_far_requested;
510
- // the froxel grid's depth curve just changed; the TAA history is on the old curve, reset it
511
- this.#reset_history = true;
512
- }
513
-
514
- this.#update_resolution();
515
- this.#update_metadata();
516
-
517
- const gr_metadata = graph_import_buffer(graph, this.#metadata.buffer, "volumetrics metadata");
518
-
519
- const resolution = this.#volumetrics_textures[0].size;
520
-
521
- // Particle lighting first, and on its own: it reads no medium. The frame graph culls it when
522
- // nothing reads it.
523
- const irradiance = graph_build_froxel_irradiance({
524
- graph,
525
- camera,
526
- view,
527
- lights,
528
- light_cluster_data,
529
- light_cluster_lookup,
530
- light_cluster_parameters,
531
- shadowmap_atlas,
532
- volumetrics_metadata: gr_metadata,
533
- resolution,
534
- shader_switches,
535
- brick4,
536
- lpv,
537
- });
538
-
539
- if (view.scene.volumetrics.source.volumes.length === 0) {
540
- // No medium: nothing scatters and nothing absorbs, so the fog is the identity everywhere
541
- // and the passes that would integrate it to that are not recorded. Nothing accumulates
542
- // either, which leaves the history stale for whenever a volume arrives.
543
- this.#reset_history = true;
544
-
545
- return {
546
- resolved: graph_import_texture(graph, this.#no_medium, "volumetrics no medium"),
547
- metadata: gr_metadata,
548
- irradiance,
549
- };
550
- }
551
-
552
- // Ping-pong the two persistent froxel textures: this frame writes [parity] and reads the
553
- // previous frame's accumulation from [parity ^ 1]. On the first frame / after a resize the
554
- // history is invalid, which #update_metadata signals via history_reset (the TAA then emits
555
- // the fresh sample). graph_build_volumetrics inserts the reprojection pass only when both
556
- // history/output handles are supplied — so leaving them undefined disables it.
557
- let taa_history;
558
- let taa_output;
559
-
560
- if (this.#taa_enabled) {
561
- const parity = view.frame_index & 1;
562
- taa_history = graph_import_texture(graph, this.#volumetrics_textures[parity ^ 1], "volumetrics taa history");
563
- taa_output = graph_import_texture(graph, this.#volumetrics_textures[parity], "volumetrics taa output");
564
- }
565
-
566
- /*
567
- The sun's transmittance and the multiple scattering are the scene sky's, built once and
568
- read from here on. Brought current now rather than in a pass: the sky submits commands of
569
- its own, which land ahead of this frame's -- recorded into the frame graph they would be
570
- paid every frame, which is what they used to be. A no-op after the first call.
571
- */
572
- const sky = view.scene.sky;
573
-
574
- sky.update();
575
-
576
- const { lut_aerial } = graph_build_volumetrics({
577
- graph,
578
- camera,
579
- lights,
580
- view,
581
- sky,
582
- light_cluster_data,
583
- light_cluster_lookup,
584
- light_cluster_parameters,
585
- shadowmap_atlas,
586
-
587
- volumetrics_metadata: gr_metadata,
588
- volumetric_resolution: resolution,
589
- sun_shadow_resolution: this.#sun_shadow_resolution,
590
-
591
- taa_history,
592
- taa_output,
593
- camera_previous: view.gpu_previous_camera_state,
594
- shader_switches,
595
- });
596
-
597
- this.#reset_history = false;
598
-
599
- return {
600
- resolved: lut_aerial,
601
- metadata: gr_metadata,
602
- irradiance,
603
- };
604
- }
605
-
606
- }
1
+ import { GPUTypedBuffer } from "../buffer/GPUTypedBuffer.js";
2
+ import { build_light_cluster_parameters } from "../light/cluster/build_light_cluster_parameters.js";
3
+ import { ShaderSwitchSet } from "../shader/compiler/ShaderSwitchSet.js";
4
+ import { graph_import_buffer } from "../shader/graph/graph_import_buffer.js";
5
+ import { graph_import_texture } from "../shader/graph/graph_import_texture.js";
6
+ import { update_frustum_far_plane } from "../shadow/map/update_frustum_far_plane.js";
7
+ import { GPUTextureContext } from "../texture/GPUTextureContext.js";
8
+ import { slice_flat_array } from "../util/slice_flat_array.js";
9
+ import {
10
+ build_volumetrics_sun_shadow_transform,
11
+ volumetrics_sun_shadow_cascade_far
12
+ } from "./build_volumetrics_sun_shadow_transform.js";
13
+ import { graph_build_volumetrics } from "./graph_build_volumetrics.js";
14
+ import { GPUIrradianceClipmap } from "./irradiance_clipmap/GPUIrradianceClipmap.js";
15
+ import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
16
+ import { VOLUMETRICS_RESOLUTION_Z } from "./VOLUMETRICS_RESOLUTION_Z.js";
17
+ import {
18
+ VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT,
19
+ VOLUMETRICS_SUN_SHADOW_CASCADE_RATIO,
20
+ VOLUMETRICS_SUN_SHADOW_LIGHT_NONE,
21
+ VOLUMETRICS_SUN_SHADOW_RESOLUTION
22
+ } from "./VOLUMETRICS_SUN_SHADOW_RESOLUTION.js";
23
+
24
+ /**
25
+ * @typedef {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} FrameGraph
26
+ * @typedef {import("../view/GPUViewContext.js").GPUViewContext} GPUViewContext
27
+ */
28
+
29
+ /**
30
+ * resolution of a single froxel in XY, in pixels
31
+ */
32
+ const TILE_SIZE = 16;
33
+
34
+ export class GPUViewVolumetrics {
35
+
36
+ /**
37
+ * @type {GPUViewContext}
38
+ */
39
+ #view
40
+
41
+ /**
42
+ * NOTE: Initialized in the constructor
43
+ * @type {GPUTextureContext[]}
44
+ */
45
+ #volumetrics_textures = [];
46
+
47
+ /**
48
+ * Should we perform temporal integration, or not?
49
+ * If set to true - the next update will discard history.
50
+ * Useful for significant scene transitions and screen resizing.
51
+ * @type {boolean}
52
+ */
53
+ #reset_history = true;
54
+
55
+ /**
56
+ * Whether to run the 3D temporal reprojection (volumetric TAA) over the per-froxel scattering
57
+ * volume. When disabled, the aerial pass integrates the raw (noisier) scattering LUT directly —
58
+ * a safe fallback if temporal artifacts appear.
59
+ * @type {boolean}
60
+ */
61
+ #taa_enabled = true;
62
+
63
+ /**
64
+ * @returns {boolean}
65
+ */
66
+ get taa_enabled() {
67
+ return this.#taa_enabled;
68
+ }
69
+
70
+ /**
71
+ * @param {boolean} v
72
+ */
73
+ set taa_enabled(v) {
74
+ if (v === this.#taa_enabled) {
75
+ return;
76
+ }
77
+ this.#taa_enabled = v;
78
+ // re-enabling must not blend against a stale / never-written history texture
79
+ this.#reset_history = true;
80
+ }
81
+
82
+ /**
83
+ * View-space distance at which the froxel grid ends and the aerial pass switches to a direct
84
+ * RDR2-style volume ray-march. Clamped to camera.far at upload, so the default (Infinity) means
85
+ * "grid covers the whole range" and the far march is a no-op.
86
+ * @type {number}
87
+ */
88
+ #froxel_far = Infinity;
89
+
90
+ /**
91
+ * The froxel depth curve uploaded last frame. The TAA history is addressed on the curve it was
92
+ * accumulated with, so when the curve changes the history is wrong at every slice and must be
93
+ * dropped. Comparing the built parameters catches every input to the curve at once -- camera
94
+ * near, camera far, the froxel-far override -- rather than watching each separately.
95
+ * @type {Float32Array}
96
+ */
97
+ #froxel_curve_previous = new Float32Array(3);
98
+
99
+ /**
100
+ * @returns {number}
101
+ */
102
+ get froxel_far() {
103
+ return this.#froxel_far;
104
+ }
105
+
106
+ /**
107
+ * @param {number} v
108
+ */
109
+ set froxel_far(v) {
110
+ this.#froxel_far = v;
111
+ }
112
+
113
+ /**
114
+ * Q-04. World -> volume and back for each cascade, innermost first, rebuilt every frame by
115
+ * {@link build_volumetrics_sun_shadow_transform} and uploaded in the metadata.
116
+ * @type {Float32Array[]}
117
+ */
118
+ #sun_shadow_ws_to_uvw = Array.from({ length: VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT }, () => new Float32Array(16));
119
+
120
+ /**
121
+ * @type {Float32Array[]}
122
+ */
123
+ #sun_shadow_uvw_to_ws = Array.from({ length: VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT }, () => new Float32Array(16));
124
+
125
+ /**
126
+ * Voxel counts one sun optical-depth cascade is dispatched at this frame. Collapses to
127
+ * `[1, 1, 1]` when the froxels are marching instead, so the passes stay in the frame graph and
128
+ * cost nothing — see `graph_build_sun_shadow_volume` for why they stay rather than going.
129
+ * @type {number[]}
130
+ */
131
+ #sun_shadow_resolution = [1, 1, 1];
132
+
133
+ /**
134
+ * World-space size of one voxel along the sun axis per cascade, i.e. each scan's step, packed
135
+ * for the `vec4f` the metadata carries.
136
+ * @type {Float32Array}
137
+ */
138
+ #sun_shadow_step_ws = new Float32Array(4);
139
+
140
+ /**
141
+ * Which directional light this frame's cascades were built for, or
142
+ * {@link VOLUMETRICS_SUN_SHADOW_LIGHT_NONE} when there was nothing to build for.
143
+ * @type {number}
144
+ */
145
+ #sun_shadow_light_index = VOLUMETRICS_SUN_SHADOW_LIGHT_NONE;
146
+
147
+ /**
148
+ * View depth each cascade covers out to, packed the same way. Trailing lanes repeat the
149
+ * outermost so a read past the end of the ladder still lands on a real cascade.
150
+ * @type {Float32Array}
151
+ */
152
+ #sun_shadow_cascade_far = new Float32Array(4);
153
+
154
+ /**
155
+ * What the fog integrates to through no medium: nothing in-scattered (.rgb 0), everything
156
+ * transmitted (.a 1). Handed out in place of the aerial LUT on a frame whose scene has no
157
+ * participating-media volume, so every surface that samples the fog keeps doing so and reads
158
+ * it as none, for one texel and no pass. Written once, at construction.
159
+ * @type {GPUTextureContext}
160
+ */
161
+ #no_medium;
162
+
163
+ /**
164
+ * @type {GPUTypedBuffer}
165
+ */
166
+ #metadata;
167
+
168
+ /**
169
+ * World-space irradiance clipmap (`GPUIrradianceClipmap`) used by particle lighting.
170
+ * @type {GPUIrradianceClipmap}
171
+ */
172
+ #irradiance_clipmap;
173
+
174
+ /**
175
+ * @type {GPUDevice}
176
+ */
177
+ #device
178
+
179
+ /**
180
+ *
181
+ * @param {GPUViewContext} view
182
+ * @param {GPUDevice} device
183
+ */
184
+ constructor(view, device) {
185
+ this.#device = device;
186
+ this.#view = view;
187
+
188
+ // initialize textures
189
+ for (let i = 0; i < 2; i++) {
190
+ const texture = new GPUTextureContext(device);
191
+
192
+ texture.descriptor.format = "rgba16float";
193
+ texture.descriptor.dimension = "3d";
194
+ texture.descriptor.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING;
195
+
196
+ this.#volumetrics_textures[i] = texture;
197
+ }
198
+
199
+ const no_medium = new GPUTextureContext(device);
200
+ no_medium.descriptor.label = "volumetrics/no medium";
201
+ no_medium.descriptor.format = "rgba16float";
202
+ no_medium.descriptor.dimension = "3d";
203
+ no_medium.descriptor.size = [1, 1, 1];
204
+ no_medium.descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
205
+
206
+ device.queue.writeTexture(
207
+ { texture: no_medium.gpu_texture },
208
+ new Float16Array([0, 0, 0, 1]),
209
+ { bytesPerRow: 8, rowsPerImage: 1 },
210
+ [1, 1, 1]
211
+ );
212
+ no_medium.incrementVersion();
213
+
214
+ this.#no_medium = no_medium;
215
+
216
+ this.#metadata = GPUTypedBuffer.create({
217
+ type: VOLUMETRICS_METADATA_STRUCT,
218
+ device,
219
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
220
+ });
221
+
222
+ this.#irradiance_clipmap = new GPUIrradianceClipmap(device);
223
+
224
+ }
225
+
226
+ /**
227
+ * @returns {GPUIrradianceClipmap}
228
+ */
229
+ get irradiance_clipmap() {
230
+ return this.#irradiance_clipmap;
231
+ }
232
+
233
+ destroy() {
234
+
235
+ const textures = this.#volumetrics_textures;
236
+
237
+ for (let i = 0; i < 2; i++) {
238
+
239
+ textures[i].destroy();
240
+
241
+ textures[i] = null;
242
+ }
243
+
244
+ this.#no_medium.destroy();
245
+ this.#irradiance_clipmap.destroy();
246
+
247
+ }
248
+
249
+ #update_resolution() {
250
+
251
+
252
+ const screen_resolution = this.#view.resolution;
253
+
254
+ const output_resolution_x = Math.ceil(screen_resolution[0] / TILE_SIZE);
255
+ const output_resolution_y = Math.ceil(screen_resolution[1] / TILE_SIZE);
256
+
257
+ const t_first = this.#volumetrics_textures[0];
258
+
259
+ if (t_first.width === output_resolution_x
260
+ && t_first.height === output_resolution_y
261
+ ) {
262
+ // already at the right resolution
263
+ return;
264
+ }
265
+
266
+ for (let i = 0; i < 2; i++) {
267
+
268
+ this.#volumetrics_textures[i].resize(
269
+ output_resolution_x,
270
+ output_resolution_y,
271
+ VOLUMETRICS_RESOLUTION_Z
272
+ );
273
+
274
+ }
275
+
276
+ // drop history
277
+ this.#reset_history = true;
278
+ }
279
+
280
+ /**
281
+ * Place this frame's sun optical-depth cascades, and say which light they cover.
282
+ *
283
+ * This is how the sun's media self-shadowing is computed — there is no second path to select
284
+ * between. The march in `chunk_integrate_optical_depth` remains for the lights the cascades do
285
+ * not cover: every point and spot light, whose rays are short enough that there is little to
286
+ * amortize, and any directional light past the first, since there is one ladder of boxes and it
287
+ * is aligned to one direction. Two situations leave the cascades covering nothing at all, and
288
+ * `VOLUMETRICS_SUN_SHADOW_LIGHT_NONE` is how they say so.
289
+ *
290
+ * The light is the first entry of the `light_directional` table, which is the first
291
+ * `DirectionalLight` in scene order — `GPULightCollection.#build` packs them in that order, so
292
+ * walking the same list here recovers the same index. In practice a scene has one sun.
293
+ *
294
+ * Every cascade is placed by the same call with a different `far`, which is what makes the
295
+ * stability argument transfer: each box is sized from the bounding sphere of the sub-frustum it
296
+ * covers, that sphere is a function of the projection alone, and the origin is snapped to whole
297
+ * voxels — so each cascade's world-space lattice is the same set of points from frame to frame
298
+ * no matter how the camera moves.
299
+ *
300
+ * @returns {number} the light index the cascades cover, or the sentinel
301
+ */
302
+ #update_sun_shadow() {
303
+ const camera = this.#view.camera.camera;
304
+
305
+ // One voxel per cascade rather than no pass at all: the lighting pass binds these textures
306
+ // unconditionally and WebGPU has no optional bindings. Nothing samples them, because the
307
+ // sentinel below matches no light index.
308
+ this.#sun_shadow_resolution = [1, 1, 1];
309
+
310
+ // an orthographic view has no fov to derive a bounding sphere from, and no volumetrics
311
+ // camera path that reaches here today — bail rather than invent one
312
+ if (typeof camera.fov !== "number") {
313
+ return VOLUMETRICS_SUN_SHADOW_LIGHT_NONE;
314
+ }
315
+
316
+ const lights = this.#view.scene.lights.source.elements;
317
+
318
+ let sun = null;
319
+
320
+ for (let i = 0; i < lights.length; i++) {
321
+ if (lights[i].isDirectionalLight === true) {
322
+ sun = lights[i];
323
+ break;
324
+ }
325
+ }
326
+
327
+ if (sun === null) {
328
+ return VOLUMETRICS_SUN_SHADOW_LIGHT_NONE;
329
+ }
330
+
331
+ const resolution = VOLUMETRICS_SUN_SHADOW_RESOLUTION;
332
+
333
+ // `forward` is the direction light TRAVELS — the same vector light_record_directional
334
+ // writes into the table, which the shader then negates to point at the light. Read off the
335
+ // CPU copy of the pose: the cascade is built here, on this side.
336
+ const direction = sun.transform.forward;
337
+
338
+ const view_matrix = camera.view_matrix;
339
+ const position = camera.transform;
340
+
341
+ // the medium lives in the froxel grid and the grid stops here, so there is nothing to
342
+ // integrate past it
343
+ const far = Math.min(this.#froxel_far, camera.far);
344
+
345
+ const cascade_count = VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT;
346
+ const cascade_ratio = VOLUMETRICS_SUN_SHADOW_CASCADE_RATIO;
347
+
348
+ // the view matrix's z row, negated: view space looks down -Z, and the row is unit length
349
+ // because the linear part is a rotation
350
+ const camera_forward = [-view_matrix[2], -view_matrix[6], -view_matrix[10]];
351
+
352
+ for (let cascade = 0; cascade < cascade_count; cascade++) {
353
+
354
+ const cascade_far = volumetrics_sun_shadow_cascade_far(far, cascade, cascade_count, cascade_ratio);
355
+
356
+ this.#sun_shadow_step_ws[cascade] = build_volumetrics_sun_shadow_transform({
357
+ ws_to_uvw: this.#sun_shadow_ws_to_uvw[cascade],
358
+ uvw_to_ws: this.#sun_shadow_uvw_to_ws[cascade],
359
+ light_direction: [direction[0], direction[1], direction[2]],
360
+ camera_position: [position.translation_x, position.translation_y, position.translation_z],
361
+ camera_forward,
362
+ fov_y: camera.fov,
363
+ aspect: camera.aspect,
364
+ near: camera.near,
365
+ far: cascade_far,
366
+ resolution,
367
+ });
368
+
369
+ this.#sun_shadow_cascade_far[cascade] = cascade_far;
370
+ }
371
+
372
+ // Trailing lanes repeat the outermost cascade rather than holding zero: the read side walks
373
+ // the ladder comparing view depth against these, and a zero would read as a cascade that
374
+ // ends at the camera.
375
+ for (let lane = cascade_count; lane < 4; lane++) {
376
+ this.#sun_shadow_cascade_far[lane] = far;
377
+ this.#sun_shadow_step_ws[lane] = this.#sun_shadow_step_ws[cascade_count - 1];
378
+ }
379
+
380
+ this.#sun_shadow_resolution = resolution;
381
+
382
+ return 0;
383
+ }
384
+
385
+ #update_metadata() {
386
+ const view = this.#view;
387
+ const camera = view.camera;
388
+
389
+ const resolution = this.#volumetrics_textures[0].size;
390
+
391
+ this.#sun_shadow_light_index = this.#update_sun_shadow();
392
+
393
+ const volumetrics_cluster_parameters = new Float32Array(3);
394
+
395
+ // The depth-curve `scale` must grow with the slice count. The hard-coded 4.06 was tuned for
396
+ // the light-cluster grid (24 slices); reusing it with 64 froxel slices collapses the log
397
+ // curve so ~half the slices bunch into the first fraction of a unit of depth, leaving almost
398
+ // none for the actual scene. Derive scale for a log-uniform distribution over
399
+ // [near + near_offset, far] instead (gives O ~= 0, i.e. depth ~ offset * (far/offset)^(s/(N-1))).
400
+ const NEAR_OFFSET = 0.1;
401
+ const depth_offset = camera.camera.near + NEAR_OFFSET;
402
+ const depth_scale = (VOLUMETRICS_RESOLUTION_Z - 1) / Math.log2(camera.camera.far / depth_offset);
403
+
404
+ build_light_cluster_parameters(
405
+ volumetrics_cluster_parameters,
406
+ 0,
407
+ VOLUMETRICS_RESOLUTION_Z,
408
+ camera.camera.near,
409
+ camera.camera.far,
410
+ NEAR_OFFSET,
411
+ depth_scale
412
+ );
413
+
414
+ // Froxel-grid curve: same construction but ending at froxel_far (<= camera.far). The grid's
415
+ // slices pack into [near, froxel_far] for extra near detail; beyond froxel_far the aerial
416
+ // pass ray-marches the volumes. froxel_far == camera.far -> identical to the LUT curve (no-op).
417
+ const froxel_far = Math.min(this.#froxel_far, camera.camera.far);
418
+ const froxel_depth_scale = (VOLUMETRICS_RESOLUTION_Z - 1) / Math.log2(froxel_far / depth_offset);
419
+ const volumetrics_froxel_cluster_parameters = new Float32Array(3);
420
+ build_light_cluster_parameters(
421
+ volumetrics_froxel_cluster_parameters,
422
+ 0,
423
+ VOLUMETRICS_RESOLUTION_Z,
424
+ camera.camera.near,
425
+ froxel_far,
426
+ NEAR_OFFSET,
427
+ froxel_depth_scale
428
+ );
429
+
430
+ // Recomputed deterministically, so a steady camera compares bitwise equal and never resets.
431
+ {
432
+ const curve = volumetrics_froxel_cluster_parameters;
433
+ const previous = this.#froxel_curve_previous;
434
+
435
+ if (curve[0] !== previous[0] || curve[1] !== previous[1] || curve[2] !== previous[2]) {
436
+ previous.set(curve);
437
+ this.#reset_history = true;
438
+ }
439
+ }
440
+
441
+ // The projection is infinite-far reverse-Z, so the camera frustum's far plane is zeroed and
442
+ // can't be sliced into froxel slabs. Rebuild a finite far plane at the logical far for the
443
+ // participating-media coverage test — exactly as the light-cluster assignment does.
444
+ const clipped_frustum = new Float32Array(24);
445
+ clipped_frustum.set(camera.camera.frustum);
446
+ update_frustum_far_plane(clipped_frustum, camera.camera.view_matrix, camera.camera.far);
447
+
448
+ this.#metadata.upload({
449
+ cluster_parameters: volumetrics_cluster_parameters,
450
+ froxel_cluster_parameters: volumetrics_froxel_cluster_parameters,
451
+ resolution,
452
+ frustum: slice_flat_array(clipped_frustum, 0, 4, 6),
453
+ history_reset: this.#reset_history ? 1 : 0,
454
+ froxel_far,
455
+ // multiscatter knobs (GUI-tunable via globals); engine defaults 6 / 0.5
456
+ ms_octave_count: globalThis.__VOL_MS_OCTAVES ?? 6,
457
+ ms_extinction_decay: globalThis.__VOL_MS_EXT_DECAY ?? 0.5,
458
+ // Q-04. The sun's media self-shadowing, out of a 16-step per-froxel march and into one
459
+ // fetch of the cascades built earlier this frame — see #update_sun_shadow.
460
+ sun_shadow_ws_to_uvw: this.#sun_shadow_ws_to_uvw,
461
+ sun_shadow_uvw_to_ws: this.#sun_shadow_uvw_to_ws,
462
+ sun_shadow_step_ws: this.#sun_shadow_step_ws,
463
+ sun_shadow_cascade_far: this.#sun_shadow_cascade_far,
464
+ sun_shadow_light_index: this.#sun_shadow_light_index,
465
+ }, this.#device.queue);
466
+ }
467
+
468
+ /**
469
+ *
470
+ * @param {Object} options
471
+ * @param {FrameGraph} options.graph
472
+ * @param {number} options.light_cluster_data
473
+ * @param {number} options.light_cluster_lookup
474
+ * @param {number} options.light_cluster_parameters
475
+ * @param {number} options.shadowmap_atlas
476
+ * @param {ShaderSwitchSet} [options.shader_switches]
477
+ * @param {import("../global_illumination/brick4/Brick4LightMap.js").Brick4LightMap} [options.brick4]
478
+ * @param {import("../global_illumination/lpv/GPULightProbeVolume.js").GPULightProbeVolume} [options.lpv]
479
+ * @returns {{resolved: number, metadata: number, irradiance: {Y: number, CoCg: number, uniform: number}}}
480
+ */
481
+ graph_update({
482
+ graph,
483
+ light_cluster_data,
484
+ light_cluster_lookup,
485
+ light_cluster_parameters,
486
+ shadowmap_atlas,
487
+ shader_switches = ShaderSwitchSet.EMPTY,
488
+ brick4,
489
+ lpv,
490
+ }) {
491
+
492
+ const view = this.#view;
493
+ const lights = view.scene.lights;
494
+ const camera = view.camera;
495
+
496
+ const taa_requested = globalThis.__VOL_TAA_ENABLED;
497
+ if (taa_requested !== undefined && taa_requested !== this.#taa_enabled) {
498
+ this.taa_enabled = taa_requested;
499
+ }
500
+
501
+ const froxel_far_requested = globalThis.__VOL_FROXEL_FAR;
502
+ if (froxel_far_requested !== undefined && froxel_far_requested !== this.#froxel_far) {
503
+ this.#froxel_far = froxel_far_requested;
504
+ // the froxel grid's depth curve just changed; the TAA history is on the old curve, reset it
505
+ this.#reset_history = true;
506
+ }
507
+
508
+ this.#update_resolution();
509
+ this.#update_metadata();
510
+
511
+ const gr_metadata = graph_import_buffer(graph, this.#metadata.buffer, "volumetrics metadata");
512
+
513
+ const resolution = this.#volumetrics_textures[0].size;
514
+
515
+ // World-space irradiance clipmap for particle lighting.
516
+ // Culled by the frame graph when no lit particles read it.
517
+ const irradiance = this.#irradiance_clipmap.graph_update({
518
+ graph,
519
+ camera,
520
+ scene_ctx: view.scene,
521
+ shadowmap_atlas,
522
+ shader_switches,
523
+ brick4,
524
+ lpv,
525
+ });
526
+
527
+ if (view.scene.volumetrics.source.volumes.length === 0) {
528
+ // No medium: nothing scatters and nothing absorbs, so the fog is the identity everywhere
529
+ // and the passes that would integrate it to that are not recorded. Nothing accumulates
530
+ // either, which leaves the history stale for whenever a volume arrives.
531
+ this.#reset_history = true;
532
+
533
+ return {
534
+ resolved: graph_import_texture(graph, this.#no_medium, "volumetrics no medium"),
535
+ metadata: gr_metadata,
536
+ irradiance,
537
+ };
538
+ }
539
+
540
+ let taa_history;
541
+ let taa_output;
542
+
543
+ if (this.#taa_enabled) {
544
+ const parity = view.frame_index & 1;
545
+ taa_history = graph_import_texture(graph, this.#volumetrics_textures[parity ^ 1], "volumetrics taa history");
546
+ taa_output = graph_import_texture(graph, this.#volumetrics_textures[parity], "volumetrics taa output");
547
+ }
548
+
549
+ const sky = view.scene.sky;
550
+
551
+ sky.update();
552
+
553
+ const { lut_aerial } = graph_build_volumetrics({
554
+ graph,
555
+ camera,
556
+ lights,
557
+ view,
558
+ sky,
559
+ light_cluster_data,
560
+ light_cluster_lookup,
561
+ light_cluster_parameters,
562
+ shadowmap_atlas,
563
+
564
+ volumetrics_metadata: gr_metadata,
565
+ volumetric_resolution: resolution,
566
+ sun_shadow_resolution: this.#sun_shadow_resolution,
567
+
568
+ taa_history,
569
+ taa_output,
570
+ camera_previous: view.gpu_previous_camera_state,
571
+ shader_switches,
572
+ });
573
+
574
+ this.#reset_history = false;
575
+
576
+ return {
577
+ resolved: lut_aerial,
578
+ metadata: gr_metadata,
579
+ irradiance,
580
+ };
581
+ }
582
+
583
+ }