@woosh/meep-engine 3.10.0 → 3.11.1

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 (164) hide show
  1. package/README.md +168 -152
  2. package/package.json +1 -1
  3. package/src/REVIEW_2026_08_06.md +610 -610
  4. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  5. package/src/core/geom/3d/shape/ConvexHullShape3D.js +28 -4
  6. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts +26 -0
  7. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts.map +1 -1
  8. package/src/engine/graphics/render/frame_graph/FrameGraph.js +47 -0
  9. package/src/engine/physics/ecs/PhysicsSystem.d.ts +0 -3
  10. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  11. package/src/engine/physics/ecs/PhysicsSystem.js +0 -3
  12. package/src/shade/device/ShadeGPUCommandContext.d.ts +21 -0
  13. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  14. package/src/shade/device/ShadeGPUCommandContext.js +940 -749
  15. package/src/shade/device/timing/GPUTimerArray.d.ts +22 -4
  16. package/src/shade/device/timing/GPUTimerArray.d.ts.map +1 -1
  17. package/src/shade/device/timing/GPUTimerArray.js +88 -14
  18. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +349 -98
  19. package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts +93 -0
  20. package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts.map +1 -0
  21. package/src/shade/device/timing/profile/GPUFrameRecorder.js +297 -0
  22. package/src/shade/device/timing/profile/GPUProfileCapture.d.ts +62 -0
  23. package/src/shade/device/timing/profile/GPUProfileCapture.d.ts.map +1 -0
  24. package/src/shade/device/timing/profile/GPUProfileCapture.js +77 -0
  25. package/src/shade/device/timing/profile/GPUProfileFrame.d.ts +73 -0
  26. package/src/shade/device/timing/profile/GPUProfileFrame.d.ts.map +1 -0
  27. package/src/shade/device/timing/profile/GPUProfileFrame.js +88 -0
  28. package/src/shade/device/timing/profile/GPUProfileLevel.d.ts +20 -0
  29. package/src/shade/device/timing/profile/GPUProfileLevel.d.ts.map +1 -0
  30. package/src/shade/device/timing/profile/GPUProfileLevel.js +50 -0
  31. package/src/shade/device/timing/profile/GPUProfileMeta.d.ts +63 -0
  32. package/src/shade/device/timing/profile/GPUProfileMeta.d.ts.map +1 -0
  33. package/src/shade/device/timing/profile/GPUProfileMeta.js +73 -0
  34. package/src/shade/device/timing/profile/GPUProfileSession.d.ts +173 -0
  35. package/src/shade/device/timing/profile/GPUProfileSession.d.ts.map +1 -0
  36. package/src/shade/device/timing/profile/GPUProfileSession.js +470 -0
  37. package/src/shade/device/timing/profile/GPUProfileSpan.d.ts +60 -0
  38. package/src/shade/device/timing/profile/GPUProfileSpan.d.ts.map +1 -0
  39. package/src/shade/device/timing/profile/GPUProfileSpan.js +65 -0
  40. package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts +19 -0
  41. package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts.map +1 -0
  42. package/src/shade/device/timing/profile/GPUProfileSpanKind.js +25 -0
  43. package/src/shade/device/timing/profile/GPUProfileTopology.d.ts +159 -0
  44. package/src/shade/device/timing/profile/GPUProfileTopology.d.ts.map +1 -0
  45. package/src/shade/device/timing/profile/GPUProfileTopology.js +202 -0
  46. package/src/shade/device/timing/profile/GPUProfileWork.d.ts +90 -0
  47. package/src/shade/device/timing/profile/GPUProfileWork.d.ts.map +1 -0
  48. package/src/shade/device/timing/profile/GPUProfileWork.js +115 -0
  49. package/src/shade/device/timing/profile/SGPTDefect.d.ts +43 -0
  50. package/src/shade/device/timing/profile/SGPTDefect.d.ts.map +1 -0
  51. package/src/shade/device/timing/profile/SGPTDefect.js +65 -0
  52. package/src/shade/device/timing/profile/SGPTHeader.d.ts +38 -0
  53. package/src/shade/device/timing/profile/SGPTHeader.d.ts.map +1 -0
  54. package/src/shade/device/timing/profile/SGPTHeader.js +46 -0
  55. package/src/shade/device/timing/profile/SGPTRecordRef.d.ts +37 -0
  56. package/src/shade/device/timing/profile/SGPTRecordRef.d.ts.map +1 -0
  57. package/src/shade/device/timing/profile/SGPTRecordRef.js +45 -0
  58. package/src/shade/device/timing/profile/SGPTWriteStream.d.ts +103 -0
  59. package/src/shade/device/timing/profile/SGPTWriteStream.d.ts.map +1 -0
  60. package/src/shade/device/timing/profile/SGPTWriteStream.js +287 -0
  61. package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts +19 -0
  62. package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts.map +1 -0
  63. package/src/shade/device/timing/profile/SGPT_DEFECT.js +26 -0
  64. package/src/shade/device/timing/profile/SGPT_FLAG.d.ts +8 -0
  65. package/src/shade/device/timing/profile/SGPT_FLAG.d.ts.map +1 -0
  66. package/src/shade/device/timing/profile/SGPT_FLAG.js +15 -0
  67. package/src/shade/device/timing/profile/SGPT_FORMAT.md +345 -0
  68. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts +10 -0
  69. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts.map +1 -0
  70. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.js +9 -0
  71. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts +17 -0
  72. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts.map +1 -0
  73. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.js +16 -0
  74. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts +16 -0
  75. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts.map +1 -0
  76. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.js +15 -0
  77. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts +15 -0
  78. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts.map +1 -0
  79. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.js +29 -0
  80. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts +7 -0
  81. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts.map +1 -0
  82. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.js +6 -0
  83. package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts +7 -0
  84. package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts.map +1 -0
  85. package/src/shade/device/timing/profile/SGPT_MAGIC.js +6 -0
  86. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts +16 -0
  87. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts.map +1 -0
  88. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.js +15 -0
  89. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts +7 -0
  90. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts.map +1 -0
  91. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.js +6 -0
  92. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts +13 -0
  93. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts.map +1 -0
  94. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.js +23 -0
  95. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts +12 -0
  96. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts.map +1 -0
  97. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.js +11 -0
  98. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts +18 -0
  99. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts.map +1 -0
  100. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.js +46 -0
  101. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts +20 -0
  102. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -0
  103. package/src/shade/device/timing/profile/frame_graph_extract_topology.js +174 -0
  104. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts +21 -0
  105. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts.map +1 -0
  106. package/src/shade/device/timing/profile/make_profiling_pass_encoder.js +114 -0
  107. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts +20 -0
  108. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts.map +1 -0
  109. package/src/shade/device/timing/profile/parse_workgroup_size.js +43 -0
  110. package/src/shade/device/timing/profile/sgpt_fourcc.d.ts +18 -0
  111. package/src/shade/device/timing/profile/sgpt_fourcc.d.ts.map +1 -0
  112. package/src/shade/device/timing/profile/sgpt_fourcc.js +34 -0
  113. package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts +42 -0
  114. package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts.map +1 -0
  115. package/src/shade/device/timing/profile/sgpt_frame_codec.js +172 -0
  116. package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts +20 -0
  117. package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts.map +1 -0
  118. package/src/shade/device/timing/profile/sgpt_meta_codec.js +65 -0
  119. package/src/shade/device/timing/profile/sgpt_read_capture.d.ts +19 -0
  120. package/src/shade/device/timing/profile/sgpt_read_capture.d.ts.map +1 -0
  121. package/src/shade/device/timing/profile/sgpt_read_capture.js +134 -0
  122. package/src/shade/device/timing/profile/sgpt_read_header.d.ts +22 -0
  123. package/src/shade/device/timing/profile/sgpt_read_header.d.ts.map +1 -0
  124. package/src/shade/device/timing/profile/sgpt_read_header.js +94 -0
  125. package/src/shade/device/timing/profile/sgpt_read_records.d.ts +27 -0
  126. package/src/shade/device/timing/profile/sgpt_read_records.d.ts.map +1 -0
  127. package/src/shade/device/timing/profile/sgpt_read_records.js +153 -0
  128. package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts +54 -0
  129. package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts.map +1 -0
  130. package/src/shade/device/timing/profile/sgpt_topology_codec.js +329 -0
  131. package/src/shade/device/timing/profile/sgpt_write_header.d.ts +22 -0
  132. package/src/shade/device/timing/profile/sgpt_write_header.d.ts.map +1 -0
  133. package/src/shade/device/timing/profile/sgpt_write_header.js +47 -0
  134. package/src/shade/device/timing/profile/sgpt_write_record.d.ts +23 -0
  135. package/src/shade/device/timing/profile/sgpt_write_record.d.ts.map +1 -0
  136. package/src/shade/device/timing/profile/sgpt_write_record.js +62 -0
  137. package/src/shade/playground/volumetrics_froxel/README.md +88 -0
  138. package/src/shade/playground/volumetrics_froxel/index.html +251 -0
  139. package/src/shade/playground/volumetrics_froxel/main.d.ts +2 -0
  140. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -0
  141. package/src/shade/playground/volumetrics_froxel/main.js +619 -0
  142. package/src/shade/renderer/Renderer.d.ts +34 -0
  143. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  144. package/src/shade/renderer/Renderer.js +2536 -2455
  145. package/src/shade/renderer/volumetrics/NOTES.md +42 -1
  146. package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.d.ts +21 -0
  147. package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.d.ts.map +1 -0
  148. package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.js +47 -0
  149. package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.d.ts.map +1 -1
  150. package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.js +19 -8
  151. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.d.ts +37 -0
  152. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.d.ts.map +1 -0
  153. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.js +56 -0
  154. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  155. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +6 -0
  156. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  157. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +367 -324
  158. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.d.ts.map +1 -1
  159. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +14 -3
  160. package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.d.ts.map +1 -1
  161. package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.js +48 -36
  162. package/src/engine/save/GameStateLoader.d.ts +0 -49
  163. package/src/engine/save/GameStateLoader.d.ts.map +0 -1
  164. package/src/engine/save/GameStateLoader.js +0 -168
@@ -1,324 +1,367 @@
1
- import { vec3f } from "../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
- import { WebGPUArray } from "../shader/type/WebGPUArray.js";
3
- import { CAMERA_UNIFORM_STRUCT } from "../camera/CAMERA_UNIFORM_STRUCT.js";
4
- import { chunk_rgb_to_luminance } from "../shader/chunk/color/chunk_rgb_to_luminance.js";
5
- import { chunk_camera_ray_from_uv } from "../shader/chunk/geometry/ray/chunk_camera_ray_from_uv.js";
6
- import { RAY_STRUCT } from "../shader/chunk/geometry/ray/RAY_STRUCT.js";
7
- import {
8
- chunk_directional_lights_iteration_mask
9
- } from "../shader/chunk/light/io/chunk_directional_lights_iteration_mask.js";
10
- import {
11
- chunk_get_directional_light_info_by_index
12
- } from "../shader/chunk/light/io/chunk_get_directional_light_info_by_index.js";
13
- import {
14
- chunk_get_point_light_info_by_index
15
- } from "../shader/chunk/light/io/chunk_get_point_light_info_by_index.js";
16
- import { chunk_get_spot_light_info_by_index } from "../shader/chunk/light/io/chunk_get_spot_light_info_by_index.js";
17
- import { chunk_random } from "../shader/chunk/random/chunk_random.js";
18
- import { chunk_random_initialize } from "../shader/chunk/random/chunk_random_initialize.js";
19
- import { chunk_texel_coordinate_to_uv } from "../shader/chunk/texture/chunk_texel_coordinate_to_uv.js";
20
- import { chunk_uv_to_texel_coordinate } from "../shader/chunk/texture/chunk_uv_to_texel_coordinate.js";
21
- import { CodeChunk } from "../shader/compiler/CodeChunk.js";
22
- import { ComputeShader } from "../shader/ComputeShader.js";
23
- import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
24
- import { chunk_shadowmap_get_light_visibility } from "../shadow/map/shader/chunk_shadowmap_get_light_visibility.js";
25
- import { SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR } from "../shadow/map/SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR.js";
26
- import { SAMPLER_LINEAR_UPSCALE } from "../texture/SAMPLER_LINEAR_UPSCALE.js";
27
- import { VIEW_UNIFORM_STRUCT } from "../view/VIEW_UNIFORM_STRUCT.js";
28
- import { chunk_cluster_depth_from_z_slice } from "../light/cluster/chunk_cluster_depth_from_z_slice.js";
29
- import { chunk_cluster_depth_to_z_slice } from "../light/cluster/chunk_cluster_depth_to_z_slice.js";
30
- import { chunk_light_cluster_counts } from "../light/cluster/chunk_light_cluster_counts.js";
31
- import { chunk_light_cluster_metadata_by_position } from "../light/cluster/chunk_light_cluster_metadata_by_position.js";
32
- import { LIGHT_CLUSTER_LOOKUP_STRUCT } from "../light/cluster/shader_cluster_assign_lights.js";
33
- import { chunk_sky_lut_compute_uv } from "../atmosphere/chunk_sky_lut_compute_uv.js";
34
- import { chunk_calculate_multiscattering } from "./chunk_calculate_multiscattering.js";
35
- import { chunk_integrate_optical_depth } from "./chunk_integrate_optical_depth.js";
36
- import { chunk_volumetrics_column_jitter } from "./chunk_volumetrics_column_jitter.js";
37
- import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
38
-
39
- const resources = new ShaderResourceSetDescriptor();
40
-
41
- resources.createGroup()
42
- .addUniform("volumetrics_metadata", VOLUMETRICS_METADATA_STRUCT)
43
- .addUniform("camera", CAMERA_UNIFORM_STRUCT)
44
- .addUniform("view", VIEW_UNIFORM_STRUCT)
45
- .addTexture("tSunTransmittance", "float", "2d")
46
- .addTexture("tMultiScattering", "float", "2d")
47
- .addSampler("sLinear", "filtering", SAMPLER_LINEAR_UPSCALE)
48
- .addTexture("tParticipatingMedia0", "float", "3d")
49
- .addTexture("tParticipatingMedia1", "float", "3d")
50
- .addStorageTexture("tLighting", "rgba16float", "3d", "write-only")
51
-
52
- // Lighting information
53
- resources.createGroup()
54
-
55
- .addStorageBuffer("light_data", WebGPUArray.u32)
56
- // clusters
57
- .addUniform("cluster_parameters", vec3f)
58
- .addStorageBuffer("cluster_lookup", LIGHT_CLUSTER_LOOKUP_STRUCT)
59
- .addStorageBuffer("cluster_data", WebGPUArray.u32)
60
- // shadows
61
- .addTexture("shadowmapAtlas", "depth")
62
- .addSampler("sCompareLinear", "comparison", SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR)
63
-
64
- const body = CodeChunk.from(
65
- //language=WGSL
66
- `
67
- @compute @workgroup_size(16,16)
68
- fn main(
69
- @builtin(global_invocation_id) voxel_coord : vec3<u32>
70
- ){
71
- let resolution = textureDimensions(tParticipatingMedia0);
72
-
73
- if(any(voxel_coord >= resolution)){
74
- // overflow
75
- return;
76
- }
77
-
78
- random_initialize(voxel_coord, vec3(view.frame_index, 0x9E3779B9, 0xBABECAFE));
79
-
80
- // Logical near/far, NOT the projection-derived range: the projection is infinite-far
81
- // reverse-Z, so its far resolves to ~3.4e38 (FLOAT32_MAX). That value would leave the last
82
- // froxel slice's z1 (and z1*z1*z1 below) at +Inf. clip_near_far is the bounded logical range,
83
- // matching the participating-media build shader.
84
- let near_far = camera.clip_near_far;
85
-
86
- // same jitter valud for all voxels in Z column
87
- // must be the same for PM and Lights
88
- let z_jitter = volumetrics_column_jitter(voxel_coord.xy, view.frame_index);
89
-
90
- let z_slice_0 = f32(voxel_coord.z ) + z_jitter;
91
- let z_slice_1 = f32(voxel_coord.z + 1) + z_jitter;
92
-
93
- let z0 = max(near_far.x, cluster_depth_from_z_slice( z_slice_0, volumetrics_metadata.froxel_cluster_parameters, f32(resolution.z) ));
94
- let z1 = min(near_far.y, cluster_depth_from_z_slice( z_slice_1, volumetrics_metadata.froxel_cluster_parameters, f32(resolution.z) ));
95
-
96
- // 2. Cubic mix gives uniform distribution in World Space Volume
97
- let z3_min = z0 * z0 * z0;
98
- let z3_max = z1 * z1 * z1;
99
-
100
- var inScatteringSum = vec3<f32>(0.0);
101
- var extinctionSum = 0.0;
102
-
103
-
104
- const SAMPLE_COUNT = 6;
105
-
106
- // integrate multiple samples inside the voxel, to reduce noise.
107
-
108
- for(var i = 0; i < SAMPLE_COUNT; i++){
109
-
110
- let jitter = vec3(
111
- random(),
112
- random(),
113
- random(),
114
- ); // [0..1] offset (jitter - 0.5) must be centered on the froxel for every axis
115
-
116
-
117
- // Interpolate the cubes, then take the cube root
118
- let z3_sample = mix(z3_min, z3_max, jitter.z);
119
- let view_depth = pow(z3_sample, 0.333333);
120
-
121
- let sample_z_index:f32 = cluster_depth_to_z_slice(
122
- view_depth,
123
- volumetrics_metadata.froxel_cluster_parameters,
124
- f32(resolution.z)
125
- );
126
-
127
- // 'saturate' prevents accidental out-of-bounds samples
128
- let tile_uv = saturate(
129
- texel_coordinate_to_uv(vec2f(voxel_coord.xy) + (jitter.xy - 0.5), resolution.xy)
130
- );
131
- let view_ray = camera_ray_from_uv(tile_uv, camera);
132
-
133
- let uvw = vec3<f32>(tile_uv, (sample_z_index + 0.5) / f32(resolution.z));
134
-
135
- let participating_media0 = textureSampleLevel(tParticipatingMedia0, sLinear, uvw, 0.0);
136
- let participating_media1 = textureSampleLevel(tParticipatingMedia1, sLinear, uvw, 0.0);
137
-
138
- let participating_media_scattering = participating_media0.rgb;
139
- let participating_media_extinction = participating_media1.rgb;
140
- let participating_media_phase_g = participating_media1.a;
141
-
142
- // Accumulate the (linear) mono extinction coefficient. Carried in tLighting.a below so the
143
- // 3D TAA accumulates sigma_t alongside the in-scatter (Hillaire: temporally integrate the
144
- // LINEAR extinction, never the non-linear transmittance).
145
- extinctionSum += rgb_to_luminance(participating_media_extinction);
146
-
147
- let view_pixel_coord = uv_to_texel_coordinate(tile_uv, vec2(view.width, view.height));
148
-
149
- let position_ws = view_ray.origin + view_ray.direction * view_depth;
150
-
151
- // Global lights (directional) — iterate over the first 32 slots
152
- // of the directional light table (max enforced by GPULightCollection).
153
- var directional_bits = directional_lights_iteration_mask(&light_data);
154
- while (directional_bits != 0u) {
155
- let directional_index = countTrailingZeros(directional_bits);
156
- directional_bits &= ~(1u << directional_index);
157
-
158
- let sun = get_directional_light_info_by_index(&light_data, directional_index);
159
-
160
- let sun_lut_uv = sky_lut_compute_uv(position_ws, sun.direction);
161
-
162
- let shadow_term = shadowmap_get_directional_light_visibility(
163
- &light_data,
164
- directional_index,
165
- position_ws,
166
- // toward-camera, matching the deferred cascade-blend convention. The consumer
167
- // (shadowmap_csm_compute_cascade_blended) negates this internally; the deferred
168
- // caller passes the surface->camera vector, whereas view_ray.direction is
169
- // camera->scene, so it must be negated here to agree.
170
- -view_ray.direction,
171
- sun.direction
172
- );
173
-
174
- let u = dot(sun.direction, view_ray.direction);
175
-
176
- // sample atmospheric transmittance
177
- let transmittance_atmospheric = textureSampleLevel(
178
- tSunTransmittance,
179
- sLinear,
180
- sun_lut_uv, 0.0
181
- ).rgb;
182
-
183
- // .direction points TOWARD the light; march the optical-depth ray toward the light (+direction).
184
- let shadow_ray = ${RAY_STRUCT.make({
185
- origin: "position_ws",
186
- direction: "sun.direction",
187
- tmax: "sun.distance",
188
- })};
189
-
190
- let optical_depth = integrate_optical_depth(shadow_ray);
191
-
192
- let ms = calculate_multiscattering(
193
- optical_depth,
194
- participating_media_scattering,
195
- participating_media_extinction,
196
- u,
197
- participating_media_phase_g,
198
- volumetrics_metadata.ms_octave_count,
199
- volumetrics_metadata.ms_extinction_decay
200
- );
201
-
202
- inScatteringSum += shadow_term * ms * transmittance_atmospheric * sun.color;
203
- }
204
-
205
- // Local lights
206
- let cluster_metadata = light_cluster_metadata_by_position(
207
- view_pixel_coord,
208
- view_depth,
209
- vec2( view.width, view.height )
210
- );
211
-
212
- let cluster_offset = cluster_metadata.offset;
213
- let point_count = light_cluster_count_point(cluster_metadata.counts);
214
- let spot_count = light_cluster_count_spot(cluster_metadata.counts);
215
-
216
- for (var j = 0u; j < point_count; j++) {
217
- let light_index = cluster_data[cluster_offset + j];
218
- var direct_light = get_point_light_info_by_index(&light_data, light_index, position_ws);
219
-
220
- if (all(direct_light.color == vec3(0.0))) {
221
- continue;
222
- }
223
-
224
- let u = dot(direct_light.direction, view_ray.direction);
225
-
226
- // .direction points TOWARD the light; march the optical-depth ray toward the light (+direction).
227
- let shadow_ray = ${RAY_STRUCT.make({
228
- origin: "position_ws",
229
- direction: "direct_light.direction",
230
- tmax: "direct_light.distance",
231
- })};
232
-
233
- let optical_depth = integrate_optical_depth(shadow_ray);
234
-
235
- let ms = calculate_multiscattering(
236
- optical_depth,
237
- participating_media_scattering,
238
- participating_media_extinction,
239
- u,
240
- participating_media_phase_g,
241
- volumetrics_metadata.ms_octave_count,
242
- volumetrics_metadata.ms_extinction_decay
243
- );
244
-
245
- inScatteringSum += ms * direct_light.color;
246
- }
247
-
248
- for (var k = 0u; k < spot_count; k++) {
249
- let light_index = cluster_data[cluster_offset + point_count + k];
250
- var direct_light = get_spot_light_info_by_index(&light_data, light_index, position_ws);
251
-
252
- if (all(direct_light.color == vec3(0.0))) {
253
- continue;
254
- }
255
-
256
- let u = dot(direct_light.direction, view_ray.direction);
257
-
258
- // .direction points TOWARD the light; march the optical-depth ray toward the light (+direction).
259
- let shadow_ray = ${RAY_STRUCT.make({
260
- origin: "position_ws",
261
- direction: "direct_light.direction",
262
- tmax: "direct_light.distance",
263
- })};
264
-
265
- let optical_depth = integrate_optical_depth(shadow_ray);
266
-
267
- let ms = calculate_multiscattering(
268
- optical_depth,
269
- participating_media_scattering,
270
- participating_media_extinction,
271
- u,
272
- participating_media_phase_g,
273
- volumetrics_metadata.ms_octave_count,
274
- volumetrics_metadata.ms_extinction_decay
275
- );
276
-
277
- inScatteringSum += ms * direct_light.color;
278
- }
279
- }
280
-
281
-
282
- var inScattering = inScatteringSum / SAMPLE_COUNT;
283
-
284
- // .a carries the mono extinction coefficient so the aerial pass can read a temporally-stable
285
- // sigma_t (after the 3D TAA) instead of re-sampling the raw per-frame-jittered PM extinction.
286
- // The composited fog transmittance is already mono (rgb_to_luminance of the integrated
287
- // throughput), so collapsing extinction to luminance only drops the subtle chromatic
288
- // depth-attenuation of the in-scatter.
289
- let extinction = extinctionSum / SAMPLE_COUNT;
290
-
291
- textureStore(tLighting, voxel_coord, vec4<f32>(inScattering, extinction));
292
- }
293
- `, [
294
- chunk_cluster_depth_from_z_slice,
295
- chunk_cluster_depth_to_z_slice,
296
- chunk_texel_coordinate_to_uv,
297
- chunk_camera_ray_from_uv,
298
- chunk_uv_to_texel_coordinate,
299
-
300
- chunk_random_initialize,
301
- chunk_random,
302
-
303
- chunk_light_cluster_metadata_by_position,
304
- chunk_light_cluster_counts,
305
- chunk_directional_lights_iteration_mask,
306
- chunk_get_directional_light_info_by_index,
307
- chunk_get_point_light_info_by_index,
308
- chunk_get_spot_light_info_by_index,
309
- chunk_shadowmap_get_light_visibility,
310
- chunk_sky_lut_compute_uv,
311
-
312
- RAY_STRUCT.declaration_chunk,
313
- chunk_integrate_optical_depth,
314
- chunk_calculate_multiscattering,
315
- chunk_rgb_to_luminance,
316
- chunk_volumetrics_column_jitter,
317
- ]
318
- )
319
-
320
- export const shader_volumetrics_build_lighting = ComputeShader.from({
321
- label: "Volumetrics / build lighting",
322
- resources,
323
- body
324
- })
1
+ import { vec3f } from "../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
+ import { WebGPUArray } from "../shader/type/WebGPUArray.js";
3
+ import { CAMERA_UNIFORM_STRUCT } from "../camera/CAMERA_UNIFORM_STRUCT.js";
4
+ import { chunk_rgb_to_luminance } from "../shader/chunk/color/chunk_rgb_to_luminance.js";
5
+ import { chunk_stbn_sample_vec3 } from "../shader/chunk/noise/chunk_stbn_sample_vec3.js";
6
+ import { chunk_camera_ray_from_uv } from "../shader/chunk/geometry/ray/chunk_camera_ray_from_uv.js";
7
+ import { RAY_STRUCT } from "../shader/chunk/geometry/ray/RAY_STRUCT.js";
8
+ import {
9
+ chunk_directional_lights_iteration_mask
10
+ } from "../shader/chunk/light/io/chunk_directional_lights_iteration_mask.js";
11
+ import {
12
+ chunk_get_directional_light_info_by_index
13
+ } from "../shader/chunk/light/io/chunk_get_directional_light_info_by_index.js";
14
+ import {
15
+ chunk_get_point_light_info_by_index
16
+ } from "../shader/chunk/light/io/chunk_get_point_light_info_by_index.js";
17
+ import { chunk_get_spot_light_info_by_index } from "../shader/chunk/light/io/chunk_get_spot_light_info_by_index.js";
18
+ import { chunk_texel_coordinate_to_uv } from "../shader/chunk/texture/chunk_texel_coordinate_to_uv.js";
19
+ import { chunk_uv_to_texel_coordinate } from "../shader/chunk/texture/chunk_uv_to_texel_coordinate.js";
20
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
21
+ import { ComputeShader } from "../shader/ComputeShader.js";
22
+ import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
23
+ import { chunk_shadowmap_get_light_visibility } from "../shadow/map/shader/chunk_shadowmap_get_light_visibility.js";
24
+ import { SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR } from "../shadow/map/SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR.js";
25
+ import { SAMPLER_LINEAR_UPSCALE } from "../texture/SAMPLER_LINEAR_UPSCALE.js";
26
+ import { VIEW_UNIFORM_STRUCT } from "../view/VIEW_UNIFORM_STRUCT.js";
27
+ import { chunk_cluster_depth_from_z_slice } from "../light/cluster/chunk_cluster_depth_from_z_slice.js";
28
+ import { chunk_cluster_depth_to_z_slice } from "../light/cluster/chunk_cluster_depth_to_z_slice.js";
29
+ import { chunk_light_cluster_counts } from "../light/cluster/chunk_light_cluster_counts.js";
30
+ import { chunk_light_cluster_metadata_by_position } from "../light/cluster/chunk_light_cluster_metadata_by_position.js";
31
+ import { LIGHT_CLUSTER_LOOKUP_STRUCT } from "../light/cluster/shader_cluster_assign_lights.js";
32
+ import { chunk_sky_lut_compute_uv } from "../atmosphere/chunk_sky_lut_compute_uv.js";
33
+ import { chunk_calculate_multiscattering } from "./chunk_calculate_multiscattering.js";
34
+ import { chunk_camera_ray_planar_depth_scale } from "./chunk_camera_ray_planar_depth_scale.js";
35
+ import { chunk_volumetrics_froxel_content_offset } from "./chunk_volumetrics_froxel_content_offset.js";
36
+ import { chunk_integrate_optical_depth } from "./chunk_integrate_optical_depth.js";
37
+ import { chunk_volumetrics_column_jitter } from "./chunk_volumetrics_column_jitter.js";
38
+ import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
39
+
40
+ const resources = new ShaderResourceSetDescriptor();
41
+
42
+ resources.createGroup()
43
+ .addUniform("volumetrics_metadata", VOLUMETRICS_METADATA_STRUCT)
44
+ .addUniform("camera", CAMERA_UNIFORM_STRUCT)
45
+ .addUniform("view", VIEW_UNIFORM_STRUCT)
46
+ .addTexture("tSunTransmittance", "float", "2d")
47
+ .addTexture("tMultiScattering", "float", "2d")
48
+ .addSampler("sLinear", "filtering", SAMPLER_LINEAR_UPSCALE)
49
+ .addTexture("tSTBN_vec3", "unfilterable-float", "3d")
50
+ .addTexture("tParticipatingMedia0", "float", "3d")
51
+ .addTexture("tParticipatingMedia1", "float", "3d")
52
+ .addStorageTexture("tLighting", "rgba16float", "3d", "write-only")
53
+
54
+ // Lighting information
55
+ resources.createGroup()
56
+
57
+ .addStorageBuffer("light_data", WebGPUArray.u32)
58
+ // clusters
59
+ .addUniform("cluster_parameters", vec3f)
60
+ .addStorageBuffer("cluster_lookup", LIGHT_CLUSTER_LOOKUP_STRUCT)
61
+ .addStorageBuffer("cluster_data", WebGPUArray.u32)
62
+ // shadows
63
+ .addTexture("shadowmapAtlas", "depth")
64
+ .addSampler("sCompareLinear", "comparison", SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR)
65
+
66
+ const body = CodeChunk.from(
67
+ //language=WGSL
68
+ `
69
+ @compute @workgroup_size(16,16)
70
+ fn main(
71
+ @builtin(global_invocation_id) voxel_coord : vec3<u32>
72
+ ){
73
+ let resolution = textureDimensions(tParticipatingMedia0);
74
+
75
+ if(any(voxel_coord >= resolution)){
76
+ // overflow
77
+ return;
78
+ }
79
+
80
+ // Logical near/far, NOT the projection-derived range: the projection is infinite-far
81
+ // reverse-Z, so its far resolves to ~3.4e38 (FLOAT32_MAX). That value would leave the last
82
+ // froxel slice's z1 (and z1*z1*z1 below) at +Inf. clip_near_far is the bounded logical range,
83
+ // matching the participating-media build shader.
84
+ let near_far = camera.clip_near_far;
85
+
86
+ // same jitter valud for all voxels in Z column
87
+ // must be the same for PM and Lights
88
+ let z_jitter = volumetrics_column_jitter(voxel_coord.xy, view.frame_index);
89
+
90
+ let z_slice_0 = f32(voxel_coord.z ) + z_jitter;
91
+ let z_slice_1 = f32(voxel_coord.z + 1) + z_jitter;
92
+
93
+ let z0 = max(near_far.x, cluster_depth_from_z_slice( z_slice_0, volumetrics_metadata.froxel_cluster_parameters, f32(resolution.z) ));
94
+ let z1 = min(near_far.y, cluster_depth_from_z_slice( z_slice_1, volumetrics_metadata.froxel_cluster_parameters, f32(resolution.z) ));
95
+
96
+ // 2. Cubic mix gives uniform distribution in World Space Volume
97
+ let z3_min = z0 * z0 * z0;
98
+ let z3_max = z1 * z1 * z1;
99
+
100
+ var inScatteringSum = vec3<f32>(0.0);
101
+ var extinctionSum = 0.0;
102
+
103
+
104
+ // One sample per froxel. The 0.95-blend TAA downstream is a ~39-effective-sample temporal
105
+ // integrator; supersampling here as well spends N times this pass removing variance that filter
106
+ // removes anyway and spends it at the Monte Carlo rate, because the samples were independent
107
+ // white noise and converged as 1/sqrt(N). Raising this is only worth it with the temporal
108
+ // filter off.
109
+ const SAMPLE_COUNT = 1u;
110
+
111
+ for(var i = 0u; i < SAMPLE_COUNT; i++){
112
+
113
+ // Spatiotemporal blue noise rather than a per-froxel hash. What lets 1 spp survive a
114
+ // temporal filter is each froxel seeing a well-distributed sequence over frames, which is
115
+ // STBN's defining property and exactly what white noise lacks — measured against the murmur
116
+ // hash it reaches the same error in roughly half the frames, across the whole window the
117
+ // 0.95 blend spans.
118
+ //
119
+ // The xy offset is load-bearing, not decoration. The aerial pass reads this same volume at
120
+ // (voxel_coord.xy, frame_index) and consumes all three channels, so sampling it there would
121
+ // not merely correlate the two passes' jitters — it would make them the same number. Two
122
+ // nuisance variables locked together stop covering the sample domain and the estimator
123
+ // converges to the wrong value, a bias that does NOT shrink with more frames, so no TAA
124
+ // window recovers it. Measured worst case |r| against any aerial channel: 1.0000 at zero
125
+ // offset, 0.0027 here.
126
+ //
127
+ // froxel z rides the temporal axis so froxels down a column do not all share one value —
128
+ // the fetch is indexed by column, and STBN's z axis is the one built to be well distributed.
129
+ const STBN_DECORRELATION_OFFSET = vec2<u32>(37u, 71u);
130
+
131
+ let noise = stbn_sample_vec3(vec3(
132
+ voxel_coord.xy + STBN_DECORRELATION_OFFSET,
133
+ (view.frame_index + voxel_coord.z) * SAMPLE_COUNT + i
134
+ ));
135
+
136
+ // [0..1] offset (jitter - 0.5) must be centered on the froxel for every axis. The depth
137
+ // axis is stratified so that raising SAMPLE_COUNT converges faster than 1/sqrt(N); a no-op
138
+ // while it is 1.
139
+ let jitter = vec3(noise.xy, (f32(i) + noise.z) / f32(SAMPLE_COUNT));
140
+
141
+
142
+ // Interpolate the cubes, then take the cube root
143
+ let z3_sample = mix(z3_min, z3_max, jitter.z);
144
+ let view_depth = pow(z3_sample, 0.333333);
145
+
146
+ let sample_z_index:f32 = cluster_depth_to_z_slice(
147
+ view_depth,
148
+ volumetrics_metadata.froxel_cluster_parameters,
149
+ f32(resolution.z)
150
+ );
151
+
152
+ // 'saturate' prevents accidental out-of-bounds samples
153
+ let tile_uv = saturate(
154
+ texel_coordinate_to_uv(vec2f(voxel_coord.xy) + (jitter.xy - 0.5), resolution.xy)
155
+ );
156
+ let view_ray = camera_ray_from_uv(tile_uv, camera);
157
+
158
+ // The PM volumes are CELL-AVERAGED: texel k holds the slab [k+j, k+1+j], so its content
159
+ // sits at slice k + j + delta, not at slice k. Addressing them with the plain texel-centre
160
+ // convention is what the boundary-sampled aerial LUT wants, and reads the medium about half
161
+ // a froxel too far out. delta is a property of the depth curve — see the chunk.
162
+ let content_offset = volumetrics_froxel_content_offset(volumetrics_metadata.froxel_cluster_parameters.z);
163
+
164
+ let uvw = vec3<f32>(tile_uv, (sample_z_index + 0.5 - content_offset) / f32(resolution.z));
165
+
166
+ let participating_media0 = textureSampleLevel(tParticipatingMedia0, sLinear, uvw, 0.0);
167
+ let participating_media1 = textureSampleLevel(tParticipatingMedia1, sLinear, uvw, 0.0);
168
+
169
+ let participating_media_scattering = participating_media0.rgb;
170
+ let participating_media_extinction = participating_media1.rgb;
171
+ let participating_media_phase_g = participating_media1.a;
172
+
173
+ // Accumulate the (linear) mono extinction coefficient. Carried in tLighting.a below so the
174
+ // 3D TAA accumulates sigma_t alongside the in-scatter (Hillaire: temporally integrate the
175
+ // LINEAR extinction, never the non-linear transmittance).
176
+ extinctionSum += rgb_to_luminance(participating_media_extinction);
177
+
178
+ let view_pixel_coord = uv_to_texel_coordinate(tile_uv, vec2(view.width, view.height));
179
+
180
+ // view_depth is a PLANAR view-space depth and view_ray.direction is unit length, so the
181
+ // depth has to be converted to a distance along the ray before stepping. Multiplying them
182
+ // directly stops on the arc of constant distance instead of the plane of constant depth,
183
+ // short by cos(theta) — exact on the centre axis, ~72% of the intended depth in the corners
184
+ // of a 50 degree vertical FOV. Radial, which is why it reads as "the fog looks a bit off"
185
+ // rather than as an offset.
186
+ //
187
+ // Everything downstream assumes the planar reading: the light cluster is looked up at the
188
+ // planar depth, the shadow lookup happens at this position, and the TAA reconstructs froxel
189
+ // centres as planar slabs.
190
+ let position_ws = view_ray.origin
191
+ + view_ray.direction * (view_depth * camera_ray_planar_depth_scale(view_ray.direction, camera));
192
+
193
+ // Global lights (directional) — iterate over the first 32 slots
194
+ // of the directional light table (max enforced by GPULightCollection).
195
+ var directional_bits = directional_lights_iteration_mask(&light_data);
196
+ while (directional_bits != 0u) {
197
+ let directional_index = countTrailingZeros(directional_bits);
198
+ directional_bits &= ~(1u << directional_index);
199
+
200
+ let sun = get_directional_light_info_by_index(&light_data, directional_index);
201
+
202
+ let sun_lut_uv = sky_lut_compute_uv(position_ws, sun.direction);
203
+
204
+ let shadow_term = shadowmap_get_directional_light_visibility(
205
+ &light_data,
206
+ directional_index,
207
+ position_ws,
208
+ // toward-camera, matching the deferred cascade-blend convention. The consumer
209
+ // (shadowmap_csm_compute_cascade_blended) negates this internally; the deferred
210
+ // caller passes the surface->camera vector, whereas view_ray.direction is
211
+ // camera->scene, so it must be negated here to agree.
212
+ -view_ray.direction,
213
+ sun.direction
214
+ );
215
+
216
+ let u = dot(sun.direction, view_ray.direction);
217
+
218
+ // sample atmospheric transmittance
219
+ let transmittance_atmospheric = textureSampleLevel(
220
+ tSunTransmittance,
221
+ sLinear,
222
+ sun_lut_uv, 0.0
223
+ ).rgb;
224
+
225
+ // .direction points TOWARD the light; march the optical-depth ray toward the light (+direction).
226
+ let shadow_ray = ${RAY_STRUCT.make({
227
+ origin: "position_ws",
228
+ direction: "sun.direction",
229
+ tmax: "sun.distance",
230
+ })};
231
+
232
+ let optical_depth = integrate_optical_depth(shadow_ray);
233
+
234
+ let ms = calculate_multiscattering(
235
+ optical_depth,
236
+ participating_media_scattering,
237
+ participating_media_extinction,
238
+ u,
239
+ participating_media_phase_g,
240
+ volumetrics_metadata.ms_octave_count,
241
+ volumetrics_metadata.ms_extinction_decay
242
+ );
243
+
244
+ inScatteringSum += shadow_term * ms * transmittance_atmospheric * sun.color;
245
+ }
246
+
247
+ // Local lights
248
+ let cluster_metadata = light_cluster_metadata_by_position(
249
+ view_pixel_coord,
250
+ view_depth,
251
+ vec2( view.width, view.height )
252
+ );
253
+
254
+ let cluster_offset = cluster_metadata.offset;
255
+ let point_count = light_cluster_count_point(cluster_metadata.counts);
256
+ let spot_count = light_cluster_count_spot(cluster_metadata.counts);
257
+
258
+ for (var j = 0u; j < point_count; j++) {
259
+ let light_index = cluster_data[cluster_offset + j];
260
+ var direct_light = get_point_light_info_by_index(&light_data, light_index, position_ws);
261
+
262
+ if (all(direct_light.color == vec3(0.0))) {
263
+ continue;
264
+ }
265
+
266
+ let u = dot(direct_light.direction, view_ray.direction);
267
+
268
+ // .direction points TOWARD the light; march the optical-depth ray toward the light (+direction).
269
+ let shadow_ray = ${RAY_STRUCT.make({
270
+ origin: "position_ws",
271
+ direction: "direct_light.direction",
272
+ tmax: "direct_light.distance",
273
+ })};
274
+
275
+ let optical_depth = integrate_optical_depth(shadow_ray);
276
+
277
+ let ms = calculate_multiscattering(
278
+ optical_depth,
279
+ participating_media_scattering,
280
+ participating_media_extinction,
281
+ u,
282
+ participating_media_phase_g,
283
+ volumetrics_metadata.ms_octave_count,
284
+ volumetrics_metadata.ms_extinction_decay
285
+ );
286
+
287
+ inScatteringSum += ms * direct_light.color;
288
+ }
289
+
290
+ for (var k = 0u; k < spot_count; k++) {
291
+ let light_index = cluster_data[cluster_offset + point_count + k];
292
+ var direct_light = get_spot_light_info_by_index(&light_data, light_index, position_ws);
293
+
294
+ if (all(direct_light.color == vec3(0.0))) {
295
+ continue;
296
+ }
297
+
298
+ let u = dot(direct_light.direction, view_ray.direction);
299
+
300
+ // .direction points TOWARD the light; march the optical-depth ray toward the light (+direction).
301
+ let shadow_ray = ${RAY_STRUCT.make({
302
+ origin: "position_ws",
303
+ direction: "direct_light.direction",
304
+ tmax: "direct_light.distance",
305
+ })};
306
+
307
+ let optical_depth = integrate_optical_depth(shadow_ray);
308
+
309
+ let ms = calculate_multiscattering(
310
+ optical_depth,
311
+ participating_media_scattering,
312
+ participating_media_extinction,
313
+ u,
314
+ participating_media_phase_g,
315
+ volumetrics_metadata.ms_octave_count,
316
+ volumetrics_metadata.ms_extinction_decay
317
+ );
318
+
319
+ inScatteringSum += ms * direct_light.color;
320
+ }
321
+ }
322
+
323
+
324
+ var inScattering = inScatteringSum / f32(SAMPLE_COUNT);
325
+
326
+ // .a carries the mono extinction coefficient so the aerial pass can read a temporally-stable
327
+ // sigma_t (after the 3D TAA) instead of re-sampling the raw per-frame-jittered PM extinction.
328
+ // The composited fog transmittance is already mono (rgb_to_luminance of the integrated
329
+ // throughput), so collapsing extinction to luminance only drops the subtle chromatic
330
+ // depth-attenuation of the in-scatter.
331
+ let extinction = extinctionSum / f32(SAMPLE_COUNT);
332
+
333
+ textureStore(tLighting, voxel_coord, vec4<f32>(inScattering, extinction));
334
+ }
335
+ `, [
336
+ chunk_cluster_depth_from_z_slice,
337
+ chunk_cluster_depth_to_z_slice,
338
+ chunk_texel_coordinate_to_uv,
339
+ chunk_camera_ray_from_uv,
340
+ chunk_camera_ray_planar_depth_scale,
341
+ chunk_volumetrics_froxel_content_offset,
342
+ chunk_uv_to_texel_coordinate,
343
+
344
+ chunk_stbn_sample_vec3,
345
+
346
+ chunk_light_cluster_metadata_by_position,
347
+ chunk_light_cluster_counts,
348
+ chunk_directional_lights_iteration_mask,
349
+ chunk_get_directional_light_info_by_index,
350
+ chunk_get_point_light_info_by_index,
351
+ chunk_get_spot_light_info_by_index,
352
+ chunk_shadowmap_get_light_visibility,
353
+ chunk_sky_lut_compute_uv,
354
+
355
+ RAY_STRUCT.declaration_chunk,
356
+ chunk_integrate_optical_depth,
357
+ chunk_calculate_multiscattering,
358
+ chunk_rgb_to_luminance,
359
+ chunk_volumetrics_column_jitter,
360
+ ]
361
+ )
362
+
363
+ export const shader_volumetrics_build_lighting = ComputeShader.from({
364
+ label: "Volumetrics / build lighting",
365
+ resources,
366
+ body
367
+ })