@woosh/meep-engine 3.42.0 → 3.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/package.json +91 -96
  2. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  3. package/src/shade/playground/vgeo_runtime/README.md +5 -4
  4. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  5. package/src/shade/renderer/Renderer.js +6 -5
  6. package/src/shade/renderer/atmosphere/GPUSky.d.ts +12 -0
  7. package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
  8. package/src/shade/renderer/atmosphere/GPUSky.js +162 -147
  9. package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.d.ts.map +1 -1
  10. package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.js +10 -3
  11. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  12. package/src/shade/renderer/particles/DESIGN.md +13 -9
  13. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +1 -1
  14. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +1 -1
  15. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  16. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +3 -2
  17. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  18. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +3 -2
  19. package/src/shade/renderer/scene/GPUSceneContext.d.ts +9 -1
  20. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  21. package/src/shade/renderer/scene/GPUSceneContext.js +23 -1
  22. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +4 -1
  23. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  24. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +74 -3
  25. package/src/shade/renderer/volumetrics/NOTES.md +1023 -957
  26. package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts +5 -4
  27. package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts.map +1 -1
  28. package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.js +5 -4
  29. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +1 -1
  30. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +1 -1
  31. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +1 -1
  32. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +1 -1
  33. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +41 -0
  34. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +1 -0
  35. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +112 -0
  36. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +9 -23
  37. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  38. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +18 -126
  39. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +22 -0
  40. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +1 -0
  41. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +228 -0
  42. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  43. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +0 -53
  44. package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts +4 -3
  45. package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts.map +1 -1
  46. package/src/shade/renderer/water/surface/chunk_water_surface_sample.js +17 -7
  47. package/src/shade/renderer/water/surface/particles/GPUWaterParticles.d.ts.map +1 -1
  48. package/src/shade/renderer/water/surface/particles/GPUWaterParticles.js +69 -23
  49. package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.d.ts.map +1 -1
  50. package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.js +13 -5
  51. package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts +29 -8
  52. package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts.map +1 -1
  53. package/src/shade/renderer/water/surface/particles/chunk_water_particle.js +155 -30
  54. package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts +6 -1
  55. package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts.map +1 -1
  56. package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.js +157 -39
  57. package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts +91 -21
  58. package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts.map +1 -1
  59. package/src/shade/renderer/water/surface/particles/shader_water_particles.js +442 -70
  60. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +11 -3
  61. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  62. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +3 -0
@@ -0,0 +1,228 @@
1
+ import { CAMERA_UNIFORM_STRUCT } from "../camera/CAMERA_UNIFORM_STRUCT.js";
2
+ import { chunk_light_cluster_counts } from "../light/cluster/chunk_light_cluster_counts.js";
3
+ import { chunk_light_cluster_metadata_by_position } from "../light/cluster/chunk_light_cluster_metadata_by_position.js";
4
+ import { LIGHT_CLUSTER_LOOKUP_STRUCT } from "../light/cluster/shader_cluster_assign_lights.js";
5
+ import { chunk_cluster_depth_from_z_slice } from "../light/cluster/chunk_cluster_depth_from_z_slice.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 { chunk_stbn_sample_vec3 } from "../shader/chunk/noise/chunk_stbn_sample_vec3.js";
9
+ import {
10
+ chunk_directional_lights_iteration_mask
11
+ } from "../shader/chunk/light/io/chunk_directional_lights_iteration_mask.js";
12
+ import {
13
+ chunk_get_directional_light_info_by_index
14
+ } from "../shader/chunk/light/io/chunk_get_directional_light_info_by_index.js";
15
+ import {
16
+ chunk_get_point_light_info_by_index
17
+ } from "../shader/chunk/light/io/chunk_get_point_light_info_by_index.js";
18
+ import { chunk_get_spot_light_info_by_index } from "../shader/chunk/light/io/chunk_get_spot_light_info_by_index.js";
19
+ import {
20
+ METRO_L1_IRRADIANCE_STRUCT
21
+ } from "../shader/chunk/spherical_harmonics/l1i/chunk_convert_to_irradiance_sh.js";
22
+ import { chunk_texel_coordinate_to_uv } from "../shader/chunk/texture/chunk_texel_coordinate_to_uv.js";
23
+ import { chunk_uv_to_texel_coordinate } from "../shader/chunk/texture/chunk_uv_to_texel_coordinate.js";
24
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
25
+ import { ComputeShader } from "../shader/ComputeShader.js";
26
+ import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
27
+ import { vec3f } from "../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
28
+ import { WebGPUArray } from "../shader/type/WebGPUArray.js";
29
+ import { chunk_shadowmap_get_light_visibility } from "../shadow/map/shader/chunk_shadowmap_get_light_visibility.js";
30
+ import { SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR } from "../shadow/map/SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR.js";
31
+ import { VIEW_UNIFORM_STRUCT } from "../view/VIEW_UNIFORM_STRUCT.js";
32
+ import { chunk_volumetrics_irradiance_sh } from "./chunk_volumetrics_irradiance_sh.js";
33
+ import { chunk_volumetrics_local_light_shadow } from "./chunk_volumetrics_local_light_shadow.js";
34
+ import { chunk_camera_ray_planar_depth_scale } from "./chunk_camera_ray_planar_depth_scale.js";
35
+ import { chunk_volumetrics_column_jitter } from "./chunk_volumetrics_column_jitter.js";
36
+ import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
37
+
38
+ /**
39
+ * Workgroup shape of this pass, `[x, y, z]` in froxels, stated to the `@workgroup_size` below and
40
+ * read back out of it by the dispatch. Flat in z and 64 invocations, for the reasons the fog's
41
+ * lighting pass gives (`VOLUMETRICS_LIGHTING_WORKGROUP_RESOLUTION`): it walks the same light
42
+ * clusters, and those are farthest apart along depth.
43
+ *
44
+ * @type {number[]}
45
+ */
46
+ export const VOLUMETRICS_IRRADIANCE_WORKGROUP_RESOLUTION = [8, 8, 1];
47
+
48
+ const resources = new ShaderResourceSetDescriptor();
49
+
50
+ // The froxel irradiance volume (chunk_volumetrics_irradiance_sh): luma SH2 in one, chroma in the .rg
51
+ // of the other. rgba16float for both, because rg16float is not a storage format without
52
+ // texture-formats-tier1. It starts from what the froxel GI stage (gi/) wrote to tGIY / tGICoCg.
53
+ resources.createGroup()
54
+ .addUniform("volumetrics_metadata", VOLUMETRICS_METADATA_STRUCT)
55
+ .addUniform("camera", CAMERA_UNIFORM_STRUCT)
56
+ .addUniform("view", VIEW_UNIFORM_STRUCT)
57
+ .addTexture("tSTBN_vec3", "unfilterable-float", "3d")
58
+ .addTexture("tGIY", "float", "3d")
59
+ .addTexture("tGICoCg", "float", "3d")
60
+ .addStorageTexture("tIrradianceY", "rgba16float", "3d", "write-only")
61
+ .addStorageTexture("tIrradianceCoCg", "rgba16float", "3d", "write-only");
62
+
63
+ // Lighting information
64
+ resources.createGroup()
65
+ .addStorageBuffer("light_data", WebGPUArray.u32)
66
+ // clusters
67
+ .addUniform("cluster_parameters", vec3f)
68
+ .addStorageBuffer("cluster_lookup", LIGHT_CLUSTER_LOOKUP_STRUCT)
69
+ .addStorageBuffer("cluster_data", WebGPUArray.u32)
70
+ // shadows
71
+ .addTexture("shadowmapAtlas", "depth")
72
+ .addSampler("sCompareLinear", "comparison", SHADOWMAP_COMPARE_SAMPLER_DESCRIPTOR);
73
+
74
+ const body = CodeChunk.from(
75
+ //language=WGSL
76
+ `
77
+ @compute @workgroup_size(${VOLUMETRICS_IRRADIANCE_WORKGROUP_RESOLUTION.join(",")})
78
+ fn main(
79
+ @builtin(global_invocation_id) voxel : vec3<u32>
80
+ ){
81
+ let resolution = textureDimensions(tIrradianceY);
82
+
83
+ if (any(voxel >= resolution)) {
84
+ return;
85
+ }
86
+
87
+ // The fog lighting pass's sample, taken the same way (shader_volumetrics_build_lighting, where
88
+ // the irradiance was written until it had a pass of its own): one point per froxel per frame,
89
+ // jittered across the slab in depth by the column jitter and the blue noise, and in xy by the
90
+ // blue noise. Never a fixed point. A particle spans many froxels and reads them trilinearly, and
91
+ // a lattice of samples that never moves shows on it as seams wherever neighbouring texels
92
+ // disagree — beside a light, along a shadow edge. Moved every frame, the lattice is resolved by
93
+ // the main TAA, which the particles are drawn ahead of.
94
+ let near_far = camera.clip_near_far;
95
+ let curve = volumetrics_metadata.froxel_cluster_parameters;
96
+
97
+ let z_jitter = volumetrics_column_jitter(voxel.xy, view.frame_index);
98
+
99
+ let z0 = max(near_far.x, cluster_depth_from_z_slice(f32(voxel.z) + z_jitter, curve, f32(resolution.z)));
100
+ let z1 = min(near_far.y, cluster_depth_from_z_slice(f32(voxel.z + 1u) + z_jitter, curve, f32(resolution.z)));
101
+
102
+ // the lighting pass's offset and temporal axis, so the two passes ask about the same point
103
+ const STBN_DECORRELATION_OFFSET = vec2<u32>(37u, 71u);
104
+
105
+ let noise = stbn_sample_vec3(vec3(voxel.xy + STBN_DECORRELATION_OFFSET, view.frame_index + voxel.z));
106
+
107
+ // interpolate the cubes, then take the cube root: uniform in world-space volume across the slab
108
+ let view_depth = pow(mix(z0 * z0 * z0, z1 * z1 * z1, noise.z), 0.333333);
109
+
110
+ let tile_uv = saturate(texel_coordinate_to_uv(vec2<f32>(voxel.xy) + (noise.xy - 0.5), resolution.xy));
111
+ let view_ray = camera_ray_from_uv(tile_uv, camera);
112
+
113
+ // view_depth is planar, so step the ray by the planar-to-distance scale
114
+ let position_ws = view_ray.origin
115
+ + view_ray.direction * (view_depth * camera_ray_planar_depth_scale(view_ray.direction, camera));
116
+
117
+ let view_pixel_coord = uv_to_texel_coordinate(tile_uv, vec2(view.width, view.height));
118
+
119
+ var irradiance: ${METRO_L1_IRRADIANCE_STRUCT.wgsl_ref};
120
+
121
+ // Each light as the surfaces around the froxel receive it: its colour and its shadow, and no
122
+ // medium on the way in. The deferred and forward paths light meshes by exactly this, and a lit
123
+ // particle beside a lit log has to agree with it — the fog is composited over both afterwards.
124
+
125
+ var directional_bits = directional_lights_iteration_mask(&light_data);
126
+ while (directional_bits != 0u) {
127
+ let directional_index = countTrailingZeros(directional_bits);
128
+ directional_bits &= ~(1u << directional_index);
129
+
130
+ let sun = get_directional_light_info_by_index(&light_data, directional_index);
131
+
132
+ let shadow_term = shadowmap_get_directional_light_visibility(
133
+ &light_data,
134
+ directional_index,
135
+ position_ws,
136
+ // toward-camera, the deferred cascade-blend convention; view_ray runs camera->scene
137
+ -view_ray.direction,
138
+ sun.direction
139
+ );
140
+
141
+ irradiance = volumetrics_irradiance_sh_add(irradiance, shadow_term * sun.color, sun.direction);
142
+ }
143
+
144
+ let cluster_metadata = light_cluster_metadata_by_position(
145
+ view_pixel_coord,
146
+ view_depth,
147
+ vec2(view.width, view.height)
148
+ );
149
+
150
+ let cluster_offset = cluster_metadata.offset;
151
+ let point_count = light_cluster_count_point(cluster_metadata.counts);
152
+ let spot_count = light_cluster_count_spot(cluster_metadata.counts);
153
+
154
+ for (var j = 0u; j < point_count; j++) {
155
+ let light_index = cluster_data[cluster_offset + j];
156
+ let direct_light = get_point_light_info_by_index(&light_data, light_index, position_ws);
157
+
158
+ if (all(direct_light.color == vec3(0.0))) {
159
+ continue;
160
+ }
161
+
162
+ let shadow_term = volumetrics_point_light_shadow(&light_data, light_index, position_ws);
163
+
164
+ irradiance = volumetrics_irradiance_sh_add(irradiance, shadow_term * direct_light.color, direct_light.direction);
165
+ }
166
+
167
+ for (var k = 0u; k < spot_count; k++) {
168
+ let light_index = cluster_data[cluster_offset + point_count + k];
169
+ let direct_light = get_spot_light_info_by_index(&light_data, light_index, position_ws);
170
+
171
+ if (all(direct_light.color == vec3(0.0))) {
172
+ continue;
173
+ }
174
+
175
+ let shadow_term = volumetrics_spot_light_shadow(&light_data, light_index, position_ws);
176
+
177
+ irradiance = volumetrics_irradiance_sh_add(irradiance, shadow_term * direct_light.color, direct_light.direction);
178
+ }
179
+
180
+ // What the renderer's indirect lighting gives this froxel, in the same encoding; the lights add
181
+ // to it. Written by the froxel GI module the mode selects, which is all this pass knows of GI.
182
+ irradiance.Y += textureLoad(tGIY, voxel, 0);
183
+ irradiance.CoCg += textureLoad(tGICoCg, voxel, 0).xy;
184
+
185
+ // nothing filters this volume, so nothing downstream would stop an Inf or a NaN here
186
+ let stored = volumetrics_irradiance_sh_storable(irradiance);
187
+
188
+ textureStore(tIrradianceY, voxel, stored.Y);
189
+ textureStore(tIrradianceCoCg, voxel, vec4<f32>(stored.CoCg, 0.0, 0.0));
190
+ }
191
+ `, [
192
+ METRO_L1_IRRADIANCE_STRUCT.declaration_chunk,
193
+ RAY_STRUCT.declaration_chunk,
194
+ chunk_volumetrics_column_jitter,
195
+ chunk_cluster_depth_from_z_slice,
196
+ chunk_stbn_sample_vec3,
197
+ chunk_texel_coordinate_to_uv,
198
+ chunk_uv_to_texel_coordinate,
199
+ chunk_camera_ray_from_uv,
200
+ chunk_camera_ray_planar_depth_scale,
201
+
202
+ chunk_light_cluster_metadata_by_position,
203
+ chunk_light_cluster_counts,
204
+ chunk_directional_lights_iteration_mask,
205
+ chunk_get_directional_light_info_by_index,
206
+ chunk_get_point_light_info_by_index,
207
+ chunk_get_spot_light_info_by_index,
208
+ chunk_shadowmap_get_light_visibility,
209
+ chunk_volumetrics_local_light_shadow,
210
+ chunk_volumetrics_irradiance_sh,
211
+ ]
212
+ );
213
+
214
+ /**
215
+ * The froxel irradiance volume (`chunk_volumetrics_irradiance_sh`): what a lit particle standing in a
216
+ * froxel is lit by. Starts from the froxel GI stage's volume and adds every directional, point and
217
+ * spot light with its shadow, at a point jittered through the froxel every frame.
218
+ *
219
+ * Its own pass, not a by-product of the fog's lighting pass: particle lighting needs no medium, so it
220
+ * runs whether or not the scene has any fog, and the frame graph culls it when nothing reads it.
221
+ *
222
+ * @type {ComputeShader}
223
+ */
224
+ export const shader_volumetrics_build_irradiance = ComputeShader.from({
225
+ label: "Volumetrics / build irradiance",
226
+ resources,
227
+ body,
228
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"shader_volumetrics_build_lighting.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;;;GAeG;AACH,wDAFU,MAAM,EAAE,CAEiD;AAkanE,8DAIE;8BAnd4B,4BAA4B"}
1
+ {"version":3,"file":"shader_volumetrics_build_lighting.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js"],"names":[],"mappings":"AA6CA;;;;;;;;;;;;;;;GAeG;AACH,wDAFU,MAAM,EAAE,CAEiD;AAiXnE,8DAIE;8BA9Z4B,4BAA4B"}
@@ -41,10 +41,6 @@ import {
41
41
  import { VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT } from "./VOLUMETRICS_SUN_SHADOW_RESOLUTION.js";
42
42
  import { chunk_volumetrics_column_jitter } from "./chunk_volumetrics_column_jitter.js";
43
43
  import { chunk_volumetrics_local_light_shadow } from "./chunk_volumetrics_local_light_shadow.js";
44
- import { chunk_volumetrics_irradiance_sh } from "./chunk_volumetrics_irradiance_sh.js";
45
- import {
46
- METRO_L1_IRRADIANCE_STRUCT
47
- } from "../shader/chunk/spherical_harmonics/l1i/chunk_convert_to_irradiance_sh.js";
48
44
  import { VOLUMETRICS_METADATA_STRUCT } from "./VOLUMETRICS_METADATA_STRUCT.js";
49
45
 
50
46
  /**
@@ -87,14 +83,6 @@ for (let cascade = 0; cascade < VOLUMETRICS_SUN_SHADOW_CASCADE_COUNT; cascade++)
87
83
 
88
84
  primary_group.addStorageTexture("tLighting", "rgba16float", "3d", "write-only")
89
85
 
90
- // The froxel irradiance volume (chunk_volumetrics_irradiance_sh): luma SH2 in one, chroma in the
91
- // .rg of the other. rgba16float for both, because rg16float is not a storage format without
92
- // texture-formats-tier1. It is what the froxel GI stage (gi/) wrote to tGIY / tGICoCg, plus the lights.
93
- primary_group.addTexture("tGIY", "float", "3d")
94
- primary_group.addTexture("tGICoCg", "float", "3d")
95
- primary_group.addStorageTexture("tIrradianceY", "rgba16float", "3d", "write-only")
96
- primary_group.addStorageTexture("tIrradianceCoCg", "rgba16float", "3d", "write-only")
97
-
98
86
  // Lighting information
99
87
  resources.createGroup()
100
88
 
@@ -144,11 +132,6 @@ fn main(
144
132
  var inScatteringSum = vec3<f32>(0.0);
145
133
  var extinctionSum = 0.0;
146
134
 
147
- // What a transparent surface at the sample receives from the same lights: their shadowed
148
- // colour, attenuated by the medium toward each light, before any scattering by it.
149
- var irradianceSum: ${METRO_L1_IRRADIANCE_STRUCT.wgsl_ref};
150
-
151
-
152
135
  // One sample per froxel. The 0.95-blend TAA downstream is a ~39-effective-sample temporal
153
136
  // integrator; supersampling here as well spends N times this pass removing variance that filter
154
137
  // removes anyway — and spends it at the Monte Carlo rate, because the samples were independent
@@ -313,15 +296,6 @@ fn main(
313
296
  );
314
297
 
315
298
  inScatteringSum += shadow_term * ms * transmittance_atmospheric * sun.color;
316
-
317
- // No atmospheric transmittance: that is the air's, and a surface in the volume is lit
318
- // the way the surfaces around it are — the deferred and forward paths light meshes by
319
- // sun.color as it stands, and a lit particle beside a lit log has to agree with it.
320
- irradianceSum = volumetrics_irradiance_sh_add(
321
- irradianceSum,
322
- shadow_term * exp(-optical_depth) * sun.color,
323
- sun.direction
324
- );
325
299
  }
326
300
 
327
301
  // Local lights
@@ -374,12 +348,6 @@ fn main(
374
348
  let shadow_term = volumetrics_point_light_shadow(&light_data, light_index, position_ws);
375
349
 
376
350
  inScatteringSum += shadow_term * ms * direct_light.color;
377
-
378
- irradianceSum = volumetrics_irradiance_sh_add(
379
- irradianceSum,
380
- shadow_term * exp(-optical_depth) * direct_light.color,
381
- direct_light.direction
382
- );
383
351
  }
384
352
 
385
353
  for (var k = 0u; k < spot_count; k++) {
@@ -415,12 +383,6 @@ fn main(
415
383
  let shadow_term = volumetrics_spot_light_shadow(&light_data, light_index, position_ws);
416
384
 
417
385
  inScatteringSum += shadow_term * ms * direct_light.color;
418
-
419
- irradianceSum = volumetrics_irradiance_sh_add(
420
- irradianceSum,
421
- shadow_term * exp(-optical_depth) * direct_light.color,
422
- direct_light.direction
423
- );
424
386
  }
425
387
  }
426
388
 
@@ -435,20 +397,6 @@ fn main(
435
397
  let extinction = extinctionSum / f32(SAMPLE_COUNT);
436
398
 
437
399
  textureStore(tLighting, voxel_coord, vec4<f32>(inScattering, extinction));
438
-
439
- irradianceSum.Y /= f32(SAMPLE_COUNT);
440
- irradianceSum.CoCg /= f32(SAMPLE_COUNT);
441
-
442
- // What the renderer's indirect lighting gives this froxel, in the same encoding; the lights add
443
- // to it. Written by the froxel GI module the mode selects, which is all this pass knows of GI.
444
- irradianceSum.Y += textureLoad(tGIY, voxel_coord, 0);
445
- irradianceSum.CoCg += textureLoad(tGICoCg, voxel_coord, 0).xy;
446
-
447
- // nothing TAA-filters this volume, so nothing downstream would stop an Inf or a NaN here
448
- let irradiance = volumetrics_irradiance_sh_storable(irradianceSum);
449
-
450
- textureStore(tIrradianceY, voxel_coord, irradiance.Y);
451
- textureStore(tIrradianceCoCg, voxel_coord, vec4<f32>(irradiance.CoCg, 0.0, 0.0));
452
400
  }
453
401
  `, [
454
402
  chunk_cluster_depth_from_z_slice,
@@ -469,7 +417,6 @@ fn main(
469
417
  chunk_get_spot_light_info_by_index,
470
418
  chunk_shadowmap_get_light_visibility,
471
419
  chunk_volumetrics_local_light_shadow,
472
- chunk_volumetrics_irradiance_sh,
473
420
  chunk_sky_lut_compute_uv,
474
421
 
475
422
  RAY_STRUCT.declaration_chunk,
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Most particle entries one point reads, over every level. The bin pass thins a crowded cell to about
3
- * WATER_PARTICLE_CELL_SHARE packets, the same ones in every cell they reach, so a point reads all of its cells
4
- * unless several levels are crowded there at once.
2
+ * Most particle entries one point reads, over every level. Where a point would read more than
3
+ * WATER_PARTICLE_CELL_SHARE, the bin pass thins its cells to about that many between them, the same packets in every
4
+ * cell they reach, and where whole fronts would still crowd it past this, its cells keep no more than their part of
5
+ * it. So a point reads all of its cells, short of a cell only one front reaches (~40 entries at most).
5
6
  */
6
7
  export const WATER_PARTICLE_GATHER_BUDGET: 64;
7
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_water_surface_sample.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/water/surface/chunk_water_surface_sample.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,8CAA+C;AAE/C;;;;;;;;;;;;GAYG;AACH,yCAFU,SAAS,CAgMyC;0BAxNlC,oCAAoC"}
1
+ {"version":3,"file":"chunk_water_surface_sample.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/water/surface/chunk_water_surface_sample.js"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,8CAA+C;AAE/C;;;;;;;;;;;;GAYG;AACH,yCAFU,SAAS,CAyMyC;0BAlOlC,oCAAoC"}
@@ -5,9 +5,10 @@ import { chunk_water_spectrum } from "./spectrum/chunk_water_spectrum.js";
5
5
  import { chunk_water_particle } from "./particles/chunk_water_particle.js";
6
6
 
7
7
  /**
8
- * Most particle entries one point reads, over every level. The bin pass thins a crowded cell to about
9
- * WATER_PARTICLE_CELL_SHARE packets, the same ones in every cell they reach, so a point reads all of its cells
10
- * unless several levels are crowded there at once.
8
+ * Most particle entries one point reads, over every level. Where a point would read more than
9
+ * WATER_PARTICLE_CELL_SHARE, the bin pass thins its cells to about that many between them, the same packets in every
10
+ * cell they reach, and where whole fronts would still crowd it past this, its cells keep no more than their part of
11
+ * it. So a point reads all of its cells, short of a cell only one front reaches (~40 entries at most).
11
12
  */
12
13
  export const WATER_PARTICLE_GATHER_BUDGET = 64;
13
14
 
@@ -113,14 +114,17 @@ fn water_surface_sum(wave: u32, p: vec2<f32>, footprint: f32) -> WaterSurfaceSum
113
114
  }
114
115
  let table = water_particle_offsets.count;
115
116
  var visited = 0u;
116
- var particle_variance = 0.0;
117
+ // the slope of what the footprint cannot draw of the packets and the same a quarter carrier cycle on, summed as the
118
+ // surface sums them, so a front's packets roughen it alike however many they are
119
+ var unresolved = vec2<f32>(0.0);
120
+ var quadrature = vec2<f32>(0.0);
117
121
  // coarsest first: the budget goes to packets the footprint can draw before those it can only roughen
118
122
  for (var from_top = 0u; from_top < WATER_PARTICLE_LEVELS && table != 0u; from_top++) {
119
123
  let level = WATER_PARTICLE_LEVELS - 1u - from_top;
120
124
  if ((levels & (1u << level)) == 0u) { continue; }
121
125
  let size = water_particle_cell_size(level);
122
126
  let cell = vec2<i32>(floor(p / size));
123
- let key = (wave << 4u) | level;
127
+ let key = water_particle_key(wave, level);
124
128
  let bucket = water_particle_bucket(wave, level, cell, table);
125
129
  var start = 0u;
126
130
  if (bucket > 0u) { start = water_particle_offsets.elements[bucket - 1u]; }
@@ -129,16 +133,22 @@ fn water_surface_sum(wave: u32, p: vec2<f32>, footprint: f32) -> WaterSurfaceSum
129
133
  visited++;
130
134
  let entry = water_particle_entries[i];
131
135
  if (entry.key != key || any(entry.cell != cell)) { continue; }
132
- let field = water_particle_field(p - entry.position, entry.angle, entry.amplitude, entry.radius, entry.width, entry.phase);
136
+ let field = water_particle_field(p - entry.position, entry.angle, entry.amplitude, entry.radius, entry.half_length,
137
+ entry.dispersion, entry.distance, entry.phase);
133
138
  // a packet is geometry while its wavelength spans eight samples, fading out by four
134
139
  let f = clamp(log2(entry.radius / (2.0 * footprint)), 0.0, 1.0);
135
- particle_variance += (1.0 - f * f) * dot(field.slope, field.slope);
140
+ let rough = sqrt(1.0 - f * f);
141
+ unresolved += rough * field.slope;
142
+ quadrature += rough * field.displacement_gradient;
136
143
  sum.height += f * field.height;
137
144
  sum.slope += f * field.slope;
138
145
  sum.displacement += f * field.displacement;
139
146
  sum.jacobian += f * field.jacobian;
140
147
  }
141
148
  }
149
+ // the slope's mean square over a carrier cycle, as the bands' variance is (Bruneton, Neyret & Holzschuch 2010): at a
150
+ // point the slope alone swings from nothing to twice it with the carrier
151
+ let particle_variance = 0.5 * (dot(unresolved, unresolved) + dot(quadrature, quadrature));
142
152
  sum.variance = variance + particle_variance;
143
153
  sum.particle_variance = particle_variance;
144
154
  return sum;
@@ -1 +1 @@
1
- {"version":3,"file":"GPUWaterParticles.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/GPUWaterParticles.js"],"names":[],"mappings":"AAuBA,kHAAkH;AAClH,+CAAgD;AAUhD;;;;GAIG;AACH;IAkBI,uFAAuF;IACvF,yBAAuC;IACvC,kGAAkG;IAClG,yBAAuC;IACvC,uGAAuG;IACvG,6BAA+C;IAC/C,mEAAmE;IACnE,2BAAyD;IACzD,uBAAyC;IAEzC;;;;;;;;;;;OAWG;IACH,qCALW,MAAM,QACN,MAAM,UACK,OAAO,8CAA8C,EAAE,sBAAsB,KAAE,IAAI,GAAC,IAAI,CA4C7G;IAED,gBAWC;IAED;;;;;;;;;;;;;;;OAeG;IACH;QATyG,KAAK,EAAnG,OAAO,iEAAiE,EAAE,UAAU;QACpE,KAAK,EAArB,MAAM;QACa,KAAK;QACL,QAAQ;QACR,SAAS;QACT,QAAQ;QACX,YAAY,EAA5B,MAAM;QACU,EAAE,EAAlB,MAAM;aA6EhB;;CACJ"}
1
+ {"version":3,"file":"GPUWaterParticles.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/GPUWaterParticles.js"],"names":[],"mappings":"AA+BA,kHAAkH;AAClH,+CAAgD;AAUhD;;;;GAIG;AACH;IAuBI,uFAAuF;IACvF,yBAAuC;IACvC,kGAAkG;IAClG,yBAAuC;IACvC,uGAAuG;IACvG,6BAA+C;IAC/C,mEAAmE;IACnE,2BAAyD;IACzD,uBAAyC;IAEzC;;;;;;;;;;;OAWG;IACH,qCALW,MAAM,QACN,MAAM,UACK,OAAO,8CAA8C,EAAE,sBAAsB,KAAE,IAAI,GAAC,IAAI,CAuD7G;IAED,gBAcC;IAED;;;;;;;;;;;;;;;OAeG;IACH;QATyG,KAAK,EAAnG,OAAO,iEAAiE,EAAE,UAAU;QACpE,KAAK,EAArB,MAAM;QACa,KAAK;QACL,QAAQ;QACR,SAAS;QACT,QAAQ;QACX,YAAY,EAA5B,MAAM;QACU,EAAE,EAAlB,MAAM;aAgGhB;;CACJ"}
@@ -10,15 +10,23 @@ import { WATER_PARTICLE_ENTRY_STRUCT, WATER_PARTICLE_STRUCT } from "./WATER_PART
10
10
  import {
11
11
  shader_water_particles_bin,
12
12
  shader_water_particles_bounds,
13
+ shader_water_particles_clear,
13
14
  shader_water_particles_emit,
14
15
  shader_water_particles_finalize,
16
+ shader_water_particles_fronts,
17
+ shader_water_particles_groups,
18
+ shader_water_particles_nest,
15
19
  shader_water_particles_rank,
20
+ shader_water_particles_runs,
16
21
  shader_water_particles_scatter,
22
+ shader_water_particles_share,
17
23
  shader_water_particles_split,
18
24
  shader_water_particles_total,
19
25
  shader_water_particles_update,
20
26
  shader_water_particles_weigh,
21
- WATER_PARTICLE_CORNERS
27
+ WATER_PARTICLE_CORNERS,
28
+ WATER_PARTICLE_CROWD_WORDS,
29
+ WATER_PARTICLE_NEST_WORDS
22
30
  } from "./shader_water_particles.js";
23
31
 
24
32
  /** The longest step a particle takes in one frame, seconds: a stalled tab does not fling fronts across a lake. */
@@ -45,10 +53,15 @@ export class GPUWaterParticles {
45
53
  #parity = 0;
46
54
  #particles = [];
47
55
  #children = null;
56
+ #images = null;
57
+ #fronts = null;
58
+ #runs = null;
59
+ #draw = null;
48
60
  #buckets = null;
49
61
  #slots = null;
50
62
  #bucket_amplitude = null;
51
63
  #bucket_weight = null;
64
+ #bucket_nest = null;
52
65
  #visible = null;
53
66
  #staging = null;
54
67
  #entries = null;
@@ -90,15 +103,26 @@ export class GPUWaterParticles {
90
103
  const array_size = 16 + capacity * WATER_PARTICLE_STRUCT.size;
91
104
  this.#particles = [buffer('a', array_size), buffer('b', array_size)];
92
105
  this.#children = buffer('children', 16 + capacity * 4);
106
+ // per particle the mirror image the step left beside it at the outline
107
+ this.#images = buffer('images', capacity * WATER_PARTICLE_STRUCT.size);
108
+ // per particle the scanned run it is in; per run its first particle and its share; per particle its run
109
+ // and how it is drawn
110
+ this.#fronts = buffer('fronts', 16 + capacity * 4);
111
+ this.#runs = buffer('runs', capacity * 8);
112
+ this.#draw = buffer('draw', capacity * 8);
93
113
  this.#buckets = buffer('buckets', 16 + this.#table_limit * 4);
94
114
  this.#slots = buffer('slots', capacity * WATER_PARTICLE_CORNERS * 4);
95
115
  this.#bucket_amplitude = buffer('bucket amplitude', this.#table_limit * 4);
96
- this.#bucket_weight = buffer('bucket weight', this.#table_limit * 4);
116
+ // the summed steepness, the largest front and front complement entered, and the entries; then the most a point
117
+ // under it reads, and its entries by key
118
+ this.#bucket_weight = buffer('bucket weight', this.#table_limit * WATER_PARTICLE_CROWD_WORDS * 4);
119
+ this.#bucket_nest = buffer('bucket nest', this.#table_limit * WATER_PARTICLE_NEST_WORDS * 4);
97
120
  this.#visible = buffer('visible', capacity * 4);
98
121
  const entries = capacity * WATER_PARTICLE_CORNERS * WATER_PARTICLE_ENTRY_STRUCT.size;
99
122
  this.#staging = buffer('staging', entries);
100
123
  this.#entries = buffer('entries', entries);
101
- this.#commands = [0, 1].map(i => device.createBuffer({ label: `Water / particles command ${i}`,
124
+ // the particles' this frame and next, and the clear of the frame's table
125
+ this.#commands = [0, 1, 2].map(i => device.createBuffer({ label: `Water / particles command ${i}`,
102
126
  size: INDIRECT_COMPUTE_COMMAND_DESCRIPTOR.size, usage: INDIRECT_COMPUTE_COMMAND_DESCRIPTOR.usage }));
103
127
  this.#rows = 0;
104
128
  this.#parity = 0;
@@ -121,11 +145,14 @@ export class GPUWaterParticles {
121
145
  }
122
146
 
123
147
  dispose() {
124
- for (const buffer of [...this.#particles, this.#children, this.#buckets, this.#slots, this.#bucket_amplitude,
125
- this.#bucket_weight, this.#visible, this.#staging, this.#entries, this.#body_bounds, ...(this.#commands ?? [])]) { buffer?.destroy(); }
148
+ for (const buffer of [...this.#particles, this.#children, this.#images, this.#fronts, this.#runs, this.#draw, this.#buckets, this.#slots,
149
+ this.#bucket_amplitude, this.#bucket_weight, this.#bucket_nest, this.#visible, this.#staging, this.#entries, this.#body_bounds, ...(this.#commands ?? [])]) {
150
+ buffer?.destroy();
151
+ }
126
152
  this.#particles = [];
127
- this.#children = this.#buckets = this.#slots = this.#bucket_amplitude = this.#staging = this.#entries = null;
128
- this.#bucket_weight = this.#visible = null;
153
+ this.#children = this.#images = this.#buckets = this.#slots = this.#bucket_amplitude = this.#staging = this.#entries = null;
154
+ this.#fronts = this.#runs = this.#draw = null;
155
+ this.#bucket_weight = this.#bucket_nest = this.#visible = null;
129
156
  this.#body_bounds = null;
130
157
  this.#commands = null;
131
158
  this.#device = null;
@@ -158,10 +185,11 @@ export class GPUWaterParticles {
158
185
  // a frame that does not step bins the particles where they are
159
186
  const next = stepping ? this.#particles[1 - this.#parity] : current;
160
187
  if (stepping) { this.#parity = 1 - this.#parity; }
161
- const [command_current, command_next] = this.#commands;
188
+ const [command_current, command_next, command_table] = this.#commands;
162
189
  const geometries = scene.geometries;
163
190
  let children = graph_import_buffer(graph, this.#children, 'Water / particle children');
164
191
  let buckets = graph_import_buffer(graph, this.#buckets, 'Water / particle buckets');
192
+ let fronts = graph_import_buffer(graph, this.#fronts, 'Water / particle fronts');
165
193
  const node = (name, execute, reads, writes) => {
166
194
  const data = {};
167
195
  const pass = graph.add(name, data, (d, resources, context) => execute(resources, context, d));
@@ -177,7 +205,7 @@ export class GPUWaterParticles {
177
205
  encoder.copyBufferToBuffer(current, 0, this.#children, 0, 4);
178
206
  build_indirect_compute_dispatch_command(64, encoder, current, 0, command_current, 0, true);
179
207
  shader_water_particles_update.dispatchIndirect({ encoder, command: command_current, bindings: {
180
- frame: { dt: step, capacity }, particles: current, children: this.#children, activity,
208
+ frame: { dt: step, capacity }, particles: current, children: this.#children, images: this.#images, activity,
181
209
  scene_database: scene.scene_database_buffer, geometries: geometries.buffer_metadata,
182
210
  blas_lookup: geometries.blas.buffer_metadata, blas_data: geometries.blas.buffer_data,
183
211
  meshlet_metadata: geometries.meshlets.buffer_metadata, meshlet_data: geometries.meshlets.buffer_data,
@@ -188,41 +216,59 @@ export class GPUWaterParticles {
188
216
  // What they leave into the next array, then the frame's splashes after them.
189
217
  node('Water / particles split and emit', (resources, { encoder }) => {
190
218
  shader_water_particles_split.dispatchIndirect({ encoder, command: command_current, bindings: {
191
- capacity, particles: current, children: this.#children, next } });
219
+ capacity, particles: current, children: this.#children, images: this.#images, next } });
192
220
  shader_water_particles_total.dispatch({ encoder, bindings: { capacity, children: this.#children, next } });
193
221
  shader_water_particles_emit.dispatchIndirect({ encoder, command: commands, command_offset: emit_command,
194
222
  bindings: { capacity, scene_database: scene.scene_database_buffer, emit_rows, steep, activity,
195
223
  particle_bounds: this.#body_bounds, next } });
196
- shader_water_particles_finalize.dispatch({ encoder, bindings: { limits: { capacity, table_limit }, next, offsets: this.#buckets } });
224
+ shader_water_particles_finalize.dispatch({ encoder, bindings: { limits: { capacity, table_limit }, next, offsets: this.#buckets,
225
+ table_command: command_table } });
197
226
  }, { children }, {});
198
227
  }
199
228
 
200
- // The frame's hash: how crowded each cell is, then each particle into the cells its support overlaps as far as
201
- // its crowd leaves it drawn, offsets, entries in order.
202
- buckets = node('Water / particles bin', (resources, { encoder }) => {
229
+ // Each front's run of particles, so that a whole one is drawn as the seven it was emitted as.
230
+ fronts = node('Water / particles fronts', (resources, { encoder }) => {
203
231
  if (!stepping) {
204
232
  // the table size the last stepping frame chose for these particles
205
- shader_water_particles_finalize.dispatch({ encoder, bindings: { limits: { capacity, table_limit }, next, offsets: this.#buckets } });
233
+ shader_water_particles_finalize.dispatch({ encoder, bindings: { limits: { capacity, table_limit }, next, offsets: this.#buckets,
234
+ table_command: command_table } });
206
235
  }
207
236
  build_indirect_compute_dispatch_command(64, encoder, next, 0, command_next, 0, true);
208
- encoder.clearBuffer(this.#buckets, 16, table_limit * 4);
209
- encoder.clearBuffer(this.#bucket_amplitude);
210
- encoder.clearBuffer(this.#bucket_weight);
237
+ encoder.copyBufferToBuffer(next, 0, this.#fronts, 0, 4);
238
+ shader_water_particles_fronts.dispatchIndirect({ encoder, command: command_next, bindings: { next, fronts: this.#fronts } });
239
+ }, {}, { fronts }).fronts;
240
+ fronts = graph_prefix_scan_csdldf({ graph, data: fronts, element_max_count: capacity });
241
+
242
+ // The frame's hash: how each particle is drawn, how crowded each cell is and so how much of each run its crowd
243
+ // leaves drawn, then each packet drawn into the cells its support overlaps, offsets, entries in order.
244
+ buckets = node('Water / particles bin', (resources, { encoder }) => {
245
+ const draw = this.#draw, runs = this.#runs;
246
+ shader_water_particles_runs.dispatchIndirect({ encoder, command: command_next, bindings: { next, fronts: this.#fronts, runs } });
247
+ shader_water_particles_groups.dispatchIndirect({ encoder, command: command_next, bindings: { next, fronts: this.#fronts, runs, draw } });
248
+ shader_water_particles_clear.dispatchIndirect({ encoder, command: command_table, bindings: {
249
+ buckets: this.#buckets, bucket_amplitude: this.#bucket_amplitude, bucket_weight: this.#bucket_weight,
250
+ bucket_nest: this.#bucket_nest } });
211
251
  shader_water_particles_weigh.dispatchIndirect({ encoder, command: command_next, bindings: {
212
- next, activity, offsets: this.#buckets, bucket_weight: this.#bucket_weight } });
252
+ next, activity, offsets: this.#buckets, draw, bucket_weight: this.#bucket_weight, body_bounds: this.#body_bounds } });
253
+ shader_water_particles_nest.dispatchIndirect({ encoder, command: command_next, bindings: {
254
+ next, activity, offsets: this.#buckets, draw, body_bounds: this.#body_bounds, bucket_weight: this.#bucket_weight,
255
+ bucket_nest: this.#bucket_nest } });
256
+ shader_water_particles_share.dispatchIndirect({ encoder, command: command_next, bindings: {
257
+ next, activity, offsets: this.#buckets, bucket_weight: this.#bucket_weight, bucket_nest: this.#bucket_nest,
258
+ body_bounds: this.#body_bounds, draw, runs } });
213
259
  shader_water_particles_bin.dispatchIndirect({ encoder, command: command_next, bindings: {
214
260
  next, activity, buckets: this.#buckets, slots: this.#slots, bucket_amplitude: this.#bucket_amplitude,
215
- body_bounds: this.#body_bounds, bucket_weight: this.#bucket_weight, visible: this.#visible } });
216
- }, {}, { buckets }).buckets;
261
+ body_bounds: this.#body_bounds, draw, runs, visible: this.#visible } });
262
+ }, { fronts }, { buckets }).buckets;
217
263
  buckets = graph_prefix_scan_csdldf({ graph, data: buckets, element_max_count: table_limit });
218
264
  node('Water / particles order and bound', (resources, { encoder }) => {
219
- const bindings = { next, offsets: this.#buckets, slots: this.#slots };
265
+ const bindings = { next, offsets: this.#buckets, slots: this.#slots, draw: this.#draw };
220
266
  shader_water_particles_scatter.dispatchIndirect({ encoder, command: command_next, bindings: { ...bindings,
221
267
  visible: this.#visible, staging: this.#staging } });
222
268
  shader_water_particles_rank.dispatchIndirect({ encoder, command: command_next, bindings: { ...bindings,
223
269
  staging: this.#staging, entries: this.#entries } });
224
270
  shader_water_particles_bounds.dispatchIndirect({ encoder, command: command_next, bindings: { next, activity,
225
- offsets: this.#buckets, bucket_amplitude: this.#bucket_amplitude, body_bounds: this.#body_bounds } });
271
+ offsets: this.#buckets, bucket_amplitude: this.#bucket_amplitude, draw: this.#draw, body_bounds: this.#body_bounds } });
226
272
  }, { buckets }, {});
227
273
  }
228
274
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WATER_PARTICLE_STRUCT.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,iDAeoB;AAEpB;;;;GAIG;AACH,uDAeyB;6BA/CI,sCAAsC"}
1
+ {"version":3,"file":"WATER_PARTICLE_STRUCT.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,iDAoBoB;AAEpB;;;;GAIG;AACH,uDAkByB;6BAvDI,sCAAsC"}