rayzee 6.4.0 → 7.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 (58) hide show
  1. package/README.md +24 -5
  2. package/dist/rayzee.es.js +4953 -4225
  3. package/dist/rayzee.es.js.map +1 -1
  4. package/dist/rayzee.umd.js +157 -236
  5. package/dist/rayzee.umd.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/EngineDefaults.js +29 -13
  8. package/src/PathTracerApp.js +119 -26
  9. package/src/Pipeline/PipelineContext.js +1 -2
  10. package/src/Pipeline/RenderPipeline.js +1 -1
  11. package/src/Pipeline/RenderStage.js +1 -1
  12. package/src/Processor/CameraOptimizer.js +0 -5
  13. package/src/Processor/GeometryExtractor.js +22 -1
  14. package/src/Processor/KernelManager.js +277 -0
  15. package/src/Processor/PackedRayBuffer.js +265 -0
  16. package/src/Processor/QueueManager.js +173 -0
  17. package/src/Processor/SceneProcessor.js +1 -0
  18. package/src/Processor/ShaderBuilder.js +11 -316
  19. package/src/Processor/StorageTexturePool.js +29 -15
  20. package/src/Processor/TextureCreator.js +6 -0
  21. package/src/Processor/VRAMTracker.js +169 -0
  22. package/src/Processor/utils.js +11 -110
  23. package/src/RenderSettings.js +1 -3
  24. package/src/Stages/ASVGF.js +76 -20
  25. package/src/Stages/BilateralFilter.js +34 -10
  26. package/src/Stages/EdgeFilter.js +2 -3
  27. package/src/Stages/MotionVector.js +16 -9
  28. package/src/Stages/NormalDepth.js +17 -5
  29. package/src/Stages/PathTracer.js +671 -1456
  30. package/src/Stages/PathTracerStage.js +1451 -0
  31. package/src/Stages/SSRC.js +32 -15
  32. package/src/Stages/Variance.js +35 -12
  33. package/src/TSL/BVHTraversal.js +7 -1
  34. package/src/TSL/Common.js +12 -2
  35. package/src/TSL/CompactKernel.js +110 -0
  36. package/src/TSL/DebugKernel.js +98 -0
  37. package/src/TSL/Environment.js +13 -11
  38. package/src/TSL/ExtendKernel.js +75 -0
  39. package/src/TSL/FinalWriteKernel.js +121 -0
  40. package/src/TSL/GenerateKernel.js +109 -0
  41. package/src/TSL/LightsSampling.js +2 -2
  42. package/src/TSL/MaterialTransmission.js +32 -2
  43. package/src/TSL/PathTracerCore.js +43 -912
  44. package/src/TSL/ShadeKernel.js +873 -0
  45. package/src/TSL/Struct.js +5 -0
  46. package/src/TSL/Subsurface.js +232 -0
  47. package/src/TSL/patches.js +81 -4
  48. package/src/index.js +3 -0
  49. package/src/managers/CameraManager.js +1 -1
  50. package/src/managers/DenoisingManager.js +40 -75
  51. package/src/managers/EnvironmentManager.js +30 -39
  52. package/src/managers/MaterialDataManager.js +60 -1
  53. package/src/managers/OverlayManager.js +7 -22
  54. package/src/managers/UniformManager.js +1 -3
  55. package/src/managers/helpers/TileHelper.js +2 -2
  56. package/src/Stages/AdaptiveSampling.js +0 -483
  57. package/src/TSL/PathTracer.js +0 -384
  58. package/src/managers/TileManager.js +0 -298
@@ -7,10 +7,8 @@
7
7
  * is mutated to preserve TSL shader graph references after compilation.
8
8
  */
9
9
 
10
- import { StorageInstancedBufferAttribute } from 'three/webgpu';
11
- import { storage } from 'three/tsl';
12
10
  import {
13
- RGBAFormat, FloatType, Vector2, Vector3, Color, Matrix4, DataTexture,
11
+ RGBAFormat, RedFormat, FloatType, Vector2, Vector3, Color, Matrix4, DataTexture,
14
12
  } from 'three';
15
13
  import { EquirectHDRInfo } from '../Processor/EquirectHDRInfo.js';
16
14
  import { ProceduralSky } from '../Processor/ProceduralSky.js';
@@ -43,12 +41,10 @@ export class EnvironmentManager {
43
41
  this.environmentTexture = this._envPlaceholder;
44
42
  this.envTexSize = new Vector2();
45
43
 
46
- // CDF storage buffer (marginal + conditional packed into one buffer).
47
- // Layout: [ marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats) ]
48
- // Conditional offset is the marginal length, which equals envResolution.y at runtime.
49
- this.envCDFStorageAttr = null;
50
- this.envCDFStorageNode = null;
51
- this._initCDFStorageBuffers();
44
+ // Importance-sampling CDF as an R32F texture (frees a Shade storage-buffer binding the limit is 10).
45
+ // Layout: (envResolution.x + 1) × envResolution.y; conditional in columns [0,W), marginal in column W.
46
+ this.envCDFTexture = null;
47
+ this._initCDFTexture();
52
48
 
53
49
  // Environment rotation
54
50
  this.environmentRotationMatrix = new Matrix4();
@@ -183,46 +179,42 @@ export class EnvironmentManager {
183
179
  * Initialize the packed CDF storage buffer with placeholder data.
184
180
  * Must be called before shader compilation so the node exists in the graph.
185
181
  *
186
- * Layout: [ marginal (size = envResolution.y) | conditional (size = envResolution.x * envResolution.y) ]
187
- * Placeholder shape is a 1x2 env map: marginal=[0,1], conditional=[0,0,1,1].
182
+ * 1×1 R32F placeholder until a real env CDF is built (env IS is off meanwhile).
188
183
  * @private
189
184
  */
190
- _initCDFStorageBuffers() {
185
+ _initCDFTexture() {
191
186
 
192
- const placeholder = new Float32Array( [ 0, 1, 0, 0, 1, 1 ] );
193
- this.envCDFStorageAttr = new StorageInstancedBufferAttribute( placeholder, 1 );
194
- this.envCDFStorageNode = storage( this.envCDFStorageAttr, 'float', placeholder.length ).toReadOnly();
187
+ this.envCDFTexture = new DataTexture( new Float32Array( [ 0 ] ), 1, 1, RedFormat, FloatType );
188
+ this.envCDFTexture.needsUpdate = true;
195
189
 
196
190
  }
197
191
 
198
192
  /**
199
- * Update the packed CDF storage buffer from equirectHdrInfo.
200
- * Concatenates marginal + conditional into one buffer.
193
+ * Rebuild the CDF texture from equirectHdrInfo. Packs the 2D conditional + 1D marginal into one
194
+ * R32F texture of (W+1)×H: conditional[cy*W+cx] at texel (cx, cy); marginal[cy] at texel (W, cy).
201
195
  * @private
202
196
  */
203
- _updateCDFStorageBuffers() {
197
+ _updateCDFTexture() {
204
198
 
205
199
  const marginal = this.equirectHdrInfo.marginalData;
206
200
  const conditional = this.equirectHdrInfo.conditionalData;
207
201
  if ( ! marginal || ! conditional ) return;
208
202
 
209
- const combined = new Float32Array( marginal.length + conditional.length );
210
- combined.set( marginal, 0 );
211
- combined.set( conditional, marginal.length );
203
+ const W = this.equirectHdrInfo.width;
204
+ const H = this.equirectHdrInfo.height;
205
+ const texW = W + 1;
206
+ const data = new Float32Array( texW * H );
207
+ for ( let cy = 0; cy < H; cy ++ ) {
212
208
 
213
- this.envCDFStorageAttr = new StorageInstancedBufferAttribute( combined, 1 );
214
- this.envCDFStorageNode.value = this.envCDFStorageAttr;
215
- this.envCDFStorageNode.bufferCount = combined.length;
209
+ const dstRow = cy * texW;
210
+ data.set( conditional.subarray( cy * W, cy * W + W ), dstRow ); // conditional → columns [0,W)
211
+ data[ dstRow + W ] = marginal[ cy ]; // marginal → column W
216
212
 
217
- }
218
-
219
- /**
220
- * Get the packed CDF storage node for shader graph.
221
- * @returns {{ cdfNode: StorageNode }}
222
- */
223
- getCDFStorageNodes() {
213
+ }
224
214
 
225
- return { cdfNode: this.envCDFStorageNode };
215
+ this.envCDFTexture?.dispose?.();
216
+ this.envCDFTexture = new DataTexture( data, texW, H, RedFormat, FloatType );
217
+ this.envCDFTexture.needsUpdate = true;
226
218
 
227
219
  }
228
220
 
@@ -278,7 +270,7 @@ export class EnvironmentManager {
278
270
 
279
271
  if ( ! this.scene.environment ) {
280
272
 
281
- this._updateCDFStorageBuffers();
273
+ this._updateCDFTexture();
282
274
  this.uniforms.set( 'envTotalSum', 0.0 );
283
275
  this.uniforms.set( 'envCompensationDelta', 0.0 );
284
276
  this.uniforms.set( 'useEnvMapIS', 0 );
@@ -293,7 +285,7 @@ export class EnvironmentManager {
293
285
 
294
286
  if ( ! textureForCDF.image ) {
295
287
 
296
- this._updateCDFStorageBuffers();
288
+ this._updateCDFTexture();
297
289
  this.uniforms.set( 'envTotalSum', 0.0 );
298
290
  this.uniforms.set( 'envCompensationDelta', 0.0 );
299
291
  this.uniforms.set( 'useEnvMapIS', 0 );
@@ -313,7 +305,7 @@ export class EnvironmentManager {
313
305
 
314
306
  this.cdfBuildTime = performance.now() - startTime;
315
307
 
316
- this._updateCDFStorageBuffers();
308
+ this._updateCDFTexture();
317
309
  this.uniforms.set( 'envTotalSum', this.equirectHdrInfo.totalSum );
318
310
  this.uniforms.set( 'envCompensationDelta', this.equirectHdrInfo.compensationDelta );
319
311
  this.uniforms.set( 'useEnvMapIS', 1 );
@@ -377,7 +369,7 @@ export class EnvironmentManager {
377
369
 
378
370
  } else {
379
371
 
380
- this._updateCDFStorageBuffers();
372
+ this._updateCDFTexture();
381
373
  this.uniforms.set( 'envTotalSum', 0.0 );
382
374
  this.uniforms.set( 'envCompensationDelta', 0.0 );
383
375
  this.uniforms.set( 'useEnvMapIS', 0 );
@@ -543,9 +535,8 @@ export class EnvironmentManager {
543
535
  this.proceduralSkyRenderer = null;
544
536
  this.simpleSkyRenderer = null;
545
537
 
546
- this.envCDFStorageAttr?.dispose?.();
547
- this.envCDFStorageAttr = null;
548
- this.envCDFStorageNode = null;
538
+ this.envCDFTexture?.dispose?.();
539
+ this.envCDFTexture = null;
549
540
 
550
541
  // Dispose the HDRI environment texture unless it's the shared placeholder
551
542
  // (the placeholder is handled separately just below).
@@ -307,6 +307,41 @@ export class MaterialDataManager {
307
307
  break;
308
308
  case 'bumpScale': data[ stride + M.BUMP_SCALE ] = value; break;
309
309
  case 'displacementScale': data[ stride + M.DISPLACEMENT_SCALE ] = value; break;
310
+ case 'subsurface': data[ stride + M.SUBSURFACE ] = value; break;
311
+ case 'subsurfaceRadiusScale': data[ stride + M.SUBSURFACE_RADIUS_SCALE ] = value; break;
312
+ case 'subsurfaceAnisotropy': data[ stride + M.SUBSURFACE_ANISOTROPY ] = value; break;
313
+ case 'subsurfaceColor':
314
+ if ( value.r !== undefined ) {
315
+
316
+ data[ stride + M.SUBSURFACE_COLOR ] = value.r;
317
+ data[ stride + M.SUBSURFACE_COLOR + 1 ] = value.g;
318
+ data[ stride + M.SUBSURFACE_COLOR + 2 ] = value.b;
319
+
320
+ } else if ( Array.isArray( value ) ) {
321
+
322
+ data[ stride + M.SUBSURFACE_COLOR ] = value[ 0 ];
323
+ data[ stride + M.SUBSURFACE_COLOR + 1 ] = value[ 1 ];
324
+ data[ stride + M.SUBSURFACE_COLOR + 2 ] = value[ 2 ];
325
+
326
+ }
327
+
328
+ break;
329
+ case 'subsurfaceRadius':
330
+ if ( Array.isArray( value ) ) {
331
+
332
+ data[ stride + M.SUBSURFACE_RADIUS ] = value[ 0 ];
333
+ data[ stride + M.SUBSURFACE_RADIUS + 1 ] = value[ 1 ];
334
+ data[ stride + M.SUBSURFACE_RADIUS + 2 ] = value[ 2 ];
335
+
336
+ } else if ( value.x !== undefined ) {
337
+
338
+ data[ stride + M.SUBSURFACE_RADIUS ] = value.x;
339
+ data[ stride + M.SUBSURFACE_RADIUS + 1 ] = value.y;
340
+ data[ stride + M.SUBSURFACE_RADIUS + 2 ] = value.z;
341
+
342
+ }
343
+
344
+ break;
310
345
  default:
311
346
  console.warn( `Unknown material property: ${property}` );
312
347
  return;
@@ -322,7 +357,7 @@ export class MaterialDataManager {
322
357
 
323
358
  }
324
359
 
325
- const featureProperties = [ 'transmission', 'clearcoat', 'sheen', 'iridescence', 'dispersion', 'transparent', 'opacity', 'alphaTest' ];
360
+ const featureProperties = [ 'transmission', 'clearcoat', 'sheen', 'iridescence', 'dispersion', 'transparent', 'opacity', 'alphaTest', 'subsurface' ];
326
361
  if ( featureProperties.includes( property ) ) {
327
362
 
328
363
  const featuresChanged = this.rescanMaterialFeatures();
@@ -446,6 +481,27 @@ export class MaterialDataManager {
446
481
  data[ stride + M.DISPLACEMENT_SCALE ] = materialData.displacementScale ?? 1;
447
482
  data[ stride + M.DISPLACEMENT_MAP_INDEX ] = materialData.displacementMap ?? - 1;
448
483
 
484
+ // Subsurface scattering
485
+ data[ stride + M.SUBSURFACE ] = materialData.subsurface ?? 0;
486
+ if ( materialData.subsurfaceColor ) {
487
+
488
+ data[ stride + M.SUBSURFACE_COLOR ] = materialData.subsurfaceColor.r ?? materialData.subsurfaceColor[ 0 ] ?? 1;
489
+ data[ stride + M.SUBSURFACE_COLOR + 1 ] = materialData.subsurfaceColor.g ?? materialData.subsurfaceColor[ 1 ] ?? 1;
490
+ data[ stride + M.SUBSURFACE_COLOR + 2 ] = materialData.subsurfaceColor.b ?? materialData.subsurfaceColor[ 2 ] ?? 1;
491
+
492
+ }
493
+
494
+ if ( materialData.subsurfaceRadius ) {
495
+
496
+ data[ stride + M.SUBSURFACE_RADIUS ] = materialData.subsurfaceRadius[ 0 ] ?? 1;
497
+ data[ stride + M.SUBSURFACE_RADIUS + 1 ] = materialData.subsurfaceRadius[ 1 ] ?? 0.2;
498
+ data[ stride + M.SUBSURFACE_RADIUS + 2 ] = materialData.subsurfaceRadius[ 2 ] ?? 0.1;
499
+
500
+ }
501
+
502
+ data[ stride + M.SUBSURFACE_RADIUS_SCALE ] = materialData.subsurfaceRadiusScale ?? 1;
503
+ data[ stride + M.SUBSURFACE_ANISOTROPY ] = materialData.subsurfaceAnisotropy ?? 0;
504
+
449
505
  // Texture transformation matrices (9 floats each, identity if missing)
450
506
  const identity = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
451
507
  const transformEntries = [
@@ -574,6 +630,7 @@ export class MaterialDataManager {
574
630
  hasIridescence: false,
575
631
  hasSheen: false,
576
632
  hasTransparency: false,
633
+ hasSubsurface: false,
577
634
  hasMultiLobeMaterials: false,
578
635
  hasMRTOutputs: true
579
636
  };
@@ -590,6 +647,7 @@ export class MaterialDataManager {
590
647
  const opacity = data[ stride + M.OPACITY ];
591
648
  const transparent = data[ stride + M.TRANSPARENT ];
592
649
  const alphaTest = data[ stride + M.ALPHA_TEST ];
650
+ const subsurface = data[ stride + M.SUBSURFACE ];
593
651
 
594
652
  if ( clearcoat > 0 ) newFeatures.hasClearcoat = true;
595
653
  if ( transmission > 0 ) newFeatures.hasTransmission = true;
@@ -597,6 +655,7 @@ export class MaterialDataManager {
597
655
  if ( iridescence > 0 ) newFeatures.hasIridescence = true;
598
656
  if ( sheen > 0 ) newFeatures.hasSheen = true;
599
657
  if ( transparent > 0 || opacity < 1.0 || alphaTest > 0 ) newFeatures.hasTransparency = true;
658
+ if ( subsurface > 0 ) newFeatures.hasSubsurface = true;
600
659
 
601
660
  const featureCount = [
602
661
  clearcoat > 0,
@@ -1,6 +1,5 @@
1
1
  import { TileHelper } from './helpers/TileHelper.js';
2
2
  import { OutlineHelper } from './helpers/OutlineHelper.js';
3
- import { EngineEvents } from '../EngineEvents.js';
4
3
 
5
4
  /**
6
5
  * OverlayManager — Unified overlay system for visual helpers.
@@ -9,7 +8,7 @@ import { EngineEvents } from '../EngineEvents.js';
9
8
  * 1. **HelperScene** — A Three.js Scene rendered on top of the WebGPU backbuffer
10
9
  * (light gizmos, bounding boxes, outlines). Renders at display resolution.
11
10
  * 2. **HUDCanvas** — A 2D `<canvas>` element overlaid via CSS for screen-space
12
- * elements (tile progress, AF points, debug labels). Completely separate
11
+ * elements (AF points, debug labels). Completely separate
13
12
  * from the WebGPU canvas, so it is never captured in saved images.
14
13
  *
15
14
  * Helpers are registered by name and implement a simple interface:
@@ -17,8 +16,8 @@ import { EngineEvents } from '../EngineEvents.js';
17
16
  *
18
17
  * @example
19
18
  * const overlay = new OverlayManager( renderer, camera );
20
- * overlay.register( 'tiles', new TileHelper() );
21
- * overlay.show( 'tiles' );
19
+ * overlay.register( 'outline', new OutlineHelper() );
20
+ * overlay.show( 'outline' );
22
21
  * // in animate():
23
22
  * overlay.render();
24
23
  */
@@ -84,7 +83,7 @@ export class OverlayManager {
84
83
  // ═══════════════════════════════════════════════════════════════
85
84
 
86
85
  /**
87
- * Creates and wires the default overlay helpers (tile progress, outline).
86
+ * Creates and wires the default overlay helpers (denoise/upscale progress, outline).
88
87
  * Call once during app init after pipeline and managers are ready.
89
88
  *
90
89
  * @param {Object} config
@@ -92,7 +91,7 @@ export class OverlayManager {
92
91
  * @param {import('three').Scene} config.meshScene
93
92
  * @param {import('../Pipeline/RenderPipeline.js').RenderPipeline} config.pipeline
94
93
  * @param {import('./DenoisingManager.js').DenoisingManager} config.denoisingManager
95
- * @param {import('three').EventDispatcher} config.app - App instance for resize/render-complete events
94
+ * @param {import('three').EventDispatcher} config.app - App instance for resize events
96
95
  * @param {number} config.renderWidth
97
96
  * @param {number} config.renderHeight
98
97
  */
@@ -100,7 +99,7 @@ export class OverlayManager {
100
99
 
101
100
  this.setHelperScene( helperScene );
102
101
 
103
- // ── Tile helper (shared across path tracer, OIDN, upscaler) ──
102
+ // ── Tile helper shows OIDN denoise + AI upscale progress ──
104
103
  const tileHelper = new TileHelper();
105
104
  this.register( 'tiles', tileHelper );
106
105
 
@@ -112,22 +111,8 @@ export class OverlayManager {
112
111
 
113
112
  } );
114
113
 
115
- // Path tracer tile events
116
- pipeline.eventBus.on( 'tile:changed', ( e ) => {
117
-
118
- if ( e.renderMode === 1 && e.tileBounds ) {
119
-
120
- tileHelper.setActiveTile( e.tileBounds );
121
- tileHelper.show();
122
-
123
- }
124
-
125
- } );
126
-
127
114
  pipeline.eventBus.on( 'pipeline:reset', () => tileHelper.hide() );
128
- app.addEventListener( EngineEvents.RENDER_COMPLETE, () => tileHelper.hide() );
129
115
 
130
- // OIDN/upscaler tile events
131
116
  this._wireDenoiserTileEvents( tileHelper, denoisingManager );
132
117
 
133
118
  // ── Outline helper ──
@@ -137,7 +122,7 @@ export class OverlayManager {
137
122
  }
138
123
 
139
124
  /**
140
- * Wires denoiser/upscaler tile progress events to the tile helper.
125
+ * Wires denoiser/upscaler tile-progress events to the tile helper.
141
126
  * These fire while the animation loop is stopped, so we trigger manual HUD redraws.
142
127
  */
143
128
  _wireDenoiserTileEvents( tileHelper, denoisingManager ) {
@@ -164,6 +164,7 @@ export class UniformManager {
164
164
  u( 'samplesPerPixel', DEFAULT_STATE.samplesPerPixel, 'int' );
165
165
  u( 'maxSamples', DEFAULT_STATE.maxSamples, 'int' );
166
166
  u( 'transmissiveBounces', DEFAULT_STATE.transmissiveBounces, 'int' );
167
+ u( 'maxSubsurfaceSteps', DEFAULT_STATE.maxSubsurfaceSteps, 'int' );
167
168
  u( 'visMode', DEFAULT_STATE.debugMode, 'int' );
168
169
  u( 'debugVisScale', DEFAULT_STATE.debugVisScale, 'float' );
169
170
 
@@ -230,9 +231,6 @@ export class UniformManager {
230
231
  this._uniforms.set( 'samplingTechnique', samplingTechniqueUniform );
231
232
  samplingTechniqueUniform.value = DEFAULT_STATE.samplingTechnique;
232
233
 
233
- ub( 'useAdaptiveSampling', DEFAULT_STATE.adaptiveSampling );
234
- u( 'adaptiveSamplingMin', DEFAULT_STATE.adaptiveSamplingMin ?? 1, 'int' );
235
- u( 'adaptiveSamplingMax', DEFAULT_STATE.adaptiveSamplingMax, 'int' );
236
234
  u( 'fireflyThreshold', DEFAULT_STATE.fireflyThreshold, 'float' );
237
235
 
238
236
  // Emissive
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * TileHelper — 2D canvas overlay showing the active tile border.
3
3
  *
4
- * Universal: works for path tracer tiled rendering, OIDN denoiser,
5
- * and AI upscaler. Callers provide pixel-space tile bounds via
4
+ * Shows progress of the OIDN denoiser and AI upscaler (both process the
5
+ * image in tiles). Callers provide pixel-space tile bounds via
6
6
  * `setActiveTile()` and control visibility via `show()` / `hide()`.
7
7
  *
8
8
  * Layer: 'hud' (rendered by OverlayManager's 2D canvas pass).