rayzee 11.3.0 → 11.4.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 (35) hide show
  1. package/README.md +33 -5
  2. package/dist/{ArchiveImporter-D735xX-4.js → ArchiveImporter-Cgyx5AF7.js} +2 -2
  3. package/dist/{BidirectionalIntegrator-M89c0AlP.js → BidirectionalIntegrator-BKF-cwH2.js} +2 -2
  4. package/dist/{NeuralRetouch-DiBH63kz.js → NeuralRetouch-C3Q2XqbF.js} +4 -3
  5. package/dist/{NeuralSuperRes-CZNQP13W.js → NeuralSuperRes-DV5TtURP.js} +11 -5
  6. package/dist/{PathTracer-BXCCVrPK.js → PathTracer-D3MvEaZK.js} +19 -12
  7. package/dist/{PhysicalSky-DqmaS0lb.js → PhysicalSky-CY4jiz6a.js} +1 -1
  8. package/dist/{RayzeeRenderer-DUSMFhmc.js → RayzeeRenderer-D23m6NqG.js} +313 -281
  9. package/dist/{ToneMapGPU-BOjpul7A.js → ToneMapGPU-CTDBvmmS.js} +31 -22
  10. package/dist/addons/archives.es.js +1 -1
  11. package/dist/addons/bidirectional.es.js +1 -1
  12. package/dist/addons/physical-sky.es.js +1 -1
  13. package/dist/rayzee-core.es.js +3 -3
  14. package/dist/rayzee.es.js +1343 -858
  15. package/dist/rayzee.umd.js +124 -113
  16. package/dist/{sceneCachePolicy-BAib5fCp.js → sceneCachePolicy-AKOEu2lf.js} +1 -1
  17. package/package.json +1 -1
  18. package/src/EngineDefaults.js +2 -2
  19. package/src/Passes/AIUpscaler.js +2 -0
  20. package/src/PathTracerApp.js +122 -16
  21. package/src/Pipeline/CompletionTracker.js +7 -3
  22. package/src/Processor/ToneMapCPU.js +16 -4
  23. package/src/Processor/ToneMapGPU.js +30 -14
  24. package/src/RayzeeRenderer.js +54 -7
  25. package/src/RenderSettings.js +4 -2
  26. package/src/Stages/AutoExposure.js +484 -494
  27. package/src/Stages/Compositor.js +27 -6
  28. package/src/Stages/LocalExposure.js +546 -0
  29. package/src/Stages/PathTracer.js +6 -4
  30. package/src/Stages/PathTracerStage.js +9 -7
  31. package/src/index.js +1 -0
  32. package/src/managers/DenoisingManager.js +11 -15
  33. package/src/managers/VideoRenderManager.js +30 -4
  34. package/src/neural/NeuralRetouch.js +1 -0
  35. package/src/neural/NeuralSuperRes.js +6 -1
@@ -104,9 +104,10 @@ export class DenoisingManager extends EventDispatcher {
104
104
  * @param {import('../Pipeline/RenderPipeline.js').RenderPipeline} params.pipeline
105
105
  * @param {Function} params.getExposure - () => current exposure value
106
106
  * @param {Function} params.getSaturation - () => current saturation value
107
+ * @param {Function} [params.getDisplayGain] - () => the shown picture's per-pixel gain (PackedToneMapper `gain`)
107
108
  * @param {import('../EngineIssues.js').IssueLog} [params.issues]
108
109
  */
109
- constructor( { renderer, mainCanvas, stages, pipeline, getExposure, getSaturation, issues = null } ) {
110
+ constructor( { renderer, mainCanvas, stages, pipeline, getExposure, getSaturation, getDisplayGain = null, issues = null } ) {
110
111
 
111
112
  super();
112
113
 
@@ -121,6 +122,7 @@ export class DenoisingManager extends EventDispatcher {
121
122
 
122
123
  this._getExposure = getExposure;
123
124
  this._getSaturation = getSaturation;
125
+ this._getDisplayGain = getDisplayGain;
124
126
 
125
127
  this.denoiser = null;
126
128
  this.upscaler = null;
@@ -409,6 +411,7 @@ export class DenoisingManager extends EventDispatcher {
409
411
  getExposure: () => this._getEffectiveExposure(),
410
412
  getToneMapping: () => this._getToneMapping(),
411
413
  getSaturation: () => this._getSaturation(),
414
+ getDisplayGain: () => this._getDisplayGain?.() ?? null,
412
415
  } );
413
416
 
414
417
  this.upscaler.enabled = DENOISER_DEFAULTS.enableUpscaler || false;
@@ -546,21 +549,11 @@ export class DenoisingManager extends EventDispatcher {
546
549
 
547
550
  /**
548
551
  * @param {boolean} enabled
549
- * @param {number} manualExposure - Restored to renderer.toneMappingExposure when disabling.
552
+ * @param {number} manualExposure - the compensation while on, the exposure again once off
550
553
  */
551
554
  setAutoExposureEnabled( enabled, manualExposure ) {
552
555
 
553
- const s = this._stages;
554
- if ( ! s.autoExposure ) return;
555
-
556
- s.autoExposure.enabled = enabled;
557
-
558
- // AutoExposure overwrites renderer.toneMappingExposure each frame; restore manual on disable.
559
- if ( ! enabled && this.renderer ) {
560
-
561
- this.renderer.toneMappingExposure = manualExposure;
562
-
563
- }
556
+ this._stages.autoExposure?.setEnabled( enabled, manualExposure );
564
557
 
565
558
  }
566
559
 
@@ -1246,6 +1239,7 @@ export class DenoisingManager extends EventDispatcher {
1246
1239
  exposure: this._getEffectiveExposure(),
1247
1240
  toneMapping: this._getToneMapping(),
1248
1241
  saturation: this._getSaturation?.() ?? 1,
1242
+ gain: this._getDisplayGain?.() ?? null,
1249
1243
  };
1250
1244
 
1251
1245
  // Scene-referred throughout. Exposure reaches the detail pass through its own `paper_white`
@@ -1561,6 +1555,9 @@ export class DenoisingManager extends EventDispatcher {
1561
1555
  this._onReset = null;
1562
1556
  this._onPostProcessRefresh = null;
1563
1557
  this._onDisplayRefresh = null;
1558
+ this._getExposure = null;
1559
+ this._getSaturation = null;
1560
+ this._getDisplayGain = null;
1564
1561
  this._issues = null;
1565
1562
 
1566
1563
  if ( this.upscalerCanvas?.parentNode ) {
@@ -1764,13 +1761,12 @@ export class DenoisingManager extends EventDispatcher {
1764
1761
  // ── Convenience (match DenoisingAPI names with reset) ────────
1765
1762
 
1766
1763
  /**
1767
- * Enables or disables auto-exposure (convenience wrapper).
1764
+ * Enables or disables auto-exposure. A finished render is metered as it stands, not restarted.
1768
1765
  * @param {boolean} enabled
1769
1766
  */
1770
1767
  setAutoExposure( enabled ) {
1771
1768
 
1772
1769
  this.setAutoExposureEnabled( enabled, this._getExposure() );
1773
- this._onReset?.();
1774
1770
 
1775
1771
  }
1776
1772
 
@@ -87,8 +87,13 @@ export class VideoRenderManager {
87
87
  // Configure for high-quality offline rendering
88
88
  app.configureForMode( 'production' );
89
89
 
90
- // Override samples per frame
91
- app.settings.setMany( { maxSamples: samplesPerFrame }, { silent: true } );
90
+ // Auto exposure adapts in video time, once a frame, not to each pass's wall clock.
91
+ const autoExposure = app.stages.autoExposure?.enabled ? app.stages.autoExposure : null;
92
+ if ( autoExposure ) autoExposure.instant = false;
93
+
94
+ // Override samples per frame. 'frames': this loop never checks the deadline, so a 'timeOnly'
95
+ // ceiling of Infinity would never finish a frame.
96
+ app.settings.setMany( { maxSamples: samplesPerFrame, renderLimitMode: 'frames' }, { silent: true, reset: false } );
92
97
  app.stages.pathTracer?.updateCompletionThreshold?.();
93
98
 
94
99
  // Disable camera controls during render
@@ -131,11 +136,23 @@ export class VideoRenderManager {
131
136
  // kill it immediately so it doesn't race with our manual render loop
132
137
  app.stopAnimation();
133
138
 
139
+ // Before accumulating: the stage meters as it goes, and a reading of this frame's view filed under the last
140
+ // one read as a scene change, moving the room the whole way each frame.
141
+ if ( autoExposure ) app._noteExposureView();
142
+
134
143
  // 3. Accumulate samples until convergence
135
144
  await this._accumulateFrame( app );
136
145
 
137
146
  if ( this._cancelled ) break;
138
147
 
148
+ if ( autoExposure ) {
149
+
150
+ await autoExposure.meter( app.pipeline.context );
151
+ autoExposure.advance( i === Math.max( 0, startFrame ) ? Infinity : frameDuration );
152
+ app._presentDisplay();
153
+
154
+ }
155
+
139
156
  // 4. Denoise if enabled
140
157
  if ( enableOIDN && app.denoisingManager?.finalDenoise ) {
141
158
 
@@ -298,17 +315,19 @@ export class VideoRenderManager {
298
315
 
299
316
  const app = this._app;
300
317
  const effective = app.settings.getEffective();
301
- const keys = Object.keys( modePresetSettings( PRODUCTION_RENDER_CONFIG ) );
318
+ const keys = [ ...Object.keys( modePresetSettings( PRODUCTION_RENDER_CONFIG ) ), 'renderLimitMode' ];
302
319
  if ( view ) keys.push( 'focusDistance' );
303
320
 
304
321
  return {
305
322
  view: view ? app.cameraManager.captureView() : null,
306
- // Everything configureForMode( 'production' ) overwrites, with who set it.
323
+ // Everything configureForMode( 'production' ) and the export overwrite, with who set it.
307
324
  settings: keys.map( key => [ key, effective[ key ] ] ),
308
325
  renderMode: app.stages.pathTracer?.renderMode?.value,
309
326
  controlsEnabled: app.cameraManager.controls?.enabled,
310
327
  oidnEnabled: app.denoisingManager?.finalDenoise,
311
328
  oidnQuality: app.denoisingManager?.oidnQuality,
329
+ instantExposure: app.stages.autoExposure?.instant,
330
+ holdExposureSamples: app.stages.autoExposure?.holdSamples,
312
331
  wasPlaying: app.animationManager?.isPlaying,
313
332
  pauseRendering: app.pauseRendering,
314
333
  };
@@ -347,6 +366,13 @@ export class VideoRenderManager {
347
366
  }
348
367
 
349
368
  app.pauseRendering = state.pauseRendering ?? false;
369
+ if ( app.stages.autoExposure ) {
370
+
371
+ app.stages.autoExposure.instant = state.instantExposure ?? false;
372
+ // configureForMode( 'production' ) set it, and nothing switches the mode back: left on, it froze the exposure while orbiting.
373
+ app.stages.autoExposure.holdSamples = state.holdExposureSamples ?? 0;
374
+
375
+ }
350
376
 
351
377
  if ( state.view ) app.cameraManager.applyPose( state.view );
352
378
 
@@ -389,6 +389,7 @@ export class NeuralRetouch {
389
389
  exposure: tone.exposure ?? 1,
390
390
  toneMapping: tone.toneMapping ?? 0,
391
391
  saturation: tone.saturation ?? 1,
392
+ gain: tone.gain,
392
393
  } );
393
394
 
394
395
  return { rgba8, width: w, height: h };
@@ -373,6 +373,7 @@ export class NeuralSuperRes {
373
373
  exposure: tone.exposure ?? 1,
374
374
  toneMapping: tone.toneMapping ?? 0,
375
375
  saturation: tone.saturation ?? 1,
376
+ gain: tone.gain,
376
377
  // The network emits its picture vertically flipped.
377
378
  flipY: true,
378
379
  } );
@@ -482,6 +483,7 @@ export async function renderUpscaled( app, {
482
483
  width: pathTracer?.width,
483
484
  height: pathTracer?.height,
484
485
  maxSamples: app.settings?.get?.( 'maxSamples' ),
486
+ renderLimitMode: app.settings?.get?.( 'renderLimitMode' ),
485
487
  finalDenoise: app.denoisingManager?.finalDenoise,
486
488
  paused: app.pauseRendering,
487
489
  };
@@ -512,7 +514,8 @@ export async function renderUpscaled( app, {
512
514
  await new Promise( r => setTimeout( r, 300 ) );
513
515
 
514
516
  app.denoisingManager.applyOIDNEnabled( true );
515
- app.settings.set( 'maxSamples', samples );
517
+ // 'frames': under 'timeOnly' the deadline, not `samples`, would end the render.
518
+ app.settings.setMany( { maxSamples: samples, renderLimitMode: 'frames' }, { reset: false } );
516
519
  const done = waitForDenoise( app, samples, timeoutMs );
517
520
  app.reset();
518
521
  await done;
@@ -529,6 +532,7 @@ export async function renderUpscaled( app, {
529
532
  exposure: app.renderer.toneMappingExposure,
530
533
  toneMapping: app.renderer.toneMapping,
531
534
  saturation: app.settings.get( 'saturation' ) ?? 1,
535
+ gain: app._displayGain?.() ?? null,
532
536
  };
533
537
 
534
538
  const { rgba8, width, height, ms, toneMs } = await sr.upscaleToRGBA8( source, tone );
@@ -550,6 +554,7 @@ export async function renderUpscaled( app, {
550
554
 
551
555
  if ( restore.width && restore.height ) app.setCanvasSize( restore.width, restore.height );
552
556
  if ( restore.maxSamples !== undefined ) app.settings?.set?.( 'maxSamples', restore.maxSamples );
557
+ if ( restore.renderLimitMode !== undefined ) app.settings?.set?.( 'renderLimitMode', restore.renderLimitMode, { reset: false } );
553
558
  if ( restore.finalDenoise !== undefined ) app.denoisingManager?.applyOIDNEnabled( restore.finalDenoise );
554
559
  app.pauseRendering = restore.paused;
555
560