rayzee 8.4.1 → 8.6.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 (65) hide show
  1. package/README.md +61 -9
  2. package/dist/{DLSSNeural-BNSJ8V3z.js → DLSSNeural-BhBCBgKf.js} +5 -5
  3. package/dist/{DLSSSuperRes-BoEW89Cl.js → DLSSSuperRes-BCA7F_k5.js} +5 -5
  4. package/dist/ToneMapGPU-BzcLheoR.js +533 -0
  5. package/dist/rayzee.es.js +27278 -9133
  6. package/dist/rayzee.umd.js +255 -187
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +15 -2
  9. package/src/Color/BakedViews.js +134 -0
  10. package/src/Color/BuiltinViews.js +261 -0
  11. package/src/Color/ColorManagement.js +1130 -0
  12. package/src/Color/ColorSpaces.js +432 -0
  13. package/src/Color/Displays.js +19 -0
  14. package/src/Color/InputColorSpaces.js +243 -0
  15. package/src/Color/LutBake.js +371 -0
  16. package/src/Color/OcioRuntime.js +338 -0
  17. package/src/Color/OcioViews.js +419 -0
  18. package/src/Color/ViewTransforms.js +323 -0
  19. package/src/Color/WorkingMatrix.js +86 -0
  20. package/src/EngineDefaults.js +84 -4
  21. package/src/EngineIssues.js +3 -1
  22. package/src/Passes/AIUpscaler.js +16 -7
  23. package/src/PathTracerApp.js +318 -35
  24. package/src/Processor/AssetLoader.js +47 -55
  25. package/src/Processor/CameraOptimizer.js +22 -6
  26. package/src/Processor/EmissiveTriangleBuilder.js +34 -2
  27. package/src/Processor/GeometryExtractor.js +151 -138
  28. package/src/Processor/LightSerializer.js +17 -20
  29. package/src/Processor/MaterialPacking.js +168 -0
  30. package/src/Processor/PackedRayBuffer.js +6 -2
  31. package/src/Processor/ProceduralSky.js +4 -0
  32. package/src/Processor/SceneProcessor.js +25 -1
  33. package/src/Processor/SimpleSky.js +8 -0
  34. package/src/Processor/TextureCreator.js +137 -91
  35. package/src/Processor/TextureReadback.js +92 -0
  36. package/src/Processor/ToneMapCPU.js +80 -165
  37. package/src/Processor/ToneMapGPU.js +122 -151
  38. package/src/RenderSettings.js +10 -1
  39. package/src/Stages/Compositor.js +11 -2
  40. package/src/Stages/PathTracer.js +21 -4
  41. package/src/Stages/PathTracerStage.js +11 -18
  42. package/src/TSL/Common.js +15 -0
  43. package/src/TSL/Debugger.js +2 -3
  44. package/src/TSL/EmissiveSampling.js +6 -8
  45. package/src/TSL/ExtendKernel.js +9 -0
  46. package/src/TSL/Fresnel.js +23 -1
  47. package/src/TSL/HitFacet.js +102 -0
  48. package/src/TSL/LightsDirect.js +12 -33
  49. package/src/TSL/LightsSampling.js +15 -7
  50. package/src/TSL/MaterialEvaluation.js +26 -19
  51. package/src/TSL/MaterialProperties.js +74 -37
  52. package/src/TSL/MaterialTransmission.js +2 -3
  53. package/src/TSL/ShadeKernel.js +34 -21
  54. package/src/TSL/ShadowTerminator.js +125 -0
  55. package/src/TSL/Struct.js +9 -0
  56. package/src/TSL/Subsurface.js +2 -3
  57. package/src/index.js +29 -2
  58. package/src/managers/DenoisingManager.js +3 -7
  59. package/src/managers/EnvironmentManager.js +20 -0
  60. package/src/managers/LightManager.js +17 -15
  61. package/src/managers/MaterialDataManager.js +118 -208
  62. package/src/managers/UniformManager.js +1 -0
  63. package/src/managers/VideoRenderManager.js +9 -9
  64. package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
  65. package/src/LightUnits.js +0 -122
@@ -13,8 +13,14 @@ import {
13
13
  MATERIAL_DATA_LAYOUT as M, TRIANGLE_DATA_LAYOUT as T, normalizeAttenuationDistance,
14
14
  TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TRI_BLOCKER_SHIFT, shadowBlockerBits
15
15
  } from '../EngineDefaults.js';
16
+ import { packMaterial, UNIT_RANGE_PROPERTIES, clampUnit } from '../Processor/MaterialPacking.js';
17
+ import { resolveMaterialTextures, MATERIAL_VALUE_SOURCE } from '../Processor/GeometryExtractor.js';
18
+ import { convertLinearTriple, convertLinearTriples, getWorkingMatrixSpace } from '../Color/WorkingMatrix.js';
16
19
  import { createLogger, fmt } from '../utils/Logger.js';
17
20
 
21
+ /** Material buffers already moved into the working space, so a re-init cannot convert twice. */
22
+ const convertedBuffers = new WeakSet();
23
+
18
24
  const log = createLogger( 'material' );
19
25
 
20
26
  const PIXELS_PER_MATERIAL = M.SLOTS_PER_MATERIAL;
@@ -24,6 +30,15 @@ const TRI_FLAGS_OFFSET = T.MATERIAL_FLAGS_OFFSET;
24
30
  // Material properties that affect the shadow-ray opaque-blocker flag.
25
31
  const BLOCKER_PROPS = new Set( [ 'transmission', 'transparent', 'opacity', 'alphaMode' ] );
26
32
 
33
+ // Map slot → sRGB pool (true) or linear pool.
34
+ const TEXTURE_POOLS = [
35
+ [ 'map', true ], [ 'emissiveMap', true ], [ 'sheenColorMap', true ], [ 'specularColorMap', true ],
36
+ [ 'normalMap', false ], [ 'bumpMap', false ], [ 'roughnessMap', false ], [ 'metalnessMap', false ],
37
+ [ 'displacementMap', false ], [ 'anisotropyMap', false ], [ 'transmissionMap', false ],
38
+ [ 'clearcoatMap', false ], [ 'clearcoatRoughnessMap', false ], [ 'sheenRoughnessMap', false ],
39
+ [ 'iridescenceMap', false ], [ 'iridescenceThicknessMap', false ], [ 'specularIntensityMap', false ],
40
+ ];
41
+
27
42
  // Scalar slots readable via getMaterialProperty().
28
43
  const SCALAR_PROPERTY_OFFSETS = {
29
44
  ior: M.IOR,
@@ -83,18 +98,72 @@ export class MaterialDataManager {
83
98
  */
84
99
  this.callbacks = {};
85
100
 
101
+ // Per material index: createMaterialObject's sources, and properties a host set since.
102
+ this._sources = [];
103
+ this._hostSet = [];
104
+
86
105
  }
87
106
 
88
107
  // ===== STORAGE BUFFER MANAGEMENT =====
89
108
 
109
+ /**
110
+ * Every colour slot in the material buffer, in floats from the start of a material.
111
+ *
112
+ * Tints only — `attenuationColor` and `subsurfaceColor` included, because they multiply
113
+ * radiance like any other. Scalars and packed flags are deliberately absent: running a
114
+ * primaries matrix over roughness would be silent and ruinous.
115
+ */
116
+ static COLOR_OFFSETS = [
117
+ M.COLOR, M.EMISSIVE, M.ATTENUATION_COLOR, M.SHEEN_COLOR, M.SPECULAR_COLOR, M.SUBSURFACE_COLOR,
118
+ ].filter( o => Number.isInteger( o ) );
119
+
120
+ /**
121
+ * Move every material tint into the working space.
122
+ *
123
+ * glTF authors colours against sRGB primaries. If the render is happening in ACEScg, the same
124
+ * numbers describe a different colour, so a base colour left unconverted is not a grade away
125
+ * from right — it is a different material. Inert until a working space is adopted.
126
+ */
127
+ _convertColorsToWorkingSpace( data ) {
128
+
129
+ // The same array is handed in again whenever the stage re-initialises, and converting it
130
+ // twice would apply the primaries matrix twice — a colour shift with no error to show for
131
+ // it. Remembered by identity, so a rebuilt buffer converts normally.
132
+ if ( convertedBuffers.has( data ) ) return;
133
+ convertedBuffers.add( data );
134
+
135
+ const n = convertLinearTriples( data, M.FLOATS_PER_MATERIAL, MaterialDataManager.COLOR_OFFSETS );
136
+ if ( n ) log.debug( `converted ${n} material colour(s) into ${getWorkingMatrixSpace()}` );
137
+
138
+ }
139
+
140
+ /** One tint, converted on its way into the buffer. */
141
+ _writeColor( data, index, r, g, b ) {
142
+
143
+ const rgb = [ r, g, b ];
144
+ if ( convertLinearTriple( rgb ) ) {
145
+
146
+ [ r, g, b ] = rgb;
147
+
148
+ }
149
+
150
+ data[ index ] = r;
151
+ data[ index + 1 ] = g;
152
+ data[ index + 2 ] = b;
153
+
154
+ }
155
+
90
156
  /**
91
157
  * Sets material data from raw Float32Array via storage buffer.
92
158
  * @param {Float32Array} matImageData
159
+ * @param {Array<Object>} [sources] - createMaterialObject().sources per material index
93
160
  */
94
- setMaterialData( matImageData ) {
161
+ setMaterialData( matImageData, sources = [] ) {
95
162
 
96
163
  if ( ! matImageData ) return;
97
164
 
165
+ this._convertColorsToWorkingSpace( matImageData );
166
+
98
167
  const vec4Count = matImageData.length / 4;
99
168
 
100
169
  if ( this.materialStorageNode ) {
@@ -111,6 +180,8 @@ export class MaterialDataManager {
111
180
  }
112
181
 
113
182
  this.materialCount = Math.floor( vec4Count / PIXELS_PER_MATERIAL );
183
+ this._sources = [ ...sources ];
184
+ this._hostSet = [];
114
185
  log.debug( `${fmt.n( this.materialCount )} materials (storage buffer)` );
115
186
 
116
187
  }
@@ -225,6 +296,20 @@ export class MaterialDataManager {
225
296
 
226
297
  }
227
298
 
299
+ /**
300
+ * Where a material property's value came from, as a MATERIAL_VALUE_SOURCE. `default` means the
301
+ * model never said and the engine filled it in.
302
+ * @param {number} materialIndex
303
+ * @param {string} property
304
+ * @returns {string|undefined} undefined for an unknown property or material
305
+ */
306
+ getMaterialPropertySource( materialIndex, property ) {
307
+
308
+ if ( this._hostSet[ materialIndex ]?.has( property ) ) return MATERIAL_VALUE_SOURCE.HOST;
309
+ return this._sources[ materialIndex ]?.[ property ];
310
+
311
+ }
312
+
228
313
  /**
229
314
  * Update a single material property in the storage buffer.
230
315
  * @param {number} materialIndex
@@ -242,21 +327,18 @@ export class MaterialDataManager {
242
327
 
243
328
  const data = this.materialStorageAttr.array;
244
329
  const stride = materialIndex * M.FLOATS_PER_MATERIAL;
330
+ if ( UNIT_RANGE_PROPERTIES.has( property ) && typeof value === 'number' ) value = clampUnit( value );
245
331
 
246
332
  switch ( property ) {
247
333
 
248
334
  case 'color':
249
335
  if ( value.r !== undefined ) {
250
336
 
251
- data[ stride + M.COLOR ] = value.r;
252
- data[ stride + M.COLOR + 1 ] = value.g;
253
- data[ stride + M.COLOR + 2 ] = value.b;
337
+ this._writeColor( data, stride + M.COLOR, value.r, value.g, value.b );
254
338
 
255
339
  } else if ( Array.isArray( value ) ) {
256
340
 
257
- data[ stride + M.COLOR ] = value[ 0 ];
258
- data[ stride + M.COLOR + 1 ] = value[ 1 ];
259
- data[ stride + M.COLOR + 2 ] = value[ 2 ];
341
+ this._writeColor( data, stride + M.COLOR, value[ 0 ], value[ 1 ], value[ 2 ] );
260
342
 
261
343
  }
262
344
 
@@ -265,15 +347,11 @@ export class MaterialDataManager {
265
347
  case 'emissive':
266
348
  if ( value.r !== undefined ) {
267
349
 
268
- data[ stride + M.EMISSIVE ] = value.r;
269
- data[ stride + M.EMISSIVE + 1 ] = value.g;
270
- data[ stride + M.EMISSIVE + 2 ] = value.b;
350
+ this._writeColor( data, stride + M.EMISSIVE, value.r, value.g, value.b );
271
351
 
272
352
  } else if ( Array.isArray( value ) ) {
273
353
 
274
- data[ stride + M.EMISSIVE ] = value[ 0 ];
275
- data[ stride + M.EMISSIVE + 1 ] = value[ 1 ];
276
- data[ stride + M.EMISSIVE + 2 ] = value[ 2 ];
354
+ this._writeColor( data, stride + M.EMISSIVE, value[ 0 ], value[ 1 ], value[ 2 ] );
277
355
 
278
356
  }
279
357
 
@@ -286,15 +364,11 @@ export class MaterialDataManager {
286
364
  case 'attenuationColor':
287
365
  if ( value.r !== undefined ) {
288
366
 
289
- data[ stride + M.ATTENUATION_COLOR ] = value.r;
290
- data[ stride + M.ATTENUATION_COLOR + 1 ] = value.g;
291
- data[ stride + M.ATTENUATION_COLOR + 2 ] = value.b;
367
+ this._writeColor( data, stride + M.ATTENUATION_COLOR, value.r, value.g, value.b );
292
368
 
293
369
  } else if ( Array.isArray( value ) ) {
294
370
 
295
- data[ stride + M.ATTENUATION_COLOR ] = value[ 0 ];
296
- data[ stride + M.ATTENUATION_COLOR + 1 ] = value[ 1 ];
297
- data[ stride + M.ATTENUATION_COLOR + 2 ] = value[ 2 ];
371
+ this._writeColor( data, stride + M.ATTENUATION_COLOR, value[ 0 ], value[ 1 ], value[ 2 ] );
298
372
 
299
373
  }
300
374
 
@@ -306,15 +380,11 @@ export class MaterialDataManager {
306
380
  case 'sheenColor':
307
381
  if ( value.r !== undefined ) {
308
382
 
309
- data[ stride + M.SHEEN_COLOR ] = value.r;
310
- data[ stride + M.SHEEN_COLOR + 1 ] = value.g;
311
- data[ stride + M.SHEEN_COLOR + 2 ] = value.b;
383
+ this._writeColor( data, stride + M.SHEEN_COLOR, value.r, value.g, value.b );
312
384
 
313
385
  } else if ( Array.isArray( value ) ) {
314
386
 
315
- data[ stride + M.SHEEN_COLOR ] = value[ 0 ];
316
- data[ stride + M.SHEEN_COLOR + 1 ] = value[ 1 ];
317
- data[ stride + M.SHEEN_COLOR + 2 ] = value[ 2 ];
387
+ this._writeColor( data, stride + M.SHEEN_COLOR, value[ 0 ], value[ 1 ], value[ 2 ] );
318
388
 
319
389
  }
320
390
 
@@ -323,15 +393,11 @@ export class MaterialDataManager {
323
393
  case 'specularColor':
324
394
  if ( value.r !== undefined ) {
325
395
 
326
- data[ stride + M.SPECULAR_COLOR ] = value.r;
327
- data[ stride + M.SPECULAR_COLOR + 1 ] = value.g;
328
- data[ stride + M.SPECULAR_COLOR + 2 ] = value.b;
396
+ this._writeColor( data, stride + M.SPECULAR_COLOR, value.r, value.g, value.b );
329
397
 
330
398
  } else if ( Array.isArray( value ) ) {
331
399
 
332
- data[ stride + M.SPECULAR_COLOR ] = value[ 0 ];
333
- data[ stride + M.SPECULAR_COLOR + 1 ] = value[ 1 ];
334
- data[ stride + M.SPECULAR_COLOR + 2 ] = value[ 2 ];
400
+ this._writeColor( data, stride + M.SPECULAR_COLOR, value[ 0 ], value[ 1 ], value[ 2 ] );
335
401
 
336
402
  }
337
403
 
@@ -383,15 +449,11 @@ export class MaterialDataManager {
383
449
  case 'subsurfaceColor':
384
450
  if ( value.r !== undefined ) {
385
451
 
386
- data[ stride + M.SUBSURFACE_COLOR ] = value.r;
387
- data[ stride + M.SUBSURFACE_COLOR + 1 ] = value.g;
388
- data[ stride + M.SUBSURFACE_COLOR + 2 ] = value.b;
452
+ this._writeColor( data, stride + M.SUBSURFACE_COLOR, value.r, value.g, value.b );
389
453
 
390
454
  } else if ( Array.isArray( value ) ) {
391
455
 
392
- data[ stride + M.SUBSURFACE_COLOR ] = value[ 0 ];
393
- data[ stride + M.SUBSURFACE_COLOR + 1 ] = value[ 1 ];
394
- data[ stride + M.SUBSURFACE_COLOR + 2 ] = value[ 2 ];
456
+ this._writeColor( data, stride + M.SUBSURFACE_COLOR, value[ 0 ], value[ 1 ], value[ 2 ] );
395
457
 
396
458
  }
397
459
 
@@ -419,6 +481,7 @@ export class MaterialDataManager {
419
481
  }
420
482
 
421
483
  this.materialStorageAttr.needsUpdate = true;
484
+ ( this._hostSet[ materialIndex ] ??= new Set() ).add( property );
422
485
 
423
486
  // Recompute triangle-data opaque-blocker flag when any input to it changes.
424
487
  if ( BLOCKER_PROPS.has( property ) ) {
@@ -434,7 +497,7 @@ export class MaterialDataManager {
434
497
  /**
435
498
  * Bulk-load an entire material object's data into the storage buffer.
436
499
  * @param {number} materialIndex
437
- * @param {Object} materialData
500
+ * @param {Object} materialData - a createMaterialObject() result
438
501
  */
439
502
  updateMaterialDataFromObject( materialIndex, materialData ) {
440
503
 
@@ -446,167 +509,24 @@ export class MaterialDataManager {
446
509
  }
447
510
 
448
511
  const data = this.materialStorageAttr.array;
449
- const stride = materialIndex * M.FLOATS_PER_MATERIAL;
450
-
451
- if ( materialData.color ) {
452
-
453
- data[ stride + M.COLOR ] = materialData.color.r ?? materialData.color[ 0 ] ?? 1;
454
- data[ stride + M.COLOR + 1 ] = materialData.color.g ?? materialData.color[ 1 ] ?? 1;
455
- data[ stride + M.COLOR + 2 ] = materialData.color.b ?? materialData.color[ 2 ] ?? 1;
456
-
457
- }
458
-
459
- data[ stride + M.METALNESS ] = materialData.metalness ?? 0;
512
+ const base = materialIndex * M.FLOATS_PER_MATERIAL;
513
+ packMaterial( data, base, materialData );
460
514
 
461
- if ( materialData.emissive ) {
515
+ // packMaterial writes tints as authored; a whole upload is converted in setMaterialData.
516
+ for ( const offset of MaterialDataManager.COLOR_OFFSETS ) {
462
517
 
463
- data[ stride + M.EMISSIVE ] = materialData.emissive.r ?? materialData.emissive[ 0 ] ?? 0;
464
- data[ stride + M.EMISSIVE + 1 ] = materialData.emissive.g ?? materialData.emissive[ 1 ] ?? 0;
465
- data[ stride + M.EMISSIVE + 2 ] = materialData.emissive.b ?? materialData.emissive[ 2 ] ?? 0;
518
+ this._writeColor( data, base + offset, data[ base + offset ], data[ base + offset + 1 ], data[ base + offset + 2 ] );
466
519
 
467
520
  }
468
521
 
469
- data[ stride + M.ROUGHNESS ] = materialData.roughness ?? 1;
470
- data[ stride + M.IOR ] = materialData.ior ?? 1.5;
471
- data[ stride + M.TRANSMISSION ] = materialData.transmission ?? 0;
472
- data[ stride + M.THICKNESS ] = materialData.thickness ?? 0.1;
473
- data[ stride + M.EMISSIVE_INTENSITY ] = materialData.emissiveIntensity ?? 1;
522
+ this._sources[ materialIndex ] = materialData.sources;
523
+ this._hostSet[ materialIndex ] = undefined;
474
524
 
475
- if ( materialData.attenuationColor ) {
476
-
477
- data[ stride + M.ATTENUATION_COLOR ] = materialData.attenuationColor.r ?? materialData.attenuationColor[ 0 ] ?? 1;
478
- data[ stride + M.ATTENUATION_COLOR + 1 ] = materialData.attenuationColor.g ?? materialData.attenuationColor[ 1 ] ?? 1;
479
- data[ stride + M.ATTENUATION_COLOR + 2 ] = materialData.attenuationColor.b ?? materialData.attenuationColor[ 2 ] ?? 1;
480
-
481
- }
482
-
483
- data[ stride + M.ATTENUATION_DISTANCE ] = normalizeAttenuationDistance( materialData.attenuationDistance );
484
- data[ stride + M.DISPERSION ] = materialData.dispersion ?? 0;
485
- data[ stride + M.VISIBLE ] = 1; // Reserved slot (per-mesh visibility handled at BLAS-pointer level)
486
- data[ stride + M.SHEEN ] = materialData.sheen ?? 0;
487
- data[ stride + M.SHEEN_ROUGHNESS ] = materialData.sheenRoughness ?? 1;
488
-
489
- if ( materialData.sheenColor ) {
490
-
491
- data[ stride + M.SHEEN_COLOR ] = materialData.sheenColor.r ?? materialData.sheenColor[ 0 ] ?? 0;
492
- data[ stride + M.SHEEN_COLOR + 1 ] = materialData.sheenColor.g ?? materialData.sheenColor[ 1 ] ?? 0;
493
- data[ stride + M.SHEEN_COLOR + 2 ] = materialData.sheenColor.b ?? materialData.sheenColor[ 2 ] ?? 0;
494
-
495
- }
496
-
497
- data[ stride + M.SPECULAR_INTENSITY ] = materialData.specularIntensity ?? 1;
498
-
499
- if ( materialData.specularColor ) {
500
-
501
- data[ stride + M.SPECULAR_COLOR ] = materialData.specularColor.r ?? materialData.specularColor[ 0 ] ?? 1;
502
- data[ stride + M.SPECULAR_COLOR + 1 ] = materialData.specularColor.g ?? materialData.specularColor[ 1 ] ?? 1;
503
- data[ stride + M.SPECULAR_COLOR + 2 ] = materialData.specularColor.b ?? materialData.specularColor[ 2 ] ?? 1;
504
-
505
- }
506
-
507
- data[ stride + M.IRIDESCENCE ] = materialData.iridescence ?? 0;
508
- data[ stride + M.IRIDESCENCE_IOR ] = materialData.iridescenceIOR ?? 1.3;
509
-
510
- if ( materialData.iridescenceThicknessRange ) {
511
-
512
- data[ stride + M.IRIDESCENCE_THICKNESS_RANGE ] = materialData.iridescenceThicknessRange[ 0 ] ?? 100;
513
- data[ stride + M.IRIDESCENCE_THICKNESS_RANGE + 1 ] = materialData.iridescenceThicknessRange[ 1 ] ?? 400;
514
-
515
- }
516
-
517
- data[ stride + M.ALBEDO_MAP_INDEX ] = materialData.map ?? - 1;
518
- data[ stride + M.NORMAL_MAP_INDEX ] = materialData.normalMap ?? - 1;
519
- data[ stride + M.ROUGHNESS_MAP_INDEX ] = materialData.roughnessMap ?? - 1;
520
- data[ stride + M.METALNESS_MAP_INDEX ] = materialData.metalnessMap ?? - 1;
521
- data[ stride + M.EMISSIVE_MAP_INDEX ] = materialData.emissiveMap ?? - 1;
522
- data[ stride + M.BUMP_MAP_INDEX ] = materialData.bumpMap ?? - 1;
523
-
524
- data[ stride + M.CLEARCOAT ] = materialData.clearcoat ?? 0;
525
- data[ stride + M.CLEARCOAT_ROUGHNESS ] = materialData.clearcoatRoughness ?? 0;
526
- data[ stride + M.OPACITY ] = materialData.opacity ?? 1;
527
- data[ stride + M.SIDE ] = materialData.side ?? 0;
528
- // Mirror side into per-triangle data so BVH traversal avoids a material-buffer read.
529
- this._patchTriangleSideForMaterial( materialIndex, materialData.side ?? 0 );
530
- // Recompute shadow-ray opaque-blocker flag (reads alphaMode/transparent/transmission/opacity from buffer).
525
+ // Both read back the block, so they must follow the write.
526
+ this._patchTriangleSideForMaterial( materialIndex, data[ base + M.SIDE ] );
531
527
  this._recomputeOpaqueBlockerForMaterial( materialIndex );
532
- data[ stride + M.TRANSPARENT ] = materialData.transparent ?? 0;
533
- data[ stride + M.ALPHA_TEST ] = materialData.alphaTest ?? 0;
534
- data[ stride + M.ALPHA_MODE ] = materialData.alphaMode ?? 0;
535
- data[ stride + M.DEPTH_WRITE ] = materialData.depthWrite ?? 1;
536
- data[ stride + M.NORMAL_SCALE ] = materialData.normalScale?.x ?? ( typeof materialData.normalScale === 'number' ? materialData.normalScale : 1 );
537
- data[ stride + M.NORMAL_SCALE + 1 ] = materialData.normalScale?.y ?? ( typeof materialData.normalScale === 'number' ? materialData.normalScale : 1 );
538
- data[ stride + M.BUMP_SCALE ] = materialData.bumpScale ?? 1;
539
- data[ stride + M.DISPLACEMENT_SCALE ] = materialData.displacementScale ?? 1;
540
- data[ stride + M.DISPLACEMENT_MAP_INDEX ] = materialData.displacementMap ?? - 1;
541
-
542
- // Subsurface scattering
543
- data[ stride + M.SUBSURFACE ] = materialData.subsurface ?? 0;
544
- if ( materialData.subsurfaceColor ) {
545
-
546
- data[ stride + M.SUBSURFACE_COLOR ] = materialData.subsurfaceColor.r ?? materialData.subsurfaceColor[ 0 ] ?? 1;
547
- data[ stride + M.SUBSURFACE_COLOR + 1 ] = materialData.subsurfaceColor.g ?? materialData.subsurfaceColor[ 1 ] ?? 1;
548
- data[ stride + M.SUBSURFACE_COLOR + 2 ] = materialData.subsurfaceColor.b ?? materialData.subsurfaceColor[ 2 ] ?? 1;
549
-
550
- }
551
-
552
- if ( materialData.subsurfaceRadius ) {
553
-
554
- data[ stride + M.SUBSURFACE_RADIUS ] = materialData.subsurfaceRadius[ 0 ] ?? 1;
555
- data[ stride + M.SUBSURFACE_RADIUS + 1 ] = materialData.subsurfaceRadius[ 1 ] ?? 0.2;
556
- data[ stride + M.SUBSURFACE_RADIUS + 2 ] = materialData.subsurfaceRadius[ 2 ] ?? 0.1;
557
-
558
- }
559
-
560
- data[ stride + M.SUBSURFACE_RADIUS_SCALE ] = materialData.subsurfaceRadiusScale ?? 1;
561
- data[ stride + M.SUBSURFACE_ANISOTROPY ] = materialData.subsurfaceAnisotropy ?? 0;
562
-
563
- // Surface specular anisotropy (map index defaults to -1 = none)
564
- data[ stride + M.ANISOTROPY ] = materialData.anisotropy ?? 0;
565
- data[ stride + M.ANISOTROPY_ROTATION ] = materialData.anisotropyRotation ?? 0;
566
- data[ stride + M.ANISOTROPY_MAP_INDEX ] = materialData.anisotropyMap ?? - 1;
567
-
568
- // Extension-texture map indices (packed bucket index, -1 = none)
569
- data[ stride + M.TRANSMISSION_MAP_INDEX ] = materialData.transmissionMap ?? - 1;
570
- data[ stride + M.CLEARCOAT_MAP_INDEX ] = materialData.clearcoatMap ?? - 1;
571
- data[ stride + M.CLEARCOAT_ROUGHNESS_MAP_INDEX ] = materialData.clearcoatRoughnessMap ?? - 1;
572
- data[ stride + M.SHEEN_COLOR_MAP_INDEX ] = materialData.sheenColorMap ?? - 1;
573
- data[ stride + M.SHEEN_ROUGHNESS_MAP_INDEX ] = materialData.sheenRoughnessMap ?? - 1;
574
- data[ stride + M.IRIDESCENCE_MAP_INDEX ] = materialData.iridescenceMap ?? - 1;
575
- data[ stride + M.IRIDESCENCE_THICKNESS_MAP_INDEX ] = materialData.iridescenceThicknessMap ?? - 1;
576
- data[ stride + M.SPECULAR_INTENSITY_MAP_INDEX ] = materialData.specularIntensityMap ?? - 1;
577
- data[ stride + M.SPECULAR_COLOR_MAP_INDEX ] = materialData.specularColorMap ?? - 1;
578
-
579
- // Texture transformation matrices (8 floats per slot = matrix elements[0..7];
580
- // element[8]=1 is reconstructed on the GPU by arrayToMat3, so it is NOT stored —
581
- // writing a 9th float here would spill into the next slot / subsurfaceColor).
582
- const identity = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
583
- const transformEntries = [
584
- { key: 'mapMatrix', offset: M.ALBEDO_TRANSFORM },
585
- { key: 'normalMapMatrices', offset: M.NORMAL_TRANSFORM },
586
- { key: 'roughnessMapMatrices', offset: M.ROUGHNESS_TRANSFORM },
587
- { key: 'metalnessMapMatrices', offset: M.METALNESS_TRANSFORM },
588
- { key: 'emissiveMapMatrices', offset: M.EMISSIVE_TRANSFORM },
589
- { key: 'bumpMapMatrices', offset: M.BUMP_TRANSFORM },
590
- { key: 'displacementMapMatrices', offset: M.DISPLACEMENT_TRANSFORM }
591
- ];
592
-
593
- for ( const { key, offset } of transformEntries ) {
594
-
595
- const matrix = materialData[ key ] ?? identity;
596
- for ( let i = 0; i < 8; i ++ ) {
597
-
598
- if ( stride + offset + i < data.length ) {
599
-
600
- data[ stride + offset + i ] = matrix[ i ];
601
-
602
- }
603
-
604
- }
605
-
606
- }
607
528
 
608
529
  this.materialStorageAttr.needsUpdate = true;
609
-
610
530
  this._notifyReset();
611
531
 
612
532
  }
@@ -625,24 +545,12 @@ export class MaterialDataManager {
625
545
  // (a genuinely new map → the caller must rebuildMaterials to add it to a bucket array).
626
546
  if ( this._srgbTexPacked || this._linearTexPacked ) {
627
547
 
628
- completeMaterialData.map = this.getPackedTextureIndex( material.map, true );
629
- completeMaterialData.emissiveMap = this.getPackedTextureIndex( material.emissiveMap, true );
630
- completeMaterialData.normalMap = this.getPackedTextureIndex( material.normalMap, false );
631
- completeMaterialData.bumpMap = this.getPackedTextureIndex( material.bumpMap, false );
632
- completeMaterialData.roughnessMap = this.getPackedTextureIndex( material.roughnessMap, false );
633
- completeMaterialData.metalnessMap = this.getPackedTextureIndex( material.metalnessMap, false );
634
- completeMaterialData.displacementMap = this.getPackedTextureIndex( material.displacementMap, false );
635
- completeMaterialData.anisotropyMap = this.getPackedTextureIndex( material.anisotropyMap, false );
636
- // Extension maps — color maps (sheenColor, specularColor) are sRGB; the rest are data (linear)
637
- completeMaterialData.transmissionMap = this.getPackedTextureIndex( material.transmissionMap, false );
638
- completeMaterialData.clearcoatMap = this.getPackedTextureIndex( material.clearcoatMap, false );
639
- completeMaterialData.clearcoatRoughnessMap = this.getPackedTextureIndex( material.clearcoatRoughnessMap, false );
640
- completeMaterialData.sheenColorMap = this.getPackedTextureIndex( material.sheenColorMap, true );
641
- completeMaterialData.sheenRoughnessMap = this.getPackedTextureIndex( material.sheenRoughnessMap, false );
642
- completeMaterialData.iridescenceMap = this.getPackedTextureIndex( material.iridescenceMap, false );
643
- completeMaterialData.iridescenceThicknessMap = this.getPackedTextureIndex( material.iridescenceThicknessMap, false );
644
- completeMaterialData.specularIntensityMap = this.getPackedTextureIndex( material.specularIntensityMap, false );
645
- completeMaterialData.specularColorMap = this.getPackedTextureIndex( material.specularColorMap, true );
548
+ const textures = resolveMaterialTextures( material );
549
+ for ( const [ key, isSrgb ] of TEXTURE_POOLS ) {
550
+
551
+ completeMaterialData[ key ] = this.getPackedTextureIndex( textures[ key ], isSrgb );
552
+
553
+ }
646
554
 
647
555
  }
648
556
 
@@ -800,6 +708,8 @@ export class MaterialDataManager {
800
708
  this.linearBuckets = null;
801
709
  this._srgbTexPacked = null;
802
710
  this._linearTexPacked = null;
711
+ this._sources = [];
712
+ this._hostSet = [];
803
713
 
804
714
  }
805
715
 
@@ -299,6 +299,7 @@ export class UniformManager {
299
299
  samplingTechniqueUniform.value = DEFAULT_STATE.samplingTechnique;
300
300
 
301
301
  u( 'fireflyThreshold', DEFAULT_STATE.fireflyThreshold, 'float' );
302
+ u( 'shadowTerminatorOffset', DEFAULT_STATE.shadowTerminatorOffset, 'float' );
302
303
 
303
304
  // Emissive
304
305
  ub( 'enableEmissiveTriangleSampling', DEFAULT_STATE.enableEmissiveTriangleSampling );
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { EngineEvents } from '../EngineEvents.js';
10
- import { PRODUCTION_RENDER_CONFIG } from '../EngineDefaults.js';
10
+ import { PRODUCTION_RENDER_CONFIG, modePresetSettings } from '../EngineDefaults.js';
11
11
  import { updateStats, getDisplaySamples } from '../Processor/utils.js';
12
12
 
13
13
  export class VideoRenderManager {
@@ -273,11 +273,11 @@ export class VideoRenderManager {
273
273
  _saveState() {
274
274
 
275
275
  const app = this._app;
276
+ const effective = app.settings.getEffective();
276
277
 
277
278
  return {
278
- maxSamples: app.settings.get( 'maxSamples' ),
279
- maxBounces: app.settings.get( 'maxBounces' ),
280
- transmissiveBounces: app.settings.get( 'transmissiveBounces' ),
279
+ // Everything configureForMode( 'production' ) overwrites, with who set it.
280
+ settings: Object.keys( modePresetSettings( PRODUCTION_RENDER_CONFIG ) ).map( key => [ key, effective[ key ] ] ),
281
281
  renderMode: app.stages.pathTracer?.renderMode?.value,
282
282
  controlsEnabled: app.cameraManager.controls?.enabled,
283
283
  oidnEnabled: app.denoisingManager?.finalDenoise,
@@ -296,11 +296,11 @@ export class VideoRenderManager {
296
296
 
297
297
  const app = this._app;
298
298
 
299
- app.settings.setMany( {
300
- maxSamples: state.maxSamples,
301
- maxBounces: state.maxBounces,
302
- transmissiveBounces: state.transmissiveBounces,
303
- }, { silent: true } );
299
+ for ( const [ key, saved ] of state.settings ) {
300
+
301
+ if ( saved ) app.settings.set( key, saved.value, { silent: true, reset: false, source: saved.source } );
302
+
303
+ }
304
304
 
305
305
  if ( app.stages.pathTracer && state.renderMode !== undefined ) {
306
306