@woosh/meep-engine 3.60.1 → 3.60.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) 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/light/GPULightCollection.d.ts.map +1 -1
  5. package/src/shade/renderer/light/GPULightCollection.js +3 -5
  6. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts +13 -5
  7. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  8. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +133 -7
  9. package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts +9 -0
  10. package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts.map +1 -1
  11. package/src/shade/renderer/light/environment/graph_filter_environment_map.js +33 -4
  12. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts +13 -0
  13. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts.map +1 -0
  14. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js +198 -0
  15. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts +3 -0
  16. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -0
  17. package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +105 -0
  18. package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.d.ts +10 -0
  19. package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.d.ts.map +1 -0
  20. package/src/shade/renderer/light/environment/sphere_probe_lod_to_roughness.js +23 -0
  21. package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts +8 -4
  22. package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts.map +1 -1
  23. package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.js +9 -5
  24. package/src/view/elements/progress/segmented/GhostTrail.d.ts +6 -0
  25. package/src/view/elements/progress/segmented/GhostTrail.d.ts.map +1 -1
  26. package/src/view/elements/progress/segmented/GhostTrail.js +14 -0
  27. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts +74 -19
  28. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts.map +1 -1
  29. package/src/view/elements/progress/segmented/SegmentedResourceBarView.js +219 -116
  30. package/src/view/elements/progress/segmented/playground/main.js +18 -11
  31. package/src/view/elements/progress/segmented/segment_cell_extent.d.ts +14 -0
  32. package/src/view/elements/progress/segmented/segment_cell_extent.d.ts.map +1 -0
  33. package/src/view/elements/progress/segmented/segment_cell_extent.js +44 -0
  34. package/src/view/elements/progress/segmented/segment_frame_transform.d.ts +15 -0
  35. package/src/view/elements/progress/segmented/segment_frame_transform.d.ts.map +1 -0
  36. package/src/view/elements/progress/segmented/segment_frame_transform.js +69 -0
  37. package/src/view/elements/progress/segmented/segment_mask_url.d.ts +24 -0
  38. package/src/view/elements/progress/segmented/segment_mask_url.d.ts.map +1 -0
  39. package/src/view/elements/progress/segmented/segment_mask_url.js +74 -0
  40. package/src/view/elements/progress/segmented/segment_mask_width.d.ts +18 -0
  41. package/src/view/elements/progress/segmented/segment_mask_width.d.ts.map +1 -0
  42. package/src/view/elements/progress/segmented/segment_mask_width.js +46 -0
  43. 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.1",
9
+ "version": "3.60.3",
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:
@@ -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
+ * fewer samples read coarser averages and more read finer ones, back toward point taps that catch or
4
+ * miss a bright texel. Measured on a single bright texel of a 1024 map at 48 places, level 1 over
5
+ * level 0 in solid-angle energy: 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005),
6
+ * 196 → 1.001 ± 0.019 — at 196 the lod is 0.5 .. 1.7, and every read below 1 blends in level 0's
7
+ * point taps. UE filters with 32 below 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":"AAqCA;;;;;;;;GAQG;AACH,kDAFU,MAAM,CAEsC;AAmJtD,yEAII;8BAlL0B,+BAA+B"}
@@ -0,0 +1,198 @@
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_pow2 } from "../../shader/chunk/math/chunk_pow2.js";
13
+ import { chunk_safe_rcp } from "../../shader/chunk/math/chunk_safe_rcp.js";
14
+ import { chunk_hammersley_2d } from "../../shader/chunk/noise/chunk_hammersley_2d.js";
15
+ import {
16
+ chunk_texture_octahedral_skirt_texel_uv
17
+ } from "../../shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.js";
18
+ import { chunk_texture_octahedral_skirt_uv } from "../../shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.js";
19
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
20
+ import { ComputeShader } from "../../shader/ComputeShader.js";
21
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
22
+ import { ShaderDescriptor } from "../../shader/ShaderDescriptor.js";
23
+ import { chunk_reflection_sample_weight } from "./chunk_reflection_sample_weight.js";
24
+ import { chunk_sphere_probe_lod_to_roughness } from "./chunk_sphere_probe_lod_to_roughness.js";
25
+ import { BSDF_ROUGHNESS_MINIMUM } from "./DEFINES.js";
26
+ import { ENVIRONMENT_MAP_SAMPLER_RESOURCE } from "./ENVIRONMENT_MAP_SAMPLER_RESOURCE.js";
27
+
28
+ const resources = new ShaderResourceSetDescriptor();
29
+
30
+ resources.createGroup()
31
+ // the map's level 0, and its plain averages from level 1 down (see shader_downsample_environment_map)
32
+ .addTexture("level_0", "float")
33
+ .addTexture("averages", "float")
34
+ .addStorageTexture("output", "rgba16float")
35
+ .add(ENVIRONMENT_MAP_SAMPLER_RESOURCE)
36
+ ;
37
+
38
+ /**
39
+ * Samples per texel. Each reads the average whose texel matches the solid angle it stands for, so
40
+ * fewer samples read coarser averages and more read finer ones, back toward point taps that catch or
41
+ * miss a bright texel. Measured on a single bright texel of a 1024 map at 48 places, level 1 over
42
+ * level 0 in solid-angle energy: 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005),
43
+ * 196 → 1.001 ± 0.019 — at 196 the lod is 0.5 .. 1.7, and every read below 1 blends in level 0's
44
+ * point taps. UE filters with 32 below roughness 0.1 and 64 above.
45
+ * @type {number}
46
+ */
47
+ export const CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT = 64;
48
+
49
+ /**
50
+ * How much a read of the averages widens the lobe, as mean squared angle per steradian a sample
51
+ * stands for. The lobe exp(2 (N·H - 1) / r^4) is a Gaussian of mean squared angle 4 r^4 (N·H =
52
+ * cos(θ/2), so N·H - 1 ≈ -θ²/8), and convolving adds variances, so the lobe is narrowed to
53
+ * r^4 - k Ω_s / 4 and lands at the width it is asked for.
54
+ *
55
+ * A box over the texel plus the bilinear tent across its neighbours predicts 1/2; 1 is measured. On
56
+ * a 1024 map's sun against level 1 from 8192 point taps (1.315° rms): k = 0 → 1.436°, 1/2 → 1.380°,
57
+ * 1 → 1.317°; the sun's tail error 110% → 49%. The two reads a lod between levels blends, and an
58
+ * average's footprint 2 + 2/n texels of the one above rather than 2, are the likely difference.
59
+ * Matching the width leaves the shape: the core is flatter than a Gaussian, and the sky's rms error
60
+ * against the reference goes 0.166 → 0.222.
61
+ * @type {number}
62
+ */
63
+ const AVERAGES_BLUR_PER_SOLID_ANGLE = 1;
64
+
65
+ /**
66
+ * Level 1 of the prefilter, by filtered importance sampling: each sample reads the plain average of
67
+ * level 0 whose texel is the solid angle the sample stands for, so a texel brighter than its
68
+ * neighbours is averaged into the coarser texel the sample reads instead of being hit or missed by
69
+ * the sample pattern. Point taps into level 0 — 196 of them over a cone of 639 level-0 texels — put
70
+ * one bright texel's share of level 1 anywhere from 0.79 to 1.21 of its energy, and an HDR sun
71
+ * came out 0.66% brighter than it went in.
72
+ *
73
+ * Samples are uniform in the cone, so each stands for the same solid angle and reads the same
74
+ * average: the averages blur the lobe by one fixed amount, which the lobe gives back by narrowing
75
+ * (see AVERAGES_BLUR_PER_SOLID_ANGLE). UE's importance sampling read fine averages in the lobe's core
76
+ * and coarse ones in its tail, and the sun came out in blocks.
77
+ *
78
+ * Level 1 only. The levels after it read the level above, already smooth at their sample spacing
79
+ * (see shader_convolve_reflection_map). Read from level 0 instead, their samples land on averages
80
+ * 8 to 16 texels a side, too coarse an octahedral grid to keep a bright texel's energy: ±11% at
81
+ * level 4, and a sky 11% brighter.
82
+ *
83
+ * @see https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling
84
+ * @see https://github.com/EpicGames/UnrealEngine/blob/5.4/Engine/Shaders/Private/ReflectionEnvironmentShaders.usf FilterPS
85
+ * @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
86
+ * @type {CodeChunk}
87
+ */
88
+ const body = CodeChunk.from(
89
+ //language=WGSL
90
+ `
91
+ /// level 0 is the map's own, the levels below it are the averages
92
+ fn sample_source_level(direction: vec3<f32>, level: u32) -> vec4<f32> {
93
+ let uv = uv_octahedral_unit_encode(direction);
94
+
95
+ if (level == 0u) {
96
+ let size = f32(textureDimensions(level_0).x);
97
+
98
+ return textureSampleLevel(level_0, sEnvironment, texture_octahedral_skirt_uv(uv, size), 0.0);
99
+ }
100
+
101
+ let average_level = level - 1u;
102
+ let size = f32(textureDimensions(averages, average_level).x);
103
+
104
+ return textureSampleLevel(averages, sEnvironment, texture_octahedral_skirt_uv(uv, size), f32(average_level));
105
+ }
106
+
107
+ @compute @workgroup_size(16,16,1)
108
+ fn main(
109
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
110
+ ){
111
+ let output_size = textureDimensions(output);
112
+
113
+ let output_texel_i = global_invocation_id.xy;
114
+
115
+ if(any(output_texel_i >= output_size)){
116
+ return;
117
+ }
118
+
119
+ /* From mip to linear roughness (same as UI). */
120
+ let roughness = max(sphere_probe_lod_to_roughness(1.0), ${BSDF_ROUGHNESS_MINIMUM});
121
+ let cone_cos = cone_cosine_from_roughness(roughness);
122
+
123
+ // the skirt too: a skirt texel takes its interior twin's uv, so it is that texel, computed again
124
+ let out_uv = texture_octahedral_skirt_texel_uv(output_texel_i, output_size.x);
125
+ let out_direction = uv_octahedral_unit_decode( out_uv );
126
+
127
+ let basis = build_orthonormal_matrix_n(out_direction);
128
+
129
+ var weight_accum = 0.0;
130
+ var radiance_accum = vec4(0.0);
131
+
132
+ const sample_count = ${CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT};
133
+
134
+ // uniform in the cone, so every sample stands for the same solid angle
135
+ let sample_solid_angle = 2.0 * PI * (1.0 - cone_cos) / f32(sample_count);
136
+
137
+ // narrowed by the blur the averages add, so the lobe comes out at the roughness asked for
138
+ let lobe_roughness = sqrt(sqrt(max(
139
+ pow2(pow2(roughness)) - ${AVERAGES_BLUR_PER_SOLID_ANGLE.toFixed(6)} * sample_solid_angle / 4.0,
140
+ pow2(pow2(${BSDF_ROUGHNESS_MINIMUM}))
141
+ )));
142
+
143
+ // level 0's octahedral square, n texels a side, spans 2 in octahedron coordinates; a texel there
144
+ // covers (2/n)^2 of it, and the octahedron's solid angle per unit area is |d|_1^3 along unit d
145
+ let level_0_interior = f32(textureDimensions(level_0).x) - 2.0;
146
+ let level_0_texel_area = 4.0 / (level_0_interior * level_0_interior);
147
+
148
+ let last_level = f32(textureNumLevels(averages));
149
+
150
+ for (var i = 0u; i < sample_count; i++) {
151
+ let rand = hammersley_2d(i, sample_count);
152
+
153
+ let in_direction = normalize(basis * uniform_sample_cone(rand, cone_cos));
154
+
155
+ let l1 = dot(abs(in_direction), vec3(1.0));
156
+ let texel_solid_angle = level_0_texel_area * l1 * l1 * l1;
157
+
158
+ let lod = clamp(0.5 * log2(sample_solid_angle / texel_solid_angle), 0.0, last_level);
159
+ let lod_floor = floor(lod);
160
+ let level = u32(lod_floor);
161
+
162
+ let radiance = mix(
163
+ sample_source_level(in_direction, level),
164
+ sample_source_level(in_direction, min(level + 1u, u32(last_level))),
165
+ lod - lod_floor
166
+ );
167
+
168
+ let weight = reflection_sample_weight(out_direction, in_direction, lobe_roughness);
169
+
170
+ radiance_accum += radiance * weight;
171
+ weight_accum += weight;
172
+ }
173
+
174
+ let out_radiance = radiance_accum * safe_rcp(weight_accum);
175
+
176
+ textureStore(output, output_texel_i, out_radiance);
177
+ }
178
+ `, [
179
+ chunk_sphere_probe_lod_to_roughness,
180
+ chunk_hammersley_2d,
181
+ chunk_build_orthonormal_matrix_n,
182
+ chunk_safe_rcp,
183
+ chunk_pow2,
184
+ chunk_uniform_sample_cone,
185
+ chunk_texture_octahedral_skirt_texel_uv,
186
+ chunk_texture_octahedral_skirt_uv,
187
+ chunk_uv_octahedral_unit_decode,
188
+ chunk_uv_octahedral_unit_encode,
189
+ chunk_reflection_sample_weight,
190
+ chunk_cone_cosine_from_roughness,
191
+ ]
192
+ );
193
+
194
+ export const shader_convolve_reflection_map_from_averages = new ComputeShader(ShaderDescriptor.from({
195
+ label: 'Convolve reflection map from averages',
196
+ body,
197
+ resources
198
+ }));
@@ -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"}