rayzee 9.5.0 → 11.0.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 (78) hide show
  1. package/README.md +22 -20
  2. package/dist/{ArchiveImporter-xuUJiklP.js → ArchiveImporter-BRQvLggb.js} +12 -13
  3. package/dist/{BidirectionalIntegrator-C5PKjJoT.js → BidirectionalIntegrator-9lZQxFVH.js} +87 -87
  4. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  5. package/dist/{ColorManagement-CheBCPaW.js → ColorManagement-oeEk7oSu.js} +171 -171
  6. package/dist/{NeuralRetouch-CW11yLZ_.js → NeuralRetouch-BCXylThH.js} +3 -3
  7. package/dist/{NeuralSuperRes-BekaecYg.js → NeuralSuperRes-sZAeS142.js} +3 -3
  8. package/dist/{PathTracer-Ccl9KOuF.js → PathTracer-CQPW06Mm.js} +2911 -2669
  9. package/dist/{PhysicalSky-Dv2PMFGW.js → PhysicalSky-BvqCw_dy.js} +2 -2
  10. package/dist/{RayzeeRenderer-BQxnzCTD.js → RayzeeRenderer-COpwotpU.js} +338 -329
  11. package/dist/{SolarLimb-ken-JbDK.js → SolarLimb-CPaeZVwz.js} +10 -10
  12. package/dist/{ToneMapGPU-BHi5JzIq.js → ToneMapGPU-CjU3osDd.js} +1 -1
  13. package/dist/{ViewTransforms-D0rTLBsX.js → ViewTransforms-CYN7bq2w.js} +2 -2
  14. package/dist/addons/archives.es.js +1 -1
  15. package/dist/addons/bidirectional.es.js +1 -1
  16. package/dist/addons/color.es.js +1 -1
  17. package/dist/addons/physical-sky.es.js +1 -1
  18. package/dist/rayzee-core.es.js +11 -11
  19. package/dist/rayzee.es.js +734 -599
  20. package/dist/rayzee.umd.js +102 -102
  21. package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
  22. package/package.json +1 -1
  23. package/src/Color/ViewTransforms.js +5 -1
  24. package/src/EngineDefaults.js +59 -1032
  25. package/src/Headless.js +6 -5
  26. package/src/Passes/OIDNTemporalHistory.js +1 -1
  27. package/src/PathTracerApp.js +5 -3
  28. package/src/Processor/AssetLoader.js +6 -5
  29. package/src/Processor/BVHBuilder.js +10 -31
  30. package/src/Processor/BVHLeafFold.js +4 -6
  31. package/src/Processor/BVHRefitter.js +3 -46
  32. package/src/Processor/BufferLayout.js +291 -0
  33. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  34. package/src/Processor/GeometryExtractor.js +3 -3
  35. package/src/Processor/HostMemory.js +7 -1
  36. package/src/Processor/InstanceTable.js +1 -1
  37. package/src/Processor/MaterialPacking.js +2 -1
  38. package/src/Processor/PBRT/PBRTSceneBuilder.js +1 -1
  39. package/src/Processor/QueueManager.js +28 -3
  40. package/src/Processor/SceneProcessor.js +4 -2
  41. package/src/Processor/SplitBLAS.js +1 -1
  42. package/src/Processor/StorageTexturePool.js +22 -1
  43. package/src/Processor/TLASBuilder.js +1 -3
  44. package/src/Processor/TextureBuckets.js +237 -0
  45. package/src/Processor/TextureCreator.js +10 -1
  46. package/src/Processor/Workers/TexturesWorker.js +5 -6
  47. package/src/RayzeeRenderer.js +29 -32
  48. package/src/RenderSettings.js +26 -28
  49. package/src/Stages/ASVGF.js +8 -7
  50. package/src/Stages/AutoExposure.js +14 -5
  51. package/src/Stages/BilateralFilter.js +2 -1
  52. package/src/Stages/DenoiserSettings.js +182 -0
  53. package/src/Stages/EdgeFilter.js +2 -1
  54. package/src/Stages/MotionVector.js +1 -1
  55. package/src/Stages/NRD.js +2 -3
  56. package/src/Stages/NormalDepth.js +3 -2
  57. package/src/Stages/PathTracer.js +72 -4
  58. package/src/Stages/PathTracerStage.js +2 -1
  59. package/src/Stages/Variance.js +1 -1
  60. package/src/TSL/BVHTraversal.js +4 -1
  61. package/src/TSL/Common.js +1 -1
  62. package/src/TSL/EmissiveSampling.js +1 -1
  63. package/src/TSL/LightVisibility.js +111 -0
  64. package/src/TSL/LightsSampling.js +151 -15
  65. package/src/TSL/Random.js +1 -1
  66. package/src/TSL/ShadeKernel.js +88 -3
  67. package/src/TSL/TextureSampling.js +1 -1
  68. package/src/core.js +5 -9
  69. package/src/index.js +10 -21
  70. package/src/integrators/BidirectionalIntegrator.js +2 -1
  71. package/src/managers/CameraManager.js +17 -7
  72. package/src/managers/DenoisingManager.js +14 -10
  73. package/src/managers/EnvironmentManager.js +32 -13
  74. package/src/managers/MaterialDataManager.js +3 -3
  75. package/src/managers/UniformManager.js +2 -2
  76. package/dist/EngineDefaults-DH3bI5t-.js +0 -844
  77. package/dist/sceneCachePolicy-BmwYOYDu.js +0 -183
  78. /package/dist/{Logger-BnHghprG.js → Logger-dDDGj9Av.js} +0 -0
@@ -9,6 +9,7 @@ import { toPortable, fromPortable } from './SceneState/portable.js';
9
9
  *
10
10
  * - `uniform` → forwarded to PathTracer.setUniform(uniform, value)
11
11
  * - `handler` → calls a named handler method for multi-stage settings
12
+ * - neither → stored only; whoever needs it reads it
12
13
  * - `reset` → whether to reset accumulation after the change (default true)
13
14
  *
14
15
  * Other layers add their own keys with {@link RenderSettings#define}.
@@ -77,14 +78,16 @@ const SETTING_ROUTES = {
77
78
  renderTimeLimit: { handler: 'handleRenderTimeLimit', reset: false },
78
79
  renderMode: { handler: 'handleRenderMode' },
79
80
  integrator: { handler: 'handleIntegrator', reset: true },
81
+ shadowRays: { handler: 'handleShadowRays', reset: true },
80
82
  environmentRotation: { handler: 'handleEnvironmentRotation' },
83
+ // Read when the kernels next build.
84
+ wavefrontSortMaterials: { handler: 'handleWavefrontSortMaterials', reset: false },
85
+ // No target: read when a model loads (setMaxTextureSize() also reprocesses the scene now).
86
+ maxTextureSize: { reset: false },
87
+ areaLightIntensityScale: { reset: false },
81
88
 
82
89
  };
83
90
 
84
- /**
85
- * Default keys to extract from ENGINE_DEFAULTS for initializing the values map.
86
- * Maps ENGINE_DEFAULTS key → RenderSettings key when they differ.
87
- */
88
91
  /** Provenance tags for getEffective(). Add-only — hosts branch on them. */
89
92
  export const SETTING_SOURCE = Object.freeze( {
90
93
  DEFAULT: 'default',
@@ -93,11 +96,6 @@ export const SETTING_SOURCE = Object.freeze( {
93
96
  MODE_PRESET: 'mode-preset',
94
97
  } );
95
98
 
96
- const DEFAULTS_KEY_MAP = {
97
- bounces: 'maxBounces',
98
- debugMode: 'visMode',
99
- };
100
-
101
99
  /**
102
100
  * Single source of truth for all render parameters.
103
101
  *
@@ -168,19 +166,21 @@ export class RenderSettings extends EventDispatcher {
168
166
 
169
167
  /**
170
168
  * Adds a setting of another layer — the viewer's, or an add-on's — with the same provenance, events, session
171
- * saving and reset as the core's. Its default comes from the defaults this store was built with.
169
+ * saving and reset as the core's. The layer brings its own default.
172
170
  * @param {string} key
173
171
  * @param {Object} route
172
+ * @param {*} [route.default] - the value until someone sets one; else the one this store was built with
174
173
  * @param {function(*, *): void} route.apply - called with ( value, prev ) on every change, and by applyAll()
175
174
  * @param {boolean} [route.reset=true] - reset accumulation after a change
176
175
  */
177
- define( key, { apply, reset = true } ) {
176
+ define( key, { default: initial, apply, reset = true } ) {
178
177
 
179
178
  if ( this._routes.has( key ) ) throw new Error( `setting "${key}" is already defined` );
180
179
  this._routes.set( key, { apply, reset } );
181
- if ( ! this._values.has( key ) && key in this._defaults ) {
180
+ const value = initial !== undefined ? initial : this._defaults[ key ];
181
+ if ( ! this._values.has( key ) && value !== undefined ) {
182
182
 
183
- this._values.set( key, this._defaults[ key ] );
183
+ this._values.set( key, value );
184
184
  this._sources.set( key, SETTING_SOURCE.DEFAULT );
185
185
 
186
186
  }
@@ -289,12 +289,24 @@ export class RenderSettings extends EventDispatcher {
289
289
 
290
290
  },
291
291
 
292
+ handleShadowRays: ( value ) => {
293
+
294
+ stages.pathTracer?.setShadowRays?.( value );
295
+
296
+ },
297
+
292
298
  handleEnvironmentRotation: ( value ) => {
293
299
 
294
300
  stages.pathTracer?.environment.setEnvironmentRotation( value );
295
301
 
296
302
  },
297
303
 
304
+ handleWavefrontSortMaterials: ( value ) => {
305
+
306
+ if ( stages.pathTracer ) stages.pathTracer.sortMaterials = value;
307
+
308
+ },
309
+
298
310
  handleInteractionModeEnabled: ( value ) => {
299
311
 
300
312
  stages.pathTracer?.setInteractionModeEnabled( value );
@@ -503,7 +515,7 @@ export class RenderSettings extends EventDispatcher {
503
515
 
504
516
  } else {
505
517
 
506
- route.apply( value, prev );
518
+ route.apply?.( value, prev );
507
519
 
508
520
  }
509
521
 
@@ -511,11 +523,9 @@ export class RenderSettings extends EventDispatcher {
511
523
 
512
524
  /**
513
525
  * Populates the values map from ENGINE_DEFAULTS.
514
- * Handles key renames via DEFAULTS_KEY_MAP.
515
526
  */
516
527
  _initDefaults( defaults ) {
517
528
 
518
- // Keys that exist in both SETTING_ROUTES and ENGINE_DEFAULTS (direct match)
519
529
  for ( const key of Object.keys( SETTING_ROUTES ) ) {
520
530
 
521
531
  if ( key in defaults ) {
@@ -527,18 +537,6 @@ export class RenderSettings extends EventDispatcher {
527
537
 
528
538
  }
529
539
 
530
- // Keys where ENGINE_DEFAULTS uses a different name
531
- for ( const [ defaultsKey, settingsKey ] of Object.entries( DEFAULTS_KEY_MAP ) ) {
532
-
533
- if ( defaultsKey in defaults ) {
534
-
535
- this._values.set( settingsKey, defaults[ defaultsKey ] );
536
- this._sources.set( settingsKey, SETTING_SOURCE.DEFAULT );
537
-
538
- }
539
-
540
- }
541
-
542
540
  }
543
541
 
544
542
  }
@@ -5,7 +5,8 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
5
5
  import { HalfFloatType, FloatType, RGBAFormat, NearestFilter, LinearFilter, Box2, Vector2 } from 'three';
6
6
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
7
7
  import { luminance } from '../TSL/Common.js';
8
- import { ALBEDO_EPS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
8
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
9
+ import { ALBEDO_EPS, ASVGF_QUALITY_PRESETS } from './DenoiserSettings.js';
9
10
 
10
11
  // Replace NaN/±Inf with a bounded value so one firefly can't permanently poison the
11
12
  // temporal EMA (mix() propagates NaN forever). Per-channel: NaN (x!=x) → 0, ±Inf → [0,1e7].
@@ -39,14 +40,14 @@ export class ASVGF extends RenderStage {
39
40
 
40
41
  this.renderer = renderer;
41
42
 
42
- this.temporalAlpha = uniform( options.temporalAlpha ?? 0.0 );
43
- // > 0 is required for firefly rejection, not just anti-lag — see ENGINE_DEFAULTS.
44
- this.gradientStrength = uniform( options.gradientStrength ?? 1.0 );
43
+ this.temporalAlpha = uniform( options.temporalAlpha ?? ASVGF_QUALITY_PRESETS.medium.temporalAlpha );
44
+ // > 0 is required for firefly rejection, not just anti-lag — see DENOISER_DEFAULTS.
45
+ this.gradientStrength = uniform( options.gradientStrength ?? ASVGF_QUALITY_PRESETS.medium.gradientStrength );
45
46
  // σ multiplier for the per-pixel noise floor (NRD luminanceSigmaScale ≈ 2).
46
- this.gradientSigmaScale = uniform( options.gradientSigmaScale ?? 2.0 );
47
+ this.gradientSigmaScale = uniform( options.gradientSigmaScale ?? ASVGF_QUALITY_PRESETS.medium.gradientSigmaScale );
47
48
  // Secondary relative floor on the normalised gradient (0 = rely on σ alone).
48
- this.gradientNoiseFloor = uniform( options.gradientNoiseFloor ?? 0.0 );
49
- this.maxAccumFrames = uniform( options.maxAccumFrames ?? 32.0 );
49
+ this.gradientNoiseFloor = uniform( options.gradientNoiseFloor ?? ASVGF_QUALITY_PRESETS.medium.gradientNoiseFloor );
50
+ this.maxAccumFrames = uniform( options.maxAccumFrames ?? ASVGF_QUALITY_PRESETS.medium.maxAccumFrames );
50
51
 
51
52
  this.resW = uniform( options.width || 1 );
52
53
  this.resH = uniform( options.height || 1 );
@@ -7,6 +7,15 @@ import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
7
7
  import { luminance } from '../TSL/Common.js';
8
8
 
9
9
  // ── Histogram constants ────────────────────────────────────
10
+ export const AUTO_EXPOSURE_DEFAULTS = {
11
+ autoExposure: false,
12
+ autoExposureKeyValue: 0.18,
13
+ autoExposureMinExposure: 0.1,
14
+ autoExposureMaxExposure: 20.0,
15
+ autoExposureAdaptSpeedBright: 3.0,
16
+ autoExposureAdaptSpeedDark: 0.5,
17
+ };
18
+
10
19
  const NUM_BINS = 256;
11
20
  const MIN_LOG_LUM = - 8.0; // ln(~0.00034) — very dark
12
21
  const MAX_LOG_LUM = 6.0; // ln(~403) — bright specular
@@ -103,11 +112,11 @@ export class AutoExposure extends RenderStage {
103
112
 
104
113
  // ── Adaptation uniforms ──────────────────────────
105
114
 
106
- this.keyValueU = uniform( options.keyValue ?? 0.18 );
107
- this.minExposureU = uniform( options.minExposure ?? 0.1 );
108
- this.maxExposureU = uniform( options.maxExposure ?? 20.0 );
109
- this.adaptSpeedBrightU = uniform( options.adaptSpeedBright ?? 3.0 );
110
- this.adaptSpeedDarkU = uniform( options.adaptSpeedDark ?? 0.5 );
115
+ this.keyValueU = uniform( options.keyValue ?? AUTO_EXPOSURE_DEFAULTS.autoExposureKeyValue );
116
+ this.minExposureU = uniform( options.minExposure ?? AUTO_EXPOSURE_DEFAULTS.autoExposureMinExposure );
117
+ this.maxExposureU = uniform( options.maxExposure ?? AUTO_EXPOSURE_DEFAULTS.autoExposureMaxExposure );
118
+ this.adaptSpeedBrightU = uniform( options.adaptSpeedBright ?? AUTO_EXPOSURE_DEFAULTS.autoExposureAdaptSpeedBright );
119
+ this.adaptSpeedDarkU = uniform( options.adaptSpeedDark ?? AUTO_EXPOSURE_DEFAULTS.autoExposureAdaptSpeedDark );
111
120
  this.epsilonU = uniform( options.epsilon ?? 0.0001 );
112
121
  this.deltaTimeU = uniform( 1.0 / 60.0 );
113
122
  this.isFirstFrameU = uniform( 1.0 ); // 1.0 = true
@@ -4,7 +4,8 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
4
  import { HalfFloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
5
5
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
6
  import { luminance } from '../TSL/Common.js';
7
- import { ALBEDO_EPS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
7
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
8
+ import { ALBEDO_EPS } from './DenoiserSettings.js';
8
9
 
9
10
  // SVGF bilateral edge-stopping weight. All three φ params are relative
10
11
  // tolerances (unitless fractions) so the filter is scale-invariant across
@@ -0,0 +1,182 @@
1
+
2
+ // Ray distance NormalDepth writes on a miss. Max finite half-float, so miss−miss diffs stay 0
3
+ // rather than Inf−Inf=NaN.
4
+ export const GBUFFER_MISS_DEPTH = 65504.0;
5
+ export const GBUFFER_MISS_THRESHOLD = 6e4;
6
+
7
+ // Albedo demodulation safety floor. ASVGF and BilateralFilter MUST use the
8
+ // same value — demod (`color / safeAlbedo`) and remod (`lighting * safeAlbedo`)
9
+ // only round-trip exactly when both sides agree.
10
+ export const ALBEDO_EPS = 0.01;
11
+
12
+ export const ASVGF_QUALITY_PRESETS = {
13
+ // phiColor / phiDepth are RELATIVE tolerances (fractions). Bigger = more
14
+ // permissive. The adaptive temporal gradient (gradientStrength > 0) is always
15
+ // on: it measures real change in units of noise σ (gradientSigmaScale), so a
16
+ // static scene reads ~0 (no convergence penalty) and only moving lights / anim
17
+ // / disocclusion drop history. See ASVGF._buildGradientCompute.
18
+ low: {
19
+ temporalAlpha: 0.1,
20
+ gradientStrength: 0.8,
21
+ gradientSigmaScale: 2.5,
22
+ gradientNoiseFloor: 0.05,
23
+ atrousIterations: 3,
24
+ phiColor: 1.0,
25
+ phiNormal: 64.0,
26
+ phiDepth: 0.1,
27
+ phiLuminance: 6.0,
28
+ maxAccumFrames: 16,
29
+ varianceBoost: 0.5
30
+ },
31
+ medium: {
32
+ temporalAlpha: 0.03,
33
+ gradientStrength: 1.0,
34
+ gradientSigmaScale: 2.5,
35
+ gradientNoiseFloor: 0.05,
36
+ atrousIterations: 4,
37
+ phiColor: 0.5,
38
+ phiNormal: 128.0,
39
+ phiDepth: 0.05,
40
+ phiLuminance: 4.0,
41
+ maxAccumFrames: 64,
42
+ varianceBoost: 1.0
43
+ },
44
+ high: {
45
+ temporalAlpha: 0.0,
46
+ gradientStrength: 1.0,
47
+ gradientSigmaScale: 2.5,
48
+ gradientNoiseFloor: 0.05,
49
+ atrousIterations: 6,
50
+ phiColor: 0.3,
51
+ phiNormal: 256.0,
52
+ phiDepth: 0.02,
53
+ phiLuminance: 2.0,
54
+ maxAccumFrames: 128,
55
+ varianceBoost: 1.5
56
+ }
57
+ };
58
+
59
+ // normHitDist = hitDist / (A + B·viewZ). A constant, not a uniform: the Shade write and the NRD
60
+ // decode must agree, and the normalization is a pure round trip.
61
+ export const NRD_HIT_DIST_A = 3.0;
62
+ export const NRD_HIT_DIST_B = 0.1;
63
+
64
+ // NRD ReBLUR port (Stages/NRD.js). Names follow nrd::ReblurSettings so NVIDIA's tuning notes apply.
65
+ export const NRD_DEFAULTS = {
66
+ maxAccumulatedFrameNum: 30,
67
+ maxFastAccumulatedFrameNum: 6,
68
+ maxStabilizedFrameNum: 63,
69
+ historyFixFrameNum: 3,
70
+ historyFixBasePixelStride: 14,
71
+ prepassBlurRadius: 30,
72
+ minBlurRadius: 1,
73
+ maxBlurRadius: 30,
74
+ lobeAngleFraction: 0.15,
75
+ roughnessFraction: 0.15,
76
+ planeDistanceSensitivity: 0.02,
77
+ minHitDistanceWeight: 0.1,
78
+ fastHistoryClampingSigmaScale: 2.0,
79
+ fireflySuppressorMinRelativeScale: 2.0,
80
+ enableAntiFirefly: true,
81
+ antilagLuminanceSigmaScale: 2.0,
82
+ antilagLuminanceSensitivity: 3.0,
83
+ disocclusionThreshold: 0.01,
84
+ convergenceS: 1.0,
85
+ convergenceB: 0.2,
86
+ convergenceP: 0.8,
87
+ // Share of the lobe the normal weight accepts before any history exists. NRD's own constant is
88
+ // 0.75, which its source flags as probably too much; at that width it smears curved surfaces.
89
+ lobeVolumePercent: 0.1,
90
+ // Progressive handover: input sample count at which the denoiser passes the render through untouched.
91
+ // 0 = 2 · maxAccumulatedFrameNum.
92
+ handoverFrames: 0,
93
+ };
94
+
95
+ // Keys a preset may override. Applying one resets every key to its default first, so a preset only
96
+ // states its deltas and `medium` can be empty.
97
+ export const NRD_PRESET_KEYS = [
98
+ 'maxAccumulatedFrameNum', 'maxFastAccumulatedFrameNum', 'maxStabilizedFrameNum',
99
+ 'historyFixFrameNum', 'prepassBlurRadius', 'maxBlurRadius', 'enableAntiFirefly',
100
+ ];
101
+
102
+ export const NRD_QUALITY_PRESETS = {
103
+ // Short history + no pre-pass: most responsive, noisiest.
104
+ low: {
105
+ maxAccumulatedFrameNum: 16,
106
+ maxFastAccumulatedFrameNum: 4,
107
+ maxStabilizedFrameNum: 16,
108
+ historyFixFrameNum: 2,
109
+ prepassBlurRadius: 0,
110
+ maxBlurRadius: 20,
111
+ enableAntiFirefly: false,
112
+ },
113
+ // nrd::ReblurSettings defaults.
114
+ medium: {},
115
+ // Longer history and wider kernels: smoother, more lag on lighting change.
116
+ high: {
117
+ maxAccumulatedFrameNum: 45,
118
+ maxFastAccumulatedFrameNum: 8,
119
+ historyFixFrameNum: 4,
120
+ prepassBlurRadius: 40,
121
+ maxBlurRadius: 40,
122
+ },
123
+ };
124
+
125
+ const ASVGF_MEDIUM = ASVGF_QUALITY_PRESETS.medium;
126
+
127
+ // What the live denoisers, the final OIDN pass and the AI upscaler start with; a host's own state seeds from these.
128
+ export const DENOISER_DEFAULTS = {
129
+ denoiserStrategy: 'none', // the live view's denoiser: 'none' | 'edgeaware' | 'asvgf' | 'nrd' | 'oidn'
130
+
131
+ // EdgeAware denoiser (spatial-only SVGF à-trous). filterStrength: final blend
132
+ // (0 = raw, 1 = filtered). edgeAtrousIterations: à-trous passes (step 1,2,4,8,16).
133
+ // edgePhiLuminance: variance-scaled luminance edge-stop. edgePhiNormal: normal cone
134
+ // exponent. edgePhiDepth: RELATIVE depth tolerance (fraction of ray distance).
135
+ filterStrength: 1.0,
136
+ edgeAtrousIterations: 5,
137
+ edgePhiLuminance: 4.0,
138
+ edgePhiNormal: 64.0,
139
+ edgePhiDepth: 0.1,
140
+
141
+ enableOIDN: false,
142
+ oidnQuality: 'fast',
143
+ // OIDN as the live-view denoiser, refreshing the accumulating image. Set by choosing 'oidn' in
144
+ // the real-time denoiser list, so that only ever one thing denoises the live view — not by
145
+ // `enableOIDN`, which is the separate question of whether the finished image gets a pass.
146
+ continuousDenoise: false,
147
+ // Lower bound on the gap between cadence denoises. DenoisingManager also floors that gap at a
148
+ // multiple of what the last denoise actually cost, and above ~1024² that is what binds — this
149
+ // value only governs where a denoise is cheap. 8 ms measured 31 refreshes/sec at 512² at an
150
+ // unchanged sample rate; 50 ms measured 18/sec for nothing in return.
151
+ continuousDenoiseInterval: 8,
152
+ // While the view moves, feed OIDN the reprojected history of restarted frames, not one sample.
153
+ oidnTemporalHistory: true,
154
+
155
+ enableUpscaler: false,
156
+ upscalerScale: 2,
157
+ upscalerQuality: 'fast',
158
+ upscalerHdr: true,
159
+
160
+ // ASVGF runs its quality preset whenever it is switched on, so its values are that preset's.
161
+ enableASVGF: false,
162
+ asvgfQualityPreset: 'medium',
163
+ asvgfTemporalAlpha: ASVGF_MEDIUM.temporalAlpha,
164
+ asvgfAtrousIterations: ASVGF_MEDIUM.atrousIterations,
165
+ asvgfPhiColor: ASVGF_MEDIUM.phiColor,
166
+ asvgfPhiNormal: ASVGF_MEDIUM.phiNormal,
167
+ asvgfPhiDepth: ASVGF_MEDIUM.phiDepth,
168
+ asvgfPhiLuminance: ASVGF_MEDIUM.phiLuminance,
169
+ asvgfVarianceBoost: ASVGF_MEDIUM.varianceBoost,
170
+ asvgfMaxAccumFrames: ASVGF_MEDIUM.maxAccumFrames,
171
+ // Must be > 0: the gradient also rejects fireflies before the EMA smears them across
172
+ // ~1/alpha frames. At 0 the denoiser measures ~3x worse than no denoiser at 1 spp.
173
+ asvgfGradientStrength: ASVGF_MEDIUM.gradientStrength,
174
+ asvgfGradientSigmaScale: ASVGF_MEDIUM.gradientSigmaScale,
175
+ asvgfGradientNoiseFloor: ASVGF_MEDIUM.gradientNoiseFloor,
176
+ asvgfDebugMode: 0,
177
+ showAsvgfHeatmap: false,
178
+
179
+ // NRD (ReBLUR port) real-time denoiser — see NRD_DEFAULTS / NRD_QUALITY_PRESETS.
180
+ nrdQualityPreset: 'medium',
181
+ nrdDebugMode: 0,
182
+ };
@@ -5,7 +5,8 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
5
5
  import { FloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
6
6
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
7
7
  import { luminance } from '../TSL/Common.js';
8
- import { ALBEDO_EPS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
8
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
9
+ import { ALBEDO_EPS } from './DenoiserSettings.js';
9
10
 
10
11
  // NormalDepth writes depth ≈ 65504 for miss (env/background) rays. This sits below
11
12
  // the sentinel and above any real ray distance, so it's a clean hit/miss flag.
@@ -3,7 +3,7 @@ import { Fn, vec2, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If,
3
3
  import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
4
  import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
5
5
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
- import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
6
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
7
7
  import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
8
8
 
9
9
  /**
package/src/Stages/NRD.js CHANGED
@@ -8,9 +8,8 @@ import { createStorageTexture } from '../Processor/StorageTexturePool.js';
8
8
  import { sanitizeRGB, sanitize1, FP16_MAX } from '../TSL/Common.js';
9
9
  import { cameraRayPoint, cameraRayUniforms, isOrthographic } from '../TSL/CameraRay.js';
10
10
  import {
11
- ALBEDO_EPS, GBUFFER_MISS_THRESHOLD as MISS_THRESHOLD, NRD_DEFAULTS,
12
- NRD_HIT_DIST_A, NRD_HIT_DIST_B,
13
- } from '../EngineDefaults.js';
11
+ NRD_DEFAULTS, NRD_HIT_DIST_A, NRD_HIT_DIST_B, ALBEDO_EPS, GBUFFER_MISS_THRESHOLD as MISS_THRESHOLD,
12
+ } from './DenoiserSettings.js';
14
13
 
15
14
  const NRD_EPS = 1e-6;
16
15
  const NORMAL_ENCODING_ERROR = 1.5 / 255.0;
@@ -3,7 +3,8 @@ import { Fn, vec2, vec4, float, int, uint, uvec2, uvec4, uniform, min, storage,
3
3
  import { RenderTarget, StorageTexture } from 'three/webgpu';
4
4
  import { HalfFloatType, RGBAFormat, RedIntegerFormat, UnsignedIntType, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
5
5
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
- import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
6
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
7
+ import { GBUFFER_MISS_DEPTH } from './DenoiserSettings.js';
7
8
  import { Ray, HitInfo, RayTracingMaterial, UVCache } from '../TSL/Struct.js';
8
9
  import { traverseBVH } from '../TSL/BVHTraversal.js';
9
10
  import { cameraRayOf, cameraRayUniforms } from '../TSL/CameraRay.js';
@@ -248,7 +249,7 @@ export class NormalDepth extends RenderStage {
248
249
 
249
250
  const result = hit.didHit.select(
250
251
  vec4( encodedNormal, depth ),
251
- vec4( 0.0, 0.0, 0.0, float( 65504.0 ) )
252
+ vec4( 0.0, 0.0, 0.0, float( GBUFFER_MISS_DEPTH ) )
252
253
  );
253
254
 
254
255
  textureStore(
@@ -16,6 +16,7 @@ import { KernelManager } from '../Processor/KernelManager.js';
16
16
  import { buildGenerateKernel, GENERATE_WG_SIZE } from '../TSL/GenerateKernel.js';
17
17
  import { buildExtendKernel, EXTEND_WG_SIZE } from '../TSL/ExtendKernel.js';
18
18
  import { buildShadeKernel, SHADE_WG_SIZE } from '../TSL/ShadeKernel.js';
19
+ import { buildVisibilityFoldKernel, buildVisibilityClearKernel } from '../TSL/LightVisibility.js';
19
20
  import { buildCompactKernel, buildCompactSubgroupKernel, COMPACT_WG_SIZE } from '../TSL/CompactKernel.js';
20
21
  import { buildFinalWriteKernel, FINALWRITE_WG_SIZE } from '../TSL/FinalWriteKernel.js';
21
22
  import { buildDebugKernel, DEBUG_WG_SIZE } from '../TSL/DebugKernel.js';
@@ -25,7 +26,8 @@ import {
25
26
  buildResetGlobalHistKernel, buildGlobalHistKernel, buildGlobalPrefixKernel, buildGlobalScatterKernel,
26
27
  SORT_GLOBAL_WG_SIZE, SORT_GLOBAL_MAX_BINS,
27
28
  } from '../TSL/SortGlobalKernels.js';
28
- import { ENGINE_DEFAULTS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
29
+ import { ENGINE_DEFAULTS } from '../EngineDefaults.js';
30
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
29
31
  import { ISSUE_CODES } from '../EngineIssues.js';
30
32
  import { createLogger, fmt } from '../utils/Logger.js';
31
33
  import { deviceMemoryGB } from '../Processor/HostMemory.js';
@@ -40,6 +42,9 @@ export const LIST_WG_SIZE = 256;
40
42
 
41
43
  // Resized per bounce iteration, each from its own registered workgroup size. Unregistered entries
42
44
  // (the sort passes when _sortMaterials is off) are skipped by setDispatchForCount.
45
+ const PICKED_SHADOW_RAYS = [ 'two', 'one' ];
46
+ const SHADOW_RAY_MODES = [ 'all', ...PICKED_SHADOW_RAYS ];
47
+
43
48
  export const BOUNCE_KERNELS = [ 'extend', 'shade', 'connect', 'merge', 'globalHist', 'globalScatter', 'compact', 'compactCopyback', 'lightCopyback' ];
44
49
 
45
50
  export class PathTracer extends PathTracerStage {
@@ -83,9 +88,10 @@ export class PathTracer extends PathTracerStage {
83
88
  // CPU sizes per-bounce kernels from last frame's survivor curve; kernels bound on ENTERING_COUNT so over-sizing is safe. (indirect dispatch not viable — three.js doesn't sync compute-written indirect buffers across submissions)
84
89
  this._useDynamicDispatch = true;
85
90
 
86
- // Global material-coherence sort: set per-build from ENGINE_DEFAULTS + material count (>8).
91
+ // Global material-coherence sort: the wavefrontSortMaterials setting, applied per build above 8 materials.
87
92
  // Reorders entering rays into material-pure workgroups before Shade; runs under dynamic dispatch
88
93
  // (compact reads the unsorted active list, so the survivor set is unchanged). Measured −8% at 1024²/8b.
94
+ this.sortMaterials = options.sortMaterials ?? ENGINE_DEFAULTS.wavefrontSortMaterials;
89
95
  this._sortMaterials = false;
90
96
 
91
97
  // Flag-gated off: perf-neutral vs atomic-append and adds a 'subgroups' feature dependency.
@@ -153,6 +159,9 @@ export class PathTracer extends PathTracerStage {
153
159
  this._outputsChanged = false;
154
160
  // The material layers the kernels were compiled with (_layersToCompile).
155
161
  this._compiledLayers = null;
162
+ this._shadowRays = 'all';
163
+ this._compiledPick = null;
164
+ this._learnsVisibility = false;
156
165
 
157
166
  // Integrators other than the path tracer's own, by name (registerIntegrator); the chosen one's instance, or
158
167
  // null for plain path tracing, which builds exactly the unidirectional kernels.
@@ -333,6 +342,44 @@ export class PathTracer extends PathTracerStage {
333
342
 
334
343
  }
335
344
 
345
+ /**
346
+ * Shadow rays a hit for the lamps, sky, sun and emitters: 'all', one per kind; 'two', the strongest and one picked
347
+ * from the rest; 'one', one picked, by unshadowed light and where each kind gets through. Rebuilds the kernels
348
+ * before the next frame; bidirectional integrators ignore it.
349
+ * @param {'all'|'two'|'one'} mode
350
+ */
351
+ setShadowRays( mode ) {
352
+
353
+ if ( ! SHADOW_RAY_MODES.includes( mode ) ) {
354
+
355
+ log.warn( `shadowRays: unknown mode "${mode}", using 'all'` );
356
+ mode = 'all';
357
+
358
+ }
359
+
360
+ if ( this._shadowRays === mode ) return;
361
+ this._shadowRays = mode;
362
+ this._outputsChanged = true;
363
+
364
+ }
365
+
366
+ /**
367
+ * The lightPick variant to compile, or null for a ray per light kind: also when the scene has no more kinds than the
368
+ * variant traces rays, where it would only add its bookkeeping.
369
+ */
370
+ _pickedShadowRays() {
371
+
372
+ if ( this._integrator || ! PICKED_SHADOW_RAYS.includes( this._shadowRays ) ) return null;
373
+ const environment = this.enableEnvironment.value > 0;
374
+ const lamps = this.numDirectionalLights.value + this.numAreaLights.value + this.numPointLights.value + this.numSpotLights.value;
375
+ const kinds = Number( lamps > 0 )
376
+ + Number( environment && this.envTotalSum.value > 0 )
377
+ + Number( environment && this.hasSun.value > 0 )
378
+ + Number( this.enableEmissiveTriangleSampling.value > 0 && this.emissiveTriangleCount.value > 0 );
379
+ return kinds > ( this._shadowRays === 'two' ? 2 : 1 ) ? this._shadowRays : null;
380
+
381
+ }
382
+
336
383
  get integrator() {
337
384
 
338
385
  return this._integrator?.name ?? 'path';
@@ -362,6 +409,8 @@ export class PathTracer extends PathTracerStage {
362
409
 
363
410
  }
364
411
 
412
+ if ( PICKED_SHADOW_RAYS.includes( this._shadowRays ) && this._pickedShadowRays() !== this._compiledPick ) this._outputsChanged = true;
413
+
365
414
  if ( this._lightBufferRealloc || this._outputsChanged ) {
366
415
 
367
416
  if ( this._kernelManager ) this._kernelManager.dispose();
@@ -511,6 +560,12 @@ export class PathTracer extends PathTracerStage {
511
560
 
512
561
  // An integrator's light subpaths first, through the same pool; every camera chunk then connects to them.
513
562
  const integrator = this._integrator?.beginFrame( loopBound ) ? this._integrator : null;
563
+ // A camera move keeps what was learned (it is kept per place in the scene); a repeatable render starts afresh.
564
+ if ( this._learnsVisibility && frameValue === 0 && ( this._pinSeedToFrame || this._lockstep || ! this.cameraChanged ) ) {
565
+
566
+ km.dispatch( 'visibilityClear' );
567
+
568
+ }
514
569
 
515
570
  // Blender-style row-band streaming: the fixed-budget path pool processes the image in bands of ≤ chunkRows
516
571
  // rows (one chunk when the frame fits the budget → identical to the pre-chunking path). See spec.
@@ -650,6 +705,7 @@ export class PathTracer extends PathTracerStage {
650
705
 
651
706
  }
652
707
 
708
+ if ( this._learnsVisibility ) km.dispatch( 'visibilityFold' );
653
709
  this._maybeReadbackCounters();
654
710
 
655
711
  // Skip the normalDepth/albedo copies when aux is off — the wavefront didn't write them and
@@ -1510,6 +1566,11 @@ export class PathTracer extends PathTracerStage {
1510
1566
  const prevNormalDepth = this.shaderBuilder.prevNormalDepthTexNode;
1511
1567
  const writeTex = this.storageTextures.getWriteTextures();
1512
1568
 
1569
+ const picked = this._pickedShadowRays();
1570
+ this._compiledPick = picked;
1571
+ const learnsVisibility = picked !== null;
1572
+ this._learnsVisibility = learnsVisibility;
1573
+ qm.setVisibilityTable( learnsVisibility );
1513
1574
  const counters = qm.getCounters();
1514
1575
 
1515
1576
  // Copy ACTIVE_RAY_COUNT into bounceCounts[currentBounce] for the readback survivor curve.
@@ -1813,8 +1874,7 @@ export class PathTracer extends PathTracerStage {
1813
1874
  };
1814
1875
 
1815
1876
  // Material-coherence sort gate (experiment): only worthwhile above a few materials.
1816
- this._sortMaterials = ( ENGINE_DEFAULTS.wavefrontSortMaterials ?? false )
1817
- && ( this.materialData?.materialCount ?? 0 ) > 8;
1877
+ this._sortMaterials = this.sortMaterials && ( this.materialData?.materialCount ?? 0 ) > 8;
1818
1878
 
1819
1879
  const extFn = buildExtendKernel( {
1820
1880
  bvhBuffer: freshBvh,
@@ -1945,6 +2005,8 @@ export class PathTracer extends PathTracerStage {
1945
2005
  bidirectional: this._integrator?.uniforms ?? null,
1946
2006
  hitDistanceEncode: this._outputs.get( 'hitDistance' )?.encode ?? null,
1947
2007
  materialLayers: this._compiledLayers,
2008
+ shadowRays: picked,
2009
+ cameraWorldMatrix: this.cameraWorldMatrix,
1948
2010
  } );
1949
2011
  this._kernelManager.register( 'shade',
1950
2012
  own( shadeFn() ).compute(
@@ -1952,6 +2014,12 @@ export class PathTracer extends PathTracerStage {
1952
2014
  [ SHADE_WG_SIZE, 1, 1 ]
1953
2015
  )
1954
2016
  );
2017
+ if ( learnsVisibility ) {
2018
+
2019
+ this._kernelManager.register( 'visibilityClear', buildVisibilityClearKernel( { counters } ) );
2020
+ this._kernelManager.register( 'visibilityFold', buildVisibilityFoldKernel( { counters } ) );
2021
+
2022
+ }
1955
2023
 
1956
2024
  // Subgroup prefix-sum variant when supported.
1957
2025
  const subgroupsOK = this._useSubgroupCompact
@@ -20,7 +20,8 @@ import { SceneProcessor } from '../Processor/SceneProcessor.js';
20
20
  import { LightSerializer } from '../Processor/LightSerializer';
21
21
 
22
22
  // Constants
23
- import { ENGINE_DEFAULTS as DEFAULT_STATE, TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
23
+ import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
24
+ import { TRIANGLE_DATA_LAYOUT } from '../Processor/BufferLayout.js';
24
25
  import { TRI_GEO_ROWS, TRI_SHADE_ROWS } from '../TSL/Common.js';
25
26
  import { createLogger, fmt } from '../utils/Logger.js';
26
27
 
@@ -4,7 +4,7 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
4
  import { FloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
5
5
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
6
  import { luminance } from '../TSL/Common.js';
7
- import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
7
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
8
8
 
9
9
  // NaN/±Inf guard: a poisoned luminance would otherwise corrupt the moment EMA forever
10
10
  // (mean/meanSq → variance → BilateralFilter sigmaL). NaN (x!=x) → 0, ±Inf → [0,1e7].
@@ -25,7 +25,10 @@ import {
25
25
  bool as tslBool,
26
26
  } from 'three/tsl';
27
27
 
28
- import { BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX, TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TLAS_LEAF_IDENTITY } from '../EngineDefaults.js';
28
+ import {
29
+ TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX,
30
+ TLAS_LEAF_IDENTITY,
31
+ } from '../Processor/BufferLayout.js';
29
32
  import { HitInfo } from './Struct.js';
30
33
  import {
31
34
  getDatafromStorageBuffer, instanceRows, instanceNormalToWorld, unpackTriangleNormal, triangleRow, shadowFlagsSettle
package/src/TSL/Common.js CHANGED
@@ -39,7 +39,7 @@ export const FP16_MAX = 65504.0;
39
39
  export const sanitize1 = ( x, ceiling = 1e7 ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, ceiling );
40
40
  export const sanitizeRGB = ( c, ceiling = 1e7 ) =>
41
41
  vec3( sanitize1( c.x, ceiling ), sanitize1( c.y, ceiling ), sanitize1( c.z, ceiling ) );
42
- import { MATERIAL_DATA_LAYOUT, TRI_BLOCKER_SHIFT, TRI_BLOCKER_ALPHA_SHIFT } from '../EngineDefaults.js';
42
+ import { TRI_BLOCKER_SHIFT, TRI_BLOCKER_ALPHA_SHIFT, MATERIAL_DATA_LAYOUT } from '../Processor/BufferLayout.js';
43
43
 
44
44
  export const MATERIAL_SLOTS = MATERIAL_DATA_LAYOUT.SLOTS_PER_MATERIAL;
45
45
  export const MATERIAL_SLOT = MATERIAL_DATA_LAYOUT.SLOT;
@@ -32,7 +32,7 @@ import {
32
32
  computeDotProductsAniso, instanceRows, instanceNormalToWorld, instancePointToWorld,
33
33
  unpackTriangleNormal, triangleRow, SHADOW_END
34
34
  } from './Common.js';
35
- import { TRI_MATERIAL_MASK } from '../EngineDefaults.js';
35
+ import { TRI_MATERIAL_MASK } from '../Processor/BufferLayout.js';
36
36
  import { getRandomSample1D, getRandomSample2D } from './Random.js';
37
37
  import { calculateMaterialPDFFromDots } from './LightsSampling.js';
38
38
  import { evaluateMaterialResponseFromDots, evaluateDiffuseTransmission } from './MaterialEvaluation.js';