@woosh/meep-engine 3.60.0 → 3.60.2

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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/shade/playground/sh_exponential/main.js +2 -2
  3. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +4 -0
  4. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts +6 -1
  5. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  6. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +12 -4
  7. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  8. package/src/shade/renderer/light/GPULightCollection.js +3 -5
  9. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts +13 -5
  10. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  11. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +133 -7
  12. package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts +9 -0
  13. package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts.map +1 -1
  14. package/src/shade/renderer/light/environment/graph_filter_environment_map.js +33 -4
  15. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts +13 -0
  16. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts.map +1 -0
  17. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js +169 -0
  18. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts +3 -0
  19. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -0
  20. package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +105 -0
  21. package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.d.ts +10 -0
  22. package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.d.ts.map +1 -0
  23. package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.js +23 -0
  24. package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts +8 -4
  25. package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts.map +1 -1
  26. package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.js +9 -5
  27. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  28. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +26 -1
  29. package/src/view/elements/progress/segmented/GhostTrail.d.ts +78 -12
  30. package/src/view/elements/progress/segmented/GhostTrail.d.ts.map +1 -1
  31. package/src/view/elements/progress/segmented/GhostTrail.js +294 -98
  32. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts +68 -24
  33. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts.map +1 -1
  34. package/src/view/elements/progress/segmented/SegmentedResourceBarView.js +196 -128
  35. package/src/view/elements/progress/segmented/playground/main.js +18 -11
  36. package/src/view/elements/progress/segmented/segment_cell_extent.d.ts +14 -0
  37. package/src/view/elements/progress/segmented/segment_cell_extent.d.ts.map +1 -0
  38. package/src/view/elements/progress/segmented/segment_cell_extent.js +44 -0
  39. package/src/view/elements/progress/segmented/segment_mask_url.d.ts +24 -0
  40. package/src/view/elements/progress/segmented/segment_mask_url.d.ts.map +1 -0
  41. package/src/view/elements/progress/segmented/segment_mask_url.js +74 -0
  42. package/src/view/elements/progress/segmented/segment_mask_width.d.ts +18 -0
  43. package/src/view/elements/progress/segmented/segment_mask_width.d.ts.map +1 -0
  44. package/src/view/elements/progress/segmented/segment_mask_width.js +46 -0
  45. package/src/view/elements/progress/segmented/segmented-resource-bar.css +6 -10
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Advanced 3D game engine",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "3.60.0",
9
+ "version": "3.60.2",
10
10
  "exports": {
11
11
  "./build/*": "./build/*",
12
12
  "./src/*": "./src/*",
@@ -1,7 +1,7 @@
1
1
  import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
2
2
  import { GPUFrameRecorder } from "../../device/timing/profile/GPUFrameRecorder.js";
3
3
  import { GraphicsContext } from "../../renderer/GraphicsContext.js";
4
- import { execute_filter_environment_map } from "../../renderer/light/environment/execute_filter_environment_map.js";
4
+ import { encode_filter_environment_map } from "../../renderer/light/environment/graph_filter_environment_map.js";
5
5
  import { load_environment_map, ProjectionMappingType } from "../../renderer/light/environment/rgbe/load_environment_map.js";
6
6
  import { SH_EXP_ENCODE_PARAMETERS_STRUCT } from "../../renderer/light/environment/sh_exponential/SH_EXP_ENCODE_PARAMETERS_STRUCT.js";
7
7
  import { SH_EXP_P4Q2_SETTINGS, SHExpEncoder } from "../../renderer/light/environment/sh_exponential/SHExpEncoder.js";
@@ -289,7 +289,7 @@ async function main() {
289
289
  const cmd = ShadeGPUCommandContext.create(graphics, 'SH exponential playground / environment');
290
290
 
291
291
  // the engine IBL row reads the prefiltered levels, exactly as the forward path does
292
- execute_filter_environment_map(environment, cmd);
292
+ encode_filter_environment_map(environment, cmd);
293
293
 
294
294
  // the reference's own grid, finer than the encoder's
295
295
  shader_sh_exp_environment_cells.dispatch({
@@ -3230,6 +3230,10 @@ a per-invocation function instead. Each is tier 2 whole, and no thread's loop ru
3230
3230
  - one bilinear depth tap toward p_b at distance `|p_b − probe|/s_c`;
3231
3231
  - weight = trilinear × `probe_visibility_weight`: the wrap back face, Chebyshev from the tap, and
3232
3232
  the crush.
3233
+ - The wrap is `((n·ω+1)/2)²`, without DDGI's + 0.2. With it, a probe behind Sponza's thin outer
3234
+ wall, which Chebyshev passes at 0.94–0.97 through the grazing texels of an 8² depth map, held ≈0.25,
3235
+ above the crush: 11% of the wall's pick, 32% in cascade 1, where the wall sits 0.15 of a spacing past
3236
+ the lattice plane, a dark lattice of blotches. Without it, 0.1% and 0.6%.
3233
3237
 
3234
3238
  No SH and no radiance is read here.
3235
3239
  4. **Pick two**, the extracted `pick2_wrs_mat2x4`, which is Brick4's selection:
@@ -4,7 +4,12 @@
4
4
  * ray (`PROBE_RAY_NONE`) is past the frame's last. The radiance the ray brings back goes over its light,
5
5
  * three half floats:
6
6
  *
7
- * - a **miss** sees the environment along its direction, `sample_environment_color`;
7
+ * - a **miss** sees the prefiltered environment along its direction, `sample_environment_map`, at the
8
+ * roughness whose lobe covers the solid angle of a radiance texel, 4π/n_r². The prefilter's lobe is the
9
+ * spherical Gaussian exp(2(N·H − 1)/m²), m the roughness squared, whose integral over the sphere is
10
+ * 4πm²·(1 − (1 − e^(−k))/k), k = 2/m²: m = 1/n_r to 0.2% at n_r = 16, so the roughness is 1/√n_r. A sun
11
+ * a few texels wide at level 0 is then a lobe a texel wide, which a texel's jittered rays all see,
12
+ * rather than a point that only some probes' jitter ever lands on;
8
13
  * - a **front-face hit** is `emissive + ρ_d·E_dir/π` (D7), its surface from `probe_clipmap_hit_surface`
9
14
  * at the hit's position, the probe's origin plus t along the ray, with the hit's normal, at its
10
15
  * cascade's voxel; E_dir is what trace left in the light;
@@ -1 +1 @@
1
- {"version":3,"file":"shader_probe_clipmap_shade.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js"],"names":[],"mappings":"AAwDA;;;;;;;;;;;;;;;;;GAiBG;AACH,yCAFU,aAAa,CAyDpB;8BAhH2B,kCAAkC"}
1
+ {"version":3,"file":"shader_probe_clipmap_shade.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js"],"names":[],"mappings":"AA2DA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,yCAFU,aAAa,CAyDpB;8BAxH2B,kCAAkC"}
@@ -1,4 +1,3 @@
1
- import { chunk_sample_environment_color } from "../../../atmosphere/chunk_sample_environment_color.js";
2
1
  import {
3
2
  MESH_CARD_ALBEDO_RESOURCE,
4
3
  MESH_CARD_EMISSIVE_RESOURCE,
@@ -11,6 +10,7 @@ import { ENVIRONMENT_MAP_SAMPLER_RESOURCE } from "../../../light/environment/ENV
11
10
  import { SCENE_DATABASE_RESOURCE } from "../../../scene/SCENE_DATABASE_RESOURCE.js";
12
11
  import { chunk_rgbe9995_decode } from "../../../shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.js";
13
12
  import { chunk_rgbe9995_encode } from "../../../shader/chunk/color/rgbe9995/chunk_rgbe9995_encode.js";
13
+ import { chunk_sample_environment_map } from "../../../shader/chunk/texture/sample/chunk_sample_environment_map.js";
14
14
  import { chunk_uv_octahedral_unit_decode } from "../../../shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js";
15
15
  import { chunk_folded_workgroup_index } from "../../../shader/chunk/grid/chunk_folded_workgroup_index.js";
16
16
  import { RECIPROCAL_PI_DECLARATION } from "../../../shader/chunk/math/RECIPROCAL_PI_DECLARATION.js";
@@ -32,9 +32,12 @@ import {
32
32
  PROBE_TRACE_READ_RESOURCE
33
33
  } from "./chunk_probe_clipmap.js";
34
34
  import { PROBE_THREAD_WORKGROUP_SIZE } from "./PROBE_CLIPMAP.js";
35
+ import { PROBE_TRACE_SETTINGS } from "./shader_probe_clipmap_trace.js";
35
36
 
36
37
  const resources = new ShaderResourceSetDescriptor();
37
38
 
39
+ resources.setImmediate("settings", PROBE_TRACE_SETTINGS);
40
+
38
41
  resources.createGroup()
39
42
  .add(PROBE_TRACE_READ_RESOURCE)
40
43
  .add(PROBE_ORIGINS_READ_RESOURCE)
@@ -60,7 +63,12 @@ resources.createGroup()
60
63
  * ray (`PROBE_RAY_NONE`) is past the frame's last. The radiance the ray brings back goes over its light,
61
64
  * three half floats:
62
65
  *
63
- * - a **miss** sees the environment along its direction, `sample_environment_color`;
66
+ * - a **miss** sees the prefiltered environment along its direction, `sample_environment_map`, at the
67
+ * roughness whose lobe covers the solid angle of a radiance texel, 4π/n_r². The prefilter's lobe is the
68
+ * spherical Gaussian exp(2(N·H − 1)/m²), m the roughness squared, whose integral over the sphere is
69
+ * 4πm²·(1 − (1 − e^(−k))/k), k = 2/m²: m = 1/n_r to 0.2% at n_r = 16, so the roughness is 1/√n_r. A sun
70
+ * a few texels wide at level 0 is then a lobe a texel wide, which a texel's jittered rays all see,
71
+ * rather than a point that only some probes' jitter ever lands on;
64
72
  * - a **front-face hit** is `emissive + ρ_d·E_dir/π` (D7), its surface from `probe_clipmap_hit_surface`
65
73
  * at the hit's position, the probe's origin plus t along the ray, with the hit's normal, at its
66
74
  * cascade's voxel; E_dir is what trace left in the light;
@@ -96,7 +104,7 @@ fn main(
96
104
  var albedo = vec3<f32>(0.0);
97
105
 
98
106
  if (kind == ${BRICK_TRACE_MISS}u) {
99
- radiance = sample_environment_color(tEnvironment, sEnvironment, direction);
107
+ radiance = sample_environment_map(tEnvironment, sEnvironment, direction, inverseSqrt(f32(settings.radiance_resolution)));
100
108
  } else if (kind == ${BRICK_TRACE_HIT}u && (record.flags & PROBE_RAY_BACK_FACE) == 0u) {
101
109
  let k = record.flags >> PROBE_RAY_PROBE_SHIFT;
102
110
  let origin = probe_clipmap_ray_origin(probe_origins[k], probe_trace.elements[k], record.direction);
@@ -121,7 +129,7 @@ fn main(
121
129
  chunk_probe_clipmap_ray_origin,
122
130
  chunk_folded_workgroup_index,
123
131
  chunk_uv_octahedral_unit_decode,
124
- chunk_sample_environment_color,
132
+ chunk_sample_environment_map,
125
133
  chunk_probe_clipmap_hit_surface,
126
134
  chunk_rgbe9995_decode,
127
135
  chunk_rgbe9995_encode,
@@ -1 +1 @@
1
- {"version":3,"file":"GPULightCollection.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/light/GPULightCollection.js"],"names":[],"mappings":"AAsDA;IAEI;;;;;OAKG;IACH,iCAFW,eAAe,QAOzB;IA6KD;;;;;;OAMG;IACH,sBALW,eAAe,aACf,eAAe,EA2CzB;IAnND;;;OAGG;IACH,6DAEC;IAeD;;OAEG;IACH,4BAEC;IAED;;;;;;OAMG;IACH,6BAEC;IAED;;;;;;;OAOG;IACH,qFAEC;IAWD;;OAEG;IACH,+CAEC;IA6DD;;;OAGG;IACH,qCAEC;IAED;;;;;;OAMG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,yCAEC;IAGD,4BAMC;IAoMD;;;OAGG;IACH,YAFW,eAAe,QAoCzB;IAED;;;;;;;;;OASG;IACH,gBAFW,OAAO,+BAA+B,EAAE,YAAY,QAkC9D;IAED;;;;;;OAMG;IACH,uBAHW,sBAAsB,mCA6BhC;IAwED,gBAUC;;CACJ;8BA1lBY,OAAO,0CAA0C,EAAE,eAAe;8BAClE,OAAO,uBAAuB,EAAE,eAAe;8BAC/C,OAAO,6BAA6B,EAAE,eAAe;8BACrD,OAAO,sBAAsB,EAAE,eAAe;4BA/B/B,gCAAgC;yCACnB,2CAA2C;kCAClD,iCAAiC;uCAH5B,wCAAwC"}
1
+ {"version":3,"file":"GPULightCollection.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/light/GPULightCollection.js"],"names":[],"mappings":"AAoDA;IAEI;;;;;OAKG;IACH,iCAFW,eAAe,QAOzB;IA6KD;;;;;;OAMG;IACH,sBALW,eAAe,aACf,eAAe,EA2CzB;IAnND;;;OAGG;IACH,6DAEC;IAeD;;OAEG;IACH,4BAEC;IAED;;;;;;OAMG;IACH,6BAEC;IAED;;;;;;;OAOG;IACH,qFAEC;IAWD;;OAEG;IACH,+CAEC;IA6DD;;;OAGG;IACH,qCAEC;IAED;;;;;;OAMG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,yCAEC;IAGD,4BAMC;IAoMD;;;OAGG;IACH,YAFW,eAAe,QAoCzB;IAED;;;;;;;;;OASG;IACH,gBAFW,OAAO,+BAA+B,EAAE,YAAY,QAkC9D;IAED;;;;;;OAMG;IACH,uBAHW,sBAAsB,mCA6BhC;IAwED,gBAUC;;CACJ;8BA1lBY,OAAO,0CAA0C,EAAE,eAAe;8BAClE,OAAO,uBAAuB,EAAE,eAAe;8BAC/C,OAAO,6BAA6B,EAAE,eAAe;8BACrD,OAAO,sBAAsB,EAAE,eAAe;4BA7B/B,gCAAgC;yCACnB,2CAA2C;kCAClD,iCAAiC;uCAH5B,wCAAwC"}
@@ -7,10 +7,8 @@ import { GPUSceneShadowmapContext } from "../shadow/map/GPUSceneShadowmapContext
7
7
  import { GPUTextureContext } from "../texture/GPUTextureContext.js";
8
8
  import { texture_mip_level_count } from "../../util/texture_mip_extent.js";
9
9
  import { texture_write_to_gpu } from "../texture/texture_write_to_gpu.js";
10
- import {
11
- enquire_filter_environment_map_pipelines,
12
- execute_filter_environment_map
13
- } from "./environment/execute_filter_environment_map.js";
10
+ import { enquire_filter_environment_map_pipelines } from "./environment/execute_filter_environment_map.js";
11
+ import { encode_filter_environment_map } from "./environment/graph_filter_environment_map.js";
14
12
  import { SPHERE_PROBE_MIPMAP_LEVELS } from "./environment/DEFINES.js";
15
13
  import { octahedral_skirt_matches } from "./environment/octahedral_skirt_matches.js";
16
14
  import { ENVIRONMENT_SH_TYPE, shader_project_environment_sh } from "./environment/shader_project_environment_sh.js";
@@ -398,7 +396,7 @@ export class GPULightCollection {
398
396
  // convolve
399
397
  const cmd = ShadeGPUCommandContext.create(ctx);
400
398
 
401
- execute_filter_environment_map(gpu_ctx, cmd);
399
+ encode_filter_environment_map(gpu_ctx, cmd);
402
400
 
403
401
  // after the prefilter, whose roughest levels are what it reads
404
402
  shader_project_environment_sh.dispatch({
@@ -1,17 +1,25 @@
1
1
  /**
2
- * @typedef {import("../../../device/pipeline/PipelineEnquiry.js").PipelineEnquiry} PipelineEnquiry
3
- */
4
- /**
5
- * Offer the pipeline {@link execute_filter_environment_map} dispatches — see {@link PipelineEnquiry}.
2
+ * Offer the pipelines {@link execute_filter_environment_map} dispatches — see {@link PipelineEnquiry}.
6
3
  *
7
4
  * @param {PipelineEnquiry} enquiry
8
5
  */
9
6
  export function enquire_filter_environment_map_pipelines(enquiry: PipelineEnquiry): void;
7
+ /**
8
+ * The plain averages of a map's level 0 the prefilter's level 1 samples from: level 1 down as far as
9
+ * its samples reach, as a texture of their own, so the map keeps its levels 1.. for the filtered
10
+ * result. Nothing reads them after the prefilter, so a frame graph holds them transiently.
11
+ *
12
+ * @param {number} map_size side of the map's level 0
13
+ * @returns {TextureResourceDescriptor}
14
+ */
15
+ export function environment_map_averages_descriptor(map_size: number): TextureResourceDescriptor;
10
16
  /**
11
17
  *
12
18
  * @param {GPUTextureContext} map
19
+ * @param {GPUTextureContext} averages described by {@link environment_map_averages_descriptor}
13
20
  * @param {ShadeGPUCommandContext} encoder
14
21
  */
15
- export function execute_filter_environment_map(map: GPUTextureContext, encoder: ShadeGPUCommandContext): void;
22
+ export function execute_filter_environment_map(map: GPUTextureContext, averages: GPUTextureContext, encoder: ShadeGPUCommandContext): void;
16
23
  export type PipelineEnquiry = import("../../../device/pipeline/PipelineEnquiry.js").PipelineEnquiry;
24
+ import { TextureResourceDescriptor } from "../../../device/graph/TextureResourceDescriptor.js";
17
25
  //# sourceMappingURL=execute_filter_environment_map.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"execute_filter_environment_map.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/execute_filter_environment_map.js"],"names":[],"mappings":"AAKA;;GAEG;AAEH;;;;GAIG;AACH,kEAFW,eAAe,QAIzB;AAED;;;;GAIG;AACH,8GAkDC;8BAnEY,OAAO,6CAA6C,EAAE,eAAe"}
1
+ {"version":3,"file":"execute_filter_environment_map.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/execute_filter_environment_map.js"],"names":[],"mappings":"AAuBA;;;;GAIG;AACH,kEAFW,eAAe,QAMzB;AAsBD;;;;;;;GAOG;AACH,8DAHW,MAAM,GACJ,yBAAyB,CAyBrC;AAED;;;;;GAKG;AACH,2IA0GC;8BAzLY,OAAO,6CAA6C,EAAE,eAAe;0CAZxC,oDAAoD"}
@@ -1,31 +1,103 @@
1
1
  import { assert } from "../../../../core/assert.js";
2
2
  import { TextureViewDescriptor } from "../../../descriptor/texture/TextureViewDescriptor.js";
3
- import { SPHERE_PROBE_MIPMAP_LEVELS } from "./DEFINES.js";
3
+ import { TextureResourceDescriptor } from "../../../device/graph/TextureResourceDescriptor.js";
4
+ import { cone_cosine_from_roughness } from "../../../../core/math/physics/brdf/cone_cosine_from_roughness.js";
5
+ import { BSDF_ROUGHNESS_MINIMUM, SPHERE_PROBE_MIPMAP_LEVELS } from "./DEFINES.js";
4
6
  import { shader_convolve_reflection_map } from "./shader_convolve_reflection_map.js";
7
+ import {
8
+ CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT,
9
+ shader_convolve_reflection_map_from_averages
10
+ } from "./shader_convolve_reflection_map_from_averages.js";
11
+ import { shader_downsample_environment_map } from "./shader_downsample_environment_map.js";
12
+ import { sphere_probe_lod_to_roughness } from "./sphere_probe_lod_to_roughness.js";
5
13
 
6
14
  /**
7
15
  * @typedef {import("../../../device/pipeline/PipelineEnquiry.js").PipelineEnquiry} PipelineEnquiry
8
16
  */
9
17
 
10
18
  /**
11
- * Offer the pipeline {@link execute_filter_environment_map} dispatches — see {@link PipelineEnquiry}.
19
+ * The smallest side a skirted level of the averages goes down to: two interior texels.
20
+ * @type {number}
21
+ */
22
+ const AVERAGES_MIN_SIZE = 4;
23
+
24
+ /**
25
+ * Offer the pipelines {@link execute_filter_environment_map} dispatches — see {@link PipelineEnquiry}.
12
26
  *
13
27
  * @param {PipelineEnquiry} enquiry
14
28
  */
15
29
  export function enquire_filter_environment_map_pipelines(enquiry) {
30
+ enquiry.compute.push(shader_downsample_environment_map.pipeline);
31
+ enquiry.compute.push(shader_convolve_reflection_map_from_averages.pipeline);
16
32
  enquiry.compute.push(shader_convolve_reflection_map.pipeline);
17
33
  }
18
34
 
35
+ /**
36
+ * How many averages level 1's samples reach. A sample reads the level whose texel is the solid angle
37
+ * it stands for, and the coarsest it asks for is where the octahedron's texels are smallest
38
+ * (|d|_1 = 1): the lod `shader_convolve_reflection_map_from_averages` takes, at its largest. 3 for a
39
+ * 1024 map, one more for each doubling.
40
+ *
41
+ * @param {number} map_size side of the map's level 0
42
+ * @returns {number}
43
+ */
44
+ function averages_read_by_level_1(map_size) {
45
+ const roughness = Math.max(sphere_probe_lod_to_roughness(1), BSDF_ROUGHNESS_MINIMUM);
46
+ const cone_solid_angle = 2 * Math.PI * (1 - cone_cosine_from_roughness(roughness));
47
+ const sample_solid_angle = cone_solid_angle / CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT;
48
+
49
+ const interior = map_size - 2;
50
+ const smallest_texel_solid_angle = 4 / (interior * interior);
51
+
52
+ return Math.max(1, Math.ceil(0.5 * Math.log2(sample_solid_angle / smallest_texel_solid_angle)));
53
+ }
54
+
55
+ /**
56
+ * The plain averages of a map's level 0 the prefilter's level 1 samples from: level 1 down as far as
57
+ * its samples reach, as a texture of their own, so the map keeps its levels 1.. for the filtered
58
+ * result. Nothing reads them after the prefilter, so a frame graph holds them transiently.
59
+ *
60
+ * @param {number} map_size side of the map's level 0
61
+ * @returns {TextureResourceDescriptor}
62
+ */
63
+ export function environment_map_averages_descriptor(map_size) {
64
+ assert.isNonNegativeInteger(map_size, 'map_size');
65
+
66
+ const size = map_size >> 1;
67
+
68
+ let available = 0;
69
+ for (let s = size; s >= AVERAGES_MIN_SIZE; s >>= 1) {
70
+ available++;
71
+ }
72
+
73
+ assert.greaterThanOrEqual(available, 1, 'environment map too small to average');
74
+
75
+ const mipLevelCount = Math.min(available, averages_read_by_level_1(map_size));
76
+
77
+ const descriptor = TextureResourceDescriptor.from({
78
+ resolution: [size, size],
79
+ format: "rgba16float",
80
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
81
+ });
82
+
83
+ descriptor.mipLevelCount = mipLevelCount;
84
+
85
+ return descriptor;
86
+ }
87
+
19
88
  /**
20
89
  *
21
90
  * @param {GPUTextureContext} map
91
+ * @param {GPUTextureContext} averages described by {@link environment_map_averages_descriptor}
22
92
  * @param {ShadeGPUCommandContext} encoder
23
93
  */
24
94
  export function execute_filter_environment_map(
25
95
  map,
96
+ averages,
26
97
  encoder
27
98
  ) {
28
99
  assert.defined(map);
100
+ assert.defined(averages);
29
101
  assert.defined(encoder);
30
102
 
31
103
  /*
@@ -45,6 +117,39 @@ export function execute_filter_environment_map(
45
117
  */
46
118
  const mips = Math.min(map.descriptor.mipLevelCount, SPHERE_PROBE_MIPMAP_LEVELS);
47
119
 
120
+ if (mips < 2) {
121
+ return;
122
+ }
123
+
124
+ const level_0 = map.obtainView(TextureViewDescriptor.from({
125
+ baseMipLevel: 0,
126
+ mipLevelCount: 1,
127
+ }));
128
+
129
+ // the averages, each from the one above it, the first from level 0
130
+ const average_levels = averages.descriptor.mipLevelCount;
131
+
132
+ for (let i = 0; i < average_levels; i++) {
133
+ const target_size = averages.size[0] >> i;
134
+ const group_count = Math.max(1, Math.ceil(target_size / 16));
135
+
136
+ shader_downsample_environment_map.dispatch({
137
+ encoder,
138
+ group_count_x: group_count,
139
+ group_count_y: group_count,
140
+ bindings: {
141
+ input: i === 0 ? level_0 : averages.obtainView(TextureViewDescriptor.from({
142
+ baseMipLevel: i - 1,
143
+ mipLevelCount: 1,
144
+ })),
145
+ output: averages.obtainView(TextureViewDescriptor.from({
146
+ baseMipLevel: i,
147
+ mipLevelCount: 1
148
+ }))
149
+ }
150
+ });
151
+ }
152
+
48
153
  for (let i = 1; i < mips; i++) {
49
154
  const target_width = map.size[0] >> i;
50
155
  const target_height = map.size[1] >> i;
@@ -54,6 +159,30 @@ export function execute_filter_environment_map(
54
159
  const dispatch_x = Math.max(1, Math.ceil(target_width / 16));
55
160
  const dispatch_y = Math.max(1, Math.ceil(target_height / 16));
56
161
 
162
+ const output = map.obtainView(TextureViewDescriptor.from({
163
+ baseMipLevel: i,
164
+ mipLevelCount: 1
165
+ }));
166
+
167
+ if (i === 1) {
168
+ // from level 0 and its averages: level 0 is too fine for the taps that cover level 1's cone
169
+ shader_convolve_reflection_map_from_averages.dispatch({
170
+ encoder,
171
+ group_count_x: dispatch_x,
172
+ group_count_y: dispatch_y,
173
+ bindings: {
174
+ level_0,
175
+ averages: averages.obtainView(TextureViewDescriptor.from({
176
+ baseMipLevel: 0,
177
+ mipLevelCount: average_levels,
178
+ })),
179
+ output
180
+ }
181
+ });
182
+
183
+ continue;
184
+ }
185
+
57
186
  shader_convolve_reflection_map.dispatch({
58
187
  encoder,
59
188
  group_count_x: dispatch_x,
@@ -64,11 +193,8 @@ export function execute_filter_environment_map(
64
193
  baseMipLevel: i - 1,
65
194
  mipLevelCount: 1,
66
195
  })),
67
- output: map.obtainView(TextureViewDescriptor.from({
68
- baseMipLevel: i,
69
- mipLevelCount: 1
70
- }))
196
+ output
71
197
  }
72
198
  });
73
199
  }
74
- }
200
+ }
@@ -9,4 +9,13 @@ export function graph_filter_environment_map({ graph, map, }: {
9
9
  graph: FrameGraph;
10
10
  map: number;
11
11
  }): number;
12
+ /**
13
+ * Prefilter a map outside a frame graph of the caller's: one of its own, so the averages are still
14
+ * transient.
15
+ *
16
+ * @param {GPUTextureContext} map
17
+ * @param {ShadeGPUCommandContext} encoder
18
+ */
19
+ export function encode_filter_environment_map(map: GPUTextureContext, encoder: ShadeGPUCommandContext): void;
20
+ import { FrameGraph } from "../../../../engine/graphics/render/frame_graph/FrameGraph.js";
12
21
  //# sourceMappingURL=graph_filter_environment_map.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph_filter_environment_map.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/graph_filter_environment_map.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH;IAJ+B,KAAK;IACT,GAAG,EAAnB,MAAM;IACJ,MAAM,CAgClB"}
1
+ {"version":3,"file":"graph_filter_environment_map.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/graph_filter_environment_map.js"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH;IAJ+B,KAAK,EAAzB,UAAU;IACM,GAAG,EAAnB,MAAM;IACJ,MAAM,CAmClB;AAED;;;;;;GAMG;AACH,6GAYC;2BApE0B,8DAA8D"}
@@ -1,4 +1,9 @@
1
- import { execute_filter_environment_map } from "./execute_filter_environment_map.js";
1
+ import { FrameGraph } from "../../../../engine/graphics/render/frame_graph/FrameGraph.js";
2
+ import { TextureResourceDescriptor } from "../../../device/graph/TextureResourceDescriptor.js";
3
+ import {
4
+ environment_map_averages_descriptor,
5
+ execute_filter_environment_map
6
+ } from "./execute_filter_environment_map.js";
2
7
 
3
8
  /**
4
9
  * @see https://github.com/mrdoob/three.js/blob/efb7510951afff90c90fd2c969675285e374b0be/src/extras/PMREMGenerator.js#L32
@@ -27,14 +32,38 @@ export function graph_filter_environment_map({
27
32
  */
28
33
  const map = resources.get(data.map);
29
34
 
30
- execute_filter_environment_map(map, context.encoder);
35
+ execute_filter_environment_map(map, resources.get(data.averages), context.encoder);
31
36
 
32
37
  }
33
38
 
34
39
  const builder = graph.add("filter env map", data, execute);
35
40
 
36
-
37
41
  data.map = builder.read(map);
42
+ data.averages = builder.create(
43
+ "env map averages",
44
+ environment_map_averages_descriptor(graph.getDescriptor(map).resolution[0])
45
+ );
38
46
 
39
47
  return builder.write(map);
40
- }
48
+ }
49
+
50
+ /**
51
+ * Prefilter a map outside a frame graph of the caller's: one of its own, so the averages are still
52
+ * transient.
53
+ *
54
+ * @param {GPUTextureContext} map
55
+ * @param {ShadeGPUCommandContext} encoder
56
+ */
57
+ export function encode_filter_environment_map(map, encoder) {
58
+ const graph = new FrameGraph("environment map prefilter");
59
+
60
+ const imported = graph.import_resource(
61
+ "environment map",
62
+ TextureResourceDescriptor.fromTexture(map.gpu_texture),
63
+ map
64
+ );
65
+
66
+ graph_filter_environment_map({ graph, map: imported });
67
+
68
+ encoder.encodeGraph(graph);
69
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Samples per texel. Each reads the average whose texel matches the solid angle it stands for, so
3
+ * the count sets the noise of the lobe's shape, not whether a bright texel is counted. Measured on a
4
+ * single bright texel of a 1024 map at 48 places, level 1 over level 0 in solid-angle energy:
5
+ * 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005), 196 → 1.001 ± 0.019 — 196 asks for a
6
+ * finer average than the 2x2 one, so it point-samples level 0 again. UE filters with 32 below
7
+ * roughness 0.1 and 64 above.
8
+ * @type {number}
9
+ */
10
+ export const CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT: number;
11
+ export const shader_convolve_reflection_map_from_averages: ComputeShader;
12
+ import { ComputeShader } from "../../shader/ComputeShader.js";
13
+ //# sourceMappingURL=shader_convolve_reflection_map_from_averages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shader_convolve_reflection_map_from_averages.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js"],"names":[],"mappings":"AAoCA;;;;;;;;GAQG;AACH,kDAFU,MAAM,CAEsC;AAuHtD,yEAII;8BAtJ0B,+BAA+B"}
@@ -0,0 +1,169 @@
1
+ import { chunk_cone_cosine_from_roughness } from "../../shader/chunk/brdf/chunk_cone_cosine_from_roughness.js";
2
+ import {
3
+ chunk_build_orthonormal_matrix_n
4
+ } from "../../shader/chunk/geometry/normal/chunk_build_orthonormal_matrix_n.js";
5
+ import {
6
+ chunk_uv_octahedral_unit_decode
7
+ } from "../../shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js";
8
+ import {
9
+ chunk_uv_octahedral_unit_encode
10
+ } from "../../shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_encode.js";
11
+ import { chunk_uniform_sample_cone } from "../../shader/chunk/geometry/ray/chunk_uniform_sample_cone.js";
12
+ import { chunk_safe_rcp } from "../../shader/chunk/math/chunk_safe_rcp.js";
13
+ import { chunk_hammersley_2d } from "../../shader/chunk/noise/chunk_hammersley_2d.js";
14
+ import {
15
+ chunk_texture_octahedral_skirt_texel_uv
16
+ } from "../../shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.js";
17
+ import { chunk_texture_octahedral_skirt_uv } from "../../shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.js";
18
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
19
+ import { ComputeShader } from "../../shader/ComputeShader.js";
20
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
21
+ import { ShaderDescriptor } from "../../shader/ShaderDescriptor.js";
22
+ import { chunk_reflection_sample_weight } from "./chunk_reflection_sample_weight.js";
23
+ import { chunk_sphere_probe_lod_to_roughness } from "./chunk_sphere_probe_lod_to_roughness.js";
24
+ import { BSDF_ROUGHNESS_MINIMUM } from "./DEFINES.js";
25
+ import { ENVIRONMENT_MAP_SAMPLER_RESOURCE } from "./ENVIRONMENT_MAP_SAMPLER_RESOURCE.js";
26
+
27
+ const resources = new ShaderResourceSetDescriptor();
28
+
29
+ resources.createGroup()
30
+ // the map's level 0, and its plain averages from level 1 down (see shader_downsample_environment_map)
31
+ .addTexture("level_0", "float")
32
+ .addTexture("averages", "float")
33
+ .addStorageTexture("output", "rgba16float")
34
+ .add(ENVIRONMENT_MAP_SAMPLER_RESOURCE)
35
+ ;
36
+
37
+ /**
38
+ * Samples per texel. Each reads the average whose texel matches the solid angle it stands for, so
39
+ * the count sets the noise of the lobe's shape, not whether a bright texel is counted. Measured on a
40
+ * single bright texel of a 1024 map at 48 places, level 1 over level 0 in solid-angle energy:
41
+ * 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005), 196 → 1.001 ± 0.019 — 196 asks for a
42
+ * finer average than the 2x2 one, so it point-samples level 0 again. UE filters with 32 below
43
+ * roughness 0.1 and 64 above.
44
+ * @type {number}
45
+ */
46
+ export const CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT = 64;
47
+
48
+ /**
49
+ * Level 1 of the prefilter, by filtered importance sampling: each sample reads the plain average of
50
+ * level 0 whose texel is the solid angle the sample stands for, so a texel brighter than its
51
+ * neighbours is averaged into the coarser texel the sample reads instead of being hit or missed by
52
+ * the sample pattern. Point taps into level 0 — 196 of them over a cone of 639 level-0 texels — put
53
+ * one bright texel's share of level 1 anywhere from 0.79 to 1.21 of its energy, and an HDR sun
54
+ * came out 0.66% brighter than it went in.
55
+ *
56
+ * Level 1 only. The levels after it read the level above, already smooth at their sample spacing
57
+ * (see shader_convolve_reflection_map). Read from level 0 instead, their samples land on averages
58
+ * 8 to 16 texels a side, too coarse an octahedral grid to keep a bright texel's energy: ±11% at
59
+ * level 4, and a sky 11% brighter.
60
+ *
61
+ * @see https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling
62
+ * @see https://github.com/EpicGames/UnrealEngine/blob/5.4/Engine/Shaders/Private/ReflectionEnvironmentShaders.usf FilterPS
63
+ * @see https://projects.blender.org/blender/blender/src/commit/866ef5b468589587395b1ee7480ea75b4c0fbc26/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_sphere_convolve_comp.glsl for the lobe
64
+ * @type {CodeChunk}
65
+ */
66
+ const body = CodeChunk.from(
67
+ //language=WGSL
68
+ `
69
+ /// level 0 is the map's own, the levels below it are the averages
70
+ fn sample_source_level(direction: vec3<f32>, level: u32) -> vec4<f32> {
71
+ let uv = uv_octahedral_unit_encode(direction);
72
+
73
+ if (level == 0u) {
74
+ let size = f32(textureDimensions(level_0).x);
75
+
76
+ return textureSampleLevel(level_0, sEnvironment, texture_octahedral_skirt_uv(uv, size), 0.0);
77
+ }
78
+
79
+ let average_level = level - 1u;
80
+ let size = f32(textureDimensions(averages, average_level).x);
81
+
82
+ return textureSampleLevel(averages, sEnvironment, texture_octahedral_skirt_uv(uv, size), f32(average_level));
83
+ }
84
+
85
+ @compute @workgroup_size(16,16,1)
86
+ fn main(
87
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
88
+ ){
89
+ let output_size = textureDimensions(output);
90
+
91
+ let output_texel_i = global_invocation_id.xy;
92
+
93
+ if(any(output_texel_i >= output_size)){
94
+ return;
95
+ }
96
+
97
+ /* From mip to linear roughness (same as UI). */
98
+ let roughness = max(sphere_probe_lod_to_roughness(1.0), ${BSDF_ROUGHNESS_MINIMUM});
99
+ let cone_cos = cone_cosine_from_roughness(roughness);
100
+
101
+ // the skirt too: a skirt texel takes its interior twin's uv, so it is that texel, computed again
102
+ let out_uv = texture_octahedral_skirt_texel_uv(output_texel_i, output_size.x);
103
+ let out_direction = uv_octahedral_unit_decode( out_uv );
104
+
105
+ let basis = build_orthonormal_matrix_n(out_direction);
106
+
107
+ var weight_accum = 0.0;
108
+ var radiance_accum = vec4(0.0);
109
+
110
+ const sample_count = ${CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT};
111
+
112
+ // uniform in the cone, so every sample stands for the same solid angle
113
+ let sample_solid_angle = 2.0 * PI * (1.0 - cone_cos) / f32(sample_count);
114
+
115
+ // level 0's octahedral square, n texels a side, spans 2 in octahedron coordinates; a texel there
116
+ // covers (2/n)^2 of it, and the octahedron's solid angle per unit area is |d|_1^3 along unit d
117
+ let level_0_interior = f32(textureDimensions(level_0).x) - 2.0;
118
+ let level_0_texel_area = 4.0 / (level_0_interior * level_0_interior);
119
+
120
+ let last_level = f32(textureNumLevels(averages));
121
+
122
+ for (var i = 0u; i < sample_count; i++) {
123
+ let rand = hammersley_2d(i, sample_count);
124
+
125
+ let in_direction = normalize(basis * uniform_sample_cone(rand, cone_cos));
126
+
127
+ let l1 = dot(abs(in_direction), vec3(1.0));
128
+ let texel_solid_angle = level_0_texel_area * l1 * l1 * l1;
129
+
130
+ let lod = clamp(0.5 * log2(sample_solid_angle / texel_solid_angle), 0.0, last_level);
131
+ let lod_floor = floor(lod);
132
+ let level = u32(lod_floor);
133
+
134
+ let radiance = mix(
135
+ sample_source_level(in_direction, level),
136
+ sample_source_level(in_direction, min(level + 1u, u32(last_level))),
137
+ lod - lod_floor
138
+ );
139
+
140
+ let weight = reflection_sample_weight(out_direction, in_direction, roughness);
141
+
142
+ radiance_accum += radiance * weight;
143
+ weight_accum += weight;
144
+ }
145
+
146
+ let out_radiance = radiance_accum * safe_rcp(weight_accum);
147
+
148
+ textureStore(output, output_texel_i, out_radiance);
149
+ }
150
+ `, [
151
+ chunk_sphere_probe_lod_to_roughness,
152
+ chunk_hammersley_2d,
153
+ chunk_build_orthonormal_matrix_n,
154
+ chunk_safe_rcp,
155
+ chunk_uniform_sample_cone,
156
+ chunk_texture_octahedral_skirt_texel_uv,
157
+ chunk_texture_octahedral_skirt_uv,
158
+ chunk_uv_octahedral_unit_decode,
159
+ chunk_uv_octahedral_unit_encode,
160
+ chunk_reflection_sample_weight,
161
+ chunk_cone_cosine_from_roughness,
162
+ ]
163
+ );
164
+
165
+ export const shader_convolve_reflection_map_from_averages = new ComputeShader(ShaderDescriptor.from({
166
+ label: 'Convolve reflection map from averages',
167
+ body,
168
+ resources
169
+ }));
@@ -0,0 +1,3 @@
1
+ export const shader_downsample_environment_map: ComputeShader;
2
+ import { ComputeShader } from "../../shader/ComputeShader.js";
3
+ //# sourceMappingURL=shader_downsample_environment_map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shader_downsample_environment_map.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/shader_downsample_environment_map.js"],"names":[],"mappings":"AAoGA,8DAII;8BAjG0B,+BAA+B"}