@woosh/meep-engine 3.50.0 → 3.52.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 (180) hide show
  1. package/package.json +1 -1
  2. package/src/engine/graphics3/make_default_environment.d.ts.map +1 -1
  3. package/src/engine/graphics3/make_default_environment.js +11 -5
  4. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  5. package/src/shade/playground/particle_ecs/moonlit_environment.d.ts.map +1 -1
  6. package/src/shade/playground/particle_ecs/moonlit_environment.js +11 -5
  7. package/src/shade/playground/sh_exponential/sh_exp_playground_shaders.d.ts.map +1 -1
  8. package/src/shade/playground/sh_exponential/sh_exp_playground_shaders.js +3 -1
  9. package/src/shade/playground/tonemap_sdr/README.md +5 -3
  10. package/src/shade/renderer/Renderer.d.ts +1 -1
  11. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  12. package/src/shade/renderer/Renderer.js +43 -15
  13. package/src/shade/renderer/ShadeToneMappingMode.d.ts +3 -2
  14. package/src/shade/renderer/ShadeToneMappingMode.js +15 -4
  15. package/src/shade/renderer/atmosphere/chunk_sample_environment_color.d.ts +3 -0
  16. package/src/shade/renderer/atmosphere/chunk_sample_environment_color.d.ts.map +1 -1
  17. package/src/shade/renderer/atmosphere/chunk_sample_environment_color.js +11 -8
  18. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts +6 -0
  19. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
  20. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +227 -215
  21. package/src/shade/renderer/deferred/SHADING_LIGHT_RESOURCE_GROUP.d.ts.map +1 -1
  22. package/src/shade/renderer/deferred/SHADING_LIGHT_RESOURCE_GROUP.js +2 -0
  23. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_irradiance.d.ts.map +1 -1
  24. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_irradiance.js +4 -2
  25. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_radiance.d.ts.map +1 -1
  26. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_radiance.js +4 -2
  27. package/src/shade/renderer/deferred/shader_background.d.ts.map +1 -1
  28. package/src/shade/renderer/deferred/shader_background.js +4 -2
  29. package/src/shade/renderer/deferred/tonemap/TonemapAces2Tables.d.ts +38 -0
  30. package/src/shade/renderer/deferred/tonemap/TonemapAces2Tables.d.ts.map +1 -0
  31. package/src/shade/renderer/deferred/tonemap/TonemapAces2Tables.js +97 -0
  32. package/src/shade/renderer/deferred/{TonemapDisplaySettings.d.ts → tonemap/TonemapDisplaySettings.d.ts} +1 -1
  33. package/src/shade/renderer/deferred/tonemap/TonemapDisplaySettings.d.ts.map +1 -0
  34. package/src/shade/renderer/deferred/{TonemapDisplaySettings.js → tonemap/TonemapDisplaySettings.js} +5 -5
  35. package/src/shade/renderer/deferred/{shader_tonemap_HDR.d.ts → tonemap/shader_tonemap_HDR.d.ts} +1 -1
  36. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR.d.ts.map +1 -0
  37. package/src/shade/renderer/deferred/{shader_tonemap_HDR.js → tonemap/shader_tonemap_HDR.js} +9 -9
  38. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_aces.d.ts +17 -0
  39. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_aces.d.ts.map +1 -0
  40. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_aces.js +101 -0
  41. package/src/shade/renderer/deferred/{shader_tonemap_HDR_none.d.ts → tonemap/shader_tonemap_HDR_none.d.ts} +1 -1
  42. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_none.d.ts.map +1 -0
  43. package/src/shade/renderer/deferred/{shader_tonemap_HDR_none.js → tonemap/shader_tonemap_HDR_none.js} +6 -6
  44. package/src/shade/renderer/deferred/{shader_tonemap_HDR_pbr_neutral.d.ts → tonemap/shader_tonemap_HDR_pbr_neutral.d.ts} +1 -1
  45. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_pbr_neutral.d.ts.map +1 -0
  46. package/src/shade/renderer/deferred/{shader_tonemap_HDR_pbr_neutral.js → tonemap/shader_tonemap_HDR_pbr_neutral.js} +8 -8
  47. package/src/shade/renderer/deferred/{shader_tonemap_LDR.d.ts → tonemap/shader_tonemap_LDR.d.ts} +1 -1
  48. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR.d.ts.map +1 -0
  49. package/src/shade/renderer/deferred/{shader_tonemap_LDR.js → tonemap/shader_tonemap_LDR.js} +10 -10
  50. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_aces.d.ts +10 -0
  51. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_aces.d.ts.map +1 -0
  52. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_aces.js +77 -0
  53. package/src/shade/renderer/deferred/{shader_tonemap_LDR_none.d.ts → tonemap/shader_tonemap_LDR_none.d.ts} +1 -1
  54. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_none.d.ts.map +1 -0
  55. package/src/shade/renderer/deferred/{shader_tonemap_LDR_none.js → tonemap/shader_tonemap_LDR_none.js} +7 -7
  56. package/src/shade/renderer/deferred/{shader_tonemap_LDR_pbr_neutral.d.ts → tonemap/shader_tonemap_LDR_pbr_neutral.d.ts} +1 -1
  57. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_pbr_neutral.d.ts.map +1 -0
  58. package/src/shade/renderer/deferred/{shader_tonemap_LDR_pbr_neutral.js → tonemap/shader_tonemap_LDR_pbr_neutral.js} +8 -8
  59. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  60. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  61. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  62. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  63. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  64. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.d.ts.map +1 -1
  65. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.js +3 -1
  66. package/src/shade/renderer/global_illumination/ibl/shader_diffuse_IBL.d.ts.map +1 -1
  67. package/src/shade/renderer/global_illumination/ibl/shader_diffuse_IBL.js +3 -1
  68. package/src/shade/renderer/global_illumination/ibl/shader_specular_IBL.d.ts.map +1 -1
  69. package/src/shade/renderer/global_illumination/ibl/shader_specular_IBL.js +3 -1
  70. package/src/shade/renderer/global_illumination/lpv/deprecated/probe_trace_compute_shader_code.d.ts.map +1 -1
  71. package/src/shade/renderer/global_illumination/lpv/deprecated/probe_trace_compute_shader_code.js +3 -1
  72. package/src/shade/renderer/global_illumination/probe/octahedral/shader_lpv_build_gbuffer.d.ts.map +1 -1
  73. package/src/shade/renderer/global_illumination/probe/octahedral/shader_lpv_build_gbuffer.js +4 -2
  74. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  75. package/src/shade/renderer/light/GPULightCollection.js +44 -0
  76. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_RESOURCE.d.ts +2 -1
  77. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_RESOURCE.d.ts.map +1 -1
  78. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_RESOURCE.js +3 -2
  79. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_SAMPLER_RESOURCE.d.ts +14 -0
  80. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_SAMPLER_RESOURCE.d.ts.map +1 -0
  81. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_SAMPLER_RESOURCE.js +25 -0
  82. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  83. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +3 -4
  84. package/src/shade/renderer/light/environment/octahedral_skirt_matches.d.ts +19 -0
  85. package/src/shade/renderer/light/environment/octahedral_skirt_matches.d.ts.map +1 -0
  86. package/src/shade/renderer/light/environment/octahedral_skirt_matches.js +67 -0
  87. package/src/shade/renderer/light/environment/octahedral_skirt_source_texel.d.ts +16 -0
  88. package/src/shade/renderer/light/environment/octahedral_skirt_source_texel.d.ts.map +1 -0
  89. package/src/shade/renderer/light/environment/octahedral_skirt_source_texel.js +35 -0
  90. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.d.ts +7 -2
  91. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.d.ts.map +1 -1
  92. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +23 -7
  93. package/src/shade/renderer/light/environment/resample_equirectangular_to_octahedral.d.ts +8 -2
  94. package/src/shade/renderer/light/environment/resample_equirectangular_to_octahedral.d.ts.map +1 -1
  95. package/src/shade/renderer/light/environment/resample_equirectangular_to_octahedral.js +24 -7
  96. package/src/shade/renderer/light/environment/sampler2d_octahedral_skirt_fill.d.ts +12 -0
  97. package/src/shade/renderer/light/environment/sampler2d_octahedral_skirt_fill.d.ts.map +1 -0
  98. package/src/shade/renderer/light/environment/sampler2d_octahedral_skirt_fill.js +54 -0
  99. package/src/shade/renderer/light/environment/sh_exponential/shader_sh_exp_environment_cells.d.ts +2 -2
  100. package/src/shade/renderer/light/environment/sh_exponential/shader_sh_exp_environment_cells.d.ts.map +1 -1
  101. package/src/shade/renderer/light/environment/sh_exponential/shader_sh_exp_environment_cells.js +5 -4
  102. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.d.ts.map +1 -1
  103. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.js +13 -9
  104. package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts.map +1 -1
  105. package/src/shade/renderer/light/environment/shader_project_environment_sh.js +3 -1
  106. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.d.ts.map +1 -1
  107. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.js +3 -1
  108. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.d.ts.map +1 -1
  109. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.js +3 -1
  110. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  111. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.d.ts.map +1 -1
  112. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.js +3 -1
  113. package/src/shade/renderer/path_tracer/graph_path_trace_scene.d.ts.map +1 -1
  114. package/src/shade/renderer/path_tracer/graph_path_trace_scene.js +3 -1
  115. package/src/shade/renderer/path_tracer/shader/chunk_render_trace_path.js +1 -1
  116. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  117. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.d.ts.map +1 -1
  118. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.js +3 -1
  119. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts +0 -5
  120. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts.map +1 -1
  121. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.js +0 -6
  122. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts.map +1 -1
  123. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.js +4 -1
  124. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
  125. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +3 -0
  126. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
  127. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +3 -0
  128. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +160 -0
  129. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +5 -1
  130. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +15 -5
  131. package/src/shade/renderer/shader/chunk/color/tonemap/aces/aces2_transform_block.d.ts +41 -0
  132. package/src/shade/renderer/shader/chunk/color/tonemap/aces/aces2_transform_block.d.ts.map +1 -0
  133. package/src/shade/renderer/shader/chunk/color/tonemap/aces/aces2_transform_block.js +692 -0
  134. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces2_output_transform.d.ts +25 -0
  135. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces2_output_transform.d.ts.map +1 -0
  136. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces2_output_transform.js +446 -0
  137. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.d.ts +22 -0
  138. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.d.ts.map +1 -0
  139. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.js +68 -0
  140. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.d.ts +32 -0
  141. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.d.ts.map +1 -0
  142. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.js +163 -0
  143. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.d.ts +10 -3
  144. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.d.ts.map +1 -1
  145. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.js +12 -4
  146. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.d.ts +15 -0
  147. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.d.ts.map +1 -0
  148. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.js +27 -0
  149. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.d.ts +18 -0
  150. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.d.ts.map +1 -0
  151. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.js +24 -0
  152. package/src/shade/renderer/shader/chunk/texture/sample/chunk_sample_environment_map.d.ts +18 -1
  153. package/src/shade/renderer/shader/chunk/texture/sample/chunk_sample_environment_map.d.ts.map +1 -1
  154. package/src/shade/renderer/shader/chunk/texture/sample/chunk_sample_environment_map.js +45 -27
  155. package/src/shade/renderer/view/GPUViewSkyContext.js +169 -169
  156. package/src/shade/renderer/volumetrics/NOTES.md +209 -0
  157. package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.d.ts.map +1 -1
  158. package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js +1 -1
  159. package/src/shade/renderer/volumetrics/chunk_volumetrics_media_group_list.d.ts +36 -0
  160. package/src/shade/renderer/volumetrics/chunk_volumetrics_media_group_list.d.ts.map +1 -0
  161. package/src/shade/renderer/volumetrics/chunk_volumetrics_media_group_list.js +129 -0
  162. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  163. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +75 -0
  164. package/src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.d.ts +26 -0
  165. package/src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.d.ts.map +1 -0
  166. package/src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.js +110 -0
  167. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
  168. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +38 -14
  169. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.d.ts.map +1 -1
  170. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +9 -4
  171. package/src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.d.ts +31 -0
  172. package/src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.d.ts.map +1 -0
  173. package/src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.js +139 -0
  174. package/src/shade/renderer/deferred/TonemapDisplaySettings.d.ts.map +0 -1
  175. package/src/shade/renderer/deferred/shader_tonemap_HDR.d.ts.map +0 -1
  176. package/src/shade/renderer/deferred/shader_tonemap_HDR_none.d.ts.map +0 -1
  177. package/src/shade/renderer/deferred/shader_tonemap_HDR_pbr_neutral.d.ts.map +0 -1
  178. package/src/shade/renderer/deferred/shader_tonemap_LDR.d.ts.map +0 -1
  179. package/src/shade/renderer/deferred/shader_tonemap_LDR_none.d.ts.map +0 -1
  180. package/src/shade/renderer/deferred/shader_tonemap_LDR_pbr_neutral.d.ts.map +0 -1
@@ -0,0 +1,129 @@
1
+ import { AABB3_STRUCT } from "../shader/chunk/geometry/aabb3/AABB3_STRUCT.js";
2
+ import { chunk_aabb3_intersects_frustum } from "../shader/chunk/geometry/aabb3/chunk_aabb3_intersects_frustum.js";
3
+ import { chunk_frustum_slice } from "../shader/chunk/geometry/frustum/chunk_frustum_slice.js";
4
+ import {
5
+ chunk_frustum_transform_plane_matrix
6
+ } from "../shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.js";
7
+ import { chunk_grid3d_to_index } from "../shader/chunk/grid/chunk_grid3d_to_index.js";
8
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
9
+ import { chunk_cluster_depth_from_z_slice } from "../light/cluster/chunk_cluster_depth_from_z_slice.js";
10
+ import { PARTICIPATING_MEDIA_VOLUME_STRUCT } from "./PARTICIPATING_MEDIA_VOLUME_STRUCT.js";
11
+
12
+ /**
13
+ * The participating-media build's workgroup, in froxels: flat in z, one slice a group. Read by its
14
+ * `@workgroup_size`, by the bin that pre-culls the volumes for each of those groups, and by the
15
+ * dispatches of both.
16
+ * @type {number[]}
17
+ */
18
+ export const VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION = [16, 16, 1];
19
+
20
+ /**
21
+ * The most volumes of its group's list a froxel of the participating-media build walks. Chosen, not
22
+ * derived. Past it the build drops the group's highest-indexed volumes, whatever each froxel would
23
+ * have made of them.
24
+ * @type {number}
25
+ */
26
+ export const VOLUMETRICS_MEDIA_GROUP_VOLUME_BUDGET = 32;
27
+
28
+ /**
29
+ * The volumes each workgroup of the participating-media build may see, as one list per group: a
30
+ * bitmask over the volumes' rows, `list_words` words a group, the groups in `grid3d_to_index` order.
31
+ * The bin writes it, and the build walks its own group's list in row order instead of every volume.
32
+ *
33
+ * - `volumetrics_media_groups` is the build's workgroup grid over a froxel grid.
34
+ * - `volumetrics_media_list_words` is the words of one group's list: as many as the volumes need, and
35
+ * never more than the buffer holds for each group, so a list sized from a count the host had
36
+ * before a volume arrived drops that volume for a frame rather than overrun.
37
+ * - `volumetrics_froxel_planes` is the metadata frustum with its Y pair turned to the froxel grid's
38
+ * rows (screen Y runs down, world Y up).
39
+ * - `volumetrics_froxel_slab` is the frustum of a box of froxels: `uv` over the grid's columns and
40
+ * rows, `slice` over its depth curve, clamped to the camera's logical near and far.
41
+ * `volumetrics_froxel_slab_between` is the same with the depths given, inside `[near, far]`.
42
+ * - `volumetrics_volume_in_slab` is the build's own rejection of a volume by a slab: its unit box
43
+ * against the slab's planes taken into the volume's frame.
44
+ *
45
+ * @type {CodeChunk}
46
+ */
47
+ export const chunk_volumetrics_media_group_list = CodeChunk.from(
48
+ //language=WGSL
49
+ `
50
+ const VOLUMETRICS_MEDIA_GROUP_XY = vec2<u32>(${VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION[0]}u, ${VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION[1]}u);
51
+ const VOLUMETRICS_MEDIA_GROUP_VOLUME_BUDGET: u32 = ${VOLUMETRICS_MEDIA_GROUP_VOLUME_BUDGET}u;
52
+
53
+ fn volumetrics_media_groups(resolution: vec3<u32>) -> vec3<u32> {
54
+ return vec3<u32>((resolution.xy + VOLUMETRICS_MEDIA_GROUP_XY - vec2<u32>(1u)) / VOLUMETRICS_MEDIA_GROUP_XY, resolution.z);
55
+ }
56
+
57
+ fn volumetrics_media_list_words(volume_count: u32, buffer_words: u32, groups: vec3<u32>) -> u32 {
58
+ return min((volume_count + 31u) / 32u, buffer_words / (groups.x * groups.y * groups.z));
59
+ }
60
+
61
+ fn volumetrics_media_list_offset(group: vec3<u32>, groups: vec3<u32>, list_words: u32) -> u32 {
62
+ return grid3d_to_index(group, groups.xy) * list_words;
63
+ }
64
+
65
+ fn volumetrics_froxel_planes(frustum: array<vec4<f32>, 6>) -> array<vec4<f32>, 6> {
66
+ // Frustum planes are [LEFT, RIGHT, BOTTOM, TOP, NEAR, FAR] (see Camera.construct_frustum_planes),
67
+ // every one facing in. The grid's rows run down the screen while world Y runs up, so the Y pair
68
+ // trades places, and stays facing in: frustum_slice takes the plane at uv 0 as it is and the one
69
+ // at uv 1 as it is, so swapped and kept they bound the slab from the inside, as X and Z do.
70
+ var planes = frustum;
71
+ let t = planes[2];
72
+ planes[2] = planes[3];
73
+ planes[3] = t;
74
+ return planes;
75
+ }
76
+
77
+ fn volumetrics_froxel_slab(
78
+ planes: array<vec4<f32>, 6>,
79
+ uv_min: vec2<f32>,
80
+ uv_max: vec2<f32>,
81
+ slice_0: f32,
82
+ slice_1: f32,
83
+ near_far: vec2<f32>,
84
+ curve: vec3<f32>,
85
+ slices: f32,
86
+ ) -> array<vec4<f32>, 6> {
87
+ let z0 = max(near_far.x, cluster_depth_from_z_slice(slice_0, curve, slices));
88
+ let z1 = min(near_far.y, cluster_depth_from_z_slice(slice_1, curve, slices));
89
+
90
+ return volumetrics_froxel_slab_between(planes, uv_min, uv_max, z0, z1, near_far);
91
+ }
92
+
93
+ fn volumetrics_froxel_slab_between(
94
+ planes: array<vec4<f32>, 6>,
95
+ uv_min: vec2<f32>,
96
+ uv_max: vec2<f32>,
97
+ z0: f32,
98
+ z1: f32,
99
+ near_far: vec2<f32>,
100
+ ) -> array<vec4<f32>, 6> {
101
+ let z0_norm = (z0 - near_far.x) / (near_far.y - near_far.x);
102
+ let z1_norm = (z1 - near_far.x) / (near_far.y - near_far.x);
103
+
104
+ return frustum_slice(planes, vec3(uv_min, z0_norm), vec3(uv_max, z1_norm));
105
+ }
106
+
107
+ fn volumetrics_volume_in_slab(slab: array<vec4<f32>, 6>, volume: ${PARTICIPATING_MEDIA_VOLUME_STRUCT.wgsl_ref}) -> bool {
108
+ // Points go world -> local through transform_inverse, so planes go by the transpose of *its*
109
+ // inverse, which is just transpose(transform) — already resident.
110
+ let local = frustum_transform_plane_matrix(slab, transpose(volume.transform));
111
+
112
+ const unit_bounds = ${AABB3_STRUCT.make({
113
+ min: "vec3(-0.5)",
114
+ max: "vec3(0.5)",
115
+ })};
116
+
117
+ return aabb3_intersects_frustum(unit_bounds, local);
118
+ }
119
+ `,
120
+ [
121
+ PARTICIPATING_MEDIA_VOLUME_STRUCT.declaration_chunk,
122
+ AABB3_STRUCT.declaration_chunk,
123
+ chunk_aabb3_intersects_frustum,
124
+ chunk_frustum_slice,
125
+ chunk_frustum_transform_plane_matrix,
126
+ chunk_cluster_depth_from_z_slice,
127
+ chunk_grid3d_to_index,
128
+ ]
129
+ );
@@ -1 +1 @@
1
- {"version":3,"file":"graph_build_volumetrics.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/graph_build_volumetrics.js"],"names":[],"mappings":"AAwWA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IAtB+B,KAAK;IACC,MAAM;IACJ,MAAM;IACV,IAAI;IACZ,GAAG;IAIH,kBAAkB,EAAlC,MAAM;IACU,oBAAoB,EAApC,MAAM;IACU,wBAAwB,EAAxC,MAAM;IACU,eAAe,EAA/B,MAAM;IACY,qBAAqB,EAAvC,MAAM,EAAE;IACU,qBAAqB,EAAvC,MAAM,EAAE;IAGQ,oBAAoB,EAApC,MAAM;IACW,WAAW,GAA5B,MAAM;IACW,UAAU,GAA3B,MAAM;IACqB,eAAe;IAChB,eAAe,GAAzC,eAAe;;;;;;EAkLzB;gCAjjB+B,uCAAuC"}
1
+ {"version":3,"file":"graph_build_volumetrics.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/graph_build_volumetrics.js"],"names":[],"mappings":"AA+aA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IAtB+B,KAAK;IACC,MAAM;IACJ,MAAM;IACV,IAAI;IACZ,GAAG;IAIH,kBAAkB,EAAlC,MAAM;IACU,oBAAoB,EAApC,MAAM;IACU,wBAAwB,EAAxC,MAAM;IACU,eAAe,EAA/B,MAAM;IACY,qBAAqB,EAAvC,MAAM,EAAE;IACU,qBAAqB,EAAvC,MAAM,EAAE;IAGQ,oBAAoB,EAApC,MAAM;IACW,WAAW,GAA5B,MAAM;IACW,UAAU,GAA3B,MAAM;IACqB,eAAe;IAChB,eAAe,GAAzC,eAAe;;;;;;EAsLzB;gCA3nB+B,uCAAuC"}
@@ -1,5 +1,6 @@
1
1
  import { assert } from "../../../core/assert.js";
2
2
  import { SamplerDescriptor } from "../../descriptor/texture/SamplerDescriptor.js";
3
+ import { BufferResourceDescriptor } from "../../device/graph/BufferResourceDescriptor.js";
3
4
  import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
4
5
  import { ShaderSwitchSet } from "../shader/compiler/ShaderSwitchSet.js";
5
6
  import { graph_compute_pass } from "../shader/graph/compute/graph_compute_pass.js";
@@ -11,6 +12,12 @@ import { DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR } from "../texture/DEFAULT_RENDER
11
12
  import { WIND_FIELD_UNIFORM } from "../wind/WIND_FIELD_UNIFORM.js";
12
13
  import { volumetrics_sun_shadow_texture_name } from "./chunk_volumetrics_sun_optical_depth.js";
13
14
  import { PARTICIPATING_MEDIA_NOISE_SAMPLER_DESCRIPTOR } from "./noise/PARTICIPATING_MEDIA_NOISE.js";
15
+ import { VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION } from "./chunk_volumetrics_media_group_list.js";
16
+ import { shader_volumetrics_bin_participating_media } from "./shader_volumetrics_bin_participating_media.js";
17
+ import {
18
+ shader_volumetrics_split_participating_media,
19
+ VOLUMETRICS_MEDIA_SPLIT_LANES
20
+ } from "./shader_volumetrics_split_participating_media.js";
14
21
  import { shader_volumetrics_build_lighting } from "./shader_volumetrics_build_lighting.js";
15
22
  import { shader_volumetrics_build_participating_media } from "./shader_volumetrics_build_participating_media.js";
16
23
  import { shader_volumetrics_build_scattering_lut } from "./shader_volumetrics_build_scattering_lut.js";
@@ -48,6 +55,21 @@ function graph_integrate_participating_media({
48
55
 
49
56
  const data = {};
50
57
 
58
+ // the media build's workgroups, each of which the bin gives a list of the volumes it may see
59
+ const groups = [
60
+ Math.ceil(resolution[0] / VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION[0]),
61
+ Math.ceil(resolution[1] / VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION[1]),
62
+ resolution[2],
63
+ ];
64
+
65
+ // A bit a volume. Sized from the scene's volumes, which the table's rows never outnumber; the
66
+ // shaders take the volume count off the table and the words a list gets off this buffer.
67
+ const volume_count = scene.volumetrics.source.volumes.length;
68
+ const list_words = Math.ceil(volume_count / 32);
69
+ const group_lists_bytes = 4 * Math.max(4, groups[0] * groups[1] * groups[2] * list_words);
70
+
71
+ // the near and the far field: a count, then a row for every volume either may hold
72
+ const field_bytes = 4 * Math.max(4, 1 + volume_count);
51
73
 
52
74
  const pass = graph.add(
53
75
  "aerial lut",
@@ -69,10 +91,40 @@ function graph_integrate_participating_media({
69
91
  */
70
92
  const output1 = resources.get(data.output1);
71
93
 
94
+ const group_lists = resources.get(data.group_lists);
95
+ const near_field = resources.get(data.near_field);
96
+ const far_field = resources.get(data.far_field);
97
+
72
98
  const encoder = context.encoder;
73
99
 
74
100
  const volumetrics_metadata = resources.get(data.volumetrics_metadata);
75
101
 
102
+ shader_volumetrics_split_participating_media.dispatch({
103
+ encoder,
104
+ bindings: {
105
+ camera: camera.buffer.buffer,
106
+ volumetrics_metadata,
107
+ scene_database: scene.scene_database_buffer,
108
+ near_field,
109
+ far_field,
110
+ },
111
+ group_count_x: Math.max(1, Math.ceil(volume_count / VOLUMETRICS_MEDIA_SPLIT_LANES)),
112
+ });
113
+
114
+ shader_volumetrics_bin_participating_media.dispatch({
115
+ encoder,
116
+ bindings: {
117
+ camera: camera.buffer.buffer,
118
+ volumetrics_metadata,
119
+ scene_database: scene.scene_database_buffer,
120
+ near_field,
121
+ group_lists,
122
+ },
123
+ group_count_x: groups[0],
124
+ group_count_y: groups[1],
125
+ group_count_z: groups[2],
126
+ });
127
+
76
128
  /**
77
129
  * The scene's noise atlas, imported this frame so a grow between frames is seen.
78
130
  * @type {GPUTextureContext}
@@ -92,6 +144,7 @@ function graph_integrate_participating_media({
92
144
  sParticipatingMediaNoise: context.graphics.samplers.obtain(PARTICIPATING_MEDIA_NOISE_SAMPLER_DESCRIPTOR),
93
145
  tOutput0: output0.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
94
146
  tOutput1: output1.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
147
+ group_lists,
95
148
  };
96
149
 
97
150
  // taken this frame rather than held: a producer that grows the field replaces its atlas
@@ -113,6 +166,23 @@ function graph_integrate_participating_media({
113
166
  });
114
167
 
115
168
  data.volumetrics_metadata = pass.read(volumetrics_metadata);
169
+ // the split appends to lists counted from zero
170
+ data.near_field = pass.create("participating media near field", BufferResourceDescriptor.from(
171
+ field_bytes,
172
+ GPUBufferUsage.STORAGE,
173
+ 0, 4,
174
+ ));
175
+ data.far_field = pass.create("participating media far field", BufferResourceDescriptor.from(
176
+ field_bytes,
177
+ GPUBufferUsage.STORAGE,
178
+ 0, 4,
179
+ ));
180
+ // the bin ORs bits into zeroed lists
181
+ data.group_lists = pass.create("participating media group lists", BufferResourceDescriptor.from(
182
+ group_lists_bytes,
183
+ GPUBufferUsage.STORAGE,
184
+ 0, group_lists_bytes,
185
+ ));
116
186
  data.noise_atlas = pass.read(graph_import_texture(graph, scene.volumetrics.noise_atlas.texture, "volumetrics noise atlas"));
117
187
 
118
188
  data.output0 = pass.create("Participating Media 0", TextureResourceDescriptor.from({
@@ -132,6 +202,7 @@ function graph_integrate_participating_media({
132
202
  participating_media0: data.output0,
133
203
  participating_media1: data.output1,
134
204
  volumetrics_metadata: data.volumetrics_metadata,
205
+ far_field: data.far_field,
135
206
  };
136
207
  }
137
208
 
@@ -421,6 +492,7 @@ export function graph_build_volumetrics({
421
492
  const {
422
493
  participating_media0,
423
494
  participating_media1,
495
+ far_field,
424
496
  } = graph_integrate_participating_media({
425
497
  graph,
426
498
  scene: view.scene,
@@ -497,6 +569,7 @@ export function graph_build_volumetrics({
497
569
 
498
570
  const light_scattering_lut = resources.get(data.light_scattering_lut);
499
571
  const sun_transmittance_lut = resources.get(data.sun_transmittance_lut);
572
+ const far_field = resources.get(data.far_field);
500
573
 
501
574
  const encoder = context.encoder;
502
575
 
@@ -526,6 +599,7 @@ export function graph_build_volumetrics({
526
599
 
527
600
  // RDR2-style far-field march inputs (used beyond froxel_far)
528
601
  scene_database: view.scene.scene_database_buffer,
602
+ far_field,
529
603
  light_data: lights.buffer_data,
530
604
  tSunTransmittance: sun_transmittance_lut.obtainView(),
531
605
 
@@ -540,6 +614,7 @@ export function graph_build_volumetrics({
540
614
  a_data.participating_media1 = a_pass.read(participating_media1);
541
615
  a_data.volumetrics_metadata = a_pass.read(volumetrics_metadata);
542
616
  a_data.sun_transmittance_lut = a_pass.read(transmittance_lut);
617
+ a_data.far_field = a_pass.read(far_field);
543
618
 
544
619
  a_data.output = a_pass.create("Integrated Volumetrics", TextureResourceDescriptor.from({
545
620
  resolution: volumetric_resolution,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Lanes of a bin workgroup. Small on purpose: a lane tests one volume at a time, so at a handful of
3
+ * volumes most lanes of any group idle, and the fewer there are the less the bin launches for
4
+ * nothing.
5
+ * @type {number}
6
+ */
7
+ export const VOLUMETRICS_MEDIA_BIN_LANES: number;
8
+ /**
9
+ * Pre-culls the scene's participating-media volumes for each workgroup of the participating-media
10
+ * build: one workgroup per build group, which tests every volume of the near field (see
11
+ * {@link shader_volumetrics_split_participating_media}) against the group's slab of froxels and
12
+ * sets the bit of each one it keeps in that group's list (see {@link chunk_volumetrics_media_group_list}).
13
+ * The lists start zeroed; the graph clears them.
14
+ *
15
+ * The slab is the union of every froxel of the group as the build slices it, widened so that
16
+ * rounding can never make the group stricter than a froxel. In xy that is a quarter of a froxel
17
+ * past the group's columns and rows. In z it is the column jitter, which moves a froxel's slab by
18
+ * up to half a slice either way, and a quarter of a slice beyond that. A volume a froxel would
19
+ * cover therefore never misses its group's list, and the build reads, through the list, exactly
20
+ * what it read walking every volume.
21
+ *
22
+ * Budget: a lane tests ⌈near-field volumes / 64⌉ volumes. Nothing is past it.
23
+ */
24
+ export const shader_volumetrics_bin_participating_media: ComputeShader;
25
+ import { ComputeShader } from "../shader/ComputeShader.js";
26
+ //# sourceMappingURL=shader_volumetrics_bin_participating_media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shader_volumetrics_bin_participating_media.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.js"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,0CAFU,MAAM,CAE8B;AAY9C;;;;;;;;;;;;;;;GAeG;AACH,uEAgEG;8BA3G2B,4BAA4B"}
@@ -0,0 +1,110 @@
1
+ import { CAMERA_UNIFORM_STRUCT } from "../camera/CAMERA_UNIFORM_STRUCT.js";
2
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
3
+ import { ComputeShader } from "../shader/ComputeShader.js";
4
+ import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
5
+ import { atomic_u32 } from "../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
6
+ import { WebGPUArray } from "../shader/type/WebGPUArray.js";
7
+ import { chunk_participating_media_volumes_iterate } from "./chunk_participating_media_volumes_iterate.js";
8
+ import { chunk_volumetrics_media_group_list } from "./chunk_volumetrics_media_group_list.js";
9
+ import { VOLUMETRICS_MEDIA_VOLUME_LIST } from "./shader_volumetrics_split_participating_media.js";
10
+ import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
11
+
12
+ /**
13
+ * Lanes of a bin workgroup. Small on purpose: a lane tests one volume at a time, so at a handful of
14
+ * volumes most lanes of any group idle, and the fewer there are the less the bin launches for
15
+ * nothing.
16
+ * @type {number}
17
+ */
18
+ export const VOLUMETRICS_MEDIA_BIN_LANES = 64;
19
+
20
+ const resources = new ShaderResourceSetDescriptor();
21
+
22
+ resources.createGroup()
23
+ .addUniform("camera", CAMERA_UNIFORM_STRUCT)
24
+ .addUniform("volumetrics_metadata", VOLUMETRICS_METADATA_STRUCT)
25
+ .addStorageBuffer("scene_database", WebGPUArray.u32)
26
+ .addStorageBuffer("near_field", VOLUMETRICS_MEDIA_VOLUME_LIST)
27
+ .addStorageBuffer("group_lists", WebGPUArray.from(atomic_u32), true)
28
+ ;
29
+
30
+ /**
31
+ * Pre-culls the scene's participating-media volumes for each workgroup of the participating-media
32
+ * build: one workgroup per build group, which tests every volume of the near field (see
33
+ * {@link shader_volumetrics_split_participating_media}) against the group's slab of froxels and
34
+ * sets the bit of each one it keeps in that group's list (see {@link chunk_volumetrics_media_group_list}).
35
+ * The lists start zeroed; the graph clears them.
36
+ *
37
+ * The slab is the union of every froxel of the group as the build slices it, widened so that
38
+ * rounding can never make the group stricter than a froxel. In xy that is a quarter of a froxel
39
+ * past the group's columns and rows. In z it is the column jitter, which moves a froxel's slab by
40
+ * up to half a slice either way, and a quarter of a slice beyond that. A volume a froxel would
41
+ * cover therefore never misses its group's list, and the build reads, through the list, exactly
42
+ * what it read walking every volume.
43
+ *
44
+ * Budget: a lane tests ⌈near-field volumes / 64⌉ volumes. Nothing is past it.
45
+ */
46
+ export const shader_volumetrics_bin_participating_media = ComputeShader.from({
47
+ label: "Volumetrics / bin participating media",
48
+ resources,
49
+ body: CodeChunk.from(
50
+ //language=WGSL
51
+ `
52
+ @compute @workgroup_size(${VOLUMETRICS_MEDIA_BIN_LANES})
53
+ fn main(
54
+ @builtin(workgroup_id) group: vec3<u32>,
55
+ @builtin(local_invocation_index) lane: u32,
56
+ ) {
57
+ let resolution = volumetrics_metadata.resolution;
58
+ let groups = volumetrics_media_groups(resolution);
59
+
60
+ if (any(group >= groups)) {
61
+ return;
62
+ }
63
+
64
+ let volume_count = participating_media_volumes_count(&scene_database);
65
+ let list_words = volumetrics_media_list_words(volume_count, arrayLength(&group_lists), groups);
66
+ let listed_count = min(volume_count, list_words * 32u);
67
+ let near_count = min(near_field.count, arrayLength(&near_field.indices));
68
+
69
+ if (lane >= near_count) {
70
+ return;
71
+ }
72
+
73
+ let near_far = camera.clip_near_far;
74
+
75
+ let column_0 = group.xy * VOLUMETRICS_MEDIA_GROUP_XY;
76
+ let column_1 = min(column_0 + VOLUMETRICS_MEDIA_GROUP_XY, resolution.xy);
77
+
78
+ let slab = volumetrics_froxel_slab(
79
+ volumetrics_froxel_planes(volumetrics_metadata.frustum),
80
+ (vec2<f32>(column_0) - 0.25) / vec2<f32>(resolution.xy),
81
+ (vec2<f32>(column_1) + 0.25) / vec2<f32>(resolution.xy),
82
+ f32(group.z) - 0.75,
83
+ f32(group.z + 1u) + 0.75,
84
+ near_far,
85
+ volumetrics_metadata.froxel_cluster_parameters,
86
+ f32(resolution.z),
87
+ );
88
+
89
+ let list = volumetrics_media_list_offset(group, groups, list_words);
90
+
91
+ for (var i = lane; i < near_count; i += ${VOLUMETRICS_MEDIA_BIN_LANES}u) {
92
+ let v = near_field.indices[i];
93
+
94
+ if (v >= listed_count) {
95
+ continue;
96
+ }
97
+
98
+ let volume = participating_media_volume_read(&scene_database, v);
99
+
100
+ if (volumetrics_volume_in_slab(slab, volume)) {
101
+ atomicOr(&group_lists[list + (v >> 5u)], 1u << (v & 31u));
102
+ }
103
+ }
104
+ }
105
+ `, [
106
+ chunk_participating_media_volumes_iterate,
107
+ chunk_volumetrics_media_group_list,
108
+ ]
109
+ ),
110
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"shader_volumetrics_build_participating_media.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js"],"names":[],"mappings":"AAsPA,yEAIE;8BAnO4B,4BAA4B"}
1
+ {"version":3,"file":"shader_volumetrics_build_participating_media.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js"],"names":[],"mappings":"AA8QA,yEAIE;8BA3P4B,4BAA4B"}
@@ -28,6 +28,10 @@ import { chunk_cluster_depth_from_z_slice } from "../light/cluster/chunk_cluster
28
28
  import { chunk_compute_volume_fade_factor } from "./chunk_compute_volume_fade_factor.js";
29
29
  import { chunk_frustum_unit_box_coverage_multisample } from "./chunk_frustum_unit_box_coverage_multisample.js";
30
30
  import { chunk_volumetrics_column_jitter } from "./chunk_volumetrics_column_jitter.js";
31
+ import {
32
+ chunk_volumetrics_media_group_list,
33
+ VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION
34
+ } from "./chunk_volumetrics_media_group_list.js";
31
35
  import { chunk_participating_media_volumes_iterate } from "./chunk_participating_media_volumes_iterate.js";
32
36
  import { chunk_participating_media_noise } from "./noise/chunk_participating_media_noise.js";
33
37
  import { PARTICIPATING_MEDIA_NOISE_SAMPLER_DESCRIPTOR } from "./noise/PARTICIPATING_MEDIA_NOISE.js";
@@ -47,12 +51,13 @@ wind_field_add_resources(resources.createGroup()
47
51
  .addTexture("tParticipatingMediaNoise", "float", "3d")
48
52
  .addSampler("sParticipatingMediaNoise", "filtering", PARTICIPATING_MEDIA_NOISE_SAMPLER_DESCRIPTOR)
49
53
  .addStorageTexture("tOutput0", "rgba16float", "3d", "write-only")
50
- .addStorageTexture("tOutput1", "rgba16float", "3d", "write-only"));
54
+ .addStorageTexture("tOutput1", "rgba16float", "3d", "write-only")
55
+ .addStorageBuffer("group_lists", WebGPUArray.u32));
51
56
 
52
57
  const body = CodeChunk.from(
53
58
  //language=WGSL
54
59
  `
55
- @compute @workgroup_size(16,16)
60
+ @compute @workgroup_size(${VOLUMETRICS_MEDIA_WORKGROUP_RESOLUTION.join(",")})
56
61
  fn main(
57
62
  @builtin(global_invocation_id) voxel_coord : vec3<u32>
58
63
  ){
@@ -86,22 +91,17 @@ fn main(
86
91
  let z1_norm = (z1 - near_far.x) / (near_far.y - near_far.x);
87
92
 
88
93
  // Frustum planes are [LEFT, RIGHT, BOTTOM, TOP, NEAR, FAR] (see Camera.construct_frustum_planes).
89
- // Only the Y plane pair needs flipping: screen-Y is top-down but world-Y is bottom-up (the
90
- // visible "above/below" swap). X already matches (screen-left -> LEFT plane). Z must NOT be
94
+ // Only the Y plane pair changes: screen-Y is top-down but world-Y is bottom-up (the visible
95
+ // "above/below" swap), so the pair trades places and keeps facing in, as the bin's slabs do.
96
+ // Negated as well, the pair faced out, and the box test below kept a volume only where it
97
+ // spanned the froxel's whole row. X already matches (screen-left -> LEFT plane). Z must NOT be
91
98
  // flipped: we slice the metadata frustum, which has a real FINITE far plane (rebuilt at the
92
99
  // logical far, since the camera's infinite projection zeroes its own far plane). With a real
93
100
  // far plane the near/far slab maps to z0_norm/z1_norm directly; swapping it inverts and
94
101
  // far-skews the slab depth (frustum_slice would place froxels near far - z*(far-near)),
95
102
  // which made fog density depend on camera.far and miss the actual volume.
96
- var input_planes = volumetrics_metadata.frustum;
97
- {
98
- var t = input_planes[2];
99
- input_planes[2] = -input_planes[3];
100
- input_planes[3] = -t;
101
- }
102
-
103
103
  let froxel_frustum = frustum_slice(
104
- input_planes,
104
+ volumetrics_froxel_planes(volumetrics_metadata.frustum),
105
105
  vec3(vec2f(voxel_coord.xy + 0u) / vec2f(resolution.xy), z0_norm),
106
106
  vec3(vec2f(voxel_coord.xy + 1u) / vec2f(resolution.xy), z1_norm),
107
107
  );
@@ -132,9 +132,32 @@ fn main(
132
132
  var noise_wind = wind_field.far_velocity;
133
133
  var noise_wind_read = false;
134
134
 
135
- let volume_count = participating_media_volumes_count(&scene_database);
135
+ // The volumes the bin kept for this froxel's workgroup, in row order: the order the walk over
136
+ // every volume had, so the sums below add up exactly as they did. The list is the group's, so
137
+ // every lane of a workgroup walks the same one.
138
+ let groups = volumetrics_media_groups(resolution);
139
+ let list_words = volumetrics_media_list_words(participating_media_volumes_count(&scene_database), arrayLength(&group_lists), groups);
140
+ let list = volumetrics_media_list_offset(vec3<u32>(voxel_coord.xy / VOLUMETRICS_MEDIA_GROUP_XY, voxel_coord.z), groups, list_words);
141
+
142
+ var bits = 0u;
143
+ var word = 0u;
144
+
145
+ // Budget: VOLUMETRICS_MEDIA_GROUP_VOLUME_BUDGET listed volumes, and a load for each of the
146
+ // list's words, ⌈volumes / 32⌉, an empty word costing the one load. Past the budget the group's
147
+ // highest-indexed volumes are dropped.
148
+ for (var listed = 0u; listed < VOLUMETRICS_MEDIA_GROUP_VOLUME_BUDGET; listed++) {
149
+ while (bits == 0u && word < list_words) {
150
+ bits = group_lists[list + word];
151
+ word++;
152
+ }
153
+
154
+ if (bits == 0u) {
155
+ break;
156
+ }
157
+
158
+ let v = (word - 1u) * 32u + countTrailingZeros(bits);
159
+ bits &= bits - 1u;
136
160
 
137
- for (var v = 0u; v < volume_count; v++) {
138
161
  let volume = participating_media_volume_read(&scene_database, v);
139
162
 
140
163
  // Transform froxel into volume's coordinate space. Points go world -> local through
@@ -241,6 +264,7 @@ fn main(
241
264
  chunk_stbn_sample_vec3,
242
265
  chunk_wind_field_sample,
243
266
  chunk_participating_media_noise,
267
+ chunk_volumetrics_media_group_list,
244
268
  ]
245
269
  )
246
270
 
@@ -1 +1 @@
1
- {"version":3,"file":"shader_volumetrics_build_scattering_lut.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js"],"names":[],"mappings":"AA4QA,oEAIG;8BAvQ2B,4BAA4B"}
1
+ {"version":3,"file":"shader_volumetrics_build_scattering_lut.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js"],"names":[],"mappings":"AAiRA,oEAIG;8BA5Q2B,4BAA4B"}
@@ -21,6 +21,7 @@ import { chunk_sky_lut_compute_uv } from "../atmosphere/chunk_sky_lut_compute_uv
21
21
  import { chunk_calculate_multiscattering } from "./chunk_calculate_multiscattering.js";
22
22
  import { chunk_camera_ray_planar_depth_scale } from "./chunk_camera_ray_planar_depth_scale.js";
23
23
  import { chunk_participating_media_volumes_iterate } from "./chunk_participating_media_volumes_iterate.js";
24
+ import { VOLUMETRICS_MEDIA_VOLUME_LIST } from "./shader_volumetrics_split_participating_media.js";
24
25
 
25
26
  const resources = new ShaderResourceSetDescriptor();
26
27
 
@@ -35,6 +36,8 @@ resources.createGroup()
35
36
  // RDR2-style far field: beyond froxel_far the pass ray-marches the scene volumes directly,
36
37
  // lit by the directional sun + atmosphere (single scattering, no self-shadow).
37
38
  .addStorageBuffer("scene_database", WebGPUArray.u32)
39
+ // the volumes any part of which lies beyond froxel_far, as the split lists them
40
+ .addStorageBuffer("far_field", VOLUMETRICS_MEDIA_VOLUME_LIST)
38
41
  .addStorageBuffer("light_data", WebGPUArray.u32)
39
42
  .addTexture("tSunTransmittance", "float", "2d")
40
43
  .addSampler("sLinear")
@@ -56,17 +59,19 @@ struct FarVolumeSample {
56
59
  }
57
60
 
58
61
  // Point-sample the scene participating-media volumes (coarse: full density inside the box, no edge
59
- // fade — that is a near-field nicety). Used only beyond the froxel grid.
62
+ // fade — that is a near-field nicety). Used only beyond the froxel grid, so only the far field's
63
+ // volumes are walked. The split lists them in no particular order, so the sums below round
64
+ // differently from frame to frame; the volumetric TAA and the final image's TAA absorb that.
60
65
  fn far_sample_volumes(world_pos: vec3<f32>) -> FarVolumeSample {
61
66
  var scattering = vec3<f32>(0.0);
62
67
  var extinction = vec3<f32>(0.0);
63
68
  var g_weighted = 0.0;
64
69
  var scattering_luma = 0.0;
65
70
 
66
- let volume_count = participating_media_volumes_count(&scene_database);
71
+ let far_count = min(far_field.count, arrayLength(&far_field.indices));
67
72
 
68
- for (var v = 0u; v < volume_count; v++) {
69
- let volume = participating_media_volume_read(&scene_database, v);
73
+ for (var i = 0u; i < far_count; i++) {
74
+ let volume = participating_media_volume_read(&scene_database, far_field.indices[i]);
70
75
  let local = (volume.transform_inverse * vec4<f32>(world_pos, 1.0)).xyz;
71
76
  if (any(abs(local) > vec3<f32>(0.5))) {
72
77
  continue;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Some of the scene's participating-media volumes, as their rows in the scene database: `indices[0,
3
+ * count)`, in no particular order.
4
+ */
5
+ export const VOLUMETRICS_MEDIA_VOLUME_LIST: WebGPUStruct;
6
+ /**
7
+ * Threads of a split workgroup, each taking one volume.
8
+ * @type {number}
9
+ */
10
+ export const VOLUMETRICS_MEDIA_SPLIT_LANES: number;
11
+ /**
12
+ * Splits the scene's participating-media volumes between the near field, the froxel grid's depths
13
+ * up to `froxel_far`, and the far field beyond it, where the aerial pass reads the volumes
14
+ * themselves. One thread a volume. A volume outside any of the grid's four side planes or its near
15
+ * plane goes to neither. Of the rest, a volume any part of which is nearer than `froxel_far` goes to
16
+ * the near field, and one any part of which is beyond goes to the far field, so a volume across
17
+ * `froxel_far` goes to both.
18
+ *
19
+ * The side planes are the grid's, widened a quarter froxel as the bin widens its groups'. The near
20
+ * plane is the one the bin's first groups start at. With `froxel_far` at its default, the camera's
21
+ * far, the discriminator is where the bin's last groups end, so any volume a group of the bin
22
+ * would keep is in the near field, and the bin reads only the near field. With `froxel_far` pulled
23
+ * nearer, the grid's last slice still reaches the camera's far. A volume wholly between the two then
24
+ * belongs to the far field alone and stops reaching that slice.
25
+ *
26
+ * The lists start at a count of zero; the graph clears them. Budget: one volume a thread.
27
+ */
28
+ export const shader_volumetrics_split_participating_media: ComputeShader;
29
+ import { WebGPUStruct } from "../shader/type/WebGPUStruct.js";
30
+ import { ComputeShader } from "../shader/ComputeShader.js";
31
+ //# sourceMappingURL=shader_volumetrics_split_participating_media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shader_volumetrics_split_participating_media.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.js"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,yDAGiC;AAUjC;;;GAGG;AACH,4CAFU,MAAM,CAEgC;AAYhD;;;;;;;;;;;;;;;;GAgBG;AACH,yEAwEG;6BA/H0B,gCAAgC;8BAJ/B,4BAA4B"}