@woosh/meep-engine 3.60.2 → 3.60.4

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 (156) hide show
  1. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts +3 -0
  2. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts.map +1 -1
  3. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +6 -0
  4. package/package.json +1 -1
  5. package/src/engine/ecs/ik/PLAN.md +1 -1
  6. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +1 -2
  7. package/src/engine/graphics/ecs/trail3d/TRAIL3D_FADE_LINEAR.d.ts +14 -0
  8. package/src/engine/graphics/ecs/trail3d/TRAIL3D_FADE_LINEAR.d.ts.map +1 -0
  9. package/src/engine/graphics/ecs/trail3d/TRAIL3D_FADE_LINEAR.js +22 -0
  10. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +19 -3
  11. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  12. package/src/engine/graphics/ecs/trail3d/Trail3D.js +31 -4
  13. package/src/engine/graphics/ecs/trail3d/advance_trail.js +1 -1
  14. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts +5 -5
  15. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts.map +1 -1
  16. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +6 -6
  17. package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts +10 -0
  18. package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts.map +1 -1
  19. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +13 -1
  20. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts +2 -2
  21. package/src/engine/physics/cloth/ecs/cloth_geodesic.js +2 -2
  22. package/src/shade/playground/probe_clipmap/README.md +1 -0
  23. package/src/shade/playground/probe_clipmap/index.html +5 -0
  24. package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -1
  25. package/src/shade/playground/probe_clipmap/main.js +16 -2
  26. package/src/shade/playground/vgeo_scene/README.md +1 -1
  27. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +4 -6
  28. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -1
  29. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +34 -36
  30. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +1 -1
  31. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
  32. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +162 -163
  33. package/src/shade/renderer/atmosphere/compute_mie_absorption.d.ts +1 -1
  34. package/src/shade/renderer/atmosphere/compute_mie_absorption.js +1 -1
  35. package/src/shade/renderer/atmosphere/hillaire/ATMOSPHERE_EARTH.js +1 -1
  36. package/src/shade/renderer/atmosphere/hillaire/ATMOSPHERE_PARAMETERS_STRUCT.js +1 -1
  37. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +164 -164
  38. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +1 -1
  39. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +2 -2
  40. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +2 -2
  41. package/src/shade/renderer/geometry/sdf/cards/mesh_cards_raster_pass_descriptor.js +2 -2
  42. package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_histogram.d.ts +1 -1
  43. package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_histogram.js +1 -1
  44. package/src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.d.ts +4 -5
  45. package/src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.d.ts.map +1 -1
  46. package/src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.js +6 -7
  47. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +1 -1
  48. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +2 -4
  49. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +1 -1
  50. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +3 -10
  51. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  52. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -0
  53. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +119 -38
  54. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +2 -2
  55. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.d.ts +12 -0
  56. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.d.ts.map +1 -1
  57. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.js +22 -0
  58. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +4 -1
  59. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  60. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +50 -4
  61. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts +6 -0
  62. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts.map +1 -1
  63. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.js +7 -0
  64. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP_RESAMPLING.d.ts +13 -0
  65. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP_RESAMPLING.d.ts.map +1 -0
  66. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP_RESAMPLING.js +13 -0
  67. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts +17 -0
  68. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts.map +1 -1
  69. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.js +20 -0
  70. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_resampled.d.ts +18 -0
  71. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_resampled.d.ts.map +1 -0
  72. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_resampled.js +42 -0
  73. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts.map +1 -1
  74. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js +5 -4
  75. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts +4 -0
  76. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts.map +1 -1
  77. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.js +13 -2
  78. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_resample.d.ts +52 -0
  79. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_resample.d.ts.map +1 -0
  80. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_resample.js +250 -0
  81. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +7 -7
  82. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +2 -2
  83. package/src/shade/renderer/light/environment/environment_level_1_blur_plan.d.ts +39 -0
  84. package/src/shade/renderer/light/environment/environment_level_1_blur_plan.d.ts.map +1 -0
  85. package/src/shade/renderer/light/environment/environment_level_1_blur_plan.js +119 -0
  86. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts +15 -7
  87. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  88. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +66 -83
  89. package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts.map +1 -1
  90. package/src/shade/renderer/light/environment/graph_filter_environment_map.js +15 -6
  91. package/src/shade/renderer/light/environment/shader_blur_environment_map.d.ts +3 -0
  92. package/src/shade/renderer/light/environment/shader_blur_environment_map.d.ts.map +1 -0
  93. package/src/shade/renderer/light/environment/shader_blur_environment_map.js +111 -0
  94. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_blurred.d.ts +3 -0
  95. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_blurred.d.ts.map +1 -0
  96. package/src/shade/renderer/light/environment/{shader_convolve_reflection_map_from_averages.js → shader_convolve_reflection_map_from_blurred.js} +25 -66
  97. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -1
  98. package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +0 -1
  99. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +1 -4
  100. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +2 -2
  101. package/src/shade/renderer/postprocess/nss/NOTES.md +14 -21
  102. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -3
  103. package/src/shade/renderer/postprocess/nss/NSS.d.ts.map +1 -1
  104. package/src/shade/renderer/postprocess/nss/NSS.js +1 -3
  105. package/src/shade/renderer/postprocess/nss/NSS_NETWORK_SCHEMA.d.ts +5 -6
  106. package/src/shade/renderer/postprocess/nss/NSS_NETWORK_SCHEMA.d.ts.map +1 -1
  107. package/src/shade/renderer/postprocess/nss/NSS_NETWORK_SCHEMA.js +9 -10
  108. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.d.ts.map +1 -1
  109. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.js +48 -49
  110. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.d.ts.map +1 -1
  111. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.js +12 -16
  112. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.js +246 -246
  113. package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts +1 -2
  114. package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts.map +1 -1
  115. package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.js +34 -35
  116. package/src/shade/renderer/shader/chunk/atmosphere/C_MIE_SCATTERING_DECLARATION.d.ts +2 -2
  117. package/src/shade/renderer/shader/chunk/atmosphere/C_MIE_SCATTERING_DECLARATION.js +2 -2
  118. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +1 -1
  119. package/src/shade/renderer/shader/chunk/atmosphere/chunk_compute_extinction.js +1 -1
  120. package/src/shade/renderer/shader/chunk/atmosphere/hillaire/DENSITY_PROFILE_LAYER_STRUCT.d.ts +1 -1
  121. package/src/shade/renderer/shader/chunk/atmosphere/hillaire/DENSITY_PROFILE_LAYER_STRUCT.js +1 -1
  122. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +4 -7
  123. package/src/shade/renderer/shader/chunk/brdf/fresnel/chunk_F_Schlick.d.ts +3 -3
  124. package/src/shade/renderer/shader/chunk/brdf/fresnel/chunk_F_Schlick.js +3 -3
  125. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +12 -19
  126. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.d.ts +9 -10
  127. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.d.ts.map +1 -1
  128. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.js +9 -10
  129. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.d.ts +4 -4
  130. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.js +5 -5
  131. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +1 -1
  132. package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.d.ts +1 -2
  133. package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.d.ts.map +1 -1
  134. package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.js +1 -2
  135. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.d.ts +14 -1
  136. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.d.ts.map +1 -1
  137. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.js +19 -6
  138. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.d.ts +10 -1
  139. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.d.ts.map +1 -1
  140. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.js +14 -6
  141. package/src/shade/renderer/shader/chunk/random/murmur/chunk_murmur3_vec3u.d.ts +1 -1
  142. package/src/shade/renderer/shader/chunk/random/murmur/chunk_murmur3_vec3u.js +1 -1
  143. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +9 -14
  144. package/src/shade/renderer/shadow/contact/FINDINGS.md +5 -5
  145. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.d.ts +2 -3
  146. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.d.ts.map +1 -1
  147. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.js +7 -8
  148. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_LOAD_FACTOR.d.ts +1 -1
  149. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_LOAD_FACTOR.js +12 -12
  150. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts +2 -2
  151. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts.map +1 -1
  152. package/src/shade/renderer/shadow/vsm/VSMPageManager.js +3 -4
  153. package/src/shade/renderer/texture/mipmaps/NOTES.md +1 -2
  154. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +1 -1
  155. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts +0 -13
  156. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts.map +0 -1
@@ -1,164 +1,164 @@
1
- import { WebGPUExtensionType } from "../../descriptor/WebGPUExtensionType.js";
2
- import { ATMOSPHERE_HEIGHT_DECLARATION } from "../shader/chunk/atmosphere/ATMOSPHERE_HEIGHT_DECLARATION.js";
3
- import { chunk_safe_acos } from "../shader/chunk/math/chunk_safe_acos.js";
4
- import { CodeChunk } from "../shader/compiler/CodeChunk.js";
5
- import { ComputeShader } from "../shader/ComputeShader.js";
6
- import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
7
- import { chunk_get_multiscatter_values } from "./chunk_get_multiscatter_values.js";
8
- import { chunk_multiscatter_sample_direction } from "./chunk_multiscatter_sample_direction.js";
9
- import { chunk_sky_lut_texel_to_unit } from "./chunk_sky_lut_texel_to_unit.js";
10
- import {
11
- MULTISCATTER_LUT_DIRECTION_COUNT,
12
- MULTISCATTER_LUT_MAX_SUBGROUP_PARTIALS
13
- } from "./MULTISCATTER_LUT_CONFIG.js";
14
- import { SAMPLER_SKY_LUT } from "./SAMPLER_SKY_LUT.js";
15
-
16
- const resources = new ShaderResourceSetDescriptor();
17
- resources.createGroup()
18
- // "float" rather than the default "unfilterable-float": rgba16float is filterable in
19
- // core WebGPU, so the LUT goes through the texture unit instead of a 4-tap manual
20
- // bilinear.
21
- .addTexture("tSkyLutTransmittance", "float")
22
- .addSampler("sSkyLut", "filtering", SAMPLER_SKY_LUT)
23
- .addStorageTexture("tSkyLutMultiscatterOut", "rgba16float")
24
- ;
25
-
26
-
27
- /**
28
- * the multiple-scattering LUT. Each pixel coordinate corresponds to a height and sun zenith angle, and
29
- * the value is the multiple scattering approximation (Psi_ms from the paper, Eq. 10).
30
- *
31
- * One workgroup per LUT texel, one thread per direction, mirroring Hillaire's
32
- * `[numthreads(1,1,64)]` `NewMultiScattCS`. The 64 directions used to be a serial loop
33
- * inside a fragment shader, which left the whole pass with 32*32 = 1024 threads of
34
- * parallelism -- a few dozen waves, one per scheduler at best, with nothing to hide the
35
- * latency of the transmittance fetches. Per-thread it is now a single direction's 20-step
36
- * march.
37
- *
38
- * The per-direction results are summed with `subgroupAdd` and then across subgroups through
39
- * workgroup memory; Hillaire uses a six-step groupshared log-reduction, which the subgroup
40
- * op collapses into one instruction plus a handful of partials.
41
- *
42
- * @see "A Scalable and Production Ready Sky and Atmosphere Rendering Technique", Hillaire (2020)
43
- * @see https://www.shadertoy.com/view/slSXRW
44
- * @type {CodeChunk}
45
- */
46
- const body = CodeChunk.from(
47
- //language=WGSL
48
- `
49
- const MULTISCATTER_DIRECTION_COUNT = ${MULTISCATTER_LUT_DIRECTION_COUNT}u;
50
- const MULTISCATTER_MAX_SUBGROUP_PARTIALS = ${MULTISCATTER_LUT_MAX_SUBGROUP_PARTIALS}u;
51
-
52
- var<workgroup> wg_partial_count: atomic<u32>;
53
- var<workgroup> wg_lum: array<vec3<f32>, MULTISCATTER_MAX_SUBGROUP_PARTIALS>;
54
- var<workgroup> wg_fms: array<vec3<f32>, MULTISCATTER_MAX_SUBGROUP_PARTIALS>;
55
-
56
- @compute @workgroup_size(${MULTISCATTER_LUT_DIRECTION_COUNT}, 1, 1)
57
- fn main(
58
- @builtin(workgroup_id) workgroup_id: vec3<u32>,
59
- @builtin(local_invocation_index) thread_index: u32,
60
- ) {
61
-
62
- if (thread_index == 0u) {
63
- atomicStore(&wg_partial_count, 0u);
64
- }
65
-
66
- // Every thread is here together anyway, so the counter's initialisation costs nothing
67
- // beyond the barrier itself. WGSL does not promise workgroup variables start zeroed.
68
- workgroupBarrier();
69
-
70
- // one workgroup per texel; the dispatch is sized from the texture, so this is in range
71
- let texel = workgroup_id.xy;
72
-
73
- let lut_size = vec2<f32>(textureDimensions(tSkyLutMultiscatterOut));
74
-
75
- // The point of each range this texel stands for: texel 0 carries the bottom and the last
76
- // texel the top, which is what lets a lookup at either end read a value computed there
77
- // rather than one from half a texel inside. sky_lut_uv is the other half of this.
78
- let unit = sky_lut_texel_to_unit(vec2<f32>(texel), lut_size);
79
-
80
- let sunCosTheta = 2.0*unit.x - 1.0;
81
- let sunTheta = safe_acos(sunCosTheta);
82
-
83
- let height = ATMOSPHERE_HEIGHT*unit.y;
84
-
85
- let pos = vec3(0.0, height, 0.0);
86
- let sunDir = normalize(vec3(0.0, sunCosTheta, -sin(sunTheta)));
87
-
88
- // pos on the +y axis and sunDir with a zero x component are exactly the preconditions
89
- // multiscatter_sample_direction's half-azimuth sample set needs
90
- let rayDir = multiscatter_sample_direction(thread_index);
91
-
92
- var lum:vec3<f32>;
93
- var f_ms:vec3<f32>;
94
-
95
- get_multiscatter_values(pos, sunDir, rayDir, &lum, &f_ms);
96
-
97
- // Uniform control flow up to here, so every one of the workgroup's threads is an active
98
- // lane and the subgroup sum covers all of them.
99
- let lum_subgroup = subgroupAdd(lum);
100
- let fms_subgroup = subgroupAdd(f_ms);
101
-
102
- // subgroupElect picks exactly one active invocation per subgroup, and the slot it claims
103
- // is whichever the counter hands out. Deriving the slot from local_invocation_index /
104
- // subgroup_size instead would assume invocations are packed into subgroups in contiguous
105
- // index order, which WGSL leaves unspecified: a mapping that is not contiguous blocks
106
- // would collide two subgroups onto one slot and silently drop a partial sum. Which slot
107
- // a subgroup lands in does not matter, only that it gets its own.
108
- //
109
- // Every lane holds the same subgroup sum, so letting them all store it would be a data
110
- // race on one address even though the values agree.
111
- if (subgroupElect()) {
112
- let slot = atomicAdd(&wg_partial_count, 1u);
113
-
114
- wg_lum[slot] = lum_subgroup;
115
- wg_fms[slot] = fms_subgroup;
116
- }
117
-
118
- workgroupBarrier();
119
-
120
- if (thread_index != 0u) {
121
- return;
122
- }
123
-
124
- // WGSL pins the subgroup size to a power of two in [4, 128], so the workgroup cannot hold
125
- // more subgroups than the partials array has room for. The clamp keeps a driver that
126
- // broke that promise reading initialised slots rather than garbage.
127
- let partial_count = min(atomicLoad(&wg_partial_count), MULTISCATTER_MAX_SUBGROUP_PARTIALS);
128
-
129
- var lum_total = vec3(0.0);
130
- var fms_total = vec3(0.0);
131
-
132
- for (var i = 0u; i < partial_count; i++) {
133
- lum_total += wg_lum[i];
134
- fms_total += wg_fms[i];
135
- }
136
-
137
- // The directions carry equal solid angle, so the mean over them is the spherical average
138
- // the paper's uniform phase reduces to.
139
- let inv_samples = 1.0 / f32(MULTISCATTER_DIRECTION_COUNT);
140
-
141
- lum_total *= inv_samples;
142
- fms_total *= inv_samples;
143
-
144
- // Equation 10 from the paper.
145
- let psi = lum_total / (1.0 - fms_total);
146
-
147
- textureStore(tSkyLutMultiscatterOut, texel, vec4(psi, 1.0));
148
- }
149
- `, [
150
- chunk_safe_acos,
151
- ATMOSPHERE_HEIGHT_DECLARATION,
152
- chunk_multiscatter_sample_direction,
153
- chunk_sky_lut_texel_to_unit,
154
- chunk_get_multiscatter_values,
155
- ]
156
- );
157
-
158
- body.addExtension(WebGPUExtensionType.Subgroups);
159
-
160
- export const shader_multiscatter_lut = ComputeShader.from({
161
- label: "Sky / multi-scatter LUT",
162
- body,
163
- resources,
164
- });
1
+ import { WebGPUExtensionType } from "../../descriptor/WebGPUExtensionType.js";
2
+ import { ATMOSPHERE_HEIGHT_DECLARATION } from "../shader/chunk/atmosphere/ATMOSPHERE_HEIGHT_DECLARATION.js";
3
+ import { chunk_safe_acos } from "../shader/chunk/math/chunk_safe_acos.js";
4
+ import { CodeChunk } from "../shader/compiler/CodeChunk.js";
5
+ import { ComputeShader } from "../shader/ComputeShader.js";
6
+ import { ShaderResourceSetDescriptor } from "../shader/resource/ShaderResourceSetDescriptor.js";
7
+ import { chunk_get_multiscatter_values } from "./chunk_get_multiscatter_values.js";
8
+ import { chunk_multiscatter_sample_direction } from "./chunk_multiscatter_sample_direction.js";
9
+ import { chunk_sky_lut_texel_to_unit } from "./chunk_sky_lut_texel_to_unit.js";
10
+ import {
11
+ MULTISCATTER_LUT_DIRECTION_COUNT,
12
+ MULTISCATTER_LUT_MAX_SUBGROUP_PARTIALS
13
+ } from "./MULTISCATTER_LUT_CONFIG.js";
14
+ import { SAMPLER_SKY_LUT } from "./SAMPLER_SKY_LUT.js";
15
+
16
+ const resources = new ShaderResourceSetDescriptor();
17
+ resources.createGroup()
18
+ // "float" rather than the default "unfilterable-float": rgba16float is filterable in
19
+ // core WebGPU, so the LUT goes through the texture unit instead of a 4-tap manual
20
+ // bilinear.
21
+ .addTexture("tSkyLutTransmittance", "float")
22
+ .addSampler("sSkyLut", "filtering", SAMPLER_SKY_LUT)
23
+ .addStorageTexture("tSkyLutMultiscatterOut", "rgba16float")
24
+ ;
25
+
26
+
27
+ /**
28
+ * the multiple-scattering LUT. Each pixel coordinate corresponds to a height and sun zenith angle, and
29
+ * the value is the multiple scattering approximation (Psi_ms from the paper, Eq. 10).
30
+ *
31
+ * One workgroup per LUT texel, one thread per direction (64 threads per workgroup).
32
+ * The 64 directions used to be a serial loop
33
+ * inside a fragment shader, which left the whole pass with 32*32 = 1024 threads of
34
+ * parallelism -- a few dozen waves, one per scheduler at best, with nothing to hide the
35
+ * latency of the transmittance fetches. Per-thread it is now a single direction's 20-step
36
+ * march.
37
+ *
38
+ * The per-direction results are summed with `subgroupAdd` and then across subgroups through
39
+ * workgroup memory, collapsing a six-step groupshared log-reduction into one subgroup
40
+ * instruction plus a handful of partials.
41
+ *
42
+ * @see "A Scalable and Production Ready Sky and Atmosphere Rendering Technique", Hillaire (2020)
43
+ * @see https://www.shadertoy.com/view/slSXRW
44
+ * @type {CodeChunk}
45
+ */
46
+ const body = CodeChunk.from(
47
+ //language=WGSL
48
+ `
49
+ const MULTISCATTER_DIRECTION_COUNT = ${MULTISCATTER_LUT_DIRECTION_COUNT}u;
50
+ const MULTISCATTER_MAX_SUBGROUP_PARTIALS = ${MULTISCATTER_LUT_MAX_SUBGROUP_PARTIALS}u;
51
+
52
+ var<workgroup> wg_partial_count: atomic<u32>;
53
+ var<workgroup> wg_lum: array<vec3<f32>, MULTISCATTER_MAX_SUBGROUP_PARTIALS>;
54
+ var<workgroup> wg_fms: array<vec3<f32>, MULTISCATTER_MAX_SUBGROUP_PARTIALS>;
55
+
56
+ @compute @workgroup_size(${MULTISCATTER_LUT_DIRECTION_COUNT}, 1, 1)
57
+ fn main(
58
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
59
+ @builtin(local_invocation_index) thread_index: u32,
60
+ ) {
61
+
62
+ if (thread_index == 0u) {
63
+ atomicStore(&wg_partial_count, 0u);
64
+ }
65
+
66
+ // Every thread is here together anyway, so the counter's initialisation costs nothing
67
+ // beyond the barrier itself. WGSL does not promise workgroup variables start zeroed.
68
+ workgroupBarrier();
69
+
70
+ // one workgroup per texel; the dispatch is sized from the texture, so this is in range
71
+ let texel = workgroup_id.xy;
72
+
73
+ let lut_size = vec2<f32>(textureDimensions(tSkyLutMultiscatterOut));
74
+
75
+ // The point of each range this texel stands for: texel 0 carries the bottom and the last
76
+ // texel the top, which is what lets a lookup at either end read a value computed there
77
+ // rather than one from half a texel inside. sky_lut_uv is the other half of this.
78
+ let unit = sky_lut_texel_to_unit(vec2<f32>(texel), lut_size);
79
+
80
+ let sunCosTheta = 2.0*unit.x - 1.0;
81
+ let sunTheta = safe_acos(sunCosTheta);
82
+
83
+ let height = ATMOSPHERE_HEIGHT*unit.y;
84
+
85
+ let pos = vec3(0.0, height, 0.0);
86
+ let sunDir = normalize(vec3(0.0, sunCosTheta, -sin(sunTheta)));
87
+
88
+ // pos on the +y axis and sunDir with a zero x component are exactly the preconditions
89
+ // multiscatter_sample_direction's half-azimuth sample set needs
90
+ let rayDir = multiscatter_sample_direction(thread_index);
91
+
92
+ var lum:vec3<f32>;
93
+ var f_ms:vec3<f32>;
94
+
95
+ get_multiscatter_values(pos, sunDir, rayDir, &lum, &f_ms);
96
+
97
+ // Uniform control flow up to here, so every one of the workgroup's threads is an active
98
+ // lane and the subgroup sum covers all of them.
99
+ let lum_subgroup = subgroupAdd(lum);
100
+ let fms_subgroup = subgroupAdd(f_ms);
101
+
102
+ // subgroupElect picks exactly one active invocation per subgroup, and the slot it claims
103
+ // is whichever the counter hands out. Deriving the slot from local_invocation_index /
104
+ // subgroup_size instead would assume invocations are packed into subgroups in contiguous
105
+ // index order, which WGSL leaves unspecified: a mapping that is not contiguous blocks
106
+ // would collide two subgroups onto one slot and silently drop a partial sum. Which slot
107
+ // a subgroup lands in does not matter, only that it gets its own.
108
+ //
109
+ // Every lane holds the same subgroup sum, so letting them all store it would be a data
110
+ // race on one address even though the values agree.
111
+ if (subgroupElect()) {
112
+ let slot = atomicAdd(&wg_partial_count, 1u);
113
+
114
+ wg_lum[slot] = lum_subgroup;
115
+ wg_fms[slot] = fms_subgroup;
116
+ }
117
+
118
+ workgroupBarrier();
119
+
120
+ if (thread_index != 0u) {
121
+ return;
122
+ }
123
+
124
+ // WGSL pins the subgroup size to a power of two in [4, 128], so the workgroup cannot hold
125
+ // more subgroups than the partials array has room for. The clamp keeps a driver that
126
+ // broke that promise reading initialised slots rather than garbage.
127
+ let partial_count = min(atomicLoad(&wg_partial_count), MULTISCATTER_MAX_SUBGROUP_PARTIALS);
128
+
129
+ var lum_total = vec3(0.0);
130
+ var fms_total = vec3(0.0);
131
+
132
+ for (var i = 0u; i < partial_count; i++) {
133
+ lum_total += wg_lum[i];
134
+ fms_total += wg_fms[i];
135
+ }
136
+
137
+ // The directions carry equal solid angle, so the mean over them is the spherical average
138
+ // the paper's uniform phase reduces to.
139
+ let inv_samples = 1.0 / f32(MULTISCATTER_DIRECTION_COUNT);
140
+
141
+ lum_total *= inv_samples;
142
+ fms_total *= inv_samples;
143
+
144
+ // Equation 10 from the paper.
145
+ let psi = lum_total / (1.0 - fms_total);
146
+
147
+ textureStore(tSkyLutMultiscatterOut, texel, vec4(psi, 1.0));
148
+ }
149
+ `, [
150
+ chunk_safe_acos,
151
+ ATMOSPHERE_HEIGHT_DECLARATION,
152
+ chunk_multiscatter_sample_direction,
153
+ chunk_sky_lut_texel_to_unit,
154
+ chunk_get_multiscatter_values,
155
+ ]
156
+ );
157
+
158
+ body.addExtension(WebGPUExtensionType.Subgroups);
159
+
160
+ export const shader_multiscatter_lut = ComputeShader.from({
161
+ label: "Sky / multi-scatter LUT",
162
+ body,
163
+ resources,
164
+ });
@@ -116,7 +116,7 @@ fn raymarch_scattering(
116
116
  let mie_absorption = mie_density * C_MIE_ABSORPTION;
117
117
  let mie_scattering = mie_density * C_MIE_SCATTERING;
118
118
 
119
- // note - taken from the Hillaire's atmosphere code: https://github.com/sebh/UnrealEngineSkyAtmosphere/blob/183ead5bdacc701b3b626347a680a2f3cd3d4fbd/Resources/RenderSkyCommon.hlsl#L248
119
+ // Rayleigh does not absorb
120
120
  let rayleigh_absorption = 0.0;
121
121
  let rayleigh_scattering = local_density.x * C_RAYLEIGH;
122
122
 
@@ -17,8 +17,8 @@ export const MESH_SDF_BAKE_BUDGET_MAX: number;
17
17
  * {@link alias} points a skinned clone's id at its source's cache, which readers follow.
18
18
  *
19
19
  * **Side.** A holder says whether it draws both sides of the geometry. A field is baked two-sided
20
- * while any of its holders does: its vote then counts no back face, so nothing is inside it (UE 5.4's
21
- * rule, which takes the side from the material). A field whose holders' side has changed since its
20
+ * while any of its holders does: its vote then counts no back face, so nothing is inside it.
21
+ * A field whose holders' side has changed since its
22
22
  * bake is baked again at the next bake, so holds moved within a frame bake nothing; a field no one
23
23
  * holds keeps the side it has.
24
24
  *
@@ -207,8 +207,8 @@ function grow_typed(array, length) {
207
207
  * {@link alias} points a skinned clone's id at its source's cache, which readers follow.
208
208
  *
209
209
  * **Side.** A holder says whether it draws both sides of the geometry. A field is baked two-sided
210
- * while any of its holders does: its vote then counts no back face, so nothing is inside it (UE 5.4's
211
- * rule, which takes the side from the material). A field whose holders' side has changed since its
210
+ * while any of its holders does: its vote then counts no back face, so nothing is inside it.
211
+ * A field whose holders' side has changed since its
212
212
  * bake is baked again at the next bake, so holds moved within a frame bake nothing; a field no one
213
213
  * holds keeps the side it has.
214
214
  *
@@ -216,8 +216,8 @@ const vertex_code = ShaderDescriptor.from({
216
216
 
217
217
  /*
218
218
  The template is handed what the G-buffer pass hands it, in the geometry's local space, the card
219
- being there: real derivatives, as this is a raster with neighbours of its own triangle, unbiased, as
220
- UE's card capture leaves its material mip bias at 0. Both functions run first, in uniform control
219
+ being there: real derivatives, as this is a raster with neighbours of its own triangle, unbiased (mip
220
+ bias at 0). Both functions run first, in uniform control
221
221
  flow, so a template may sample with implicit derivatives.
222
222
 
223
223
  Coverage is drawn as the trace draws it, a texel's surface opaque with probability its opacity, from
@@ -7,7 +7,7 @@ export const MESH_CARDS_HISTOGRAM_SETTINGS: WebGPUStruct;
7
7
  * six directions, over a resident geometry's level-0 occupancy — a brick is occupied when its entry
8
8
  * holds a slot. Walking its column along the direction, a thread finds every run start: an occupied
9
9
  * brick at layer z whose predecessor is empty or outside the grid. A run start's m is the layer after
10
- * the previous run's end, or 0 for the first: UE's MinRayZ. The thread counts each run start into
10
+ * the previous run's end, or 0 for the first. The thread counts each run start into
11
11
  * its direction's histogram by (m, z), which the layout's scratch holds cleared.
12
12
  *
13
13
  * @type {ComputeShader}
@@ -31,7 +31,7 @@ resources.createGroup()
31
31
  * six directions, over a resident geometry's level-0 occupancy — a brick is occupied when its entry
32
32
  * holds a slot. Walking its column along the direction, a thread finds every run start: an occupied
33
33
  * brick at layer z whose predecessor is empty or outside the grid. A run start's m is the layer after
34
- * the previous run's end, or 0 for the first: UE's MinRayZ. The thread counts each run start into
34
+ * the previous run's end, or 0 for the first. The thread counts each run start into
35
35
  * its direction's histogram by (m, z), which the layout's scratch holds cleared.
36
36
  *
37
37
  * @type {ComputeShader}
@@ -10,8 +10,7 @@ export const BLAS_BAND_VOTE_STRATA: number;
10
10
  */
11
11
  export const BLAS_BAND_VOTE_RAYS: number;
12
12
  /**
13
- * A geometry's signed distance within a band, signed by UE 5.4's vote (`MeshDistanceFieldUtilities.cpp`,
14
- * `FMeshDistanceFieldAsyncTask::DoWork`):
13
+ * A geometry's signed distance within a band, signed by a hemisphere ray vote:
15
14
  *
16
15
  * - The nearest point within the band, through the BLAS. With none, the point is a band away, outside,
17
16
  * and casts no ray.
@@ -20,9 +19,9 @@ export const BLAS_BAND_VOTE_RAYS: number;
20
19
  * than a quarter of them meet a back face.
21
20
  * - A two-sided geometry has no back face: it is outside throughout, and casts no ray either.
22
21
  *
23
- * The directions are UE's, made as its stream runs, ray after ray: `FRandomStream(0)` drives two
24
- * `GenerateStratifiedUniformHemisphereSamples(49)` on Shirley and Chiu's concentric map, and the second
25
- * hemisphere's Z is negated. UE's Z is our Y.
22
+ * The directions are made as the LCG stream runs, ray after ray: a seed of 0 drives two
23
+ * 49-sample stratified uniform hemisphere sets on Shirley and Chiu's concentric map, and the second
24
+ * hemisphere's pole is negated (+Y and -Y hemispheres).
26
25
  *
27
26
  * ```wgsl
28
27
  * fn blas_query_band_sdf(point: vec3<f32>, geometry_index: u32, band: f32, two_sided: bool, contact, stack, stack_top: u32) -> f32
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_blas_query_band_sdf.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.js"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,oCAFU,MAAM,CAEuB;AAEvC;;;GAGG;AACH,kCAFU,MAAM,CAEqE;AAErF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wCAFU,SAAS,CAgHjB;0BAlJwB,oCAAoC"}
1
+ {"version":3,"file":"chunk_blas_query_band_sdf.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.js"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,oCAFU,MAAM,CAEuB;AAEvC;;;GAGG;AACH,kCAFU,MAAM,CAEqE;AAErF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wCAFU,SAAS,CAgHjB;0BAjJwB,oCAAoC"}
@@ -28,8 +28,7 @@ export const BLAS_BAND_VOTE_STRATA = 7;
28
28
  export const BLAS_BAND_VOTE_RAYS = 2 * BLAS_BAND_VOTE_STRATA * BLAS_BAND_VOTE_STRATA;
29
29
 
30
30
  /**
31
- * A geometry's signed distance within a band, signed by UE 5.4's vote (`MeshDistanceFieldUtilities.cpp`,
32
- * `FMeshDistanceFieldAsyncTask::DoWork`):
31
+ * A geometry's signed distance within a band, signed by a hemisphere ray vote:
33
32
  *
34
33
  * - The nearest point within the band, through the BLAS. With none, the point is a band away, outside,
35
34
  * and casts no ray.
@@ -38,9 +37,9 @@ export const BLAS_BAND_VOTE_RAYS = 2 * BLAS_BAND_VOTE_STRATA * BLAS_BAND_VOTE_ST
38
37
  * than a quarter of them meet a back face.
39
38
  * - A two-sided geometry has no back face: it is outside throughout, and casts no ray either.
40
39
  *
41
- * The directions are UE's, made as its stream runs, ray after ray: `FRandomStream(0)` drives two
42
- * `GenerateStratifiedUniformHemisphereSamples(49)` on Shirley and Chiu's concentric map, and the second
43
- * hemisphere's Z is negated. UE's Z is our Y.
40
+ * The directions are made as the LCG stream runs, ray after ray: a seed of 0 drives two
41
+ * 49-sample stratified uniform hemisphere sets on Shirley and Chiu's concentric map, and the second
42
+ * hemisphere's pole is negated (+Y and -Y hemispheres).
44
43
  *
45
44
  * ```wgsl
46
45
  * fn blas_query_band_sdf(point: vec3<f32>, geometry_index: u32, band: f32, two_sided: bool, contact, stack, stack_top: u32) -> f32
@@ -55,7 +54,7 @@ const BLAS_BAND_VOTE_STRATA = ${BLAS_BAND_VOTE_STRATA}u;
55
54
  const BLAS_BAND_VOTE_HEMISPHERE = ${BLAS_BAND_VOTE_STRATA * BLAS_BAND_VOTE_STRATA}u;
56
55
  const BLAS_BAND_VOTE_RAYS = ${BLAS_BAND_VOTE_RAYS}u;
57
56
 
58
- /** FRandomStream::GetFraction: the stream's next fraction in [0, 1) */
57
+ /** Next LCG fraction in [0, 1) */
59
58
  fn blas_band_vote_fraction(seed: ptr<function, u32>) -> f32 {
60
59
  *seed = *seed * 196314165u + 907633515u;
61
60
 
@@ -145,7 +144,7 @@ fn blas_query_band_sdf(
145
144
  }
146
145
  }
147
146
 
148
- // UE's Hit > 0 && HitBack > 0.25 · samples, of which the first follows from the second
147
+ // Inside when hits > 0 and back-face hits > 0.25 · samples (the first follows from the second)
149
148
  return select(distance, -distance, 4u * back > BLAS_BAND_VOTE_RAYS);
150
149
  }
151
150
  `, [
@@ -128,7 +128,7 @@ The pin was then added on top to protect cut seams. The pin is *correct* and mus
128
128
  Generalized quadrics — Garland & Heckbert 1998 (*Simplifying Surfaces with Color and Texture using
129
129
  Quadric Error Metrics*) for the naive `(3+m)`-dimensional lift, **Hoppe 1999** (*New Quadric Metric
130
130
  for Simplifying Meshes with Appearance Attributes*) for the form worth implementing. Unreal's Nanite
131
- uses the latter (`MeshSimplify.h` / `Quadric.h`: attribute-carrying quadrics, per-attribute weights,
131
+ uses the latter (attribute-carrying quadrics, per-attribute weights,
132
132
  and a separate edge quadric for boundary and seam penalties); `meshoptimizer`'s
133
133
  `meshopt_simplifyWithAttributes` is a far more readable second reference for the same maths and for
134
134
  weight calibration.
@@ -179,10 +179,8 @@ band (`vgeo_assemble_pages`). A page is therefore spatially local, and a boundin
179
179
  one means something. Pack pages arbitrarily and every page bound covers the model.
180
180
 
181
181
  **Nanite does exactly this combination**, which settles the compatibility question outright.
182
- Cluster data lives in a paged, streamed virtual byte buffer
183
- (`Nanite.StreamingManager.ClusterPageData`) with the BVH in
184
- `Nanite.StreamingManager.Hierarchy`; nodes form a BVH within a mesh connecting clusters
185
- across detail levels; `PersistentNodeAndClusterCull` in `NaniteHierarchyTraversal.ush` walks
182
+ Cluster data lives in a paged, streamed virtual byte buffer with a separate hierarchy;
183
+ nodes form a BVH within a mesh connecting clusters across detail levels; a persistent traversal kernel walks
186
184
  it on the GPU from an MPMC queue, where only nodes generate new work and clusters are only
187
185
  consumed. And when a child's memory is not present, **processing stops at the parent and a
188
186
  request is made for the child** — a lower-detail LOD is what you see until it arrives. That
@@ -207,7 +207,7 @@ With wedges, the same, once per wedge — plus the one new decision:
207
207
  > wedge count does.
208
208
 
209
209
  Attribute equality, within a tolerance. Two wedges that describe the same appearance are one wedge;
210
- two that do not stay two. This is Nanite's rule (`MeshSimplifier` matches corners by attribute
210
+ two that do not stay two. This is Nanite's rule (matching corners by attribute
211
211
  equality on a collapse) and it has the properties that matter:
212
212
 
213
213
  - On a smooth mesh every wedge at a vertex is equal to every other, so there is exactly one, and the
@@ -185,8 +185,7 @@ Revision 2 made τ an absolute per-view value. That splits the one quality lever
185
185
  slider sets `runtime.tau`, and an absolute shadow τ of 2 becomes *finer* than the main view the moment
186
186
  the slider goes to 4 — and the reference it cited is relative. So a view carries **`tau_scale`**,
187
187
  default 1, and cuts at `runtime.tau × tau_scale`. Whoever owns a view may set it; nothing in the runtime
188
- knows what kind of view it is. Unreal's `r.Shadow.NaniteLODBias` (default 1, scale 2^−bias) is the
189
- data point for shadow views; S4 measures before anything but 1 is set.
188
+ knows what kind of view it is. S4 measures before anything but 1 is set.
190
189
 
191
190
  **Shadow views default to 2** *(after S4)*: the shadow context sets it on every view it owns
192
191
  (`SHADOWMAP_VGEO_TAU_SCALE`), and the runtime still knows nothing of what kind of view it cuts for.
@@ -235,8 +234,7 @@ re-selects it. Its first draw is forced at creation, when little but the roots i
235
234
  redraws it touches nothing, so the LRU evicts exactly the pages only it needs. Left alone it would keep
236
235
  a root-level cut indefinitely. So a map whose own views' last reading was **clamped**, and under which
237
236
  pages have installed since, is re-selected like an invalid one. That keys on the map's own incomplete
238
- cut rather than on every LOD change, which is what keeps it clear of the invalidation storm Unreal's
239
- `r.Nanite.VSMInvalidateOnLODDelta` warns about.
237
+ cut rather than on every LOD change, which keeps it clear of an invalidation storm when pages stream in.
240
238
 
241
239
  ### 2.6 Room for proxies — not built
242
240
 
@@ -511,16 +509,11 @@ in turn. That flickers 0.17% of the screen per frame in the authored view and 0.
511
509
 
512
510
  ## References
513
511
 
514
- Read 2026-09-28. Unreal's source is not public; its facts here come from Epic's documentation and
515
- from a UE 5.4.4 console-variable reference that reproduces source locations.
512
+ Read 2026-09-28.
516
513
 
517
514
  - Epic, *Virtual Shadow Maps in Unreal Engine* — per-view Nanite rendering, one pass per light
518
515
  class, 17 directional clipmap levels, page caching and invalidation.
519
516
  https://dev.epicgames.com/documentation/en-us/unreal-engine/virtual-shadow-maps-in-unreal-engine
520
- - `r.Shadow.NaniteLODBias`, default 1, scale 2^−bias (ShadowSceneRenderer.cpp:50, :103 in 5.4.4).
521
- https://indxzero.github.io/ue544cvarwiki/articles/r.shadow.nanitelodbias/
522
- - `r.Nanite.VSMInvalidateOnLODDelta`, default 0, experimental (NaniteCullRaster.cpp:117).
523
- https://indxzero.github.io/ue544cvarwiki/articles/r.nanite.vsminvalidateonloddelta/
524
517
  - Epic, *Hardware Ray Tracing* — ray-traced shadows trace the Nanite fallback mesh.
525
518
  https://dev.epicgames.com/documentation/en-us/unreal-engine/hardware-ray-tracing-in-unreal-engine
526
519
  - Bevy, `crates/bevy_pbr/src/meshlet/cull_shared.wesl` (`lod_error_is_imperceptible`) and
@@ -1 +1 @@
1
- {"version":3,"file":"GPUProbeClipmapGI.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js"],"names":[],"mappings":"AAeA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IA0DI;;OAEG;IACH,sBAFW,eAAe,EAMzB;IAED;;OAEG;IACH,qCAEC;IAED;;OAEG;IACH,qCAEC;IAED;;OAEG;IACH,oCAEC;IAED;;;;;;;;;;;;;;OAcG;IACH;QAT+B,KAAK,EAAzB,UAAU;QACc,IAAI,EAA5B,cAAc;QACgB,QAAQ,EAAtC,oBAAoB;QACJ,KAAK,EAArB,MAAM;QACU,MAAM,EAAtB,MAAM;QACK,IAAI;QACE,MAAM,GAAvB,MAAM;aAiGhB;IAED;;;OAGG;IACH,0BAEC;IAED;;;;;;;;;OASG;IACH,4CAUC;IAwDD;;;;;;;;;;OAUG;IACH;QAN+B,KAAK,EAAzB,UAAU;QACc,IAAI,EAA5B,cAAc;QACgB,QAAQ,EAAtC,oBAAoB;QACF,UAAU,EAA5B,MAAM,EAAE;QACN,MAAM,CAmBlB;IAED;;;;;OAKG;IACH,uBAFW,aAAa,QASvB;IAED;;;OAGG;IACH,0BAHW,aAAa,GACX,OAAO,CAanB;IAyCD;;;OAGG;IACH,gBAoBC;IAED,gBAcC;;CACJ;yBApcY,OAAO,8DAA8D,EAAE,UAAU;8BACjF,OAAO,0BAA0B,EAAE,eAAe;6BAClD,OAAO,8BAA8B,EAAE,cAAc;mCACrD,OAAO,2BAA2B,EAAE,oBAAoB;4BACxD,OAAO,4BAA4B,EAAE,aAAa;gCAjB/B,4BAA4B;gCAQ5B,4BAA4B;uCAErB,6BAA6B"}
1
+ {"version":3,"file":"GPUProbeClipmapGI.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js"],"names":[],"mappings":"AAeA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IA0DI;;OAEG;IACH,sBAFW,eAAe,EAMzB;IAED;;OAEG;IACH,qCAEC;IAED;;OAEG;IACH,qCAEC;IAED;;OAEG;IACH,oCAEC;IAED;;;;;;;;;;;;;;OAcG;IACH;QAT+B,KAAK,EAAzB,UAAU;QACc,IAAI,EAA5B,cAAc;QACgB,QAAQ,EAAtC,oBAAoB;QACJ,KAAK,EAArB,MAAM;QACU,MAAM,EAAtB,MAAM;QACK,IAAI;QACE,MAAM,GAAvB,MAAM;aAkGhB;IAED;;;OAGG;IACH,0BAEC;IAED;;;;;;;;;OASG;IACH,4CAUC;IAwDD;;;;;;;;;;OAUG;IACH;QAN+B,KAAK,EAAzB,UAAU;QACc,IAAI,EAA5B,cAAc;QACgB,QAAQ,EAAtC,oBAAoB;QACF,UAAU,EAA5B,MAAM,EAAE;QACN,MAAM,CAmBlB;IAED;;;;;OAKG;IACH,uBAFW,aAAa,QASvB;IAED;;;OAGG;IACH,0BAHW,aAAa,GACX,OAAO,CAanB;IAyCD;;;OAGG;IACH,gBAoBC;IAED,gBAcC;;CACJ;yBArcY,OAAO,8DAA8D,EAAE,UAAU;8BACjF,OAAO,0BAA0B,EAAE,eAAe;6BAClD,OAAO,8BAA8B,EAAE,cAAc;mCACrD,OAAO,2BAA2B,EAAE,oBAAoB;4BACxD,OAAO,4BAA4B,EAAE,aAAa;gCAjB/B,4BAA4B;gCAQ5B,4BAA4B;uCAErB,6BAA6B"}
@@ -237,6 +237,7 @@ export class GPUProbeClipmapGI {
237
237
  scene,
238
238
  materials,
239
239
  card_rows: this.#holds.rows_buffer,
240
+ resampling: settings.resampling,
240
241
  });
241
242
  }
242
243