rayzee 5.10.2 → 6.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 (43) hide show
  1. package/README.md +82 -22
  2. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js +2 -0
  3. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js.map +1 -0
  4. package/dist/rayzee.es.js +1299 -1843
  5. package/dist/rayzee.es.js.map +1 -1
  6. package/dist/rayzee.umd.js +50 -74
  7. package/dist/rayzee.umd.js.map +1 -1
  8. package/package.json +1 -4
  9. package/src/AssetConfig.js +56 -0
  10. package/src/EngineDefaults.js +5 -3
  11. package/src/EngineEvents.js +1 -0
  12. package/src/Passes/AIUpscaler.js +44 -22
  13. package/src/Passes/OIDNDenoiser.js +4 -104
  14. package/src/PathTracerApp.js +59 -63
  15. package/src/Processor/AssetLoader.js +5 -2
  16. package/src/Processor/TextureCreator.js +42 -15
  17. package/src/Processor/Workers/AIUpscalerWorker.js +21 -6
  18. package/src/Stages/ASVGF.js +6 -27
  19. package/src/Stages/AdaptiveSampling.js +10 -26
  20. package/src/Stages/PathTracer.js +4 -5
  21. package/src/TSL/BVHTraversal.js +2 -18
  22. package/src/TSL/Clearcoat.js +1 -2
  23. package/src/TSL/Common.js +0 -13
  24. package/src/TSL/EmissiveSampling.js +0 -16
  25. package/src/TSL/Environment.js +0 -7
  26. package/src/TSL/LightsDirect.js +3 -379
  27. package/src/TSL/LightsSampling.js +0 -171
  28. package/src/TSL/MaterialEvaluation.js +3 -103
  29. package/src/TSL/MaterialProperties.js +1 -56
  30. package/src/TSL/MaterialSampling.js +2 -284
  31. package/src/TSL/MaterialTransmission.js +0 -93
  32. package/src/TSL/Random.js +0 -23
  33. package/src/TSL/Struct.js +0 -21
  34. package/src/TSL/TextureSampling.js +0 -69
  35. package/src/index.js +5 -2
  36. package/src/managers/DenoisingManager.js +13 -5
  37. package/src/managers/OverlayManager.js +14 -2
  38. package/src/managers/VideoRenderManager.js +4 -4
  39. package/dist/assets/AIUpscalerWorker-D58dcMrY.js +0 -2
  40. package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +0 -1
  41. package/src/Processor/createRenderTargetHelper.js +0 -521
  42. package/src/TSL/RayIntersection.js +0 -162
  43. package/src/managers/helpers/StatsHelper.js +0 -45
@@ -351,7 +351,8 @@ class TextureCache {
351
351
  const width = texture.image.width || 0;
352
352
  const height = texture.image.height || 0;
353
353
  const src = texture.image.src || texture.uuid || '';
354
- hash += `${width}x${height}_${src.slice( - 8 )}_`;
354
+ const flipFlag = texture.flipY === false ? 'n' : 'f';
355
+ hash += `${width}x${height}_${src.slice( - 8 )}_${flipFlag}_`;
355
356
 
356
357
  }
357
358
 
@@ -681,12 +682,16 @@ export class TextureCreator {
681
682
 
682
683
  if ( ! texture?.image ) continue;
683
684
 
685
+ const flipY = texture.flipY !== false;
686
+
684
687
  try {
685
688
 
686
689
  // Option 1: Direct ImageBitmap transfer (when supported)
687
690
  if ( typeof createImageBitmap !== 'undefined' && texture.image instanceof HTMLImageElement ) {
688
691
 
689
- const bitmap = await createImageBitmap( texture.image );
692
+ const bitmap = await createImageBitmap( texture.image, {
693
+ imageOrientation: flipY ? 'flipY' : 'none',
694
+ } );
690
695
  texturesData.push( {
691
696
  bitmap: bitmap,
692
697
  width: texture.image.width,
@@ -696,16 +701,22 @@ export class TextureCreator {
696
701
 
697
702
  } else { // Option 2: Efficient canvas-based transfer
698
703
 
699
- const pair = this.canvasPool.getCanvasWithContext( texture.image.width, texture.image.height );
704
+ const w = texture.image.width;
705
+ const h = texture.image.height;
706
+ const bitmap = await createImageBitmap( texture.image, {
707
+ imageOrientation: flipY ? 'flipY' : 'none',
708
+ } );
709
+ const pair = this.canvasPool.getCanvasWithContext( w, h );
700
710
 
701
- pair.context.drawImage( texture.image, 0, 0 );
702
- const imageData = pair.context.getImageData( 0, 0, texture.image.width, texture.image.height );
711
+ pair.context.drawImage( bitmap, 0, 0 );
712
+ bitmap.close();
713
+ const imageData = pair.context.getImageData( 0, 0, w, h );
703
714
 
704
715
  // Transfer the underlying ArrayBuffer directly
705
716
  texturesData.push( {
706
717
  data: imageData.data.buffer, // Direct buffer transfer
707
- width: texture.image.width,
708
- height: texture.image.height,
718
+ width: w,
719
+ height: h,
709
720
  isImageData: true
710
721
  } );
711
722
 
@@ -759,11 +770,13 @@ export class TextureCreator {
759
770
  for ( let i = batchStart; i < batchEnd; i ++ ) {
760
771
 
761
772
  const texture = validTextures[ i ];
773
+ const flipY = texture.flipY !== false;
762
774
 
763
775
  const bitmapPromise = createImageBitmap( texture.image, {
764
776
  resizeWidth: maxWidth,
765
777
  resizeHeight: maxHeight,
766
- resizeQuality: 'high'
778
+ resizeQuality: 'high',
779
+ imageOrientation: flipY ? 'flipY' : 'none',
767
780
  } );
768
781
 
769
782
  batchPromises.push(
@@ -815,9 +828,16 @@ export class TextureCreator {
815
828
  for ( let i = 0; i < validTextures.length; i ++ ) {
816
829
 
817
830
  const texture = validTextures[ i ];
831
+ const bitmap = await createImageBitmap( texture.image, {
832
+ resizeWidth: maxWidth,
833
+ resizeHeight: maxHeight,
834
+ resizeQuality: 'high',
835
+ imageOrientation: texture.flipY !== false ? 'flipY' : 'none',
836
+ } );
818
837
 
819
838
  pair.context.clearRect( 0, 0, maxWidth, maxHeight );
820
- pair.context.drawImage( texture.image, 0, 0, maxWidth, maxHeight );
839
+ pair.context.drawImage( bitmap, 0, 0 );
840
+ bitmap.close();
821
841
 
822
842
  const imageData = pair.context.getImageData( 0, 0, maxWidth, maxHeight );
823
843
  const offset = maxWidth * maxHeight * 4 * i;
@@ -853,9 +873,16 @@ export class TextureCreator {
853
873
  for ( let i = 0; i < validTextures.length; i ++ ) {
854
874
 
855
875
  const texture = validTextures[ i ];
876
+ const bitmap = await createImageBitmap( texture.image, {
877
+ resizeWidth: maxWidth,
878
+ resizeHeight: maxHeight,
879
+ resizeQuality: 'high',
880
+ imageOrientation: texture.flipY !== false ? 'flipY' : 'none',
881
+ } );
856
882
 
857
883
  pair.context.clearRect( 0, 0, maxWidth, maxHeight );
858
- pair.context.drawImage( texture.image, 0, 0, maxWidth, maxHeight );
884
+ pair.context.drawImage( bitmap, 0, 0 );
885
+ bitmap.close();
859
886
 
860
887
  const imageData = pair.context.getImageData( 0, 0, maxWidth, maxHeight );
861
888
  const offset = maxWidth * maxHeight * 4 * i;
@@ -1301,7 +1328,7 @@ export class TextureCreator {
1301
1328
  const mip = tex.mipmaps[ 0 ];
1302
1329
  const idx = normalized.length;
1303
1330
  normalized.push( null ); // placeholder — filled after Promise.all
1304
- bitmapJobs.push( { index: idx, promise: _rawPixelsToBitmap( mip.data, mip.width, mip.height ) } );
1331
+ bitmapJobs.push( { index: idx, flipY: tex.flipY, promise: _rawPixelsToBitmap( mip.data, mip.width, mip.height ) } );
1305
1332
  continue;
1306
1333
 
1307
1334
  }
@@ -1323,7 +1350,7 @@ export class TextureCreator {
1323
1350
 
1324
1351
  const idx = normalized.length;
1325
1352
  normalized.push( null );
1326
- bitmapJobs.push( { index: idx, promise: _rawPixelsToBitmap( tex.image.data, tex.image.width, tex.image.height ) } );
1353
+ bitmapJobs.push( { index: idx, flipY: tex.flipY, promise: _rawPixelsToBitmap( tex.image.data, tex.image.width, tex.image.height ) } );
1327
1354
  continue;
1328
1355
 
1329
1356
  }
@@ -1339,14 +1366,14 @@ export class TextureCreator {
1339
1366
 
1340
1367
  for ( let i = 0; i < bitmapJobs.length; i ++ ) {
1341
1368
 
1342
- const { index } = bitmapJobs[ i ];
1369
+ const { index, flipY } = bitmapJobs[ i ];
1343
1370
  const result = results[ i ];
1344
1371
 
1345
1372
  if ( result.status === 'fulfilled' ) {
1346
1373
 
1347
1374
  const bitmap = result.value;
1348
1375
  bitmapsToClose.push( bitmap );
1349
- normalized[ index ] = { image: bitmap };
1376
+ normalized[ index ] = { image: bitmap, flipY };
1350
1377
 
1351
1378
  } else {
1352
1379
 
@@ -1367,7 +1394,7 @@ export class TextureCreator {
1367
1394
  const placeholder = new Uint8ClampedArray( [ 255, 255, 255, 255 ] );
1368
1395
  const bitmap = await createImageBitmap( new ImageData( placeholder, 1, 1 ) );
1369
1396
  bitmapsToClose.push( bitmap );
1370
- normalized[ i ] = { image: bitmap };
1397
+ normalized[ i ] = { image: bitmap, flipY: false };
1371
1398
 
1372
1399
  }
1373
1400
 
@@ -14,8 +14,13 @@
14
14
  * { type: 'error', message, id? }
15
15
  */
16
16
 
17
- // Loaded lazily via CDN to avoid bundling the 69 MB onnxruntime-web package
18
- const ORT_CDN_URL = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs';
17
+ // Asset config supplied via 'load' message from the main thread.
18
+ // Defaults match the upstream Rayzee deployment; override via configureAssets().
19
+ let _assetConfig = {
20
+ ortRuntimeUrl: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs',
21
+ ortWasmPaths: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/',
22
+ cacheNamespace: 'rayzee',
23
+ };
19
24
 
20
25
  let ort = null;
21
26
 
@@ -23,17 +28,23 @@ async function getOrt() {
23
28
 
24
29
  if ( ort ) return ort;
25
30
 
26
- ort = await import( /* @vite-ignore */ ORT_CDN_URL );
31
+ ort = await import( /* @vite-ignore */ _assetConfig.ortRuntimeUrl );
27
32
 
28
33
  // WASM paths for CDN delivery — WebGPU EP still uses WASM for lightweight shape ops
29
- ort.env.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/';
34
+ ort.env.wasm.wasmPaths = _assetConfig.ortWasmPaths;
30
35
  ort.env.logLevel = 'error';
31
36
 
32
37
  return ort;
33
38
 
34
39
  }
35
40
 
36
- const IDB_NAME = 'ai-upscaler-models';
41
+ // IndexedDB names are namespaced so multiple consumers on the same origin don't collide.
42
+ function getIdbName() {
43
+
44
+ return `${_assetConfig.cacheNamespace}:ai-upscaler-models`;
45
+
46
+ }
47
+
37
48
  const IDB_STORE = 'models';
38
49
 
39
50
  let session = null;
@@ -45,7 +56,7 @@ function openDB() {
45
56
 
46
57
  return new Promise( ( resolve, reject ) => {
47
58
 
48
- const req = indexedDB.open( IDB_NAME, 1 );
59
+ const req = indexedDB.open( getIdbName(), 1 );
49
60
  req.onupgradeneeded = () => req.result.createObjectStore( IDB_STORE );
50
61
  req.onsuccess = () => resolve( req.result );
51
62
  req.onerror = () => reject( req.error );
@@ -204,6 +215,10 @@ self.onmessage = async ( e ) => {
204
215
 
205
216
  if ( type === 'load' ) {
206
217
 
218
+ // Apply asset overrides from main thread before any network or cache access
219
+ if ( e.data.ortRuntimeUrl ) _assetConfig.ortRuntimeUrl = e.data.ortRuntimeUrl;
220
+ if ( e.data.ortWasmPaths ) _assetConfig.ortWasmPaths = e.data.ortWasmPaths;
221
+ if ( e.data.cacheNamespace ) _assetConfig.cacheNamespace = e.data.cacheNamespace;
207
222
  await loadModel( e.data.url, e.data.sessionOptions );
208
223
 
209
224
  } else if ( type === 'infer' ) {
@@ -4,7 +4,6 @@ import { Fn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform,
4
4
  import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
5
5
  import { HalfFloatType, FloatType, RGBAFormat, NearestFilter, LinearFilter } from 'three';
6
6
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
7
- import { createRenderTargetHelper } from '../Processor/createRenderTargetHelper.js';
8
7
  import { luminance } from '../TSL/Common.js';
9
8
 
10
9
  /**
@@ -25,7 +24,6 @@ export class ASVGF extends RenderStage {
25
24
  } );
26
25
 
27
26
  this.renderer = renderer;
28
- this.debugContainer = options.debugContainer || null;
29
27
 
30
28
  this.temporalAlpha = uniform( options.temporalAlpha ?? 0.1 );
31
29
  this.phiColor = uniform( options.phiColor ?? 10.0 );
@@ -101,17 +99,6 @@ export class ASVGF extends RenderStage {
101
99
 
102
100
  this._buildHeatmapCompute();
103
101
 
104
- this.heatmapHelper = createRenderTargetHelper( this.renderer, this.heatmapTarget, {
105
- width: 400,
106
- height: 400,
107
- position: 'bottom-right',
108
- theme: 'dark',
109
- title: 'ASVGF Debug',
110
- autoUpdate: false
111
- } );
112
- this.heatmapHelper.hide();
113
- ( this.debugContainer || document.body ).appendChild( this.heatmapHelper );
114
-
115
102
  this.frameCount = 0;
116
103
 
117
104
  }
@@ -568,7 +555,6 @@ export class ASVGF extends RenderStage {
568
555
 
569
556
  this.renderer.compute( this._heatmapComputeNode );
570
557
  this.renderer.copyTextureToTexture( this._heatmapStorageTex, this.heatmapTarget.texture );
571
- this.heatmapHelper.update();
572
558
 
573
559
  }
574
560
 
@@ -576,18 +562,14 @@ export class ASVGF extends RenderStage {
576
562
 
577
563
  }
578
564
 
579
- toggleHeatmap( enabled ) {
565
+ /**
566
+ * Enable or disable the heatmap compute pass. When enabled, the heatmap
567
+ * is rendered each frame to {@link this.heatmapTarget} (a public RenderTarget)
568
+ * for the host to display however it wants.
569
+ */
570
+ setHeatmapEnabled( enabled ) {
580
571
 
581
572
  this.showHeatmap = enabled;
582
- if ( enabled ) {
583
-
584
- this.heatmapHelper.show();
585
-
586
- } else {
587
-
588
- this.heatmapHelper.hide();
589
-
590
- }
591
573
 
592
574
  }
593
575
 
@@ -663,9 +645,6 @@ export class ASVGF extends RenderStage {
663
645
  this._heatmapMotionTexNode?.dispose();
664
646
  this._heatmapGradientTexNode?.dispose();
665
647
 
666
- // also removes the DOM node
667
- this.heatmapHelper?.dispose();
668
-
669
648
  }
670
649
 
671
650
  }
@@ -4,7 +4,6 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
4
  import { NearestFilter, LinearFilter, RGBAFormat, HalfFloatType, FloatType } from 'three';
5
5
  import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
6
  import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
7
- import { createRenderTargetHelper } from '../Processor/createRenderTargetHelper.js';
8
7
 
9
8
  // ── wgslFn helpers ──────────────────────────────────────────
10
9
 
@@ -150,7 +149,6 @@ export class AdaptiveSampling extends RenderStage {
150
149
  } );
151
150
 
152
151
  this.renderer = renderer;
153
- this.debugContainer = options.debugContainer || null;
154
152
  this.frameNumber = 0;
155
153
  this.delayByFrames = options.delayByFrames ?? 2;
156
154
  this.showAdaptiveSamplingHelper = false;
@@ -191,7 +189,8 @@ export class AdaptiveSampling extends RenderStage {
191
189
  this._heatmapStorageTex.minFilter = NearestFilter;
192
190
  this._heatmapStorageTex.magFilter = NearestFilter;
193
191
 
194
- // Heatmap render target — FloatType for clean CPU readback via createRenderTargetHelper
192
+ // Heatmap render target — FloatType, exposed as a public field for hosts to
193
+ // display via their own readback helper.
195
194
  this.heatmapTarget = new RenderTarget( w, h, {
196
195
  format: RGBAFormat,
197
196
  type: FloatType,
@@ -214,18 +213,6 @@ export class AdaptiveSampling extends RenderStage {
214
213
  this._buildCompute();
215
214
  this._buildHeatmapCompute();
216
215
 
217
- // Floating overlay for heatmap visualization
218
- this.helper = createRenderTargetHelper( this.renderer, this.heatmapTarget, {
219
- width: 400,
220
- height: 400,
221
- position: 'bottom-right',
222
- theme: 'dark',
223
- title: 'Adaptive Sampling',
224
- autoUpdate: false
225
- } );
226
- this.helper.hide();
227
- ( this.debugContainer || document.body ).appendChild( this.helper );
228
-
229
216
  }
230
217
 
231
218
  setupEventListeners() {
@@ -303,8 +290,8 @@ export class AdaptiveSampling extends RenderStage {
303
290
  * Reads the sampling guidance StorageTexture via textureLoad and maps
304
291
  * normalizedSamples to a smooth blue→cyan→green→yellow→red gradient.
305
292
  * Converged pixels are desaturated, brightness is modulated by variance.
306
- * Writes to _heatmapStorageTex, then copied to heatmapTarget for
307
- * createRenderTargetHelper display.
293
+ * Writes to _heatmapStorageTex, then copied to the public heatmapTarget
294
+ * RenderTarget so the host can display it.
308
295
  */
309
296
  _buildHeatmapCompute() {
310
297
 
@@ -343,13 +330,13 @@ export class AdaptiveSampling extends RenderStage {
343
330
  }
344
331
 
345
332
  /**
346
- * Toggle heatmap overlay visibility.
347
- * @param {boolean} val show/hide
333
+ * Enable or disable the heatmap compute pass. When enabled, the heatmap is
334
+ * rendered each frame to {@link this.heatmapTarget} (a public RenderTarget)
335
+ * for the host to display however it wants.
348
336
  */
349
- toggleHelper( val ) {
337
+ setHeatmapEnabled( enabled ) {
350
338
 
351
- this.showAdaptiveSamplingHelper = val;
352
- val ? this.helper.show() : this.helper.hide();
339
+ this.showAdaptiveSamplingHelper = enabled;
353
340
 
354
341
  }
355
342
 
@@ -387,12 +374,11 @@ export class AdaptiveSampling extends RenderStage {
387
374
  // (StorageTexture extends Texture, works as regular texture for sampling)
388
375
  context.setTexture( 'adaptiveSampling:output', this._outputStorageTex );
389
376
 
390
- // Render heatmap + update helper overlay if visualization enabled
377
+ // Render heatmap into public heatmapTarget when enabled
391
378
  if ( this.showAdaptiveSamplingHelper ) {
392
379
 
393
380
  this.renderer.compute( this._heatmapComputeNode );
394
381
  this.renderer.copyTextureToTexture( this._heatmapStorageTex, this.heatmapTarget.texture );
395
- this.helper.update();
396
382
 
397
383
  }
398
384
 
@@ -484,7 +470,6 @@ export class AdaptiveSampling extends RenderStage {
484
470
  this._outputStorageTex?.dispose();
485
471
  this.heatmapTarget?.dispose();
486
472
  this._varianceTexNode?.dispose();
487
- this.helper?.dispose();
488
473
 
489
474
  this._computeNode = null;
490
475
  this._heatmapComputeNode = null;
@@ -492,7 +477,6 @@ export class AdaptiveSampling extends RenderStage {
492
477
  this._outputStorageTex = null;
493
478
  this.heatmapTarget = null;
494
479
  this._varianceTexNode = null;
495
- this.helper = null;
496
480
 
497
481
  }
498
482
 
@@ -25,10 +25,7 @@ import { LightSerializer } from '../Processor/LightSerializer';
25
25
 
26
26
  // Constants
27
27
  import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
28
-
29
- // STBN atlases - original source: https://github.com/NVIDIA-RTX/STBN/blob/main/Assets/STBN.zip
30
- const stbnScalarAtlas = 'https://assets.rayzee.atulmourya.com/noise/stbn_scalar_atlas.png';
31
- const stbnVec2Atlas = 'https://assets.rayzee.atulmourya.com/noise/stbn_vec2_atlas.png';
28
+ import { getAssetConfig } from '../AssetConfig.js';
32
29
 
33
30
  /**
34
31
  * Data layout constants
@@ -50,7 +47,7 @@ const BVH_VEC4_PER_NODE = 4;
50
47
  * Events emitted:
51
48
  * - pathtracer:frameComplete - When a frame finishes rendering
52
49
  * - camera:moved - When camera position/orientation changes
53
- * - tile:changed - When current tile changes (for OverlayManager TileHelper)
50
+ * - tile:changed - When current tile changes (for OverlayManager TileHelper). @internal — payload carries internal tile coordinates and may change without notice.
54
51
  * - asvgf:reset - Request ASVGF to reset temporal data
55
52
  * - asvgf:updateParameters - Update ASVGF parameters
56
53
  * - asvgf:setTemporal - Enable/disable ASVGF temporal accumulation
@@ -397,6 +394,8 @@ export class PathTracer extends RenderStage {
397
394
 
398
395
  };
399
396
 
397
+ const { stbnScalarAtlas, stbnVec2Atlas } = getAssetConfig();
398
+
400
399
  loader.load( stbnScalarAtlas, ( tex ) => {
401
400
 
402
401
  this.stbnScalarTexture = configure( tex );
@@ -117,27 +117,11 @@ const fastRayAABBDst = wgslFn( `
117
117
  }
118
118
  ` );
119
119
 
120
- // ================================================================================
121
- // VISIBILITY FUNCTIONS
122
- // ================================================================================
123
-
124
- // Side culling — 1 buffer read (slot 10 only)
125
- // Per-mesh visibility handled at BLAS-pointer level; material visibility always 1.
126
- export const passesSideCulling = Fn( ( [ materialIndex, rayDirection, normal, materialBuffer ] ) => {
127
-
128
- const sideData = getDatafromStorageBuffer( materialBuffer, materialIndex, int( MATERIAL_SLOT.OPACITY_ALPHA ), int( MATERIAL_SLOTS ) );
129
- const side = int( sideData.g );
130
- const rayDotNormal = rayDirection.dot( normal );
131
- const doubleSide = side.equal( int( 2 ) );
132
- const frontSide = side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) );
133
- const backSide = side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) );
134
- return doubleSide.or( frontSide ).or( backSide );
135
-
136
- } );
137
-
138
120
  // ================================================================================
139
121
  // MAIN BVH TRAVERSAL
140
122
  // ================================================================================
123
+ // Side culling is performed inline inside traverseBVH/traverseBVHShadow using
124
+ // the per-triangle side flag stored in normalCData.w (slot 5, .w channel).
141
125
 
142
126
  export const traverseBVH = Fn( ( [
143
127
  ray,
@@ -1,6 +1,5 @@
1
1
  // Clearcoat BRDF - Ported from clearcoat.fs
2
- // Note: evaluateLayeredBRDF and calculateLayerAttenuation functions
3
- // are in MaterialEvaluation.js
2
+ // Note: evaluateLayeredBRDF lives in MaterialEvaluation.js
4
3
 
5
4
  import {
6
5
  Fn,
package/src/TSL/Common.js CHANGED
@@ -126,19 +126,6 @@ export const applyDithering = wgslFn( `
126
126
  }
127
127
  ` );
128
128
 
129
- // Firefly clamping — exact port of GLSL
130
- export const reduceFireflies = wgslFn( `
131
- fn reduceFireflies( color: vec3f, maxValue: f32 ) -> vec3f {
132
-
133
- let lum = dot( color, vec3f( 0.2126, 0.7152, 0.0722 ) );
134
- if ( lum > maxValue ) {
135
- return color * ( maxValue / lum );
136
- }
137
- return color;
138
-
139
- }
140
- ` );
141
-
142
129
  // Construct tangent-bitangent-normal matrix — exact port of GLSL
143
130
  export const constructTBN = wgslFn( `
144
131
  fn constructTBN( N: vec3f ) -> mat3x3f {
@@ -260,22 +260,6 @@ export const isEmissive = Fn( ( [ material ] ) => {
260
260
 
261
261
  } );
262
262
 
263
- // Calculate emissive power of a triangle
264
- export const calculateEmissivePower = Fn( ( [ material, area ] ) => {
265
-
266
- const result = float( 0.0 ).toVar();
267
-
268
- If( isEmissive( material ), () => {
269
-
270
- const avgEmissive = material.emissive.x.add( material.emissive.y ).add( material.emissive.z ).div( 3.0 );
271
- result.assign( avgEmissive.mul( material.emissiveIntensity ).mul( area ) );
272
-
273
- } );
274
-
275
- return result;
276
-
277
- } );
278
-
279
263
  // ================================================================================
280
264
  // TRIANGLE DATA ACCESS
281
265
  // ================================================================================
@@ -28,13 +28,6 @@ export const equirectUvToDirection = /*@__PURE__*/ wgslFn( `
28
28
  }
29
29
  ` );
30
30
 
31
- // Sample environment map color in a given direction
32
- export const sampleEquirectColor = Fn( ( [ environment, direction, environmentMatrix ] ) => {
33
-
34
- return texture( environment, equirectDirectionToUv( { direction, environmentMatrix } ), 0 ).rgb;
35
-
36
- } );
37
-
38
31
  // Calculate PDF for uniform sphere sampling with Jacobian
39
32
  export const equirectDirectionPdf = /*@__PURE__*/ wgslFn( `
40
33
  fn equirectDirectionPdf( direction: vec3f, environmentMatrix: mat4x4f ) -> f32 {