@woosh/meep-engine 3.61.1 → 3.61.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 (114) hide show
  1. package/package.json +1 -1
  2. package/src/engine/network/sim/SimActionExecutor.d.ts +4 -0
  3. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  4. package/src/engine/network/sim/SimActionExecutor.js +4 -0
  5. package/src/generation/foliage/material/foliage_atlas_cutout.d.ts.map +1 -1
  6. package/src/generation/foliage/material/foliage_atlas_cutout.js +21 -5
  7. package/src/generation/foliage/mesh/tree_cluster_representatives.d.ts +8 -0
  8. package/src/generation/foliage/mesh/tree_cluster_representatives.d.ts.map +1 -1
  9. package/src/generation/foliage/mesh/tree_cluster_representatives.js +178 -78
  10. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
  11. package/src/generation/foliage/mesh/tree_mesh_build_bark.js +178 -97
  12. package/src/generation/foliage/skeleton/ShootLightField.d.ts +1 -0
  13. package/src/generation/foliage/skeleton/ShootLightField.d.ts.map +1 -1
  14. package/src/generation/foliage/skeleton/ShootLightField.js +88 -17
  15. package/src/generation/foliage/skeleton/ShootWoodHash.d.ts +5 -2
  16. package/src/generation/foliage/skeleton/ShootWoodHash.d.ts.map +1 -1
  17. package/src/generation/foliage/skeleton/ShootWoodHash.js +191 -25
  18. package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -1
  19. package/src/shade/playground/probe_clipmap/main.js +4 -0
  20. package/src/shade/playground/probe_clipmap_box/README.md +55 -0
  21. package/src/shade/playground/probe_clipmap_box/index.html +185 -0
  22. package/src/shade/playground/probe_clipmap_box/main.d.ts +8 -0
  23. package/src/shade/playground/probe_clipmap_box/main.d.ts.map +1 -0
  24. package/src/shade/playground/probe_clipmap_box/main.js +323 -0
  25. package/src/shade/playground/probe_clipmap_box/scene.d.ts +93 -0
  26. package/src/shade/playground/probe_clipmap_box/scene.d.ts.map +1 -0
  27. package/src/shade/playground/probe_clipmap_box/scene.js +256 -0
  28. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  29. package/src/shade/renderer/Renderer.js +6 -1
  30. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_decode.d.ts +12 -0
  31. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_decode.d.ts.map +1 -1
  32. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_decode.js +49 -24
  33. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_encode.d.ts +12 -0
  34. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_encode.d.ts.map +1 -1
  35. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_brick4_sh3_color_encode.js +50 -19
  36. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_sh3_compress.d.ts.map +1 -1
  37. package/src/shade/renderer/global_illumination/brick4/gpu/encoding/v2/chunk_sh3_compress.js +6 -2
  38. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts +5 -1
  39. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  40. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +7 -3
  41. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +6 -4
  42. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  43. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +10 -9
  44. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts +12 -0
  45. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts.map +1 -1
  46. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.js +14 -0
  47. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts +17 -22
  48. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts.map +1 -1
  49. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.js +53 -65
  50. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.d.ts +2 -3
  51. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.d.ts.map +1 -1
  52. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.js +9 -20
  53. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts +2 -2
  54. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts.map +1 -1
  55. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js +14 -19
  56. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts +1 -1
  57. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts.map +1 -1
  58. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.js +8 -4
  59. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.js +1 -1
  60. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_bounce.d.ts +2 -1
  61. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_bounce.d.ts.map +1 -1
  62. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_bounce.js +13 -18
  63. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_candidate.d.ts +1 -1
  64. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_candidate.d.ts.map +1 -1
  65. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_candidate.js +4 -2
  66. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.d.ts.map +1 -1
  67. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.js +4 -6
  68. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.d.ts.map +1 -1
  69. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.js +4 -7
  70. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.d.ts +4 -3
  71. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.d.ts.map +1 -1
  72. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_project.js +14 -13
  73. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts +3 -4
  74. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts.map +1 -1
  75. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.js +7 -15
  76. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -1
  77. package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +5 -1
  78. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts +26 -8
  79. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts.map +1 -1
  80. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +170 -142
  81. package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.d.ts +1 -1
  82. package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.d.ts.map +1 -1
  83. package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.js +5 -8
  84. package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.d.ts +15 -4
  85. package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.d.ts.map +1 -1
  86. package/src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.js +33 -7
  87. package/src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.d.ts +40 -0
  88. package/src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.d.ts.map +1 -0
  89. package/src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.js +75 -0
  90. package/src/shade/renderer/shader/chunk/noise/chunk_vdc_radical_inverse.d.ts.map +1 -1
  91. package/src/shade/renderer/shader/chunk/noise/chunk_vdc_radical_inverse.js +3 -1
  92. package/src/shade/renderer/shader/chunk/random/chunk_hash_lk.d.ts +28 -0
  93. package/src/shade/renderer/shader/chunk/random/chunk_hash_lk.d.ts.map +1 -0
  94. package/src/shade/renderer/shader/chunk/random/chunk_hash_lk.js +41 -0
  95. package/src/shade/renderer/shader/chunk/spherical_harmonics/chunk_sh3_color_add_scaled.d.ts +12 -0
  96. package/src/shade/renderer/shader/chunk/spherical_harmonics/chunk_sh3_color_add_scaled.d.ts.map +1 -0
  97. package/src/shade/renderer/shader/chunk/spherical_harmonics/chunk_sh3_color_add_scaled.js +24 -0
  98. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +3 -2
  99. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -1
  100. package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +4 -0
  101. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +2 -5
  102. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -1
  103. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +3 -20
  104. package/src/shade/renderer/water/chunk_water_gi.d.ts +1 -1
  105. package/src/shade/renderer/water/chunk_water_gi.d.ts.map +1 -1
  106. package/src/shade/renderer/water/chunk_water_gi.js +6 -8
  107. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  108. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +76 -32
  109. package/src/core/math/hash/squirrel3.d.ts +0 -17
  110. package/src/core/math/hash/squirrel3.d.ts.map +0 -1
  111. package/src/core/math/hash/squirrel3.js +0 -30
  112. package/src/shade/renderer/shader/chunk/random/chunk_hash_squirrel3.d.ts +0 -12
  113. package/src/shade/renderer/shader/chunk/random/chunk_hash_squirrel3.d.ts.map +0 -1
  114. package/src/shade/renderer/shader/chunk/random/chunk_hash_squirrel3.js +0 -30
@@ -12,11 +12,28 @@ import { shader_autoexposure_read_histogram } from "./shader_autoexposure_read_h
12
12
  * @typedef {import("../../../device/pipeline/PipelineEnquiry.js").PipelineEnquiry} PipelineEnquiry
13
13
  */
14
14
 
15
+ const HISTOGRAM_DESCRIPTOR = BufferResourceDescriptor.empty(
16
+ AUTOEXPOSURE_WRITE_HISTOGRAM_STRUCT.aligned_size,
17
+ GPUBufferUsage.STORAGE,
18
+ );
19
+
20
+ const TARGET_LUMINANCE_DESCRIPTOR = BufferResourceDescriptor.from(
21
+ F32_CONTAINER_STRUCT.size,
22
+ GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM,
23
+ );
24
+
15
25
  /**
16
- * Calculates exposure for eye adaptation
26
+ * The exposure: the factor the tonemapper multiplies the scene's linear radiance by ahead of its curve, metered from
27
+ * the frame and adapted to it ({@link update}), or set by {@link exposure_compensation} alone ({@link unadapted}).
28
+ *
29
+ * The factor lives in one buffer for the manager's life, {@link multiplier}, which a frame's {@link update} or
30
+ * {@link unadapted} writes over. A pass recorded ahead of that write in the frame reads the factor the last frame was
31
+ * shown with: the exposure of the image on screen, Unreal Engine's `View.PreExposure`.
17
32
  */
18
33
  export class GPUCameraExposureManager {
19
34
  /**
35
+ * The adapted luminance: last frame's, which this frame's adaptation reads, and this frame's, which it writes.
36
+ * They swap every {@link update}.
20
37
  *
21
38
  * @type {GPUTypedBuffer[]}
22
39
  */
@@ -24,6 +41,12 @@ export class GPUCameraExposureManager {
24
41
 
25
42
  #frame_index = 0;
26
43
 
44
+ /**
45
+ * {@link F32_CONTAINER_STRUCT}: the factor the last {@link update} or {@link unadapted} wrote.
46
+ * @type {GPUTypedBuffer}
47
+ */
48
+ #multiplier;
49
+
27
50
  /**
28
51
  * How fast do we adapt to brighter environments?
29
52
  * in f-stops per second.
@@ -54,6 +77,17 @@ export class GPUCameraExposureManager {
54
77
  */
55
78
  exposure_compensation = 0.0;
56
79
 
80
+ // what each pass's execution reads, kept across frames: a frame records them and runs them before the next does
81
+ #unadapted_data = { output: -1 };
82
+ #unadapted_value = { value: 1.0 };
83
+ #build_data = { input_color: -1, output: -1 };
84
+ #parse_data = { input_histogram: -1, output: -1 };
85
+ #adapt_data = { goal: -1, previous: -1, adapted: -1, out_multiplier: -1 };
86
+ #adapt_settings = { speed_up: 0, speed_down: 0, time_delta: 0, exp_transition_distance: 0, compensation: 0 };
87
+ #build_bindings = { tInput: null, histogram: null };
88
+ #parse_bindings = { histogram: null, output: null };
89
+ #adapt_bindings = { goal: null, previous: null, settings: this.#adapt_settings, adapted: null, out_multiplier: null };
90
+
57
91
  /**
58
92
  *
59
93
  * @param {GPUDevice} device
@@ -72,12 +106,36 @@ export class GPUCameraExposureManager {
72
106
  });
73
107
  }
74
108
 
109
+ // a factor of one until the first frame writes its own
110
+ this.#multiplier = GPUTypedBuffer.create({
111
+ label: 'exposure multiplier',
112
+ device,
113
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
114
+ type: F32_CONTAINER_STRUCT,
115
+ initial_value: {
116
+ value: 1.0
117
+ }
118
+ });
119
+
75
120
  }
76
121
 
77
122
  destroy() {
78
123
  for (const buffer of this.#exposure_buffers) {
79
124
  buffer.destroy();
80
125
  }
126
+
127
+ this.#multiplier.destroy();
128
+ }
129
+
130
+ /**
131
+ * The factor, {@link F32_CONTAINER_STRUCT}, as the last {@link update} or {@link unadapted} wrote it. A frame
132
+ * imports it into its graph once, and hands the handle both to the passes that read it ahead of the exposure,
133
+ * which take the last frame's factor, and to {@link update} or {@link unadapted}, which write this frame's.
134
+ *
135
+ * @returns {GPUBuffer} usable as a uniform or read-only storage
136
+ */
137
+ get multiplier() {
138
+ return this.#multiplier.buffer;
81
139
  }
82
140
 
83
141
  /**
@@ -93,62 +151,61 @@ export class GPUCameraExposureManager {
93
151
  }
94
152
 
95
153
  /**
154
+ * This frame's factor from {@link exposure_compensation} alone: 2^compensation.
96
155
  *
97
156
  * @param {Object} options
98
157
  * @param {FrameGraph} options.graph
99
- * @return {number}
158
+ * @param {number} options.multiplier the graph handle of {@link multiplier}, imported into `graph`
159
+ * @return {number} the handle of {@link multiplier} with this frame's factor written
100
160
  */
101
161
  unadapted({
102
162
  graph,
163
+ multiplier,
103
164
  }) {
165
+ assert.defined(graph, 'graph');
166
+ assert.isNonNegativeInteger(multiplier, 'multiplier');
104
167
 
105
- const data = {};
106
-
107
- const pass = graph.add("unadapted", data,
108
-
109
- /**
110
- *
111
- * @param data
112
- * @param {FramePassResources} resources
113
- * @param {GraphicsExecutionContext} context
114
- */
115
- (data, resources, context) => {
168
+ const data = this.#unadapted_data;
169
+ const pass = graph.add("unadapted", data, this.#execute_unadapted);
116
170
 
117
- const output = resources.get(data.output);
171
+ data.output = pass.write(multiplier);
118
172
 
173
+ return data.output;
174
+ }
119
175
 
120
- /**
121
- *
122
- * @type {ShadeGPUCommandContext}
123
- */
124
- const encoder = context.encoder;
125
-
126
- encoder.writeValueBuffer(output, 0, F32_CONTAINER_STRUCT, {
127
- // compensation is in f-stops: each stop doubles/halves brightness
128
- value: Math.pow(2, this.exposure_compensation),
129
- });
130
-
131
- });
132
-
133
- data.output = pass.create("final", BufferResourceDescriptor.from(
134
- F32_CONTAINER_STRUCT.size,
135
- GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
136
- ));
176
+ /**
177
+ * @param data
178
+ * @param {FramePassResources} resources
179
+ * @param {GraphicsExecutionContext} context
180
+ */
181
+ #execute_unadapted = (data, resources, context) => {
182
+ /**
183
+ * @type {ShadeGPUCommandContext}
184
+ */
185
+ const encoder = context.encoder;
137
186
 
138
- return data.output;
187
+ // compensation is in f-stops: each stop doubles/halves brightness
188
+ this.#unadapted_value.value = Math.pow(2, this.exposure_compensation);
139
189
 
190
+ // in the command stream, so a pass recorded ahead of this one still reads the last frame's factor
191
+ encoder.writeValueBuffer(resources.get(data.output), 0, F32_CONTAINER_STRUCT, this.#unadapted_value);
140
192
  }
141
193
 
142
194
  /**
195
+ * This frame's factor metered from `input_color`: the luminance adapted toward the frame's, from the last
196
+ * frame's, by the adaptation speeds; its factor is middle grey over it, times 2^{@link exposure_compensation}.
143
197
  *
144
198
  * @param {Object} options
145
199
  * @param {FrameGraph} options.graph
146
200
  * @param {number} options.input_color
201
+ * @param {number} options.multiplier the graph handle of {@link multiplier}, imported into `graph`
147
202
  * @param {number} [options.time_delta_seconds]
203
+ * @return {number} the handle of {@link multiplier} with this frame's factor written
148
204
  */
149
205
  update({
150
206
  input_color,
151
207
  graph,
208
+ multiplier,
152
209
  time_delta_seconds = 0.01666,
153
210
  }) {
154
211
 
@@ -160,142 +217,113 @@ export class GPUCameraExposureManager {
160
217
  this used to be given, is Karis-averaged for precisely that purpose.
161
218
  */
162
219
 
220
+ assert.defined(graph, 'graph');
221
+ assert.isNonNegativeInteger(multiplier, 'multiplier');
163
222
  assert.isNumber(time_delta_seconds, 'time_delta_seconds');
164
223
  assert.greaterThanOrEqual(time_delta_seconds, 0, 'time_delta_seconds');
165
224
 
166
225
  this.#frame_index++;
167
226
 
168
- const build_data = {};
227
+ const build_data = this.#build_data;
228
+ const build_pass = graph.add("build histogram", build_data, this.#execute_build_histogram);
169
229
 
170
- /**
171
- *
172
- * @param data
173
- * @param {FramePassResources} resources
174
- * @param {GraphicsExecutionContext} context
175
- */
176
- function build_histogram(data, resources, context) {
177
- const input_color = resources.get(data.input_color);
178
- const output = resources.get(data.output);
230
+ build_data.input_color = build_pass.read(input_color);
231
+ build_data.output = build_pass.create("histogram", HISTOGRAM_DESCRIPTOR);
179
232
 
180
- const gx = Math.ceil(input_color.width / 16);
181
- const gy = Math.ceil(input_color.height / 16);
233
+ const parse_data = this.#parse_data;
234
+ const parse_pass = graph.add("parse histogram", parse_data, this.#execute_parse_histogram);
182
235
 
183
- if (gx <= 0 || gy <= 0) {
184
- // nothing to dispatch
185
- return;
186
- }
236
+ parse_data.input_histogram = parse_pass.read(build_data.output);
237
+ parse_data.output = parse_pass.create("target luminance", TARGET_LUMINANCE_DESCRIPTOR);
187
238
 
188
- shader_autoexposure_build_histogram.dispatch({
189
- encoder: context.encoder,
190
- group_count_x: gx,
191
- group_count_y: gy,
192
- bindings: {
193
- tInput: input_color.obtainView(),
194
- histogram: output,
195
- },
196
- });
239
+ const exposure_buffer_previous = this.#exposure_buffers[this.#frame_index % 2];
240
+ const exposure_buffer_current = this.#exposure_buffers[(this.#frame_index + 1) % 2];
197
241
 
198
- }
242
+ // the frame's step; the rest of the settings are read as the pass runs
243
+ this.#adapt_settings.time_delta = time_delta_seconds;
199
244
 
200
- const build_pass = graph.add("build histogram", build_data, build_histogram);
245
+ const adapt_data = this.#adapt_data;
246
+ const adapt_pass = graph.add("adaptation", adapt_data, this.#execute_adapt);
201
247
 
202
- build_data.input_color = build_pass.read(input_color);
203
- build_data.output = build_pass.create("histogram", BufferResourceDescriptor.empty(
204
- AUTOEXPOSURE_WRITE_HISTOGRAM_STRUCT.aligned_size,
205
- GPUBufferUsage.STORAGE,
206
- ));
248
+ adapt_data.goal = adapt_pass.read(parse_data.output);
249
+ adapt_data.previous = adapt_pass.read(graph_import_buffer(graph, exposure_buffer_previous.buffer, "previous adapted luminance"));
250
+ adapt_data.adapted = adapt_pass.write(graph_import_buffer(graph, exposure_buffer_current.buffer, "adapted luminance"));
251
+ adapt_data.out_multiplier = adapt_pass.write(multiplier);
252
+
253
+ return adapt_data.out_multiplier;
254
+ }
207
255
 
208
- //
256
+ /**
257
+ * @param data
258
+ * @param {FramePassResources} resources
259
+ * @param {GraphicsExecutionContext} context
260
+ */
261
+ #execute_build_histogram = (data, resources, context) => {
262
+ const input_color = resources.get(data.input_color);
263
+ const output = resources.get(data.output);
209
264
 
210
- const parse_data = {};
265
+ const gx = Math.ceil(input_color.width / 16);
266
+ const gy = Math.ceil(input_color.height / 16);
211
267
 
212
- /**
213
- *
214
- * @param data
215
- * @param {FramePassResources} resources
216
- * @param {GraphicsExecutionContext} context
217
- */
218
- function parse_histogram(data, resources, context) {
219
- const input_histogram = resources.get(data.input_histogram);
220
- const output = resources.get(data.output);
221
-
222
- shader_autoexposure_read_histogram.dispatch({
223
- encoder: context.encoder,
224
- bindings: {
225
- histogram: input_histogram,
226
- output: output,
227
- },
228
- });
268
+ if (gx <= 0 || gy <= 0) {
269
+ // nothing to dispatch
270
+ return;
229
271
  }
230
272
 
231
- // ---------
273
+ const bindings = this.#build_bindings;
232
274
 
233
- const exposure_buffer_previous = this.#exposure_buffers[this.#frame_index % 2];
234
- const exposure_buffer_current = this.#exposure_buffers[(this.#frame_index + 1) % 2];
275
+ bindings.tInput = input_color.obtainView();
276
+ bindings.histogram = output;
235
277
 
236
- const gr_exposure_data_previous = graph_import_buffer(graph, exposure_buffer_previous.buffer, "previous exposure");
237
- const gr_exposure_data_current = graph_import_buffer(graph, exposure_buffer_current.buffer, "previous exposure");
278
+ shader_autoexposure_build_histogram.dispatch({
279
+ encoder: context.encoder,
280
+ group_count_x: gx,
281
+ group_count_y: gy,
282
+ bindings,
283
+ });
284
+ }
238
285
 
239
- const parse_pass = graph.add("parse histogram", parse_data, parse_histogram);
286
+ /**
287
+ * @param data
288
+ * @param {FramePassResources} resources
289
+ * @param {GraphicsExecutionContext} context
290
+ */
291
+ #execute_parse_histogram = (data, resources, context) => {
292
+ const bindings = this.#parse_bindings;
240
293
 
241
- parse_data.input_histogram = parse_pass.read(build_data.output);
242
- parse_data.output = parse_pass.create("target luminance", BufferResourceDescriptor.from(
243
- F32_CONTAINER_STRUCT.size,
244
- GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM,
245
- ));
294
+ bindings.histogram = resources.get(data.input_histogram);
295
+ bindings.output = resources.get(data.output);
246
296
 
297
+ shader_autoexposure_read_histogram.dispatch({
298
+ encoder: context.encoder,
299
+ bindings,
300
+ });
301
+ }
247
302
 
248
- // ---------
303
+ /**
304
+ * @param data
305
+ * @param {FramePassResources} resources
306
+ * @param {GraphicsExecutionContext} context
307
+ */
308
+ #execute_adapt = (data, resources, context) => {
309
+ const settings = this.#adapt_settings;
249
310
 
250
- const adapt_data = {};
311
+ settings.speed_up = this.adaptation_speed_up;
312
+ settings.speed_down = this.adaptation_speed_down;
313
+ settings.exp_transition_distance = this.exp_transition_distance;
314
+ settings.compensation = this.exposure_compensation;
251
315
 
252
- /**
253
- *
254
- * @param data
255
- * @param {FramePassResources} resources
256
- * @param {GraphicsExecutionContext} context
257
- */
258
- const adapt = (data, resources, context) => {
259
-
260
- const encoder = context.encoder;
261
-
262
- const settings = {
263
- speed_up: this.adaptation_speed_up,
264
- speed_down: this.adaptation_speed_down,
265
- time_delta: time_delta_seconds,
266
- exp_transition_distance: this.exp_transition_distance,
267
- compensation: this.exposure_compensation,
268
- };
269
-
270
-
271
- const goal = resources.get(data.goal);
272
- const previous = resources.get(data.previous);
273
- const adapted = resources.get(data.adapted);
274
- const out_multiplier = resources.get(data.out_multiplier);
275
-
276
- shader_autoexposure_adapt.dispatch({
277
- encoder,
278
- bindings: {
279
- goal,
280
- previous,
281
- settings,
282
- adapted,
283
- out_multiplier,
284
- }
285
- });
286
- }
316
+ const bindings = this.#adapt_bindings;
287
317
 
288
- const adapt_pass = graph.add("adaptation", adapt_data, adapt);
318
+ bindings.goal = resources.get(data.goal);
319
+ bindings.previous = resources.get(data.previous);
320
+ bindings.adapted = resources.get(data.adapted);
321
+ bindings.out_multiplier = resources.get(data.out_multiplier);
289
322
 
290
- adapt_data.goal = adapt_pass.read(parse_data.output);
291
- adapt_data.previous = adapt_pass.read(gr_exposure_data_previous);
292
- adapt_data.adapted = adapt_pass.write(gr_exposure_data_current);
293
- adapt_data.out_multiplier = adapt_pass.create("final", BufferResourceDescriptor.from(
294
- F32_CONTAINER_STRUCT.size,
295
- GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM,
296
- ));
297
-
298
- return adapt_data.out_multiplier;
323
+ shader_autoexposure_adapt.dispatch({
324
+ encoder: context.encoder,
325
+ bindings,
326
+ });
299
327
  }
300
328
 
301
- }
329
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * {@link chunk_forward_sample_indirect} from the probe clipmap (PROBE_CLIPMAP_PROPOSAL_2026_10_27.md §9.5): one
3
3
  * pick at the fragment by its shading normal and view, jittered by a hash of its position and normal that
4
- * changes every frame, as Brick4's is, for both lobes. The irradiance is π × the picked SH resolved at the normal,
4
+ * changes every frame, as Brick4's is, for both lobes. The irradiance is the picked SH resolved at the normal,
5
5
  * the specular the pick's radiance at the fragment's roughness (§9.3); where no probe weighs anything, both are
6
6
  * the environment's. Reads the probes' group, `view` and `tEnvironment`.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_forward_sample_indirect_probe_clipmap.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.js"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,0DAFU,SAAS,CAoCjB;0BA7CwB,oCAAoC"}
1
+ {"version":3,"file":"chunk_forward_sample_indirect_probe_clipmap.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.js"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,0DAFU,SAAS,CAkCjB;0BA3CwB,oCAAoC"}
@@ -4,17 +4,16 @@ import { chunk_probe_clipmap_pick } from "../../global_illumination/probe_clipma
4
4
  import {
5
5
  chunk_probe_clipmap_sample_radiance
6
6
  } from "../../global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js";
7
- import { PI_DECLARATION } from "../../shader/chunk/math/PI_DECLARATION.js";
8
7
  import { chunk_jitter_from_vectors } from "../../shader/chunk/random/chunk_jitter_from_vectors.js";
9
8
  import {
10
- chunk_resolve_irradiance_sh_to_diffuse
11
- } from "../../shader/chunk/spherical_harmonics/l1i/chunk_resolve_irradiance_sh_to_diffuse.js";
9
+ chunk_sh3_color_get_irradiance_at
10
+ } from "../../shader/chunk/spherical_harmonics/chunk_sh3_color_get_irradiance_at.js";
12
11
  import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
13
12
 
14
13
  /**
15
14
  * {@link chunk_forward_sample_indirect} from the probe clipmap (PROBE_CLIPMAP_PROPOSAL_2026_10_27.md §9.5): one
16
15
  * pick at the fragment by its shading normal and view, jittered by a hash of its position and normal that
17
- * changes every frame, as Brick4's is, for both lobes. The irradiance is π × the picked SH resolved at the normal,
16
+ * changes every frame, as Brick4's is, for both lobes. The irradiance is the picked SH resolved at the normal,
18
17
  * the specular the pick's radiance at the fragment's roughness (§9.3); where no probe weighs anything, both are
19
18
  * the environment's. Reads the probes' group, `view` and `tEnvironment`.
20
19
  *
@@ -37,8 +36,7 @@ fn forward_sample_indirect(
37
36
  let probes = probe_clipmap_sh_of(pick);
38
37
 
39
38
  if (probes.found) {
40
- // the resolve gives E/π
41
- *irradiance = PI * resolve_irradiance_sh_to_diffuse(probes.sh.Y, probes.sh.CoCg, normal);
39
+ *irradiance = sh3_color_get_irradiance_at(normal, probes.sh);
42
40
  *radiance = probe_clipmap_radiance_of(pick, position_ws, normal, view_direction, roughness).radiance;
43
41
  } else {
44
42
  *irradiance = get_ibl_irradiance(normal);
@@ -50,8 +48,7 @@ fn forward_sample_indirect(
50
48
  chunk_get_ibl_irradiance,
51
49
  chunk_probe_clipmap_pick,
52
50
  chunk_probe_clipmap_sample_radiance,
53
- chunk_resolve_irradiance_sh_to_diffuse,
51
+ chunk_sh3_color_get_irradiance_at,
54
52
  chunk_jitter_from_vectors,
55
- PI_DECLARATION,
56
53
  ]
57
54
  );
@@ -6,16 +6,27 @@
6
6
  *
7
7
  * ```wgsl
8
8
  * fn multiscale_mean_seed(sample: vec3f) -> MultiscaleMean
9
- * fn multiscale_mean_add(e: MultiscaleMean, sample: vec3f, short_blend: f32, variance_blend: f32, mean_blend_min: f32, clip: bool) -> MultiscaleMean
9
+ * fn multiscale_mean_add(e: MultiscaleMean, sample: vec3f, short_blend: f32, variance_blend: f32, mean_blend_min: f32, clip: bool, exposure: f32) -> MultiscaleMean
10
+ * fn multiscale_mean_deviation_floor(short_mean: vec3f, exposure: f32) -> vec3f
10
11
  * ```
11
12
  *
12
13
  * `MultiscaleMean` is {@link MULTISCALE_MEAN_STRUCT}.
13
14
  *
15
+ * - **Exposure**: the factor the display multiplies the samples by ahead of its tone curve, Unreal Engine's
16
+ * `View.PreExposure`. The reference's two absolute constants, its variance floor of 1e-5 and its firefly offset
17
+ * of 0.1, are in exposed units, as Unreal states its world caches' thresholds (`MaxRayIntensity ·
18
+ * OneOverPreExposure`): over a stream scaled by 1/E at an exposure of E the estimator does what it does over
19
+ * the stream at an exposure of one, so it tells a change from noise as far as the display shows it. Held to the
20
+ * positive normal f32 range, where neither constant overflows or vanishes.
21
+ * - **Deviation floor**: the least deviation the estimator divides by, √1e-5 / exposure, and never under one
22
+ * half-float step of the short mean, `max(|short_mean|·2⁻¹⁰, 2⁻²⁴)`: the state is stored in halves, so a
23
+ * smaller gap between the means is their rounding, and the floor stays above zero at any exposure. Taken as
24
+ * `max(floor, √variance)`, the reference's `√max(floor², variance)` without squaring the floor.
14
25
  * - **Seed**: both means at the first sample, nothing of variance, no blend reduction, an inconsistency of one,
15
26
  * as WickedEngine's surfels seed it.
16
- * - **Clip**: a sample past `0.1 + short_mean + 8·deviation` a channel is held there, a firefly. The 0.1 is
17
- * the reference's, in the radiance's own units. A caller whose variance does not yet rest on a window of
18
- * samples passes `clip` false: there is no spread to clip to.
27
+ * - **Clip**: a sample past `0.1 / exposure + short_mean + 8·deviation` a channel is held there, a firefly. A
28
+ * caller whose variance does not yet rest on a window of samples passes `clip` false: there is no spread to clip
29
+ * to.
19
30
  * - **Short mean**: blended by `short_blend`; the variance by `variance_blend`, the reference's half of it, so
20
31
  * that its window outlasts the short mean's.
21
32
  * - **Long mean**: blended by the catch-up, `smoothstep(relative · max(0.02, inconsistency − 0.2))` held to
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_multiscale_mean.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,oCAFU,SAAS,CA4CZ;0BAtFmB,6BAA6B"}
1
+ {"version":3,"file":"chunk_multiscale_mean.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/filter/chunk_multiscale_mean.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,oCAFU,SAAS,CAuDZ;0BAhHmB,6BAA6B"}
@@ -2,6 +2,10 @@ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
2
  import { chunk_rgb_to_luminance } from "../color/chunk_rgb_to_luminance.js";
3
3
  import { MULTISCALE_MEAN_STRUCT } from "./MULTISCALE_MEAN_STRUCT.js";
4
4
 
5
+ /** 2⁻¹²⁶ and 2¹²⁷, the positive normal f32 range's ends */
6
+ const EXPOSURE_MIN = '1.17549435e-38';
7
+ const EXPOSURE_MAX = '1.70141183e38';
8
+
5
9
  /**
6
10
  * The multi-scale mean estimator (MSME) of a stream of RGB samples: a long mean that blends slowly while the
7
11
  * stream is steady and noisy, and catches up with a short mean once the two disagree by more than the stream's
@@ -10,16 +14,27 @@ import { MULTISCALE_MEAN_STRUCT } from "./MULTISCALE_MEAN_STRUCT.js";
10
14
  *
11
15
  * ```wgsl
12
16
  * fn multiscale_mean_seed(sample: vec3f) -> MultiscaleMean
13
- * fn multiscale_mean_add(e: MultiscaleMean, sample: vec3f, short_blend: f32, variance_blend: f32, mean_blend_min: f32, clip: bool) -> MultiscaleMean
17
+ * fn multiscale_mean_add(e: MultiscaleMean, sample: vec3f, short_blend: f32, variance_blend: f32, mean_blend_min: f32, clip: bool, exposure: f32) -> MultiscaleMean
18
+ * fn multiscale_mean_deviation_floor(short_mean: vec3f, exposure: f32) -> vec3f
14
19
  * ```
15
20
  *
16
21
  * `MultiscaleMean` is {@link MULTISCALE_MEAN_STRUCT}.
17
22
  *
23
+ * - **Exposure**: the factor the display multiplies the samples by ahead of its tone curve, Unreal Engine's
24
+ * `View.PreExposure`. The reference's two absolute constants, its variance floor of 1e-5 and its firefly offset
25
+ * of 0.1, are in exposed units, as Unreal states its world caches' thresholds (`MaxRayIntensity ·
26
+ * OneOverPreExposure`): over a stream scaled by 1/E at an exposure of E the estimator does what it does over
27
+ * the stream at an exposure of one, so it tells a change from noise as far as the display shows it. Held to the
28
+ * positive normal f32 range, where neither constant overflows or vanishes.
29
+ * - **Deviation floor**: the least deviation the estimator divides by, √1e-5 / exposure, and never under one
30
+ * half-float step of the short mean, `max(|short_mean|·2⁻¹⁰, 2⁻²⁴)`: the state is stored in halves, so a
31
+ * smaller gap between the means is their rounding, and the floor stays above zero at any exposure. Taken as
32
+ * `max(floor, √variance)`, the reference's `√max(floor², variance)` without squaring the floor.
18
33
  * - **Seed**: both means at the first sample, nothing of variance, no blend reduction, an inconsistency of one,
19
34
  * as WickedEngine's surfels seed it.
20
- * - **Clip**: a sample past `0.1 + short_mean + 8·deviation` a channel is held there, a firefly. The 0.1 is
21
- * the reference's, in the radiance's own units. A caller whose variance does not yet rest on a window of
22
- * samples passes `clip` false: there is no spread to clip to.
35
+ * - **Clip**: a sample past `0.1 / exposure + short_mean + 8·deviation` a channel is held there, a firefly. A
36
+ * caller whose variance does not yet rest on a window of samples passes `clip` false: there is no spread to clip
37
+ * to.
23
38
  * - **Short mean**: blended by `short_blend`; the variance by `variance_blend`, the reference's half of it, so
24
39
  * that its window outlasts the short mean's.
25
40
  * - **Long mean**: blended by the catch-up, `smoothstep(relative · max(0.02, inconsistency − 0.2))` held to
@@ -55,17 +70,28 @@ fn multiscale_mean_seed(sample: vec3f) -> ${MULTISCALE_MEAN_STRUCT.wgsl_ref} {
55
70
  })};
56
71
  }
57
72
 
58
- fn multiscale_mean_add(e: ${MULTISCALE_MEAN_STRUCT.wgsl_ref}, sample: vec3f, short_blend: f32, variance_blend: f32, mean_blend_min: f32, clip: bool) -> ${MULTISCALE_MEAN_STRUCT.wgsl_ref} {
73
+ // the exposure, held to the positive normal f32 range
74
+ fn multiscale_mean_exposure(exposure: f32) -> f32 {
75
+ return clamp(exposure, ${EXPOSURE_MIN}, ${EXPOSURE_MAX});
76
+ }
77
+
78
+ fn multiscale_mean_deviation_floor(short_mean: vec3f, exposure: f32) -> vec3f {
79
+ return max(vec3f(sqrt(1e-5) / multiscale_mean_exposure(exposure)), max(abs(short_mean) * exp2(-10.0), vec3f(exp2(-24.0))));
80
+ }
81
+
82
+ fn multiscale_mean_add(e: ${MULTISCALE_MEAN_STRUCT.wgsl_ref}, sample: vec3f, short_blend: f32, variance_blend: f32, mean_blend_min: f32, clip: bool, exposure: f32) -> ${MULTISCALE_MEAN_STRUCT.wgsl_ref} {
59
83
  var y = sample;
60
84
 
61
85
  if (clip) {
62
- y = min(y, 0.1 + e.short_mean + 8.0 * sqrt(max(vec3f(1e-5), e.variance)));
86
+ let spread = max(multiscale_mean_deviation_floor(e.short_mean, exposure), sqrt(max(vec3f(0.0), e.variance)));
87
+
88
+ y = min(y, 0.1 / multiscale_mean_exposure(exposure) + e.short_mean + 8.0 * spread);
63
89
  }
64
90
 
65
91
  let delta = y - e.short_mean;
66
92
  let short_mean = mix(e.short_mean, y, short_blend);
67
93
  let variance = mix(e.variance, delta * (y - short_mean), variance_blend);
68
- let deviation = sqrt(max(vec3f(1e-5), variance));
94
+ let deviation = max(multiscale_mean_deviation_floor(short_mean, exposure), sqrt(max(vec3f(0.0), variance)));
69
95
 
70
96
  let relative = rgb_to_luminance(abs(e.mean - short_mean) / deviation);
71
97
  let inconsistency = mix(e.inconsistency, relative, short_blend);
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Evaluates a 2D shuffled, Owen-scrambled Sobol (0, 2)-sequence point in [0, 1)^2
3
+ * entirely in registers with zero lookup tables.
4
+ *
5
+ * Every power-of-two prefix of 2^m samples forms a fully stratified (0, m, 2)-net
6
+ * across all elementary dyadic rectangles of area 2^-m, while per-pixel seeding
7
+ * decorrelates sample sets across pixels and bounce dimensions:
8
+ *
9
+ * 1. **Index shuffling (MSB-to-LSB):** `sample_idx` is Owen-scrambled top-down via
10
+ * `reverseBits(hash_lk(reverseBits(sample_idx), ...))`. This maps every aligned
11
+ * block of 2^m contiguous indices `[k * 2^m, (k + 1) * 2^m)` bijectively onto
12
+ * another aligned 2^m block, randomizing traversal order per pixel while keeping
13
+ * bits `i_31..i_m` constant across the block so prefix stratification holds.
14
+ * 2. **Dimension 0 (Van der Corput):** Prior to radical inversion, the base-2
15
+ * Van der Corput sequence is the identity on `i`. Because bit 0 of `i` (`i_0`)
16
+ * already represents the geometric MSB (2^-1), `hash_lk` is applied directly
17
+ * followed by a single `reverseBits`.
18
+ * 3. **Dimension 1 (Sobol D1):** Computed without direction-number tables by
19
+ * multiplying `i` by the 32x32 upper-triangular Pascal matrix over GF(2)
20
+ * (`P_{k,j} = C(j, k) mod 2`, the 5-fold Kronecker power of `[1 1; 0 1]`) using
21
+ * 5 logarithmic butterfly shift-XOR stages before coordinate scrambling.
22
+ *
23
+ * For multi-bounce path tracing, pass a combined hash of `(pixel_coord, bounce_dim_pair)`
24
+ * as `pixel_seed` to construct padded 2D (0, 2)-sequences without high-dimensional
25
+ * direction tables.
26
+ *
27
+ * @see Brent Burley, "Practical Hash-based Owen Scrambling", Journal of Computer
28
+ * Graphics Techniques (JCGT), vol. 9, no. 4, pp. 1-20, 2020.
29
+ * http://jcgt.org/published/0009/04/01/
30
+ * @see Nathan Vegdahl, "Building a Better LK Hash", 2021.
31
+ * https://psychopath.io/post/2021_01_30_building_a_better_lk_hash
32
+ * @see Ilya M. Sobol', "On the distribution of points in a cube and the approximate
33
+ * evaluation of integrals", USSR Computational Mathematics and Mathematical
34
+ * Physics, vol. 7, no. 4, pp. 86-112, 1967.
35
+ *
36
+ * @type {CodeChunk}
37
+ */
38
+ export const chunk_sobol_sample_vec2: CodeChunk;
39
+ import { CodeChunk } from "../../compiler/CodeChunk.js";
40
+ //# sourceMappingURL=chunk_sobol_sample_vec2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk_sobol_sample_vec2.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/shader/chunk/noise/chunk_sobol_sample_vec2.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,sCAFU,SAAS,CAkClB;0BA1EyB,6BAA6B"}