@woosh/meep-engine 3.11.2 → 3.11.3

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 (168) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts +16 -2
  4. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts.map +1 -1
  5. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +28 -4
  6. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +1 -1
  7. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
  8. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +5 -2
  9. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +564 -451
  10. package/src/shade/device/ShadeGPUCommandContext.d.ts +18 -2
  11. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  12. package/src/shade/device/ShadeGPUCommandContext.js +994 -940
  13. package/src/shade/device/immediate_data_supported.d.ts +33 -0
  14. package/src/shade/device/immediate_data_supported.d.ts.map +1 -0
  15. package/src/shade/device/immediate_data_supported.js +53 -0
  16. package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts +18 -0
  17. package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts.map +1 -1
  18. package/src/shade/device/mock/SoftwareGPUCommandEncoder.js +24 -0
  19. package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts +7 -0
  20. package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts.map +1 -1
  21. package/src/shade/device/mock/SoftwareGPUComputePassEncoder.js +49 -1
  22. package/src/shade/device/mock/SoftwareGPUDevice.d.ts.map +1 -1
  23. package/src/shade/device/mock/SoftwareGPUDevice.js +20 -2
  24. package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts +65 -0
  25. package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts.map +1 -0
  26. package/src/shade/device/mock/SoftwareGPUImmediateData.js +160 -0
  27. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +7 -0
  28. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
  29. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +50 -0
  30. package/src/shade/device/mock/pipeline_immediate_size.d.ts +11 -0
  31. package/src/shade/device/mock/pipeline_immediate_size.d.ts.map +1 -0
  32. package/src/shade/device/mock/pipeline_immediate_size.js +18 -0
  33. package/src/shade/device/pipeline/PipelineLayoutManager.d.ts.map +1 -1
  34. package/src/shade/device/pipeline/PipelineLayoutManager.js +1 -0
  35. package/src/shade/renderer/GraphicsContext.d.ts +9 -2
  36. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  37. package/src/shade/renderer/GraphicsContext.js +13 -1
  38. package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
  39. package/src/shade/renderer/atmosphere/GPUSky.js +145 -142
  40. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +34 -0
  41. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -0
  42. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +36 -0
  43. package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts +20 -0
  44. package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts.map +1 -0
  45. package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.js +25 -0
  46. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +14 -1
  47. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
  48. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +163 -177
  49. package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts +20 -0
  50. package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts.map +1 -0
  51. package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.js +46 -0
  52. package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts +11 -1
  53. package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts.map +1 -1
  54. package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.js +57 -27
  55. package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts +19 -1
  56. package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts.map +1 -1
  57. package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.js +69 -33
  58. package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts +13 -0
  59. package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts.map +1 -1
  60. package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.js +35 -35
  61. package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts +13 -0
  62. package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts.map +1 -0
  63. package/src/shade/renderer/atmosphere/chunk_sky_lut_params.js +38 -0
  64. package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts +17 -0
  65. package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts.map +1 -0
  66. package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.js +29 -0
  67. package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts +21 -0
  68. package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts.map +1 -0
  69. package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.js +42 -0
  70. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts +2 -2
  71. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
  72. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +165 -84
  73. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
  74. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +215 -204
  75. package/src/shade/renderer/atmosphere/shader_transmittance_lut.d.ts.map +1 -1
  76. package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +79 -67
  77. package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
  78. package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +6 -2
  79. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  80. package/src/shade/renderer/postprocess/gtao/GTAO.js +4 -0
  81. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  82. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +341 -345
  83. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
  84. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  85. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
  86. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
  87. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +7 -3
  88. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
  89. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +77 -81
  90. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  91. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +5 -1
  92. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  93. package/src/shade/renderer/shader/ComputeShader.js +6 -2
  94. package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
  95. package/src/shade/renderer/shader/ImageShader.js +4 -1
  96. package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
  97. package/src/shade/renderer/shader/ShaderDescriptor.js +26 -0
  98. package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts +18 -0
  99. package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts.map +1 -0
  100. package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.js +32 -0
  101. package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts +7 -0
  102. package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts.map +1 -1
  103. package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.js +17 -6
  104. package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts +21 -0
  105. package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts.map +1 -0
  106. package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.js +69 -0
  107. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts +9 -0
  108. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts.map +1 -0
  109. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.js +22 -0
  110. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts +16 -0
  111. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts.map +1 -0
  112. package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.js +41 -0
  113. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts +11 -0
  114. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts.map +1 -1
  115. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.js +15 -19
  116. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts +23 -0
  117. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts.map +1 -0
  118. package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.js +44 -0
  119. package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.d.ts.map +1 -1
  120. package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js +1 -4
  121. package/src/shade/renderer/shader/compiler/CodeChunk.d.ts +7 -1
  122. package/src/shade/renderer/shader/compiler/CodeChunk.d.ts.map +1 -1
  123. package/src/shade/renderer/shader/compiler/CodeChunk.js +37 -2
  124. package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.d.ts.map +1 -1
  125. package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.js +6 -0
  126. package/src/shade/renderer/shader/graph/compute/graph_compute_indirect_pass.js +1 -1
  127. package/src/shade/renderer/shader/graph/compute/graph_compute_pass.js +1 -1
  128. package/src/shade/renderer/shader/graph/graph_image_pass.js +1 -1
  129. package/src/shade/renderer/shader/graph/graph_prepare_bindings.d.ts.map +1 -1
  130. package/src/shade/renderer/shader/graph/graph_prepare_bindings.js +7 -0
  131. package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts +3 -1
  132. package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts.map +1 -1
  133. package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.js +12 -1
  134. package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
  135. package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +1 -0
  136. package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts +21 -0
  137. package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts.map +1 -0
  138. package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.js +21 -0
  139. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts +59 -1
  140. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  141. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +195 -2
  142. package/src/shade/renderer/shader/type/type_has_arrays.d.ts +14 -0
  143. package/src/shade/renderer/shader/type/type_has_arrays.d.ts.map +1 -0
  144. package/src/shade/renderer/shader/type/type_has_arrays.js +33 -0
  145. package/src/shade/renderer/view/GPUViewSkyContext.d.ts.map +1 -1
  146. package/src/shade/renderer/view/GPUViewSkyContext.js +169 -173
  147. package/src/shade/renderer/volumetrics/NOTES.md +238 -0
  148. package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.js +145 -145
  149. package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts.map +1 -1
  150. package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.js +38 -15
  151. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +14 -2
  152. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  153. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +594 -577
  154. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +16 -0
  155. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  156. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +19 -2
  157. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
  158. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +29 -18
  159. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +260 -260
  160. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts +53 -3
  161. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts.map +1 -1
  162. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.js +175 -59
  163. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +8 -1
  164. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  165. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +1325 -1297
  166. package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts +0 -2
  167. package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts.map +0 -1
  168. package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.js +0 -1
@@ -1,578 +1,595 @@
1
-
2
-
3
- import { assert } from "../../../core/assert.js";
4
- import { SamplerDescriptor } from "../../descriptor/texture/SamplerDescriptor.js";
5
- import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
6
- import { SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR } from "../shadow/map/SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR.js";
7
- import { STATIC_GRAPHICS_ENGINE_ASSETS } from "../STATIC_GRAPHICS_ENGINE_ASSETS.js";
8
- import { DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR } from "../texture/DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR.js";
9
- import { graph_compute_pass } from "../shader/graph/compute/graph_compute_pass.js";
10
- import { graph_import_buffer } from "../shader/graph/graph_import_buffer.js";
11
- import { shader_multiscatter_lut } from "../atmosphere/shader_multiscatter_lut.js";
12
- import { shader_transmittance_lut } from "../atmosphere/shader_transmittance_lut.js";
13
- import { shader_volumetrics_build_lighting } from "./shader_volumetrics_build_lighting.js";
14
- import { shader_volumetrics_build_participating_media } from "./shader_volumetrics_build_participating_media.js";
15
- import { shader_volumetrics_build_scattering_lut } from "./shader_volumetrics_build_scattering_lut.js";
16
- import { shader_volumetrics_build_sun_shadow_volume } from "./shader_volumetrics_build_sun_shadow_volume.js";
17
- import {
18
- VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT,
19
- VOLUMETRICS_SUN_SHADOW_WORKGROUP_RESOLUTION
20
- } from "./VOLUMETRICS_SUN_SHADOW_RESOLUTION.js";
21
- import { volumetrics_sun_shadow_texture_name } from "./chunk_volumetrics_sun_optical_depth.js";
22
- import { shader_volumetrics_taa } from "./taa/shader_volumetrics_taa.js";
23
-
24
- /**
25
- *
26
- * @param {FrameGraph} graph
27
- */
28
- function graph_integrate_sky({ graph }) {
29
- const t_data = {};
30
-
31
- const t_pass = graph.add("transmittance", t_data, (data, resources, context) => {
32
-
33
- const output = resources.get(data.output);
34
-
35
- const encoder = context.encoder;
36
-
37
- const settings_type = shader_transmittance_lut.descriptor.resources.getResourceByName("settings").buffer_type;
38
-
39
- const settings = encoder.allocateTransientValueBuffer(settings_type, {
40
- steps: 40
41
- });
42
-
43
- shader_transmittance_lut.draw({
44
- encoder: encoder,
45
- bindings: {
46
- settings
47
- },
48
- colorAttachments: [{
49
- view: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
50
- loadOp: "clear",
51
- storeOp: "store",
52
- }]
53
- })
54
- });
55
-
56
- t_data.output = t_pass.create("transmittance lut", TextureResourceDescriptor.from({
57
- resolution: [256, 64],
58
- format: "rgba16float",
59
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
60
- }));
61
-
62
- const m_data = {};
63
-
64
- const m_pass = graph.add("multiscatter lut", m_data, (data, resources, context) => {
65
- const output = resources.get(data.output);
66
-
67
- const settings_type = shader_multiscatter_lut.descriptor.resources.getResourceByName("settings").buffer_type;
68
-
69
- const encoder = context.encoder;
70
-
71
- const settings = encoder.allocateTransientValueBuffer(settings_type, {
72
- steps: 20,
73
- sqrt_samples: 8,
74
- });
75
-
76
- const tSkyLutTransmittance = resources.get(data.tSkyLutTransmittance);
77
-
78
- shader_multiscatter_lut.draw({
79
- encoder: encoder,
80
- bindings: {
81
- settings,
82
- tSkyLutTransmittance
83
- },
84
- colorAttachments: [{
85
- view: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
86
- loadOp: "clear",
87
- storeOp: "store",
88
- }]
89
- })
90
- });
91
-
92
- m_data.output = m_pass.create("multiscatter lut", TextureResourceDescriptor.from({
93
- resolution: [32, 32],
94
- format: "rgba16float",
95
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
96
- }))
97
-
98
- m_data.tSkyLutTransmittance = m_pass.read(t_data.output);
99
-
100
-
101
- return { transmittance_lut: t_data.output, multiscatted_lut: m_data.output };
102
- }
103
-
104
-
105
- /**
106
- *
107
- * @param {FrameGraph} graph
108
- * @param {GPUCameraContext} camera
109
- * @param {GPUSceneContext} scene
110
- * @param {GPUViewContext} view
111
- * @param {number} volumetrics_metadata
112
- * @param {number[]} resolution output resolution
113
- */
114
- function graph_integrate_participating_media({
115
- graph,
116
- camera,
117
- scene,
118
- view,
119
- volumetrics_metadata,
120
- resolution,
121
- }) {
122
-
123
- const data = {};
124
-
125
-
126
- const pass = graph.add(
127
- "aerial lut",
128
- data,
129
- /**
130
- *
131
- * @param {} data
132
- * @param {FramePassResources} resources
133
- * @param {GraphicsExecutionContext} context
134
- */
135
- (data, resources, context) => {
136
-
137
- /**
138
- * @type {GPUTextureContext}
139
- */
140
- const output0 = resources.get(data.output0);
141
- /**
142
- * @type {GPUTextureContext}
143
- */
144
- const output1 = resources.get(data.output1);
145
-
146
- const encoder = context.encoder;
147
-
148
- const volumetrics_metadata = resources.get(data.volumetrics_metadata);
149
-
150
- shader_volumetrics_build_participating_media.dispatch({
151
- encoder,
152
- group_count_x: Math.ceil(output0.width / 16),
153
- group_count_y: Math.ceil(output0.height / 16),
154
- group_count_z: output0.depth,
155
- bindings: {
156
- camera: camera.buffer.buffer,
157
- view: view.uniform_buffer.buffer,
158
- volumetrics_metadata,
159
- scene_volumetrics_metadata: scene.volumetrics.metadata_buffer.buffer,
160
- scene_volumes: scene.volumetrics.table.buffer,
161
- tOutput0: output0.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
162
- tOutput1: output1.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
163
- }
164
- });
165
-
166
-
167
- });
168
-
169
- data.volumetrics_metadata = pass.read(volumetrics_metadata);
170
-
171
- data.output0 = pass.create("Participating Media 0", TextureResourceDescriptor.from({
172
- resolution,
173
- dimension: "3d",
174
- format: "rgba16float",
175
- usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
176
- }));
177
- data.output1 = pass.create("Participating Media 1", TextureResourceDescriptor.from({
178
- resolution,
179
- dimension: "3d",
180
- format: "rgba16float",
181
- usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
182
- }));
183
-
184
- return {
185
- participating_media0: data.output0,
186
- participating_media1: data.output1,
187
- volumetrics_metadata: data.volumetrics_metadata,
188
- };
189
- }
190
-
191
- /**
192
- * Q-04. The sun's optical depth, integrated once per frame into cascaded light-aligned volumes so
193
- * the lighting pass can read it in a single fetch instead of marching toward the sun from every
194
- * froxel.
195
- *
196
- * Sits between the participating-media build and the lighting pass because it reads the former and
197
- * is read by the latter; the frame graph derives that ordering from the handles alone. The cascades
198
- * are ordered among themselves for the same reason: each one is seeded from the value the cascade
199
- * outside it already computed at its entry face, so the outermost is built first and the handle it
200
- * returns is what makes the next pass wait for it.
201
- *
202
- * A texture per cascade rather than one texture with the cascades stacked along w. Stacking would
203
- * put a pass's own output and its input in the same texture, which WebGPU rejects — a subresource
204
- * cannot be bound as writable storage and as a sampled texture at once and there is no
205
- * subresource finer than the whole 3D texture to split it on.
206
- *
207
- * Always present, even with the mode off the volumes are then dispatched at 1x1x1 (see
208
- * `GPUViewVolumetrics`), which keeps the graph's topology identical on both sides of the A/B. A
209
- * pass appearing and disappearing would change resource aliasing and pass scheduling along with the
210
- * thing being measured, and this subsystem has already produced one confident wrong answer from a
211
- * measurement where the two sides did not pay the same fixed costs.
212
- *
213
- * @param {FrameGraph} graph
214
- * @param {GPUCameraContext} camera
215
- * @param {number} participating_media1
216
- * @param {number} volumetrics_metadata
217
- * @param {number[]} resolution voxel counts of one cascade
218
- * @returns {number[]} handles of the cascades, innermost first
219
- */
220
- function graph_build_sun_shadow_volume({
221
- graph,
222
- camera,
223
- participating_media1,
224
- volumetrics_metadata,
225
- resolution,
226
- }) {
227
-
228
- const camera_buffer = graph_import_buffer(graph, camera.buffer.buffer, "volumetrics sun shadow camera");
229
-
230
- const group_count = [
231
- // one invocation per COLUMN along the sun axis, so only x and y are dispatched
232
- Math.ceil(resolution[0] / VOLUMETRICS_SUN_SHADOW_WORKGROUP_RESOLUTION[0]),
233
- Math.ceil(resolution[1] / VOLUMETRICS_SUN_SHADOW_WORKGROUP_RESOLUTION[1]),
234
- 1,
235
- ];
236
-
237
- const cascades = [];
238
-
239
- for (let cascade = VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT - 1; cascade >= 0; cascade--) {
240
-
241
- const bindings = {
242
- camera: camera_buffer,
243
- volumetrics_metadata,
244
- tParticipatingMedia1: participating_media1,
245
- tSunShadowVolume: TextureResourceDescriptor.from({
246
- resolution,
247
- dimension: "3d",
248
- format: "rgba16float",
249
- usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING
250
- }),
251
- };
252
-
253
- if (cascade < VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT - 1) {
254
- bindings.tSunShadowOuter = cascades[cascade + 1];
255
- }
256
-
257
- const outputs = graph_compute_pass({
258
- graph,
259
- shader: shader_volumetrics_build_sun_shadow_volume[cascade],
260
- group_count,
261
- bindings,
262
- });
263
-
264
- cascades[cascade] = outputs.tSunShadowVolume;
265
- }
266
-
267
- return cascades;
268
- }
269
-
270
- /**
271
- *
272
- * @param {FrameGraph} graph
273
- * @param participating_media0
274
- * @param participating_media1
275
- * @param volumetrics_metadata
276
- * @param light_cluster_data
277
- * @param light_cluster_lookup
278
- * @param light_cluster_parameters
279
- * @param shadowmap_atlas
280
- * @param sun_transmittance_lut
281
- * @param sun_multiple_scattering_lut
282
- * @param {number[]} sun_shadow_cascades handles of the Q-04 optical-depth cascades, innermost first
283
- * @param lights
284
- * @param view
285
- * @param camera
286
- * @param resolution
287
- */
288
- function graph_integrate_light_scattering({
289
- graph,
290
- participating_media0,
291
- participating_media1,
292
- volumetrics_metadata,
293
- light_cluster_data,
294
- light_cluster_lookup,
295
- light_cluster_parameters,
296
- shadowmap_atlas,
297
- sun_transmittance_lut,
298
- sun_multiple_scattering_lut,
299
- sun_shadow_cascades,
300
- lights,
301
- view,
302
- camera,
303
- resolution,
304
- }) {
305
-
306
- const data = {};
307
-
308
- const pass = graph.add("scattering lut", data, (data, resources, context) => {
309
- const output = resources.get(data.output);
310
-
311
- const volumetrics_metadata = resources.get(data.volumetrics_metadata);
312
-
313
- const participating_media0 = resources.get(data.participating_media0);
314
- const participating_media1 = resources.get(data.participating_media1);
315
-
316
- const light_cluster_data = resources.get(data.light_cluster_data);
317
- const light_cluster_lookup = resources.get(data.light_cluster_lookup);
318
- const light_cluster_parameters = resources.get(data.light_cluster_parameters);
319
- const shadowmap_atlas = resources.get(data.shadowmap_atlas);
320
-
321
- const sun_transmittance_lut = resources.get(data.sun_transmittance_lut);
322
- const sun_multiple_scattering_lut = resources.get(data.sun_multiple_scattering_lut);
323
- const sun_shadow_cascades = data.sun_shadow_cascades.map(handle => resources.get(handle));
324
-
325
- const sampler_shadowmap = context.graphics.samplers.obtain(SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR);
326
- const sLinear = context.graphics.samplers.obtain(SamplerDescriptor.from({
327
- magFilter: "linear",
328
- }));
329
-
330
- // Spatiotemporal blue noise for the per-froxel sample offset. The pass takes one sample and
331
- // leans on the volumetric TAA to integrate, which only works if each froxel's sequence over
332
- // frames is well distributed — the property a per-froxel hash does not have.
333
- const tSTBN_vec3 = context.graphics.textures.obtain(STATIC_GRAPHICS_ENGINE_ASSETS.stbn_vec3);
334
-
335
- const encoder = context.encoder;
336
-
337
- shader_volumetrics_build_lighting.dispatch({
338
- encoder,
339
- group_count_x: Math.ceil(output.width / 16),
340
- group_count_y: Math.ceil(output.height / 16),
341
- group_count_z: output.depth,
342
- bindings: {
343
- camera: camera.buffer.buffer,
344
- view: view.uniform_buffer.buffer,
345
- volumetrics_metadata,
346
- sLinear,
347
- tSunTransmittance: sun_transmittance_lut.obtainView(),
348
- tMultiScattering: sun_multiple_scattering_lut.obtainView(),
349
- tSTBN_vec3: tSTBN_vec3.obtainView(),
350
- tParticipatingMedia0: participating_media0.obtainView(),
351
- tParticipatingMedia1: participating_media1.obtainView(),
352
- ...Object.fromEntries(sun_shadow_cascades.map(
353
- (texture, cascade) => [volumetrics_sun_shadow_texture_name(cascade), texture.obtainView()]
354
- )),
355
- tLighting: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
356
- //
357
- light_data: lights.buffer_data,
358
-
359
- cluster_data: light_cluster_data,
360
- cluster_lookup: light_cluster_lookup,
361
- cluster_parameters: light_cluster_parameters,
362
-
363
- shadowmapAtlas: shadowmap_atlas.obtainView(),
364
- sCompareLinear: sampler_shadowmap,
365
- }
366
- })
367
- });
368
-
369
- data.participating_media0 = pass.read(participating_media0);
370
- data.participating_media1 = pass.read(participating_media1);
371
- data.volumetrics_metadata = pass.read(volumetrics_metadata);
372
-
373
- data.shadowmap_atlas = pass.read(shadowmap_atlas);
374
- data.light_cluster_data = pass.read(light_cluster_data);
375
- data.light_cluster_lookup = pass.read(light_cluster_lookup);
376
- data.light_cluster_parameters = pass.read(light_cluster_parameters);
377
-
378
- data.sun_transmittance_lut = pass.read(sun_transmittance_lut);
379
- data.sun_multiple_scattering_lut = pass.read(sun_multiple_scattering_lut);
380
- data.sun_shadow_cascades = sun_shadow_cascades.map(handle => pass.read(handle));
381
-
382
- data.output = pass.create("Light Scattering LUT", TextureResourceDescriptor.from({
383
- resolution,
384
- dimension: "3d",
385
- format: "rgba16float",
386
- usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
387
- }));
388
-
389
-
390
- return data.output;
391
-
392
- }
393
-
394
- /**
395
- *
396
- * @param {FrameGraph} graph
397
- * @param {GPUCameraContext} camera
398
- * @param {GPULightCollection} lights
399
- * @param {GPUViewContext} view
400
- * @param {number} light_cluster_data
401
- * @param {number} light_cluster_lookup
402
- * @param {number} light_cluster_parameters
403
- * @param {number} shadowmap_atlas
404
- * @param {number[]} volumetric_resolution
405
- * @param {number[]} sun_shadow_resolution voxel counts of one Q-04 sun optical-depth cascade;
406
- * `[1, 1, 1]` when the froxels are marching instead, which keeps the passes in the graph without
407
- * doing any work
408
- * @param {number} volumetrics_metadata
409
- * @param {number} [taa_history] imported previous-frame accumulated scattering volume
410
- * @param {number} [taa_output] imported destination for this frame's accumulated scattering volume
411
- * @param {GPUCameraContext} [camera_previous] previous-frame camera, for temporal reprojection
412
- */
413
- export function graph_build_volumetrics({
414
- graph,
415
- camera,
416
- lights,
417
- view,
418
- light_cluster_data,
419
- light_cluster_lookup,
420
- light_cluster_parameters,
421
- shadowmap_atlas,
422
- volumetric_resolution,
423
- sun_shadow_resolution,
424
- volumetrics_metadata,
425
- taa_history,
426
- taa_output,
427
- camera_previous,
428
- }) {
429
-
430
- assert.defined(view, 'view');
431
- assert.defined(volumetric_resolution, 'volumetric_resolution');
432
- assert.defined(sun_shadow_resolution, 'sun_shadow_resolution');
433
-
434
- const { transmittance_lut, multiscatted_lut } = graph_integrate_sky({ graph: graph });
435
-
436
- const {
437
- participating_media0,
438
- participating_media1,
439
- } = graph_integrate_participating_media({
440
- graph,
441
- scene: view.scene,
442
- view,
443
- resolution: volumetric_resolution,
444
- volumetrics_metadata,
445
- camera,
446
- });
447
-
448
- const sun_shadow_cascades = graph_build_sun_shadow_volume({
449
- graph,
450
- camera,
451
- participating_media1,
452
- volumetrics_metadata,
453
- resolution: sun_shadow_resolution,
454
- });
455
-
456
- const light_scattering_lut = graph_integrate_light_scattering({
457
- graph,
458
- resolution: volumetric_resolution,
459
- camera,
460
- view,
461
- sun_transmittance_lut: transmittance_lut,
462
- sun_multiple_scattering_lut: multiscatted_lut,
463
- sun_shadow_cascades,
464
- participating_media0,
465
- participating_media1,
466
- volumetrics_metadata,
467
- light_cluster_data,
468
- light_cluster_lookup,
469
- light_cluster_parameters,
470
- shadowmap_atlas,
471
- lights,
472
- })
473
-
474
- // Temporal reprojection of the per-froxel lit scattering volume, applied BEFORE aerial
475
- // integration — reprojecting the already-integrated aerial LUT would double-integrate along the
476
- // view ray. Optional: skipped when no history ping-pong is supplied (TAA disabled), in which
477
- // case the aerial pass consumes the raw (noisier) scattering LUT directly.
478
- let volume_lighting_resolved = light_scattering_lut;
479
-
480
- if (taa_history !== undefined && taa_output !== undefined) {
481
- const taa_outputs = graph_compute_pass({
482
- graph,
483
- shader: shader_volumetrics_taa,
484
- group_count: [
485
- Math.ceil(volumetric_resolution[0] / 8),
486
- Math.ceil(volumetric_resolution[1] / 8),
487
- Math.ceil(volumetric_resolution[2] / 4),
488
- ],
489
- bindings: {
490
- t_current: light_scattering_lut,
491
- t_history: taa_history,
492
- t_output: taa_output,
493
- volumetrics_metadata,
494
- camera_current: graph_import_buffer(graph, camera.buffer.buffer, "volumetrics taa camera"),
495
- camera_previous: graph_import_buffer(graph, camera_previous.buffer.buffer, "volumetrics taa camera previous"),
496
- },
497
- });
498
-
499
- volume_lighting_resolved = taa_outputs.t_output;
500
- }
501
-
502
- const a_data = {};
503
-
504
- const a_pass = graph.add("aerial lut", a_data, (data, resources, context) => {
505
- const output = resources.get(data.output);
506
-
507
- const participating_media0 = resources.get(data.participating_media0);
508
- const participating_media1 = resources.get(data.participating_media1);
509
- const volumetrics_metadata = resources.get(data.volumetrics_metadata);
510
-
511
- const light_scattering_lut = resources.get(data.light_scattering_lut);
512
- const sun_transmittance_lut = resources.get(data.sun_transmittance_lut);
513
-
514
- const encoder = context.encoder;
515
-
516
-
517
- const graphics = context.graphics;
518
- const device = graphics.device;
519
-
520
- const sLinear = graphics.samplers.obtain(SamplerDescriptor.from({
521
- magFilter: "linear",
522
- }));
523
-
524
- const tSTBN_vec3 = graphics.textures.obtain(STATIC_GRAPHICS_ENGINE_ASSETS.stbn_vec3);
525
-
526
- shader_volumetrics_build_scattering_lut.dispatch({
527
- encoder,
528
- group_count_x: Math.ceil(output.width / 16),
529
- group_count_y: Math.ceil(output.height / 16),
530
- bindings: {
531
- camera: camera.buffer.buffer,
532
- view: view.uniform_buffer.buffer,
533
- volumetrics_metadata,
534
- tSTBN_vec3: tSTBN_vec3.obtainView(),
535
- sLinear,
536
-
537
- tVolumeLighting: light_scattering_lut.obtainView(),
538
- tParticipatingMedia0: participating_media0.obtainView(),
539
- tParticipatingMedia1: participating_media1.obtainView(),
540
-
541
- // RDR2-style far-field march inputs (used beyond froxel_far)
542
- scene_volumetrics_metadata: view.scene.volumetrics.metadata_buffer.buffer,
543
- scene_volumes: view.scene.volumetrics.table.buffer,
544
- light_data: lights.buffer_data,
545
- tSunTransmittance: sun_transmittance_lut.obtainView(),
546
-
547
- tOutput: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
548
- }
549
- });
550
-
551
-
552
- });
553
-
554
- a_data.participating_media0 = a_pass.read(participating_media0);
555
- a_data.participating_media1 = a_pass.read(participating_media1);
556
- a_data.volumetrics_metadata = a_pass.read(volumetrics_metadata);
557
- a_data.sun_transmittance_lut = a_pass.read(transmittance_lut);
558
-
559
- a_data.output = a_pass.create("Integrated Volumetrics", TextureResourceDescriptor.from({
560
- resolution: volumetric_resolution,
561
- dimension: "3d",
562
- format: "rgba16float",
563
- usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING
564
- }))
565
-
566
-
567
- a_data.light_scattering_lut = a_pass.read(volume_lighting_resolved);
568
-
569
-
570
- // TODO add temporal reprojection
571
-
572
- return {
573
- lut_transmittance: transmittance_lut,
574
- lut_multiple_scattering: multiscatted_lut,
575
- lut_aerial: a_data.output,
576
- volumetrics_metadata,
577
- };
1
+ import { assert } from "../../../core/assert.js";
2
+ import { SamplerDescriptor } from "../../descriptor/texture/SamplerDescriptor.js";
3
+ import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
4
+ import { shader_multiscatter_lut } from "../atmosphere/shader_multiscatter_lut.js";
5
+ import { shader_transmittance_lut } from "../atmosphere/shader_transmittance_lut.js";
6
+ import { graph_compute_pass } from "../shader/graph/compute/graph_compute_pass.js";
7
+ import { graph_import_buffer } from "../shader/graph/graph_import_buffer.js";
8
+ import { SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR } from "../shadow/map/SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR.js";
9
+ import { STATIC_GRAPHICS_ENGINE_ASSETS } from "../STATIC_GRAPHICS_ENGINE_ASSETS.js";
10
+ import { DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR } from "../texture/DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR.js";
11
+ import { volumetrics_sun_shadow_texture_name } from "./chunk_volumetrics_sun_optical_depth.js";
12
+ import {
13
+ shader_volumetrics_build_lighting,
14
+ VOLUMETRICS_LIGHTING_WORKGROUP_RESOLUTION
15
+ } from "./shader_volumetrics_build_lighting.js";
16
+ import { shader_volumetrics_build_participating_media } from "./shader_volumetrics_build_participating_media.js";
17
+ import { shader_volumetrics_build_scattering_lut } from "./shader_volumetrics_build_scattering_lut.js";
18
+ import {
19
+ shader_volumetrics_build_sun_shadow_volume_outermost,
20
+ shader_volumetrics_build_sun_shadow_volume_seeded
21
+ } from "./shader_volumetrics_build_sun_shadow_volume.js";
22
+ import { shader_volumetrics_taa } from "./taa/shader_volumetrics_taa.js";
23
+ import {
24
+ VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT,
25
+ VOLUMETRICS_SUN_SHADOW_WORKGROUP_RESOLUTION,
26
+ volumetrics_sun_shadow_sub_steps
27
+ } from "./VOLUMETRICS_SUN_SHADOW_RESOLUTION.js";
28
+
29
+ /**
30
+ * Transmittance LUT, then the multiple-scattering LUT that reads it. Exported for
31
+ * `graph_integrate_sky.spec.js`, which pins the dispatch shape and the transient's usage
32
+ * flags -- neither of which the WGSL validity gate can see.
33
+ *
34
+ * @param {FrameGraph} graph
35
+ * @returns {{transmittance_lut: number, multiscatted_lut: number}} resource handles
36
+ */
37
+ export function graph_integrate_sky({ graph }) {
38
+
39
+ graph.push_scope("integrate sky");
40
+
41
+ const t_data = {};
42
+
43
+ const t_pass = graph.add("transmittance", t_data, (data, resources, context) => {
44
+
45
+ const output = resources.get(data.output);
46
+
47
+ const encoder = context.encoder;
48
+
49
+ const settings_type = shader_transmittance_lut.descriptor.resources.getResourceByName("settings").buffer_type;
50
+
51
+ const settings = encoder.allocateTransientValueBuffer(settings_type, {
52
+ steps: 40,
53
+ // taken off the target being drawn into, so the generator cannot be told a size
54
+ // the texture does not have
55
+ resolution: [output.width, output.height],
56
+ });
57
+
58
+ shader_transmittance_lut.draw({
59
+ encoder: encoder,
60
+ bindings: {
61
+ settings
62
+ },
63
+ colorAttachments: [{
64
+ view: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
65
+ loadOp: "clear",
66
+ storeOp: "store",
67
+ }]
68
+ })
69
+ });
70
+
71
+ t_data.output = t_pass.create("transmittance lut", TextureResourceDescriptor.from({
72
+ resolution: [256, 64],
73
+ format: "rgba16float",
74
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
75
+ }));
76
+
77
+ // The transient does not exist yet at graph-build time, so the dispatch cannot be
78
+ // counted off it the way GPUSky does; one local value feeds both instead.
79
+ const multiscatter_resolution = [32, 32];
80
+
81
+ const multiscatter_outputs = graph_compute_pass({
82
+ graph,
83
+ shader: shader_multiscatter_lut,
84
+ // one workgroup per LUT texel, each thread integrating one direction
85
+ group_count: [multiscatter_resolution[0], multiscatter_resolution[1], 1],
86
+ bindings: {
87
+ tSkyLutTransmittance: t_data.output,
88
+ tSkyLutMultiscatterOut: TextureResourceDescriptor.from({
89
+ resolution: multiscatter_resolution,
90
+ format: "rgba16float",
91
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING
92
+ })
93
+ }
94
+ });
95
+
96
+ graph.pop_scope();
97
+
98
+ return { transmittance_lut: t_data.output, multiscatted_lut: multiscatter_outputs.tSkyLutMultiscatterOut };
99
+ }
100
+
101
+
102
+ /**
103
+ *
104
+ * @param {FrameGraph} graph
105
+ * @param {GPUCameraContext} camera
106
+ * @param {GPUSceneContext} scene
107
+ * @param {GPUViewContext} view
108
+ * @param {number} volumetrics_metadata
109
+ * @param {number[]} resolution output resolution
110
+ */
111
+ function graph_integrate_participating_media({
112
+ graph,
113
+ camera,
114
+ scene,
115
+ view,
116
+ volumetrics_metadata,
117
+ resolution,
118
+ }) {
119
+
120
+ const data = {};
121
+
122
+
123
+ const pass = graph.add(
124
+ "aerial lut",
125
+ data,
126
+ /**
127
+ *
128
+ * @param {} data
129
+ * @param {FramePassResources} resources
130
+ * @param {GraphicsExecutionContext} context
131
+ */
132
+ (data, resources, context) => {
133
+
134
+ /**
135
+ * @type {GPUTextureContext}
136
+ */
137
+ const output0 = resources.get(data.output0);
138
+ /**
139
+ * @type {GPUTextureContext}
140
+ */
141
+ const output1 = resources.get(data.output1);
142
+
143
+ const encoder = context.encoder;
144
+
145
+ const volumetrics_metadata = resources.get(data.volumetrics_metadata);
146
+
147
+ shader_volumetrics_build_participating_media.dispatch({
148
+ encoder,
149
+ group_count_x: Math.ceil(output0.width / 16),
150
+ group_count_y: Math.ceil(output0.height / 16),
151
+ group_count_z: output0.depth,
152
+ bindings: {
153
+ camera: camera.buffer.buffer,
154
+ view: view.uniform_buffer.buffer,
155
+ volumetrics_metadata,
156
+ scene_volumetrics_metadata: scene.volumetrics.metadata_buffer.buffer,
157
+ scene_volumes: scene.volumetrics.table.buffer,
158
+ tOutput0: output0.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
159
+ tOutput1: output1.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
160
+ }
161
+ });
162
+
163
+
164
+ });
165
+
166
+ data.volumetrics_metadata = pass.read(volumetrics_metadata);
167
+
168
+ data.output0 = pass.create("Participating Media 0", TextureResourceDescriptor.from({
169
+ resolution,
170
+ dimension: "3d",
171
+ format: "rgba16float",
172
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
173
+ }));
174
+ data.output1 = pass.create("Participating Media 1", TextureResourceDescriptor.from({
175
+ resolution,
176
+ dimension: "3d",
177
+ format: "rgba16float",
178
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
179
+ }));
180
+
181
+ return {
182
+ participating_media0: data.output0,
183
+ participating_media1: data.output1,
184
+ volumetrics_metadata: data.volumetrics_metadata,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Q-04. The sun's optical depth, integrated once per frame into cascaded light-aligned volumes so
190
+ * the lighting pass can read it in a single fetch instead of marching toward the sun from every
191
+ * froxel.
192
+ *
193
+ * Sits between the participating-media build and the lighting pass because it reads the former and
194
+ * is read by the latter; the frame graph derives that ordering from the handles alone. The cascades
195
+ * are ordered among themselves for the same reason: each one is seeded from the value the cascade
196
+ * outside it already computed at its entry face, so the outermost is built first and the handle it
197
+ * returns is what makes the next pass wait for it.
198
+ *
199
+ * A texture per cascade rather than one texture with the cascades stacked along w. Stacking would
200
+ * put a pass's own output and its input in the same texture, which WebGPU rejects — a subresource
201
+ * cannot be bound as writable storage and as a sampled texture at once — and there is no
202
+ * subresource finer than the whole 3D texture to split it on.
203
+ *
204
+ * Always present, even with the mode off the volumes are then dispatched at 1x1x1 (see
205
+ * `GPUViewVolumetrics`), which keeps the graph's topology identical on both sides of the A/B. A
206
+ * pass appearing and disappearing would change resource aliasing and pass scheduling along with the
207
+ * thing being measured, and this subsystem has already produced one confident wrong answer from a
208
+ * measurement where the two sides did not pay the same fixed costs.
209
+ *
210
+ * @param {FrameGraph} graph
211
+ * @param {GPUCameraContext} camera
212
+ * @param {number} participating_media1
213
+ * @param {number} volumetrics_metadata
214
+ * @param {number[]} resolution voxel counts of one cascade
215
+ * @returns {number[]} handles of the cascades, innermost first
216
+ */
217
+ function graph_build_sun_shadow_volume({
218
+ graph,
219
+ camera,
220
+ participating_media1,
221
+ volumetrics_metadata,
222
+ resolution,
223
+ }) {
224
+
225
+ const camera_buffer = graph_import_buffer(graph, camera.buffer.buffer, "volumetrics sun shadow camera");
226
+
227
+ const group_count = [
228
+ // one invocation per COLUMN along the sun axis, so only x and y are dispatched
229
+ Math.ceil(resolution[0] / VOLUMETRICS_SUN_SHADOW_WORKGROUP_RESOLUTION[0]),
230
+ Math.ceil(resolution[1] / VOLUMETRICS_SUN_SHADOW_WORKGROUP_RESOLUTION[1]),
231
+ 1,
232
+ ];
233
+
234
+ const cascades = [];
235
+
236
+ for (let cascade = VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT - 1; cascade >= 0; cascade--) {
237
+
238
+ // Every cascade but the outermost starts its scan from the one outside it — which the
239
+ // descending loop has already added, and which is the one difference the two build shaders
240
+ // exist for.
241
+ const seeded = cascade < VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT - 1;
242
+
243
+ const bindings = {
244
+ // which cascade this dispatch is building; the shader is shared across all of them
245
+ settings: {
246
+ cascade,
247
+ sub_steps: volumetrics_sun_shadow_sub_steps(cascade),
248
+ },
249
+ camera: camera_buffer,
250
+ volumetrics_metadata,
251
+ tParticipatingMedia1: participating_media1,
252
+ tSunShadowVolume: TextureResourceDescriptor.from({
253
+ resolution,
254
+ dimension: "3d",
255
+ format: "rgba16float",
256
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING
257
+ }),
258
+ };
259
+
260
+ if (seeded) {
261
+ bindings.tSunShadowOuter = cascades[cascade + 1];
262
+ }
263
+
264
+ const outputs = graph_compute_pass({
265
+ graph,
266
+ shader: seeded
267
+ ? shader_volumetrics_build_sun_shadow_volume_seeded
268
+ : shader_volumetrics_build_sun_shadow_volume_outermost,
269
+ group_count,
270
+ bindings,
271
+ });
272
+
273
+ cascades[cascade] = outputs.tSunShadowVolume;
274
+ }
275
+
276
+ return cascades;
277
+ }
278
+
279
+ /**
280
+ *
281
+ * @param {FrameGraph} graph
282
+ * @param participating_media0
283
+ * @param participating_media1
284
+ * @param volumetrics_metadata
285
+ * @param light_cluster_data
286
+ * @param light_cluster_lookup
287
+ * @param light_cluster_parameters
288
+ * @param shadowmap_atlas
289
+ * @param sun_transmittance_lut
290
+ * @param sun_multiple_scattering_lut
291
+ * @param {number[]} sun_shadow_cascades handles of the Q-04 optical-depth cascades, innermost first
292
+ * @param lights
293
+ * @param view
294
+ * @param camera
295
+ * @param resolution
296
+ */
297
+ function graph_integrate_light_scattering({
298
+ graph,
299
+ participating_media0,
300
+ participating_media1,
301
+ volumetrics_metadata,
302
+ light_cluster_data,
303
+ light_cluster_lookup,
304
+ light_cluster_parameters,
305
+ shadowmap_atlas,
306
+ sun_transmittance_lut,
307
+ sun_multiple_scattering_lut,
308
+ sun_shadow_cascades,
309
+ lights,
310
+ view,
311
+ camera,
312
+ resolution,
313
+ }) {
314
+
315
+ const data = {};
316
+
317
+ const pass = graph.add("scattering lut", data, (data, resources, context) => {
318
+ const output = resources.get(data.output);
319
+
320
+ const volumetrics_metadata = resources.get(data.volumetrics_metadata);
321
+
322
+ const participating_media0 = resources.get(data.participating_media0);
323
+ const participating_media1 = resources.get(data.participating_media1);
324
+
325
+ const light_cluster_data = resources.get(data.light_cluster_data);
326
+ const light_cluster_lookup = resources.get(data.light_cluster_lookup);
327
+ const light_cluster_parameters = resources.get(data.light_cluster_parameters);
328
+ const shadowmap_atlas = resources.get(data.shadowmap_atlas);
329
+
330
+ const sun_transmittance_lut = resources.get(data.sun_transmittance_lut);
331
+ const sun_multiple_scattering_lut = resources.get(data.sun_multiple_scattering_lut);
332
+ const sun_shadow_cascades = data.sun_shadow_cascades.map(handle => resources.get(handle));
333
+
334
+ const sampler_shadowmap = context.graphics.samplers.obtain(SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR);
335
+ const sLinear = context.graphics.samplers.obtain(SamplerDescriptor.from({
336
+ magFilter: "linear",
337
+ }));
338
+
339
+ // Spatiotemporal blue noise for the per-froxel sample offset. The pass takes one sample and
340
+ // leans on the volumetric TAA to integrate, which only works if each froxel's sequence over
341
+ // frames is well distributed — the property a per-froxel hash does not have.
342
+ const tSTBN_vec3 = context.graphics.textures.obtain(STATIC_GRAPHICS_ENGINE_ASSETS.stbn_vec3);
343
+
344
+ const encoder = context.encoder;
345
+
346
+ const bindings = {
347
+ camera: camera.buffer.buffer,
348
+ view: view.uniform_buffer.buffer,
349
+ volumetrics_metadata,
350
+ sLinear,
351
+ tSunTransmittance: sun_transmittance_lut.obtainView(),
352
+ tMultiScattering: sun_multiple_scattering_lut.obtainView(),
353
+ tSTBN_vec3: tSTBN_vec3.obtainView(),
354
+ tParticipatingMedia0: participating_media0.obtainView(),
355
+ tParticipatingMedia1: participating_media1.obtainView(),
356
+ tLighting: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
357
+ //
358
+ light_data: lights.buffer_data,
359
+
360
+ cluster_data: light_cluster_data,
361
+ cluster_lookup: light_cluster_lookup,
362
+ cluster_parameters: light_cluster_parameters,
363
+
364
+ shadowmapAtlas: shadowmap_atlas.obtainView(),
365
+ sCompareLinear: sampler_shadowmap,
366
+ };
367
+
368
+ // One texture per cascade, under the names the read side declares. Bound here rather than
369
+ // above because the count is a constant of the structure, not of this pass.
370
+ for (let cascade = 0; cascade < sun_shadow_cascades.length; cascade++) {
371
+ bindings[volumetrics_sun_shadow_texture_name(cascade)] = sun_shadow_cascades[cascade].obtainView();
372
+ }
373
+
374
+ shader_volumetrics_build_lighting.dispatch({
375
+ encoder,
376
+ group_count_x: Math.ceil(output.width / VOLUMETRICS_LIGHTING_WORKGROUP_RESOLUTION[0]),
377
+ group_count_y: Math.ceil(output.height / VOLUMETRICS_LIGHTING_WORKGROUP_RESOLUTION[1]),
378
+ group_count_z: Math.ceil(output.depth / VOLUMETRICS_LIGHTING_WORKGROUP_RESOLUTION[2]),
379
+ bindings
380
+ })
381
+ });
382
+
383
+ data.participating_media0 = pass.read(participating_media0);
384
+ data.participating_media1 = pass.read(participating_media1);
385
+ data.volumetrics_metadata = pass.read(volumetrics_metadata);
386
+
387
+ data.shadowmap_atlas = pass.read(shadowmap_atlas);
388
+ data.light_cluster_data = pass.read(light_cluster_data);
389
+ data.light_cluster_lookup = pass.read(light_cluster_lookup);
390
+ data.light_cluster_parameters = pass.read(light_cluster_parameters);
391
+
392
+ data.sun_transmittance_lut = pass.read(sun_transmittance_lut);
393
+ data.sun_multiple_scattering_lut = pass.read(sun_multiple_scattering_lut);
394
+ data.sun_shadow_cascades = sun_shadow_cascades.map(handle => pass.read(handle));
395
+
396
+ data.output = pass.create("Light Scattering LUT", TextureResourceDescriptor.from({
397
+ resolution,
398
+ dimension: "3d",
399
+ format: "rgba16float",
400
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
401
+ }));
402
+
403
+
404
+ return data.output;
405
+
406
+ }
407
+
408
+ /**
409
+ *
410
+ * @param {FrameGraph} graph
411
+ * @param {GPUCameraContext} camera
412
+ * @param {GPULightCollection} lights
413
+ * @param {GPUViewContext} view
414
+ * @param {number} light_cluster_data
415
+ * @param {number} light_cluster_lookup
416
+ * @param {number} light_cluster_parameters
417
+ * @param {number} shadowmap_atlas
418
+ * @param {number[]} volumetric_resolution
419
+ * @param {number[]} sun_shadow_resolution voxel counts of one Q-04 sun optical-depth cascade;
420
+ * `[1, 1, 1]` when the froxels are marching instead, which keeps the passes in the graph without
421
+ * doing any work
422
+ * @param {number} volumetrics_metadata
423
+ * @param {number} [taa_history] imported previous-frame accumulated scattering volume
424
+ * @param {number} [taa_output] imported destination for this frame's accumulated scattering volume
425
+ * @param {GPUCameraContext} [camera_previous] previous-frame camera, for temporal reprojection
426
+ */
427
+ export function graph_build_volumetrics({
428
+ graph,
429
+ camera,
430
+ lights,
431
+ view,
432
+ light_cluster_data,
433
+ light_cluster_lookup,
434
+ light_cluster_parameters,
435
+ shadowmap_atlas,
436
+ volumetric_resolution,
437
+ sun_shadow_resolution,
438
+ volumetrics_metadata,
439
+ taa_history,
440
+ taa_output,
441
+ camera_previous,
442
+ }) {
443
+
444
+ assert.defined(view, 'view');
445
+ assert.defined(volumetric_resolution, 'volumetric_resolution');
446
+ assert.defined(sun_shadow_resolution, 'sun_shadow_resolution');
447
+
448
+ graph.push_scope("build volumetrics");
449
+
450
+ const { transmittance_lut, multiscatted_lut } = graph_integrate_sky({ graph: graph });
451
+
452
+ const {
453
+ participating_media0,
454
+ participating_media1,
455
+ } = graph_integrate_participating_media({
456
+ graph,
457
+ scene: view.scene,
458
+ view,
459
+ resolution: volumetric_resolution,
460
+ volumetrics_metadata,
461
+ camera,
462
+ });
463
+
464
+ const sun_shadow_cascades = graph_build_sun_shadow_volume({
465
+ graph,
466
+ camera,
467
+ participating_media1,
468
+ volumetrics_metadata,
469
+ resolution: sun_shadow_resolution,
470
+ });
471
+
472
+ const light_scattering_lut = graph_integrate_light_scattering({
473
+ graph,
474
+ resolution: volumetric_resolution,
475
+ camera,
476
+ view,
477
+ sun_transmittance_lut: transmittance_lut,
478
+ sun_multiple_scattering_lut: multiscatted_lut,
479
+ sun_shadow_cascades,
480
+ participating_media0,
481
+ participating_media1,
482
+ volumetrics_metadata,
483
+ light_cluster_data,
484
+ light_cluster_lookup,
485
+ light_cluster_parameters,
486
+ shadowmap_atlas,
487
+ lights,
488
+ })
489
+
490
+ // Temporal reprojection of the per-froxel lit scattering volume, applied BEFORE aerial
491
+ // integration — reprojecting the already-integrated aerial LUT would double-integrate along the
492
+ // view ray. Optional: skipped when no history ping-pong is supplied (TAA disabled), in which
493
+ // case the aerial pass consumes the raw (noisier) scattering LUT directly.
494
+ let volume_lighting_resolved = light_scattering_lut;
495
+
496
+ if (taa_history !== undefined && taa_output !== undefined) {
497
+ const taa_outputs = graph_compute_pass({
498
+ graph,
499
+ shader: shader_volumetrics_taa,
500
+ group_count: [
501
+ Math.ceil(volumetric_resolution[0] / 8),
502
+ Math.ceil(volumetric_resolution[1] / 8),
503
+ Math.ceil(volumetric_resolution[2] / 4),
504
+ ],
505
+ bindings: {
506
+ t_current: light_scattering_lut,
507
+ t_history: taa_history,
508
+ t_output: taa_output,
509
+ volumetrics_metadata,
510
+ camera_current: graph_import_buffer(graph, camera.buffer.buffer, "volumetrics taa camera"),
511
+ camera_previous: graph_import_buffer(graph, camera_previous.buffer.buffer, "volumetrics taa camera previous"),
512
+ },
513
+ });
514
+
515
+ volume_lighting_resolved = taa_outputs.t_output;
516
+ }
517
+
518
+ const a_data = {};
519
+
520
+ const a_pass = graph.add("aerial lut", a_data, (data, resources, context) => {
521
+ const output = resources.get(data.output);
522
+
523
+ const participating_media0 = resources.get(data.participating_media0);
524
+ const participating_media1 = resources.get(data.participating_media1);
525
+ const volumetrics_metadata = resources.get(data.volumetrics_metadata);
526
+
527
+ const light_scattering_lut = resources.get(data.light_scattering_lut);
528
+ const sun_transmittance_lut = resources.get(data.sun_transmittance_lut);
529
+
530
+ const encoder = context.encoder;
531
+
532
+
533
+ const graphics = context.graphics;
534
+
535
+ const sLinear = graphics.samplers.obtain(SamplerDescriptor.from({
536
+ magFilter: "linear",
537
+ }));
538
+
539
+ const tSTBN_vec3 = graphics.textures.obtain(STATIC_GRAPHICS_ENGINE_ASSETS.stbn_vec3);
540
+
541
+ shader_volumetrics_build_scattering_lut.dispatch({
542
+ encoder,
543
+ group_count_x: Math.ceil(output.width / 16),
544
+ group_count_y: Math.ceil(output.height / 16),
545
+ bindings: {
546
+ camera: camera.buffer.buffer,
547
+ view: view.uniform_buffer.buffer,
548
+ volumetrics_metadata,
549
+ tSTBN_vec3: tSTBN_vec3.obtainView(),
550
+ sLinear,
551
+
552
+ tVolumeLighting: light_scattering_lut.obtainView(),
553
+ tParticipatingMedia0: participating_media0.obtainView(),
554
+ tParticipatingMedia1: participating_media1.obtainView(),
555
+
556
+ // RDR2-style far-field march inputs (used beyond froxel_far)
557
+ scene_volumetrics_metadata: view.scene.volumetrics.metadata_buffer.buffer,
558
+ scene_volumes: view.scene.volumetrics.table.buffer,
559
+ light_data: lights.buffer_data,
560
+ tSunTransmittance: sun_transmittance_lut.obtainView(),
561
+
562
+ tOutput: output.obtainView(DEFAULT_RENDER_TARGET_VIEW_DESCRIPTOR),
563
+ }
564
+ });
565
+
566
+
567
+ });
568
+
569
+ a_data.participating_media0 = a_pass.read(participating_media0);
570
+ a_data.participating_media1 = a_pass.read(participating_media1);
571
+ a_data.volumetrics_metadata = a_pass.read(volumetrics_metadata);
572
+ a_data.sun_transmittance_lut = a_pass.read(transmittance_lut);
573
+
574
+ a_data.output = a_pass.create("Integrated Volumetrics", TextureResourceDescriptor.from({
575
+ resolution: volumetric_resolution,
576
+ dimension: "3d",
577
+ format: "rgba16float",
578
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING
579
+ }))
580
+
581
+
582
+ a_data.light_scattering_lut = a_pass.read(volume_lighting_resolved);
583
+
584
+
585
+ // TODO add temporal reprojection
586
+
587
+ graph.pop_scope();
588
+
589
+ return {
590
+ lut_transmittance: transmittance_lut,
591
+ lut_multiple_scattering: multiscatted_lut,
592
+ lut_aerial: a_data.output,
593
+ volumetrics_metadata,
594
+ };
578
595
  }