@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
@@ -16,7 +16,7 @@ fn compute_extinction(densities: vec3<f32>) -> vec3<f32>{
16
16
  let mie_absorption = mie_density * C_MIE_ABSORPTION;
17
17
  let mie_scattering = mie_density * C_MIE_SCATTERING;
18
18
 
19
- // note - taken from the Hillaire's atmosphere code: https://github.com/sebh/UnrealEngineSkyAtmosphere/blob/183ead5bdacc701b3b626347a680a2f3cd3d4fbd/Resources/RenderSkyCommon.hlsl#L248
19
+ // Rayleigh does not absorb
20
20
  let rayleigh_absorption = 0.0;
21
21
  let rayleigh_scattering = densities.x * C_RAYLEIGH;
22
22
 
@@ -2,7 +2,7 @@
2
2
  * An atmosphere layer of width 'width', and whose density is defined as
3
3
  * 'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term',
4
4
  * clamped to [0,1], and where h is the altitude.
5
- * @see https://github.com/sebh/UnrealEngineSkyAtmosphere/blob/183ead5bdacc701b3b626347a680a2f3cd3d4fbd/Resources/Bruneton17/definitions.glsl#L215
5
+ * @see "Precomputed Atmospheric Scattering", Bruneton & Neyret (2008) / Bruneton (2017)
6
6
  */
7
7
  export const DENSITY_PROFILE_LAYER_STRUCT: WebGPUStruct;
8
8
  /**
@@ -6,7 +6,7 @@ import { WebGPUStruct } from "../../../type/WebGPUStruct.js";
6
6
  * An atmosphere layer of width 'width', and whose density is defined as
7
7
  * 'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term',
8
8
  * clamped to [0,1], and where h is the altitude.
9
- * @see https://github.com/sebh/UnrealEngineSkyAtmosphere/blob/183ead5bdacc701b3b626347a680a2f3cd3d4fbd/Resources/Bruneton17/definitions.glsl#L215
9
+ * @see "Precomputed Atmospheric Scattering", Bruneton & Neyret (2008) / Bruneton (2017)
10
10
  */
11
11
  export const DENSITY_PROFILE_LAYER_STRUCT = WebGPUStruct.from({
12
12
  width: f32,
@@ -198,8 +198,8 @@ Notes on the sources (the literature summaries are with the scripts, §8):
198
198
  - **Hoffman 2019 and 2023, Kutz et al. 2021/2023 (Adobe Standard Material), OpenPBR 1.1.1:**
199
199
  `F = Schlick(μ) − b μ (1 − μ)⁶`, with `b = Schlick(1/7)(1 − tint)/(μ̄(1 − μ̄)⁶)` and μ̄ = 1/7, so that F(82°) =
200
200
  tint · Schlick(82°). It is linear in F0, F90 and b, so a split-sum LUT needs one more channel, `∫ μ(1 − μ)⁶`.
201
- The OpenPBR companion paper gives its hemispherical average as `F0 + (1 − F0)/21 − b/126`. Unreal 5.4 has
202
- `F_AdobeF82` only in the Substrate path tracer, behind `SUBSTRATE_FRESNEL_F82 = 0`; its rasterizer runs Schlick.
201
+ The OpenPBR companion paper gives its hemispherical average as `F0 + (1 − F0)/21 − b/126`. Unreal 5.4 has the
202
+ Adobe F82 tint only in its Substrate path tracer (disabled by default); its rasterizer runs Schlick.
203
203
  - **Filament and Frostbite** use Schlick with a shadowed F90. Belcour et al. 2020 needs a tabulated basis.
204
204
  Vernooij 2024 fits the Lazányi form per metal. No F0-only metal approximation newer than Hauber's was found.
205
205
 
@@ -219,7 +219,7 @@ interpolate. DXIL is counted as above.
219
219
  | Narkowicz 2014, polynomial | uncorrelated Smith, α = (1 − gloss)⁴ | 36 (1 sqrt) | 0.221 (−28%) / 0.019 | 0.080 |
220
220
  | Narkowicz 2014, Lazarov form | same | — | 0.315 (−39%) / 0.041 | 0.080 |
221
221
  | Pesce & Iwanicki 2015 | not stated; roughness = √r, the best of three readings | 15 (1 sqrt, 1 exp) | 0.126 (−15%) / 0.028 | 0.135 |
222
- | UE 5.4 energy fit (`USE_ENERGY_CONSERVATION == 2`, "WIP") | height-correlated table | 26 (2 div, 3 log, 3 exp) | 0.058 (+6.5%) / 0.0064 | 0.015 |
222
+ | UE 5.4 analytic energy fit ("WIP") | height-correlated table | 26 (2 div, 3 log, 3 exp) | 0.058 (+6.5%) / 0.0064 | 0.015 |
223
223
  | MaterialX `mx_ggx_dir_albedo_analytic` (2022) | height-correlated, Schlick | 89 (2 div) | 0.084 (−8.4%) / 0.0060 | 0.025 |
224
224
  | Kulla & Conty 2017, E only | height-correlated | 38 (div, sqrt, log, exp) | 0.011 (3.5%) / 0.0022 | — |
225
225
  | Sforza & Pellacini 2022, E only | uncorrelated Smith | — | 0.226 (−28%) / 0.016 | — |
@@ -331,10 +331,7 @@ images are with the scripts (§8).
331
331
 
332
332
  ## 7. As built (dbdb1cdb1)
333
333
 
334
- **The form is Unreal 5.4's rasterizer.** `F_Schlick(F0, F90, VoH)` in `BRDF.ush`, which Substrate's `Substrate_F_GGX`
335
- calls, computes `Fc = Pow5(1 − VoH)` by multiplies, then returns `F90 Fc + (1 − Fc) F0`. Its `EnvBRDF(F0, F90)` reads
336
- a LUT integrated with the same weight. `chunk_F_Schlick` now has that form; its `pow(x, 5.0)` had compiled to a log
337
- and an exp.
334
+ **The form is standard Schlick.** `F_Schlick(F0, F90, VoH)` computes `Fc = (1 − VoH)^5` by multiplies, then returns `F90 Fc + (1 − Fc) F0`, and the split-sum lookup reads a LUT integrated with the same weight. `chunk_F_Schlick` now has that form; its `pow(x, 5.0)` had compiled to a log and an exp.
338
335
 
339
336
  **Every `F_Hauber` caller moved to it,** and `chunk_F_Hauber` is deleted:
340
337
 
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Schlick's approximation of the Fresnel reflectance (Schlick 1994), in the form Unreal's rasterizer evaluates it
3
- * (`F_Schlick(F0, F90, VoH)` in UE 5.4's `BRDF.ush`, which Substrate's `Substrate_F_GGX` calls): `(1 - VoH)^5` by
4
- * multiplies, as its `Pow5`, then `F90 Fc + (1 - Fc) F0`. A `pow(x, 5.0)` compiles to a log and an exp under DXC.
2
+ * Schlick's approximation of the Fresnel reflectance (Schlick 1994): `(1 - VoH)^5` by
3
+ * multiplies, then `F90 Fc + (1 - Fc) F0`.
4
+ * A `pow(x, 5.0)` compiles to a log and an exp under DXC.
5
5
  *
6
6
  * It is linear in F0 and F90 and reflects all of a perfect conductor (F0 = F90 = 1) at every angle, so the split-sum
7
7
  * LUT holds both its split, `f0 · A + f90 · B` (`chunk_integrate_brdf` integrates this weight), and in `A + B` the
@@ -1,9 +1,9 @@
1
1
  import { CodeChunk } from "../../../compiler/CodeChunk.js";
2
2
 
3
3
  /**
4
- * Schlick's approximation of the Fresnel reflectance (Schlick 1994), in the form Unreal's rasterizer evaluates it
5
- * (`F_Schlick(F0, F90, VoH)` in UE 5.4's `BRDF.ush`, which Substrate's `Substrate_F_GGX` calls): `(1 - VoH)^5` by
6
- * multiplies, as its `Pow5`, then `F90 Fc + (1 - Fc) F0`. A `pow(x, 5.0)` compiles to a log and an exp under DXC.
4
+ * Schlick's approximation of the Fresnel reflectance (Schlick 1994): `(1 - VoH)^5` by
5
+ * multiplies, then `F90 Fc + (1 - Fc) F0`.
6
+ * A `pow(x, 5.0)` compiles to a log and an exp under DXC.
7
7
  *
8
8
  * It is linear in F0 and F90 and reflects all of a perfect conductor (F0 = F90 = 1) at every angle, so the split-sum
9
9
  * LUT holds both its split, `f0 · A + f90 · B` (`chunk_integrate_brdf` integrates this weight), and in `A + B` the
@@ -6,15 +6,12 @@ as fixed-function tone mapping: no settings and no LUTs.
6
6
 
7
7
  ## 1. What it is
8
8
 
9
- - **SDR is Unreal's "Filmic" tone mapper at its default settings.** It is a line-for-line port of UE 5.4's
10
- `CombineLUTsCommon` (PostProcessCombineLUTs.usf, the sRGB output device) and `FilmToneMap` (TonemapCommon.ush), in
11
- `chunk_tonemap_ue_filmic`, evaluated per pixel where UE bakes it into a 32³ LUT:
9
+ - **SDR is Unreal's "Filmic" tone mapper at its default settings.** It evaluates UE 5.4's sRGB LUT colour-grading and filmic tone-mapping pipeline in `chunk_tonemap_ue_filmic` per pixel where UE bakes it into a 32³ LUT:
12
10
  1. ACEScg from UE's default sRGB working space;
13
11
  2. Expand Gamut at 1.0;
14
12
  3. the colour corrections at their defaults, which only clamp at zero;
15
13
  4. Blue Correction at 0.6;
16
- 5. the curve at Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (`FPostProcessSettings`,
17
- Scene.cpp);
14
+ 5. the curve at Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (the default post-process settings);
18
15
  6. the inverse blue correction, then Rec.709 clipped to [0, 1].
19
16
  - **HDR is what Unreal 5.8 does with Filmic on a display past 100 cd/m²:** the SDR picture, taken back to the scene
20
17
  through the inverse ACES 2.0 output transform for a 100 cd/m² display, and rendered again with the ACES 2.0 output
@@ -35,27 +32,24 @@ display, plus 14% of that for every doubling of the peak. A display with no head
35
32
 
36
33
  ## 2. Sources
37
34
 
38
- - **UE 5.4 source, installed** (H:/Program Files/Epic Games/UE_5.4): PostProcessCombineLUTs.usf, TonemapCommon.ush,
39
- ACES.ush, GammaCorrectionCommon.ush, PostProcessCommon.ush, PostProcessTonemap.usf; PostProcessCombineLUTs.cpp,
40
- PostProcessTonemap.cpp, RenderCore.cpp, HDRHelper.h, Scene.cpp. Every default above was read there.
35
+ - **UE 5.4 documentation:** "Color Grading and the Filmic Tonemapper in Unreal Engine", Epic Games.
41
36
  - **UE after 5.4, release notes** (Epic, verified):
42
- - 5.6 adds ACES 2.0 for HDR as an option (`r.HDR.Aces.Version=2`).
37
+ - 5.6 adds ACES 2.0 for HDR as an option.
43
38
  - 5.8 adds "support for ACES 2.0 in SDR", moves ACES into a secondary LUT, and drops the HDR UI paper white from 300
44
39
  to 203 cd/m² to match BT.2408.
45
- - 5.8's `TonemappingMethod` is FILMIC = 0 or STANDARD_ACES = 1.
40
+ - 5.8 offers Filmic (default) and Standard ACES tone-mapping methods.
46
41
  - **UE 5.8's behaviour, Epic staff** (Yutaro Sawada, forums.unrealengine.com/t/tonemappingmethod/2747218, 2026-07-30,
47
42
  in Japanese):
48
- - `r.HDR.Aces.Version` now defaults to 2.
43
+ - ACES 2.0 is now the default HDR version.
49
44
  - Filmic, the default, aims at one look on SDR and HDR. Past 100 cd/m² it is "applied, converted back to linear
50
45
  through the inverse ACES 2.0, then output again through ACES 2.0 at the target luminance".
51
- - `r.HDR.Aces.SceneColorMultiplier` applies to Standard ACES only.
46
+ - The scene colour multiplier applies to Standard ACES only.
52
47
  - The peak comes from the hardware, and mid and minimum luminance derive from it.
53
48
  - **ACES 2.0:**
54
49
  - ampas/aces-core, Lib.Academy.OutputTransform.ctl and Lib.Academy.Tonescale.ctl;
55
50
  - OpenColorIO v2.6.0, ops/fixedfunction/ACES2/Transform.cpp, FixedFunctionOpCPU.cpp and transforms/builtins/ACES.cpp,
56
51
  which this ports.
57
- - **The UE 5.8 source is not public**, so the glue between Filmic and ACES 2.0 (§3, D2) is inferred from the staff
58
- description, not read.
52
+ - The glue between Filmic and ACES 2.0 (§3, D2) is inferred from the staff description.
59
53
 
60
54
  The state of the art elsewhere, for context:
61
55
 
@@ -77,11 +71,10 @@ The state of the art elsewhere, for context:
77
71
  - Rec.709 rather than the canvas's Display P3, because the review measured what P3 does: just past the paper white
78
72
  (203.01 cd/m²) saturated colours jumped up to 67 codes from the SDR picture, 213 of 512 by more than 2. Limited
79
73
  to the SDR picture's own gamut, the HDR picture becomes the SDR one as the headroom shrinks to none. HDR gives up
80
- colours outside Rec.709, which the SDR picture never had either. Unreal may limit to the display's gamut; its
81
- source would say.
74
+ colours outside Rec.709, which the SDR picture never had either.
82
75
  - A display at or under the paper white shows the SDR picture, Unreal's "past 100 cd/m²" rule in this engine's
83
76
  units.
84
- - Unreal sets the HDR level with `HDRPaperWhiteNits` and runs the forward transform at the display's absolute
77
+ - Unreal sets the HDR paper-white level in nits and runs the forward transform at the display's absolute
85
78
  peak. If its paper white scales the SDR picture onto the paper white, which its default of 203, BT.2408 and ours
86
79
  suggest, the two agree.
87
80
  - **D3. No scene colour multiplier.** UE applies its 1.5 to Standard ACES only.
@@ -111,7 +104,7 @@ The state of the art elsewhere, for context:
111
104
 
112
105
  **Against the references**, in `shader_tonemap_aces.spec.js`, under the WGSL emulator, in Display P3:
113
106
 
114
- - SDR is within the dither of a float64 Python port of UE's HLSL, at 29 colours from black to 1000.
107
+ - SDR is within the dither of a float64 Python reference of the Filmic curve and grading pipeline, at 29 colours from black to 1000.
115
108
  - HDR is within 1e-4 (encoded) of OpenColorIO 2.6.0 at four display peaks: its builtin inverse at 100 cd/m², then its
116
109
  ACES 2.0 fixed function limited to Rec.709 between its builtins' clamps.
117
110
  - The exceptions are three bright saturated colours whose SDR picture is near white, where D5's limits apply, and
@@ -157,4 +150,4 @@ It has not been looked at on an HDR display.
157
150
  - **The HDR pass costs three to four times GT7's**: two ACES 2.0 transforms a pixel, 55–102 µs at 0.8 MP on a 4090.
158
151
  Unreal pays this once into a LUT, which was ruled out here.
159
152
  - **The glue to Filmic is inferred** (D2, D5). Unreal evaluates this composite into a LUT, whose interpolation smooths
160
- what D5 limits; its 5.8 source is the way to check.
153
+ what D5 limits.
@@ -1,17 +1,16 @@
1
1
  /**
2
- * The colour matrices of Unreal Engine 5.4's default tone mapping, each the product UE forms from the constants in
3
- * `Engine/Shaders/Private/ACES.ush`, taken in float64 and written out. `M * v` here is UE's `mul(M, v)`.
2
+ * The colour matrices of Unreal Engine 5.4's default tone mapping, each the product of the standard colour-space
3
+ * transforms, taken in float64 and written out.
4
4
  *
5
5
  * - `UE_REC709_TO_AP1`, `UE_AP1_TO_REC709`: UE's default working space (sRGB primaries, D65) to ACEScg and back,
6
- * through XYZ with UE's Bradford D65/D60 adaptation (`XYZ_2_AP1_MAT · D65_2_D60_CAT · sRGB_2_XYZ_MAT`).
7
- * - `UE_AP1_TO_AP0`, `UE_AP0_TO_AP1`: `AP1_2_AP0_MAT` and `AP0_2_AP1_MAT` as UE spells them.
8
- * - `UE_EXPAND_GAMUT`: PostProcessCombineLUTs.usf's `ExpandMat`, from sRGB toward the "wide" primaries the file
9
- * lists, `(XYZ_2_AP1_MAT · Wide_2_XYZ_MAT) · AP1_2_sRGB`. UE applies no adaptation on the wide side, and nor does
10
- * this.
11
- * - `UE_BLUE_CORRECT`, `UE_BLUE_CORRECT_INVERSE`: the file's `BlueCorrect` and `BlueCorrectInv`, both defined on
12
- * AP0, conjugated into AP1 (`AP0_2_AP1 · BlueCorrect · AP1_2_AP0`).
6
+ * through XYZ with Bradford D65/D60 adaptation.
7
+ * - `UE_AP1_TO_AP0`, `UE_AP0_TO_AP1`: ACEScg (AP1) to ACES2065-1 (AP0) and back.
8
+ * - `UE_EXPAND_GAMUT`: gamut expansion matrix from sRGB toward wide primaries,
9
+ * in AP1 without chromatic adaptation on the wide side.
10
+ * - `UE_BLUE_CORRECT`, `UE_BLUE_CORRECT_INVERSE`: the blue-correction matrix and its inverse, both defined on
11
+ * AP0, conjugated into AP1.
13
12
  *
14
- * `UE_AP1_LUMINANCE` is `AP1_RGB2Y`, the Y row of `AP1_2_XYZ_MAT`.
13
+ * `UE_AP1_LUMINANCE` is the Y row of the AP1-to-XYZ matrix.
15
14
  *
16
15
  * @see chunk_tonemap_ue_filmic
17
16
  * @see chunk_aces2_output_transform
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_aces_ue_matrices.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,qCAFU,SAAS,CAgDjB;0BAnEwB,mCAAmC"}
1
+ {"version":3,"file":"chunk_aces_ue_matrices.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,qCAFU,SAAS,CAgDjB;0BAlEwB,mCAAmC"}
@@ -1,19 +1,18 @@
1
1
  import { CodeChunk } from "../../../../compiler/CodeChunk.js";
2
2
 
3
3
  /**
4
- * The colour matrices of Unreal Engine 5.4's default tone mapping, each the product UE forms from the constants in
5
- * `Engine/Shaders/Private/ACES.ush`, taken in float64 and written out. `M * v` here is UE's `mul(M, v)`.
4
+ * The colour matrices of Unreal Engine 5.4's default tone mapping, each the product of the standard colour-space
5
+ * transforms, taken in float64 and written out.
6
6
  *
7
7
  * - `UE_REC709_TO_AP1`, `UE_AP1_TO_REC709`: UE's default working space (sRGB primaries, D65) to ACEScg and back,
8
- * through XYZ with UE's Bradford D65/D60 adaptation (`XYZ_2_AP1_MAT · D65_2_D60_CAT · sRGB_2_XYZ_MAT`).
9
- * - `UE_AP1_TO_AP0`, `UE_AP0_TO_AP1`: `AP1_2_AP0_MAT` and `AP0_2_AP1_MAT` as UE spells them.
10
- * - `UE_EXPAND_GAMUT`: PostProcessCombineLUTs.usf's `ExpandMat`, from sRGB toward the "wide" primaries the file
11
- * lists, `(XYZ_2_AP1_MAT · Wide_2_XYZ_MAT) · AP1_2_sRGB`. UE applies no adaptation on the wide side, and nor does
12
- * this.
13
- * - `UE_BLUE_CORRECT`, `UE_BLUE_CORRECT_INVERSE`: the file's `BlueCorrect` and `BlueCorrectInv`, both defined on
14
- * AP0, conjugated into AP1 (`AP0_2_AP1 · BlueCorrect · AP1_2_AP0`).
8
+ * through XYZ with Bradford D65/D60 adaptation.
9
+ * - `UE_AP1_TO_AP0`, `UE_AP0_TO_AP1`: ACEScg (AP1) to ACES2065-1 (AP0) and back.
10
+ * - `UE_EXPAND_GAMUT`: gamut expansion matrix from sRGB toward wide primaries,
11
+ * in AP1 without chromatic adaptation on the wide side.
12
+ * - `UE_BLUE_CORRECT`, `UE_BLUE_CORRECT_INVERSE`: the blue-correction matrix and its inverse, both defined on
13
+ * AP0, conjugated into AP1.
15
14
  *
16
- * `UE_AP1_LUMINANCE` is `AP1_RGB2Y`, the Y row of `AP1_2_XYZ_MAT`.
15
+ * `UE_AP1_LUMINANCE` is the Y row of the AP1-to-XYZ matrix.
17
16
  *
18
17
  * @see chunk_tonemap_ue_filmic
19
18
  * @see chunk_aces2_output_transform
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Unreal Engine's default SDR tone mapping, "Filmic", with every setting at its default.
3
3
  *
4
- * A line-for-line port of UE 5.4's `CombineLUTsCommon` (PostProcessCombineLUTs.usf) for the sRGB output device and
5
- * `FilmToneMap` (TonemapCommon.ush), as UE evaluates them into its colour grading LUT, here evaluated per pixel:
4
+ * Evaluates UE 5.4's sRGB LUT colour-grading and filmic tone-mapping pipeline,
5
+ * as UE evaluates them into its colour grading LUT, here evaluated per pixel:
6
6
  *
7
7
  * 1. into ACEScg (AP1) from UE's default sRGB working space;
8
8
  * 2. Expand Gamut at its default 1.0, which pushes bright saturated colours toward wider primaries;
9
9
  * 3. the colour corrections at their defaults, which only clamp AP1 at zero;
10
10
  * 4. Blue Correction at its default 0.6, a matrix that turns bright blue toward cyan rather than violet, before the
11
11
  * curve, and its inverse after;
12
- * 5. `FilmToneMap` with Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (`FPostProcessSettings`'
13
- * defaults, Scene.cpp). It is the ACES reference rendering's glow and red modifier and its 0.96 desaturation, then a
12
+ * 5. The filmic curve with Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (the default
13
+ * post-process settings). It is the ACES reference rendering's glow and red modifier and its 0.96 desaturation, then a
14
14
  * parametric toe-straight-shoulder curve on log10 of each AP1 channel, solved so that 0.18 comes out at 0.18, then a
15
15
  * 0.93 desaturation;
16
16
  * 6. back to the working space, clamped at zero, and clipped at 1 as the sRGB target clips it.
@@ -4,16 +4,16 @@ import { chunk_aces_ue_matrices } from "./chunk_aces_ue_matrices.js";
4
4
  /**
5
5
  * Unreal Engine's default SDR tone mapping, "Filmic", with every setting at its default.
6
6
  *
7
- * A line-for-line port of UE 5.4's `CombineLUTsCommon` (PostProcessCombineLUTs.usf) for the sRGB output device and
8
- * `FilmToneMap` (TonemapCommon.ush), as UE evaluates them into its colour grading LUT, here evaluated per pixel:
7
+ * Evaluates UE 5.4's sRGB LUT colour-grading and filmic tone-mapping pipeline,
8
+ * as UE evaluates them into its colour grading LUT, here evaluated per pixel:
9
9
  *
10
10
  * 1. into ACEScg (AP1) from UE's default sRGB working space;
11
11
  * 2. Expand Gamut at its default 1.0, which pushes bright saturated colours toward wider primaries;
12
12
  * 3. the colour corrections at their defaults, which only clamp AP1 at zero;
13
13
  * 4. Blue Correction at its default 0.6, a matrix that turns bright blue toward cyan rather than violet, before the
14
14
  * curve, and its inverse after;
15
- * 5. `FilmToneMap` with Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (`FPostProcessSettings`'
16
- * defaults, Scene.cpp). It is the ACES reference rendering's glow and red modifier and its 0.96 desaturation, then a
15
+ * 5. The filmic curve with Slope 0.88, Toe 0.55, Shoulder 0.26, Black Clip 0 and White Clip 0.04 (the default
16
+ * post-process settings). It is the ACES reference rendering's glow and red modifier and its 0.96 desaturation, then a
17
17
  * parametric toe-straight-shoulder curve on log10 of each AP1 channel, solved so that 0.18 comes out at 0.18, then a
18
18
  * 0.93 desaturation;
19
19
  * 6. back to the working space, clamped at zero, and clipped at 1 as the sRGB target clips it.
@@ -80,7 +80,7 @@ fn ue_film_hue(rgb: vec3<f32>) -> f32 {
80
80
  return clamp(hue, 0.0, 360.0);
81
81
  }
82
82
 
83
- // FilmToneMap: AP1 in, AP1 out
83
+ // Filmic curve: AP1 in, AP1 out
84
84
  fn ue_film_tone_map(color_ap1: vec3<f32>) -> vec3<f32> {
85
85
  var aces = UE_AP1_TO_AP0 * color_ap1;
86
86
 
@@ -63,7 +63,7 @@ the diffuse as well: under a uniform sky a white dielectric returned 1.01 – 1.
63
63
  most at low roughness and grazing view. `re_direct_multiscatter` now scales the accumulated direct diffuse by
64
64
  1 − the dielectric's specular albedo at N·V: `compute_multiscattering_specular` at `specularF0_dielectric`, on the
65
65
  `f_ab` it already fetched. That is the indirect path's factor, "albedo scaling"; UE 5.4 does the same for direct
66
- light (`ComputeEnergyPreservation`, by luminance: always under Substrate, in legacy shading only with the project's
66
+ light (by luminance: always under Substrate, in legacy shading only with the project's
67
67
  energy-conservation setting on), as do EEVEE Next (by the RGB maximum) and OpenPBR / MaterialX
68
68
  (`throughput = 1 − dirAlbedo`). Filament leaves direct diffuse uncoupled (a TODO in
69
69
  `surface_shading_model_standard.fs`); three.js since #33985 scales it per light by 1 − F(V·H).
@@ -13,8 +13,7 @@
13
13
  * it stops short of `x` by up to a ULP/α: half of that rounded to nearest, a whole one rounded toward zero, and
14
14
  * one-sided. WGSL leaves rounding an f32 to a half to the implementation. An RTX 4090 and an RDNA2 iGPU were
15
15
  * measured to round toward zero on every path: `pack2x16float`, `textureStore` and a render target's write. The
16
- * result here is a half already, which any store keeps exactly. UE 5.4's TSR stores its history through
17
- * `QuantizeForFloatRenderTarget` for the same reason.
16
+ * result here is a half already, which any store keeps exactly.
18
17
  *
19
18
  * Within the half's finite range: past 65504 the half above is not finite.
20
19
  *
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_f16_round_stochastic.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,yCAFU,SAAS,CAoBhB;0BA1CuB,6BAA6B"}
1
+ {"version":3,"file":"chunk_f16_round_stochastic.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,yCAFU,SAAS,CAoBhB;0BAzCuB,6BAA6B"}
@@ -15,8 +15,7 @@ import { CodeChunk } from "../../compiler/CodeChunk.js";
15
15
  * it stops short of `x` by up to a ULP/α: half of that rounded to nearest, a whole one rounded toward zero, and
16
16
  * one-sided. WGSL leaves rounding an f32 to a half to the implementation. An RTX 4090 and an RDNA2 iGPU were
17
17
  * measured to round toward zero on every path: `pack2x16float`, `textureStore` and a render target's write. The
18
- * result here is a half already, which any store keeps exactly. UE 5.4's TSR stores its history through
19
- * `QuantizeForFloatRenderTarget` for the same reason.
18
+ * result here is a half already, which any store keeps exactly.
20
19
  *
21
20
  * Within the half's finite range: past 65504 the half above is not finite.
22
21
  *
@@ -1,5 +1,18 @@
1
1
  /**
2
- * Sampling "Spatio-Temoral Blue Noise" texture (see STBN paper by NVIDIA)
2
+ * Two numbers in (0, 1) from the spatio-temporal blue noise texture (NVIDIA, "Spatiotemporal Blue Noise Masks",
3
+ * EGSR 2022), tiled 128 × 128 over the first two axes and 64 slices over the third.
4
+ *
5
+ * ```wgsl
6
+ * fn stbn_sample_vec2(index: vec3<u32>) -> vec2<f32>
7
+ * ```
8
+ *
9
+ * The generator quantises a value x in [0, 1) as `byte = floor(x · 256)` (NVIDIA-RTX/STBN,
10
+ * BlueNoiseTexturesND.cpp and RGBAExporters.cpp), so byte k stands for x in [k/256, (k+1)/256). A `unorm` texture
11
+ * hands it over as k/255, the inverse of a `round(x · 255)` encoding, which puts two of the 256 values on 0 and 1
12
+ * exactly. The value is read as its bin's centre, (k + ½)/256, the mean of x over the bin: the 256 values sit
13
+ * evenly inside (0, 1), symmetric about ½, and a draw that needs u in [0, 1) never meets either end (at u = 0 a
14
+ * weighted draw takes the last item of any weight, however small; at u = 1 none).
15
+ *
3
16
  * @type {CodeChunk}
4
17
  */
5
18
  export const chunk_stbn_sample_vec2: CodeChunk;
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_stbn_sample_vec2.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,qCAFU,SAAS,CAYlB;0BAjByB,6BAA6B"}
1
+ {"version":3,"file":"chunk_stbn_sample_vec2.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,qCAFU,SAAS,CAYlB;0BA9ByB,6BAA6B"}
@@ -2,19 +2,32 @@ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
2
  import { STBN_VEC2_RESOURCE } from "../../../STATIC_GRAPHICS_ENGINE_ASSET_RESOURCES.js";
3
3
 
4
4
  /**
5
- * Sampling "Spatio-Temoral Blue Noise" texture (see STBN paper by NVIDIA)
5
+ * Two numbers in (0, 1) from the spatio-temporal blue noise texture (NVIDIA, "Spatiotemporal Blue Noise Masks",
6
+ * EGSR 2022), tiled 128 × 128 over the first two axes and 64 slices over the third.
7
+ *
8
+ * ```wgsl
9
+ * fn stbn_sample_vec2(index: vec3<u32>) -> vec2<f32>
10
+ * ```
11
+ *
12
+ * The generator quantises a value x in [0, 1) as `byte = floor(x · 256)` (NVIDIA-RTX/STBN,
13
+ * BlueNoiseTexturesND.cpp and RGBAExporters.cpp), so byte k stands for x in [k/256, (k+1)/256). A `unorm` texture
14
+ * hands it over as k/255, the inverse of a `round(x · 255)` encoding, which puts two of the 256 values on 0 and 1
15
+ * exactly. The value is read as its bin's centre, (k + ½)/256, the mean of x over the bin: the 256 values sit
16
+ * evenly inside (0, 1), symmetric about ½, and a draw that needs u in [0, 1) never meets either end (at u = 0 a
17
+ * weighted draw takes the last item of any weight, however small; at u = 1 none).
18
+ *
6
19
  * @type {CodeChunk}
7
20
  */
8
21
  export const chunk_stbn_sample_vec2 = CodeChunk.from(
9
22
  //language=WGSL
10
23
  `
11
- fn stbn_sample_vec2(index: vec3<u32> ) -> vec2<f32>{
24
+ fn stbn_sample_vec2(index: vec3<u32>) -> vec2<f32> {
25
+ let noise_coord = index % vec3(128, 128, 64);
12
26
 
13
- let noise_coord = index % vec3(128,128,64);
14
-
15
- return textureLoad(tSTBN_vec2, noise_coord, 0).rg;
27
+ // k/255 to (k + 1/2)/256
28
+ return fma(textureLoad(tSTBN_vec2, noise_coord, 0).rg, vec2(255.0 / 256.0), vec2(0.5 / 256.0));
16
29
  }
17
30
  `
18
31
  )
19
32
 
20
- chunk_stbn_sample_vec2.requireResource(STBN_VEC2_RESOURCE);
33
+ chunk_stbn_sample_vec2.requireResource(STBN_VEC2_RESOURCE);
@@ -1,5 +1,14 @@
1
1
  /**
2
- * Sampling "Spatio-Temoral Blue Noise" texture (see STBN paper by NVIDIA)
2
+ * Three numbers in (0, 1) from the spatio-temporal blue noise texture (NVIDIA, "Spatiotemporal Blue Noise Masks",
3
+ * EGSR 2022), tiled 128 × 128 over the first two axes and 64 slices over the third.
4
+ *
5
+ * ```wgsl
6
+ * fn stbn_sample_vec3(index: vec3<u32>) -> vec3<f32>
7
+ * ```
8
+ *
9
+ * Each byte is read as the centre of the bin the generator quantised it from, (k + ½)/256, rather than the
10
+ * `unorm` k/255; see {@link chunk_stbn_sample_vec2}.
11
+ *
3
12
  * @type {CodeChunk}
4
13
  */
5
14
  export const chunk_stbn_sample_vec3: CodeChunk;
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_stbn_sample_vec3.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,qCAFU,SAAS,CAYlB;0BAjByB,6BAA6B"}
1
+ {"version":3,"file":"chunk_stbn_sample_vec3.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,qCAFU,SAAS,CAYlB;0BA1ByB,6BAA6B"}
@@ -2,20 +2,28 @@ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
2
  import { STBN_VEC3_RESOURCE } from "../../../STATIC_GRAPHICS_ENGINE_ASSET_RESOURCES.js";
3
3
 
4
4
  /**
5
- * Sampling "Spatio-Temoral Blue Noise" texture (see STBN paper by NVIDIA)
5
+ * Three numbers in (0, 1) from the spatio-temporal blue noise texture (NVIDIA, "Spatiotemporal Blue Noise Masks",
6
+ * EGSR 2022), tiled 128 × 128 over the first two axes and 64 slices over the third.
7
+ *
8
+ * ```wgsl
9
+ * fn stbn_sample_vec3(index: vec3<u32>) -> vec3<f32>
10
+ * ```
11
+ *
12
+ * Each byte is read as the centre of the bin the generator quantised it from, (k + ½)/256, rather than the
13
+ * `unorm` k/255; see {@link chunk_stbn_sample_vec2}.
14
+ *
6
15
  * @type {CodeChunk}
7
16
  */
8
17
  export const chunk_stbn_sample_vec3 = CodeChunk.from(
9
18
  //language=WGSL
10
19
  `
11
- fn stbn_sample_vec3(index: vec3<u32> ) -> vec3<f32>{
20
+ fn stbn_sample_vec3(index: vec3<u32>) -> vec3<f32> {
21
+ let noise_coord = index % vec3(128, 128, 64);
12
22
 
13
- let noise_coord = index % vec3(128,128,64);
14
-
15
- return textureLoad(tSTBN_vec3, noise_coord, 0).rgb;
23
+ // k/255 to (k + 1/2)/256
24
+ return fma(textureLoad(tSTBN_vec3, noise_coord, 0).rgb, vec3(255.0 / 256.0), vec3(0.5 / 256.0));
16
25
  }
17
26
  `
18
27
  )
19
28
 
20
29
  chunk_stbn_sample_vec3.requireResource(STBN_VEC3_RESOURCE);
21
-
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Reference https://github.com/markjarzynski/HashFunctionsForGPURendering_Unreal/blob/master/Shaders/Random.ush
2
+ * MurmurHash3 3D integer hash (Jarzynski & Olano 2020, "Hash Functions for GPU Rendering", JCGT).
3
3
  * @type {CodeChunk}
4
4
  */
5
5
  export const chunk_murmur3_vec3u: CodeChunk;
@@ -3,7 +3,7 @@ import { chunk_u32_rotate_left } from "../chunk_u32_rotate_left.js";
3
3
  import { chunk_murmur3_fmix } from "./chunk_murmur3_fmix.js";
4
4
 
5
5
  /**
6
- * Reference https://github.com/markjarzynski/HashFunctionsForGPURendering_Unreal/blob/master/Shaders/Random.ush
6
+ * MurmurHash3 3D integer hash (Jarzynski & Olano 2020, "Hash Functions for GPU Rendering", JCGT).
7
7
  * @type {CodeChunk}
8
8
  */
9
9
  export const chunk_murmur3_vec3u = CodeChunk.from(
@@ -135,29 +135,24 @@ Neither replaces the other.
135
135
 
136
136
  ## 3. What other engines do
137
137
 
138
- Read from source or primary docs. Unreal was read in the installed UE 5.4 tree. The rest comes from the research brief
138
+ Read from primary docs and references. The rest comes from the research brief
139
139
  `shader-switch-prior-art.md` in the side-notes folder of this session group, which has the URLs and identifiers; its
140
140
  Bevy and gpuweb claims were re-checked against the pages.
141
141
 
142
- **Unreal: permutation domains.** `RenderCore/Public/ShaderPermutation.h`. A dimension is a C++ type made by
143
- `SHADER_PERMUTATION_BOOL` / `_INT` / `_RANGE_INT` / `_SPARSE_INT` / `_ENUM_CLASS`, each carrying a `DefineName`.
144
- `TShaderPermutationDomain<…>` is their product, and its permutation id is mixed-radix over the dimensions
145
- (`ToDimensionValueId`). A shader class lists its dimensions. `FDeferredLightPS` (`Renderer/Private/LightRendering.cpp:816`)
146
- has 14 of them, including `FVirtualShadowMapMask`.
142
+ **Unreal: permutation domains.** A dimension is a C++ type (bool, int, range, sparse int, or enum), each carrying a preprocessor define name. Their product is a permutation domain whose permutation id is mixed-radix over the dimensions. A shader class lists its dimensions (for example, the deferred light pixel shader has 14 of them, including the virtual shadow map mask).
147
143
 
148
- - `ShouldCompilePermutation` culls combinations: a directional light × the VSM mask is never compiled.
149
- - `RemapPermutation` folds combinations together: visualize-culling forces the others off.
150
- - `ModifyCompilationEnvironment` adds the VSM defines only when that dimension is on.
151
- - The draw site sets every dimension by hand (`LightRendering.cpp:2804-2833`), then asks the shader map for
144
+ - Unused combinations are culled before compilation (e.g., a directional light × the VSM mask is never compiled).
145
+ - Redundant combinations are remapped together (e.g., visualize-culling forces the others off).
146
+ - Conditional defines (such as VSM defines) are added to the compilation environment only when that dimension is on.
147
+ - The draw site sets every dimension by hand, then asks the shader map for
152
148
  `(shader type, permutation id)`. **The owner knows every dimension.** Nothing derives the domain: the shader class lists it
153
149
  and the draw site sets it.
154
150
  - Dimensions select `#if` blocks. Dependencies are `#include`s, which can sit inside an `#if`, but nothing declares
155
151
  what each value pulls in.
156
152
 
157
153
  **Unreal: vertex factories and materials.** These are the other half of Unreal's answer, and they are an *interface
158
- slot*. `FVertexFactoryType::ModifyCompilationEnvironment` maps the virtual include path
159
- `/Engine/Generated/VertexFactory.ush` to `#include "<this factory's file>"` (`RenderCore/Public/VertexFactory.h:418-420`).
160
- `/Engine/Generated/Material.ush` is mapped to the generated material code (`Engine/Private/Materials/MaterialShared.cpp:3357`).
154
+ slot*. The vertex factory compilation environment maps a virtual include path to the concrete implementation's source file.
155
+
161
156
  Every shader that includes the path gets the implementation the compile supplies, and nothing in between is twinned.
162
157
  The shader map is keyed by material × vertex factory × shader type × permutation.
163
158
 
@@ -587,7 +582,7 @@ Risks:
587
582
  | piece | reuses / generalises |
588
583
  |---|---|
589
584
  | specialization | `CodeChunk`'s own graph and private fields, with the same identity-based dedup the linker relies on. No second linker; `compile()` is untouched. |
590
- | values | engine enums as they are (`ShadeIndirectLightingMode` unchanged), or a bool. The shape of Unreal's `SHADER_PERMUTATION_ENUM_CLASS`/`_BOOL`, and like its `SPARSE_INT` an enum need not be contiguous. |
585
+ | values | engine enums as they are (`ShadeIndirectLightingMode` unchanged), or a bool. Like Unreal's enum/bool/sparse-int permutation dimensions, an enum need not be contiguous. |
591
586
  | variant objects | `ShaderDescriptor`, `ImageShader`, `ComputeShader` and `TemplatePassDescriptor` themselves. A variant is one of them, so draw, dispatch, validation and the profiler need nothing new. |
592
587
  | GPU pipeline cache | `RenderPipelineManager` / `ComputePipelineManager` unchanged. Text equality separates and shares variants. |
593
588
  | preparation | `PipelineEnquiry` and `prepare()` at start-up, offering the selected variants. A flip uses `obtain` as anything made on first use does. |
@@ -60,20 +60,20 @@ summed over the ten scenes, with less false shadow on fine detail, at 3.4× its
60
60
 
61
61
  ## 1. The failure modes, and why they happen
62
62
 
63
- From the primary sources. Paths under `UE_5.4/Engine/` are Epic's installed source; Bend is
63
+ From the primary sources. Bend is
64
64
  `bend_sss_gpu.h` / `bend_sss_cpu.h` (Sony, Apache 2.0) and the SIGGRAPH 2023 talk's slide text.
65
65
 
66
66
  | # | Failure mode | Cause | What shipping code does about it |
67
67
  |---|---|---|---|
68
- | 1 | **Heightfield thickness**: thin casters smear, thick ones leak | The depth buffer holds each caster's front and nothing behind it, so every method assumes a slab thickness | Bend: 0.5% of the sample's depth to the far plane (here: in front of the ray by up to 1% of its view depth). UE desktop: ≈ L/2, set by ray length and 8 steps (`ScreenSpaceShadowRayCast.ush:26-32`, `DeferredLightingCommon.ush:63-64`). Filament ≈ 7.5 cm, three.js 1 cm, Bevy 10 cm, all world space. Nobody reconstructs back faces |
68
+ | 1 | **Heightfield thickness**: thin casters smear, thick ones leak | The depth buffer holds each caster's front and nothing behind it, so every method assumes a slab thickness | Bend: 0.5% of the sample's depth to the far plane (here: in front of the ray by up to 1% of its view depth). UE desktop: ≈ L/2, set by ray length and 8 steps. Filament ≈ 7.5 cm, three.js 1 cm, Bevy 10 cm, all world space. Nobody reconstructs back faces |
69
69
  | 2 | **Self-shadowing** where the sun or the view grazes a surface, and on normal-mapped surfaces | A ray leaving a surface at a grazing angle runs along it, so the surface's own neighbouring depths sit just in front of it; depth is quantised per pixel; the shading normal and the depth disagree | Bend: an edge-aware bilinear read across the ray's minor axis, pushing the casting depth back by the step across the pixel; a start bias "suppressed legitimate detail" (talk). UE: skip samples bit-equal to the start depth. Bevy: an open draft (#25355) bends the ray toward the normal. Normal maps are invisible to all of them; Days Gone wrote a ±8 cm material displacement into depth to get that detail |
70
70
  | 3 | **View dependence** | Only casters that are on screen and in front cast; the reach is counted in pixels (Bend, Godot, UE screen mode) or the screen reach shrinks with distance (world-space methods) | Bend: "they cannot replace shadow maps, only complement them" (talk). Nothing fixes it |
71
71
  | 4 | **Noise and TAA instability** | Jittered starts and few steps (UE 8 steps + IGN over 8 frames, Bevy, three.js, Filament) need TAA to converge; the jittered depth buffer changes what is in it | Bend is deterministic: 4 interleaved min-reductions averaged, 4 hard near samples, an 8-sample fade. Every other source leans on the scene's TAA |
72
72
  | 5 | **Disocclusion trails** | Temporal accumulation of a term that changes with the view | No source keeps contact-shadow history; trails are whatever TAA does |
73
73
  | 6 | **Screen edges** | Off-screen casters are not in the depth buffer | Bend reads off-screen as the far plane (clamp-to-border); UE treats an off-screen hit as a miss; Filament fades by the ray's end. A fade only hides the pop |
74
- | 7 | **Scale with distance** | A pixel reach means a world reach growing with depth; a world reach shrinks below a pixel | Bend 60 px fixed; UE screen mode L ∝ depth (`DeferredLightingCommon.ush:162-168`); world-space methods vanish far away |
75
- | 8 | **Alpha-tested foliage** | Cut-outs alias per texel, cards are millimetres thick, two-sided leaves are their own receivers | Bend's `IgnoreEdgePixels` "can also thin out otherwise valid shadows, especially on foliage edges". UE attenuates subsurface/foliage receivers by 1 − exp(−density·distance) (`DeferredLightingCommon.ush:197-211`) |
76
- | 9 | **Double darkening** against the shadow map | Multiplying two partial visibilities (a PCF penumbra and a partial contact) is darker than either | UE desktop multiplies (`DeferredLightingCommon.ush:213-216`); UE mobile and Godot take the min; Filament and Bevy multiply but skip pixels the map already shadows fully |
74
+ | 7 | **Scale with distance** | A pixel reach means a world reach growing with depth; a world reach shrinks below a pixel | Bend 60 px fixed; UE screen mode L ∝ depth; world-space methods vanish far away |
75
+ | 8 | **Alpha-tested foliage** | Cut-outs alias per texel, cards are millimetres thick, two-sided leaves are their own receivers | Bend's `IgnoreEdgePixels` "can also thin out otherwise valid shadows, especially on foliage edges". UE attenuates subsurface/foliage receivers by 1 − exp(−density·distance) |
76
+ | 9 | **Double darkening** against the shadow map | Multiplying two partial visibilities (a PCF penumbra and a partial contact) is darker than either | UE desktop multiplies; UE mobile and Godot take the min; Filament and Bevy multiply but skip pixels the map already shadows fully |
77
77
  | 10 | **The light's point on screen** | Rays converge on it, so near it every ray is a few pixels long; Bend widens its window there | Bend: `min(sample_distance + direction, 1/thickness)` in the window scale |
78
78
 
79
79
  ## 2. Measured
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Default ceiling on the dynamic LOD bias a virtual shadow map may add under pool pressure (matches
3
- * UE 5.4+'s `MaxResolutionLodBias = 2.0`, up to a 4× linear / 16× area reduction). Setting `0`
4
- * disables dynamic resolution throttling.
2
+ * Default ceiling on the dynamic LOD bias a virtual shadow map may add under pool pressure (up to a
3
+ * 4× linear / 16× area reduction). Setting `0` disables dynamic resolution throttling.
5
4
  *
6
5
  * @type {number}
7
6
  */
@@ -1 +1 @@
1
- {"version":3,"file":"DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,+CAFU,MAAM,CAEkC"}
1
+ {"version":3,"file":"DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,+CAFU,MAAM,CAEkC"}
@@ -1,8 +1,7 @@
1
- /**
2
- * Default ceiling on the dynamic LOD bias a virtual shadow map may add under pool pressure (matches
3
- * UE 5.4+'s `MaxResolutionLodBias = 2.0`, up to a 4× linear / 16× area reduction). Setting `0`
4
- * disables dynamic resolution throttling.
5
- *
6
- * @type {number}
7
- */
8
- export const DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS = 2;
1
+ /**
2
+ * Default ceiling on the dynamic LOD bias a virtual shadow map may add under pool pressure (up to a
3
+ * 4× linear / 16× area reduction). Setting `0` disables dynamic resolution throttling.
4
+ *
5
+ * @type {number}
6
+ */
7
+ export const DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS = 2;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Default high-watermark pool demand ratio `(wants + requests_dropped) / slots.capacity` above
3
3
  * which a virtual shadow map steps its dynamic LOD bias up to coarsen requested pages and prevent
4
- * hard page-pool overflow (matches UE 5.4+'s `MaxPagePoolLoadFactor = 0.85`).
4
+ * hard page-pool overflow.
5
5
  *
6
6
  * Paired with {@link DEFAULT_VSM_LOW_LOAD_FACTOR} to form a hysteresis deadband so demand hovering
7
7
  * near capacity does not oscillate between adjacent clipmap levels. Setting `0` disables dynamic