@woosh/meep-engine 3.65.0 → 3.67.0

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 (50) hide show
  1. package/package.json +1 -1
  2. package/src/format/lut/cube/CubeLut.d.ts +38 -0
  3. package/src/format/lut/cube/CubeLut.d.ts.map +1 -0
  4. package/src/format/lut/cube/CubeLut.js +23 -0
  5. package/src/format/lut/cube/cube_read.d.ts +23 -0
  6. package/src/format/lut/cube/cube_read.d.ts.map +1 -0
  7. package/src/format/lut/cube/cube_read.js +199 -0
  8. package/src/format/lut/cube/cube_write.d.ts +15 -0
  9. package/src/format/lut/cube/cube_write.d.ts.map +1 -0
  10. package/src/format/lut/cube/cube_write.js +90 -0
  11. package/src/shade/playground/probe_clipmap/README.md +1 -0
  12. package/src/shade/playground/probe_clipmap/index.html +5 -0
  13. package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -1
  14. package/src/shade/playground/probe_clipmap/main.js +7 -0
  15. package/src/shade/renderer/Renderer.js +12 -12
  16. package/src/shade/renderer/deferred/tonemap/ColorGrade.d.ts +73 -4
  17. package/src/shade/renderer/deferred/tonemap/ColorGrade.d.ts.map +1 -1
  18. package/src/shade/renderer/deferred/tonemap/ColorGrade.js +113 -7
  19. package/src/shade/renderer/deferred/tonemap/TONEMAP_PURKINJE_SHIFT.d.ts +12 -0
  20. package/src/shade/renderer/deferred/tonemap/TONEMAP_PURKINJE_SHIFT.d.ts.map +1 -0
  21. package/src/shade/renderer/deferred/tonemap/TONEMAP_PURKINJE_SHIFT.js +12 -0
  22. package/src/shade/renderer/deferred/tonemap/ToneMapper.d.ts +15 -1
  23. package/src/shade/renderer/deferred/tonemap/ToneMapper.d.ts.map +1 -1
  24. package/src/shade/renderer/deferred/tonemap/ToneMapper.js +31 -3
  25. package/src/shade/renderer/deferred/tonemap/chunk_tonemap_purkinje.d.ts +25 -0
  26. package/src/shade/renderer/deferred/tonemap/chunk_tonemap_purkinje.d.ts.map +1 -0
  27. package/src/shade/renderer/deferred/tonemap/chunk_tonemap_purkinje.js +51 -0
  28. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_lut.d.ts +2 -2
  29. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_lut.d.ts.map +1 -1
  30. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_lut.js +6 -3
  31. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_lut.d.ts +3 -2
  32. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_lut.d.ts.map +1 -1
  33. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_lut.js +7 -3
  34. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts +35 -9
  35. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts.map +1 -1
  36. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +81 -11
  37. package/src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.d.ts.map +1 -1
  38. package/src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.js +4 -1
  39. package/src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.d.ts +6 -0
  40. package/src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.d.ts.map +1 -1
  41. package/src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.js +7 -0
  42. package/src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.d.ts +4 -4
  43. package/src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.d.ts.map +1 -1
  44. package/src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.js +86 -5
  45. package/src/shade/renderer/shader/chunk/color/tonemap/purkinje/chunk_apply_purkinje_shift.d.ts +65 -0
  46. package/src/shade/renderer/shader/chunk/color/tonemap/purkinje/chunk_apply_purkinje_shift.d.ts.map +1 -0
  47. package/src/shade/renderer/shader/chunk/color/tonemap/purkinje/chunk_apply_purkinje_shift.js +136 -0
  48. package/src/shade/renderer/shader/chunk/color/tonemap/purkinjie/chunk_apply_purkinjie_shift.d.ts +0 -11
  49. package/src/shade/renderer/shader/chunk/color/tonemap/purkinjie/chunk_apply_purkinjie_shift.d.ts.map +0 -1
  50. package/src/shade/renderer/shader/chunk/color/tonemap/purkinjie/chunk_apply_purkinjie_shift.js +0 -56
@@ -5,6 +5,10 @@
5
5
  * The factor lives in one buffer for the manager's life, {@link multiplier}, which a frame's {@link update} or
6
6
  * {@link unadapted} writes over. A pass recorded ahead of that write in the frame reads the factor the last frame was
7
7
  * shown with: the exposure of the image on screen, Unreal Engine's `View.PreExposure`.
8
+ *
9
+ * Beside it, {@link adaptation}: the luminance the view is adapted to, cd/m², written by the same pass. Adapted, it is
10
+ * the luminance the eye has adapted to, without {@link exposure_compensation}; unadapted, it is the luminance the
11
+ * exposure shows at middle grey.
8
12
  */
9
13
  export class GPUCameraExposureManager {
10
14
  /**
@@ -44,9 +48,17 @@ export class GPUCameraExposureManager {
44
48
  * imports it into its graph once, and hands the handle both to the passes that read it ahead of the exposure,
45
49
  * which take the last frame's factor, and to {@link update} or {@link unadapted}, which write this frame's.
46
50
  *
47
- * @returns {GPUBuffer} usable as a uniform or read-only storage
51
+ * @returns {GPUBuffer} usable as a uniform or read-only storage, and as a copy source, to read it back
48
52
  */
49
53
  get multiplier(): GPUBuffer;
54
+ /**
55
+ * The luminance the view is adapted to, cd/m², {@link F32_CONTAINER_STRUCT}, as the last {@link update} or
56
+ * {@link unadapted} wrote it. Imported into a frame's graph once and handed to {@link update} or {@link unadapted},
57
+ * like {@link multiplier}.
58
+ *
59
+ * @returns {GPUBuffer} usable as a uniform or read-only storage
60
+ */
61
+ get adaptation(): GPUBuffer;
50
62
  /**
51
63
  * Offer the pipelines {@link update} meters and adapts with — see {@link PipelineEnquiry}.
52
64
  * {@link unadapted} dispatches none.
@@ -55,34 +67,48 @@ export class GPUCameraExposureManager {
55
67
  */
56
68
  enquirePipelines(enquiry: PipelineEnquiry): void;
57
69
  /**
58
- * This frame's factor from {@link exposure_compensation} alone: 2^compensation.
70
+ * This frame's factor from {@link exposure_compensation} alone: 2^compensation. Its adaptation is the luminance
71
+ * that factor shows at middle grey, 0.18 / 2^compensation cd/m².
59
72
  *
60
73
  * @param {Object} options
61
74
  * @param {FrameGraph} options.graph
62
75
  * @param {number} options.multiplier the graph handle of {@link multiplier}, imported into `graph`
63
- * @return {number} the handle of {@link multiplier} with this frame's factor written
76
+ * @param {number} options.adaptation the graph handle of {@link adaptation}, imported into `graph`
77
+ * @return {{multiplier: number, adaptation: number}} the handles of {@link multiplier} and {@link adaptation}
78
+ * with this frame's written; the object is reused by the next call
64
79
  */
65
- unadapted({ graph, multiplier, }: {
80
+ unadapted({ graph, multiplier, adaptation, }: {
66
81
  graph: FrameGraph;
67
82
  multiplier: number;
68
- }): number;
83
+ adaptation: number;
84
+ }): {
85
+ multiplier: number;
86
+ adaptation: number;
87
+ };
69
88
  /**
70
89
  * This frame's factor metered from `input_color`: the luminance adapted toward the frame's, from the last
71
- * frame's, by the adaptation speeds; its factor is middle grey over it, times 2^{@link exposure_compensation}.
90
+ * frame's, by the adaptation speeds; its factor is middle grey over it, times 2^{@link exposure_compensation}. Its
91
+ * adaptation is that adapted luminance, without the compensation.
72
92
  *
73
93
  * @param {Object} options
74
94
  * @param {FrameGraph} options.graph
75
95
  * @param {number} options.input_color
76
96
  * @param {number} options.multiplier the graph handle of {@link multiplier}, imported into `graph`
97
+ * @param {number} options.adaptation the graph handle of {@link adaptation}, imported into `graph`
77
98
  * @param {number} [options.time_delta_seconds]
78
- * @return {number} the handle of {@link multiplier} with this frame's factor written
99
+ * @return {{multiplier: number, adaptation: number}} the handles of {@link multiplier} and {@link adaptation}
100
+ * with this frame's written; the object is reused by the next call
79
101
  */
80
- update({ input_color, graph, multiplier, time_delta_seconds, }: {
102
+ update({ input_color, graph, multiplier, adaptation, time_delta_seconds, }: {
81
103
  graph: FrameGraph;
82
104
  input_color: number;
83
105
  multiplier: number;
106
+ adaptation: number;
84
107
  time_delta_seconds?: number;
85
- }): number;
108
+ }): {
109
+ multiplier: number;
110
+ adaptation: number;
111
+ };
86
112
  #private;
87
113
  }
88
114
  export type PipelineEnquiry = import("../../../device/pipeline/PipelineEnquiry.js").PipelineEnquiry;
@@ -1 +1 @@
1
- {"version":3,"file":"GPUCameraExposureManager.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js"],"names":[],"mappings":"AAwBA;;;;;;;GAOG;AACH;IA0DI;;;OAGG;IACH,+BAyBC;IAtED;;;;OAIG;IACH,qBAFU,MAAM,CAEU;IAE1B;;;;OAIG;IACH,uBAFU,MAAM,CAEY;IAE5B;;;;;OAKG;IACH,yBAFU,MAAM,CAEc;IAE9B;;;;;OAKG;IACH,uBAFU,MAAM,CAEY;IA4C5B,gBAMC;IAED;;;;;;OAMG;IACH,4BAEC;IAED;;;;;OAKG;IACH,0BAFW,eAAe,QAMzB;IAED;;;;;;;OAOG;IACH;QAJ+B,KAAK;QACT,UAAU,EAA1B,MAAM;QACL,MAAM,CAejB;IAoBD;;;;;;;;;;OAUG;IACH;QAN+B,KAAK;QACT,WAAW,EAA3B,MAAM;QACU,UAAU,EAA1B,MAAM;QACW,kBAAkB,GAAnC,MAAM;QACL,MAAM,CAmDjB;;CA2EJ;8BA7TY,OAAO,6CAA6C,EAAE,eAAe"}
1
+ {"version":3,"file":"GPUCameraExposureManager.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js"],"names":[],"mappings":"AA6BA;;;;;;;;;;;GAWG;AACH;IAuEI;;;OAGG;IACH,+BAoCC;IAlFD;;;;OAIG;IACH,qBAFU,MAAM,CAEU;IAE1B;;;;OAIG;IACH,uBAFU,MAAM,CAEY;IAE5B;;;;;OAKG;IACH,yBAFU,MAAM,CAEc;IAE9B;;;;;OAKG;IACH,uBAFU,MAAM,CAEY;IAwD5B,gBAOC;IAED;;;;;;OAMG;IACH,4BAEC;IAED;;;;;;OAMG;IACH,4BAEC;IAED;;;;;OAKG;IACH,0BAFW,eAAe,QAMzB;IAED;;;;;;;;;;OAUG;IACH;QAN+B,KAAK;QACT,UAAU,EAA1B,MAAM;QACU,UAAU,EAA1B,MAAM;QACL;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CAwBnD;IAsBD;;;;;;;;;;;;;OAaG;IACH;QAR+B,KAAK;QACT,WAAW,EAA3B,MAAM;QACU,UAAU,EAA1B,MAAM;QACU,UAAU,EAA1B,MAAM;QACW,kBAAkB,GAAnC,MAAM;QACL;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CA4DnD;;CA4EJ;8BAnYY,OAAO,6CAA6C,EAAE,eAAe"}
@@ -17,6 +17,11 @@ const HISTOGRAM_DESCRIPTOR = BufferResourceDescriptor.empty(
17
17
  GPUBufferUsage.STORAGE,
18
18
  );
19
19
 
20
+ /**
21
+ * The luminance an exposure of one shows at middle grey, cd/m²: the scene value the adaptation maps to 0.18.
22
+ */
23
+ const MIDDLE_GREY = 0.18;
24
+
20
25
  const TARGET_LUMINANCE_DESCRIPTOR = BufferResourceDescriptor.from(
21
26
  F32_CONTAINER_STRUCT.size,
22
27
  GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM,
@@ -29,6 +34,10 @@ const TARGET_LUMINANCE_DESCRIPTOR = BufferResourceDescriptor.from(
29
34
  * The factor lives in one buffer for the manager's life, {@link multiplier}, which a frame's {@link update} or
30
35
  * {@link unadapted} writes over. A pass recorded ahead of that write in the frame reads the factor the last frame was
31
36
  * shown with: the exposure of the image on screen, Unreal Engine's `View.PreExposure`.
37
+ *
38
+ * Beside it, {@link adaptation}: the luminance the view is adapted to, cd/m², written by the same pass. Adapted, it is
39
+ * the luminance the eye has adapted to, without {@link exposure_compensation}; unadapted, it is the luminance the
40
+ * exposure shows at middle grey.
32
41
  */
33
42
  export class GPUCameraExposureManager {
34
43
  /**
@@ -47,6 +56,18 @@ export class GPUCameraExposureManager {
47
56
  */
48
57
  #multiplier;
49
58
 
59
+ /**
60
+ * {@link F32_CONTAINER_STRUCT}: the adaptation the last {@link update} or {@link unadapted} wrote, cd/m².
61
+ * @type {GPUTypedBuffer}
62
+ */
63
+ #adaptation;
64
+
65
+ /**
66
+ * The handles {@link update} and {@link unadapted} return, kept across frames.
67
+ * @type {{multiplier: number, adaptation: number}}
68
+ */
69
+ #written = { multiplier: -1, adaptation: -1 };
70
+
50
71
  /**
51
72
  * How fast do we adapt to brighter environments?
52
73
  * in f-stops per second.
@@ -78,15 +99,16 @@ export class GPUCameraExposureManager {
78
99
  exposure_compensation = 0.0;
79
100
 
80
101
  // 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 };
102
+ #unadapted_data = { output: -1, adaptation: -1 };
82
103
  #unadapted_value = { value: 1.0 };
104
+ #unadapted_adaptation = { value: MIDDLE_GREY };
83
105
  #build_data = { input_color: -1, output: -1 };
84
106
  #parse_data = { input_histogram: -1, output: -1 };
85
- #adapt_data = { goal: -1, previous: -1, adapted: -1, out_multiplier: -1 };
107
+ #adapt_data = { goal: -1, previous: -1, adapted: -1, out_multiplier: -1, out_adaptation: -1 };
86
108
  #adapt_settings = { speed_up: 0, speed_down: 0, time_delta: 0, exp_transition_distance: 0, compensation: 0 };
87
109
  #build_bindings = { tInput: null, histogram: null };
88
110
  #parse_bindings = { histogram: null, output: null };
89
- #adapt_bindings = { goal: null, previous: null, settings: this.#adapt_settings, adapted: null, out_multiplier: null };
111
+ #adapt_bindings = { goal: null, previous: null, settings: this.#adapt_settings, adapted: null, out_multiplier: null, out_adaptation: null };
90
112
 
91
113
  /**
92
114
  *
@@ -110,13 +132,24 @@ export class GPUCameraExposureManager {
110
132
  this.#multiplier = GPUTypedBuffer.create({
111
133
  label: 'exposure multiplier',
112
134
  device,
113
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
135
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
114
136
  type: F32_CONTAINER_STRUCT,
115
137
  initial_value: {
116
138
  value: 1.0
117
139
  }
118
140
  });
119
141
 
142
+ // what a factor of one shows at middle grey, until the first frame writes its own
143
+ this.#adaptation = GPUTypedBuffer.create({
144
+ label: 'exposure adaptation',
145
+ device,
146
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
147
+ type: F32_CONTAINER_STRUCT,
148
+ initial_value: {
149
+ value: MIDDLE_GREY
150
+ }
151
+ });
152
+
120
153
  }
121
154
 
122
155
  destroy() {
@@ -125,6 +158,7 @@ export class GPUCameraExposureManager {
125
158
  }
126
159
 
127
160
  this.#multiplier.destroy();
161
+ this.#adaptation.destroy();
128
162
  }
129
163
 
130
164
  /**
@@ -132,12 +166,23 @@ export class GPUCameraExposureManager {
132
166
  * imports it into its graph once, and hands the handle both to the passes that read it ahead of the exposure,
133
167
  * which take the last frame's factor, and to {@link update} or {@link unadapted}, which write this frame's.
134
168
  *
135
- * @returns {GPUBuffer} usable as a uniform or read-only storage
169
+ * @returns {GPUBuffer} usable as a uniform or read-only storage, and as a copy source, to read it back
136
170
  */
137
171
  get multiplier() {
138
172
  return this.#multiplier.buffer;
139
173
  }
140
174
 
175
+ /**
176
+ * The luminance the view is adapted to, cd/m², {@link F32_CONTAINER_STRUCT}, as the last {@link update} or
177
+ * {@link unadapted} wrote it. Imported into a frame's graph once and handed to {@link update} or {@link unadapted},
178
+ * like {@link multiplier}.
179
+ *
180
+ * @returns {GPUBuffer} usable as a uniform or read-only storage
181
+ */
182
+ get adaptation() {
183
+ return this.#adaptation.buffer;
184
+ }
185
+
141
186
  /**
142
187
  * Offer the pipelines {@link update} meters and adapts with — see {@link PipelineEnquiry}.
143
188
  * {@link unadapted} dispatches none.
@@ -151,26 +196,37 @@ export class GPUCameraExposureManager {
151
196
  }
152
197
 
153
198
  /**
154
- * This frame's factor from {@link exposure_compensation} alone: 2^compensation.
199
+ * This frame's factor from {@link exposure_compensation} alone: 2^compensation. Its adaptation is the luminance
200
+ * that factor shows at middle grey, 0.18 / 2^compensation cd/m².
155
201
  *
156
202
  * @param {Object} options
157
203
  * @param {FrameGraph} options.graph
158
204
  * @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
205
+ * @param {number} options.adaptation the graph handle of {@link adaptation}, imported into `graph`
206
+ * @return {{multiplier: number, adaptation: number}} the handles of {@link multiplier} and {@link adaptation}
207
+ * with this frame's written; the object is reused by the next call
160
208
  */
161
209
  unadapted({
162
210
  graph,
163
211
  multiplier,
212
+ adaptation,
164
213
  }) {
165
214
  assert.defined(graph, 'graph');
166
215
  assert.isNonNegativeInteger(multiplier, 'multiplier');
216
+ assert.isNonNegativeInteger(adaptation, 'adaptation');
167
217
 
168
218
  const data = this.#unadapted_data;
169
219
  const pass = graph.add("unadapted", data, this.#execute_unadapted);
170
220
 
171
221
  data.output = pass.write(multiplier);
222
+ data.adaptation = pass.write(adaptation);
223
+
224
+ const written = this.#written;
172
225
 
173
- return data.output;
226
+ written.multiplier = data.output;
227
+ written.adaptation = data.adaptation;
228
+
229
+ return written;
174
230
  }
175
231
 
176
232
  /**
@@ -186,26 +242,32 @@ export class GPUCameraExposureManager {
186
242
 
187
243
  // compensation is in f-stops: each stop doubles/halves brightness
188
244
  this.#unadapted_value.value = Math.pow(2, this.exposure_compensation);
245
+ this.#unadapted_adaptation.value = MIDDLE_GREY / this.#unadapted_value.value;
189
246
 
190
247
  // in the command stream, so a pass recorded ahead of this one still reads the last frame's factor
191
248
  encoder.writeValueBuffer(resources.get(data.output), 0, F32_CONTAINER_STRUCT, this.#unadapted_value);
249
+ encoder.writeValueBuffer(resources.get(data.adaptation), 0, F32_CONTAINER_STRUCT, this.#unadapted_adaptation);
192
250
  }
193
251
 
194
252
  /**
195
253
  * 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}.
254
+ * frame's, by the adaptation speeds; its factor is middle grey over it, times 2^{@link exposure_compensation}. Its
255
+ * adaptation is that adapted luminance, without the compensation.
197
256
  *
198
257
  * @param {Object} options
199
258
  * @param {FrameGraph} options.graph
200
259
  * @param {number} options.input_color
201
260
  * @param {number} options.multiplier the graph handle of {@link multiplier}, imported into `graph`
261
+ * @param {number} options.adaptation the graph handle of {@link adaptation}, imported into `graph`
202
262
  * @param {number} [options.time_delta_seconds]
203
- * @return {number} the handle of {@link multiplier} with this frame's factor written
263
+ * @return {{multiplier: number, adaptation: number}} the handles of {@link multiplier} and {@link adaptation}
264
+ * with this frame's written; the object is reused by the next call
204
265
  */
205
266
  update({
206
267
  input_color,
207
268
  graph,
208
269
  multiplier,
270
+ adaptation,
209
271
  time_delta_seconds = 0.01666,
210
272
  }) {
211
273
 
@@ -219,6 +281,7 @@ export class GPUCameraExposureManager {
219
281
 
220
282
  assert.defined(graph, 'graph');
221
283
  assert.isNonNegativeInteger(multiplier, 'multiplier');
284
+ assert.isNonNegativeInteger(adaptation, 'adaptation');
222
285
  assert.isNumber(time_delta_seconds, 'time_delta_seconds');
223
286
  assert.greaterThanOrEqual(time_delta_seconds, 0, 'time_delta_seconds');
224
287
 
@@ -249,8 +312,14 @@ export class GPUCameraExposureManager {
249
312
  adapt_data.previous = adapt_pass.read(graph_import_buffer(graph, exposure_buffer_previous.buffer, "previous adapted luminance"));
250
313
  adapt_data.adapted = adapt_pass.write(graph_import_buffer(graph, exposure_buffer_current.buffer, "adapted luminance"));
251
314
  adapt_data.out_multiplier = adapt_pass.write(multiplier);
315
+ adapt_data.out_adaptation = adapt_pass.write(adaptation);
316
+
317
+ const written = this.#written;
318
+
319
+ written.multiplier = adapt_data.out_multiplier;
320
+ written.adaptation = adapt_data.out_adaptation;
252
321
 
253
- return adapt_data.out_multiplier;
322
+ return written;
254
323
  }
255
324
 
256
325
  /**
@@ -319,6 +388,7 @@ export class GPUCameraExposureManager {
319
388
  bindings.previous = resources.get(data.previous);
320
389
  bindings.adapted = resources.get(data.adapted);
321
390
  bindings.out_multiplier = resources.get(data.out_multiplier);
391
+ bindings.out_adaptation = resources.get(data.out_adaptation);
322
392
 
323
393
  shader_autoexposure_adapt.dispatch({
324
394
  encoder: context.encoder,
@@ -1 +1 @@
1
- {"version":3,"file":"shader_autoexposure_adapt.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.js"],"names":[],"mappings":"AASA,sDAsBE;AA+FF,sDAIG;6BAjI0B,mCAAmC;8BAElC,+BAA+B"}
1
+ {"version":3,"file":"shader_autoexposure_adapt.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.js"],"names":[],"mappings":"AASA,sDAsBE;AAkGF,sDAIG;6BApI0B,mCAAmC;8BAElC,+BAA+B"}
@@ -41,6 +41,7 @@ resources.createGroup()
41
41
  .addUniform("previous", F32_CONTAINER_STRUCT)
42
42
  .addStorageBuffer("adapted", F32_CONTAINER_STRUCT, true)
43
43
  .addStorageBuffer("out_multiplier", F32_CONTAINER_STRUCT, true)
44
+ .addStorageBuffer("out_adaptation", F32_CONTAINER_STRUCT, true)
44
45
  ;
45
46
 
46
47
 
@@ -64,6 +65,7 @@ fn main(){
64
65
  */
65
66
  if (!(goal.value > 0.0)) {
66
67
  adapted.value = previous.value;
68
+ out_adaptation.value = previous.value;
67
69
 
68
70
  out_multiplier.value = (MIDDLE_GREY / current_linear) * exp2(settings.compensation);
69
71
 
@@ -113,7 +115,8 @@ fn main(){
113
115
  // convert back to linear space
114
116
  let new_linear = exp2(new_ev);
115
117
  adapted.value = new_linear;
116
-
118
+ out_adaptation.value = new_linear;
119
+
117
120
  // apply final transforms
118
121
 
119
122
  let shifted = MIDDLE_GREY / adapted.value;
@@ -9,6 +9,12 @@ export const M3_REC709_TO_AP1: Float64Array;
9
9
  * @type {Float64Array}
10
10
  */
11
11
  export const M3_AP1_TO_REC709: Float64Array;
12
+ /**
13
+ * AP1's contribution of each channel to Y, the row Unreal calls `AP1_RGB2Y`: the shader's `AP1_LUMINANCE`, for code that
14
+ * measures AP1 luma on the CPU.
15
+ * @type {Float64Array}
16
+ */
17
+ export const AP1_LUMINANCE: Float64Array;
12
18
  /**
13
19
  * - `CRM_FROM_REC709_TO_AP1` and `CRM_FROM_AP1_TO_REC709`: {@link M3_REC709_TO_AP1} and its inverse.
14
20
  * - `AP1_LUMINANCE`: AP1's contribution of each channel to Y, the row Unreal calls `AP1_RGB2Y`.
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_CRM_REC709_AP1.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.js"],"names":[],"mappings":"AAqBA;;;;GAIG;AACH,+BAFU,YAAY,CAE8B;AAIpD;;;GAGG;AACH,+BAFU,YAAY,CAE8B;AAYpD;;;;;;;GAOG;AACH,mCAFU,SAAS,CASjB;0BAzDwB,gCAAgC"}
1
+ {"version":3,"file":"chunk_CRM_REC709_AP1.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.js"],"names":[],"mappings":"AAqBA;;;;GAIG;AACH,+BAFU,YAAY,CAE8B;AAIpD;;;GAGG;AACH,+BAFU,YAAY,CAE8B;AAGpD;;;;GAIG;AACH,4BAFU,YAAY,CAEoE;AAW1F;;;;;;;GAOG;AACH,mCAFU,SAAS,CASjB;0BAhEwB,gCAAgC"}
@@ -35,6 +35,13 @@ m3_multiply(M3_REC709_TO_AP1, XYZ_TO_AP1, M3_REC709_TO_AP1);
35
35
  export const M3_AP1_TO_REC709 = new Float64Array(9);
36
36
  m3_invert(M3_AP1_TO_REC709, M3_REC709_TO_AP1);
37
37
 
38
+ /**
39
+ * AP1's contribution of each channel to Y, the row Unreal calls `AP1_RGB2Y`: the shader's `AP1_LUMINANCE`, for code that
40
+ * measures AP1 luma on the CPU.
41
+ * @type {Float64Array}
42
+ */
43
+ export const AP1_LUMINANCE = Float64Array.of(AP1_TO_XYZ[3], AP1_TO_XYZ[4], AP1_TO_XYZ[5]);
44
+
38
45
  /**
39
46
  * @param {Float64Array} m row-major
40
47
  * @returns {string} a WGSL `mat3x3<f32>` constructor, column by column
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * `color_grade(scene, grade, keys)`: exposed scene-linear Rec.709 through a {@link ColorGrade}, white balance, then
3
- * primaries, then hue curves, to Rec.709 again, not negative. `grade` is the grade's {@link COLOR_GRADE_STRUCT} and
4
- * `keys` the curves' keyframes. See `ColorGrade` for what each stage does.
3
+ * primaries, then saturation against luma, then hue curves, to Rec.709 again, not negative. `grade` is the grade's
4
+ * {@link COLOR_GRADE_STRUCT} and `keys` the curves' keyframes. See `ColorGrade` for what each stage does.
5
5
  *
6
- * Also defines each stage on its own: `color_grade_primaries(ap1, grade)` and
7
- * `color_grade_hue_curves(color, counts, keys)`.
6
+ * Also defines each stage on its own: `color_grade_primaries(ap1, grade)`,
7
+ * `color_grade_luma_saturation(ap1, counts, keys, first)` and `color_grade_hue_curves(color, counts, keys)`.
8
8
  *
9
9
  * @type {CodeChunk}
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_color_grade.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.js"],"names":[],"mappings":"AASA;;;;;;;;;GASG;AACH,gCAFU,SAAS,CA0HjB;0BAxIwB,gCAAgC"}
1
+ {"version":3,"file":"chunk_color_grade.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.js"],"names":[],"mappings":"AASA;;;;;;;;;GASG;AACH,gCAFU,SAAS,CA2MjB;0BAzNwB,gCAAgC"}
@@ -9,11 +9,11 @@ import { chunk_oklab_to_linear_srgb } from "../oklab/chunk_oklab_to_linear_srgb.
9
9
 
10
10
  /**
11
11
  * `color_grade(scene, grade, keys)`: exposed scene-linear Rec.709 through a {@link ColorGrade}, white balance, then
12
- * primaries, then hue curves, to Rec.709 again, not negative. `grade` is the grade's {@link COLOR_GRADE_STRUCT} and
13
- * `keys` the curves' keyframes. See `ColorGrade` for what each stage does.
12
+ * primaries, then saturation against luma, then hue curves, to Rec.709 again, not negative. `grade` is the grade's
13
+ * {@link COLOR_GRADE_STRUCT} and `keys` the curves' keyframes. See `ColorGrade` for what each stage does.
14
14
  *
15
- * Also defines each stage on its own: `color_grade_primaries(ap1, grade)` and
16
- * `color_grade_hue_curves(color, counts, keys)`.
15
+ * Also defines each stage on its own: `color_grade_primaries(ap1, grade)`,
16
+ * `color_grade_luma_saturation(ap1, counts, keys, first)` and `color_grade_hue_curves(color, counts, keys)`.
17
17
  *
18
18
  * @type {CodeChunk}
19
19
  */
@@ -65,6 +65,78 @@ fn color_grade_primaries(ap1: vec3<f32>, grade: ${COLOR_GRADE_STRUCT.wgsl_ref})
65
65
  return shadows * weight_shadows + midtones * weight_midtones + highlights * weight_highlights;
66
66
  }
67
67
 
68
+ // each AP1 channel drawn away from the luma by its factor, read at the luma's stops from middle grey: the all-channel
69
+ // curve times the channel's own; counts are the red, green, blue and all-channel curves' keys, in that order, starting
70
+ // at key 'first'
71
+ fn color_grade_luma_saturation(
72
+ ap1: vec3<f32>,
73
+ counts: vec4<u32>,
74
+ keys: ptr<storage, array<${ANIMATION_CURVE_KEYFRAME_STRUCT.wgsl_ref}>, read>,
75
+ first: u32,
76
+ ) -> vec3<f32> {
77
+ let luma = dot(ap1, AP1_LUMINANCE);
78
+
79
+ // a colour with no luma has no tonal place to read the curves at, and nothing to saturate
80
+ if (all(counts == vec4<u32>(0u)) || luma <= 0.0) {
81
+ return ap1;
82
+ }
83
+
84
+ let stops = log2(luma / 0.18);
85
+
86
+ var saturation = vec3<f32>(1.0);
87
+
88
+ if (counts.x > 0u) {
89
+ saturation.x = animation_curve_evaluate_keys(keys, first, counts.x, stops);
90
+ }
91
+ if (counts.y > 0u) {
92
+ saturation.y = animation_curve_evaluate_keys(keys, first + counts.x, counts.y, stops);
93
+ }
94
+ if (counts.z > 0u) {
95
+ saturation.z = animation_curve_evaluate_keys(keys, first + counts.x + counts.y, counts.z, stops);
96
+ }
97
+ if (counts.w > 0u) {
98
+ saturation *= animation_curve_evaluate_keys(keys, first + counts.x + counts.y + counts.z, counts.w, stops);
99
+ }
100
+
101
+ return max(vec3<f32>(0.0), mix(vec3<f32>(luma), ap1, saturation));
102
+ }
103
+
104
+ // each AP1 channel multiplied by its gain, and every channel by 2^exposure, read at the luma's stops from middle grey;
105
+ // counts are the red, green and blue gain curves' keys and the exposure curve's, in that order, starting at key 'first'
106
+ fn color_grade_luma_gain(
107
+ ap1: vec3<f32>,
108
+ counts: vec4<u32>,
109
+ keys: ptr<storage, array<${ANIMATION_CURVE_KEYFRAME_STRUCT.wgsl_ref}>, read>,
110
+ first: u32,
111
+ ) -> vec3<f32> {
112
+ let luma = dot(ap1, AP1_LUMINANCE);
113
+
114
+ // a colour with no luma has no tonal place to read the curves at
115
+ if (all(counts == vec4<u32>(0u)) || luma <= 0.0) {
116
+ return ap1;
117
+ }
118
+
119
+ let stops = log2(luma / 0.18);
120
+
121
+ var gain = vec3<f32>(1.0);
122
+ var exposure = 0.0;
123
+
124
+ if (counts.x > 0u) {
125
+ gain.x = animation_curve_evaluate_keys(keys, first, counts.x, stops);
126
+ }
127
+ if (counts.y > 0u) {
128
+ gain.y = animation_curve_evaluate_keys(keys, first + counts.x, counts.y, stops);
129
+ }
130
+ if (counts.z > 0u) {
131
+ gain.z = animation_curve_evaluate_keys(keys, first + counts.x + counts.y, counts.z, stops);
132
+ }
133
+ if (counts.w > 0u) {
134
+ exposure = animation_curve_evaluate_keys(keys, first + counts.x + counts.y + counts.z, counts.w, stops);
135
+ }
136
+
137
+ return max(vec3<f32>(0.0), ap1 * gain * exp2(exposure));
138
+ }
139
+
68
140
  // hue shift, chroma scale and lightness against OkLCh hue; counts.xyz are the three curves' keys, in that order
69
141
  fn color_grade_hue_curves(
70
142
  color: vec3<f32>,
@@ -123,7 +195,16 @@ fn color_grade(
123
195
  ) -> vec3<f32> {
124
196
  let white_balance = mat3x3<f32>(grade.white_balance_c0.xyz, grade.white_balance_c1.xyz, grade.white_balance_c2.xyz);
125
197
 
126
- let ap1 = color_grade_primaries(CRM_FROM_REC709_TO_AP1 * (white_balance * scene), grade);
198
+ let primaries = color_grade_primaries(CRM_FROM_REC709_TO_AP1 * (white_balance * scene), grade);
199
+
200
+ // the luma curves' keys follow the hue curves': saturation's, then gain's
201
+ let counts = grade.curve_counts;
202
+ let saturation_counts = grade.luma_curve_counts;
203
+ let hue_keys = counts.x + counts.y + counts.z;
204
+ let saturation_keys = saturation_counts.x + saturation_counts.y + saturation_counts.z + saturation_counts.w;
205
+
206
+ let gained = color_grade_luma_gain(primaries, grade.luma_gain_counts, keys, hue_keys + saturation_keys);
207
+ let ap1 = color_grade_luma_saturation(gained, saturation_counts, keys, hue_keys);
127
208
 
128
209
  let graded = max(CRM_FROM_AP1_TO_REC709 * ap1, vec3<f32>(0.0));
129
210
 
@@ -0,0 +1,65 @@
1
+ /**
2
+ * How much of the eye's response is the rods', at an adaptation: 1 at and below {@link PURKINJE_MESOPIC_MIN_NITS}, 0 at
3
+ * and above {@link PURKINJE_MESOPIC_MAX_NITS}, linear in log luminance in between. The share `apply_purkinje_shift`
4
+ * blends by, for code that needs it on the CPU.
5
+ *
6
+ * @param {number} adaptation_luminance cd/m²
7
+ * @returns {number} 0 to 1
8
+ */
9
+ export function purkinje_rod_share(adaptation_luminance: number): number;
10
+ /**
11
+ * Rec.709 photopic luminance weights: what the cones see of a linear Rec.709 colour.
12
+ * @type {ReadonlyArray<number>}
13
+ */
14
+ export const PURKINJE_REC709_PHOTOPIC: ReadonlyArray<number>;
15
+ /**
16
+ * Rec.709 scotopic luminance weights: what the rods see of a linear Rec.709 colour. They sum to 1, so a grey has the
17
+ * same scotopic luminance as photopic.
18
+ * @type {ReadonlyArray<number>}
19
+ */
20
+ export const PURKINJE_REC709_SCOTOPIC: ReadonlyArray<number>;
21
+ /**
22
+ * "Rod blue": the colour the scotopic signal is carried by. Artistic: (0.3, 0.5, 0.9) mimics the sensation of the
23
+ * rods' 507 nm peak.
24
+ * @type {ReadonlyArray<number>}
25
+ */
26
+ export const PURKINJE_NIGHT_TINT: ReadonlyArray<number>;
27
+ /**
28
+ * CIE 191's mesopic range, cd/m²: rods alone below the lower bound (scotopic), cones alone above the upper (photopic).
29
+ */
30
+ export const PURKINJE_MESOPIC_MIN_NITS: 0.005;
31
+ export const PURKINJE_MESOPIC_MAX_NITS: 5;
32
+ /**
33
+ * Purkinje shift.
34
+ * Human vision system shifts color perception away from red in night-time conditions.
35
+ *
36
+ * ```wgsl
37
+ * fn apply_purkinje_shift(color_rec709: vec3<f32>, adaptation_luminance: f32) -> vec3<f32>
38
+ * fn purkinje_rod_share(adaptation_luminance: f32) -> f32
39
+ * ```
40
+ *
41
+ * `color_rec709` is scene-linear Rec.709; `adaptation_luminance` is the luminance the eye is adapted to, cd/m². Above
42
+ * 5 cd/m² the colour is returned as it is; below 0.005 cd/m² it is the rod response alone, its scotopic luminance
43
+ * carried by a blue tint; in between the two blend linearly in log luminance, by `purkinje_rod_share`. The shift is
44
+ * linear in the colour, so it gives the same result ahead of the exposure as after it.
45
+ *
46
+ * The constants are the module's exports ({@link PURKINJE_REC709_SCOTOPIC} and the rest), and
47
+ * {@link purkinje_rod_share} is the share on the CPU.
48
+ *
49
+ * NOTE: this should go before the tonemapper.
50
+ *
51
+ * Notes on deriving it from the rod and cone responses: PURKINJE_PHYSICAL_DERIVATION_NOTES.md, beside this file.
52
+ *
53
+ * @see J. Patry, "Real-Time Samurai Cinema: Lighting, Atmosphere, and Tonemapping in Ghost of Tsushima", Advances in
54
+ * Real-Time Rendering in Games, SIGGRAPH 2021 — the primary reference.
55
+ * https://www.advances.realtimerendering.com/s2021/jpatry_advances2021/index.html
56
+ * @see A. G. Kirk, J. F. O'Brien, "Perceptually based tone mapping for low-light conditions", ACM TOG 30(4), 2011.
57
+ * https://www2.eecs.berkeley.edu/Pubs/TechRpts/2011/EECS-2011-91.pdf
58
+ * @see D. Cao, J. Pokorny, V. C. Smith, A. J. Zele, "Rod contributions to color perception: linear with rod
59
+ * contrast", Vision Research 48(26), 2008.
60
+ *
61
+ * @type {CodeChunk}
62
+ */
63
+ export const chunk_apply_purkinje_shift: CodeChunk;
64
+ import { CodeChunk } from "../../../../compiler/CodeChunk.js";
65
+ //# sourceMappingURL=chunk_apply_purkinje_shift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk_apply_purkinje_shift.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/shade/renderer/shader/chunk/color/tonemap/purkinje/chunk_apply_purkinje_shift.js"],"names":[],"mappings":"AAiCA;;;;;;;GAOG;AACH,yDAHW,MAAM,GACJ,MAAM,CAQlB;AA7CD;;;GAGG;AACH,uCAFU,cAAc,MAAM,CAAC,CAEiD;AAEhF;;;;GAIG;AACH,uCAFU,cAAc,MAAM,CAAC,CAEiD;AAEhF;;;;GAIG;AACH,kCAFU,cAAc,MAAM,CAAC,CAEmC;AAElE;;GAEG;AACH,8CAA+C;AAC/C,0CAA2C;AAwC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,yCAFU,SAAS,CAwCjB;0BAvIwB,mCAAmC"}