rayzee 5.4.0 → 5.4.1

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.
@@ -43,19 +43,8 @@ const BVH_STRIDE = 4;
43
43
  const TRI_STRIDE = 8;
44
44
  const HUGE_VAL = 1e8;
45
45
 
46
- // Per-mesh visibility buffer (set by ShaderBuilder before graph construction)
47
- let _meshVisibilityBuffer = null;
48
-
49
- /**
50
- * Set the per-mesh visibility storage buffer node.
51
- * Must be called before the shader graph is constructed (i.e., before setupCompute).
52
- * @param {StorageNode} buffer - TSL storage node indexed by meshIndex
53
- */
54
- export function setMeshVisibilityBuffer( buffer ) {
55
-
56
- _meshVisibilityBuffer = buffer;
57
-
58
- }
46
+ // Per-mesh visibility is now packed into the TLAS BLAS-pointer leaf's slot [2]
47
+ // by TLASBuilder.flatten() — eliminates the dedicated meshVisibility storage buffer.
59
48
 
60
49
  // ================================================================================
61
50
  // STACK HELPERS (Native WGSL array via TSL ArrayNode)
@@ -276,35 +265,17 @@ export const traverseBVH = Fn( ( [
276
265
 
277
266
  } ).Else( () => {
278
267
 
279
- // BLAS-pointer leaf (marker -2) — push BLAS root node onto stack
280
- // nodeData0: [blasRootNodeIndex, meshIndex, 0, -2]
268
+ // BLAS-pointer leaf (marker -2) — push BLAS root onto stack if mesh is visible
269
+ // nodeData0: [blasRootNodeIndex, meshIndex, visibility, -2]
270
+ // Visibility is free-fetched with the leaf — no extra storage read.
281
271
  const blasRoot = int( nodeData0.x ).toVar();
282
272
 
283
- if ( _meshVisibilityBuffer ) {
284
-
285
- // Per-mesh visibility check — skip entire BLAS if mesh is hidden
286
- // getDatafromStorageBuffer( buffer, stride=1, sampleIndex=meshIdx, dataOffset=0 )
287
- const meshIdx = int( nodeData0.y ).toVar();
288
- const meshVis = getDatafromStorageBuffer( _meshVisibilityBuffer, int( 1 ), meshIdx, int( 0 ) ).x;
289
-
290
- If( meshVis.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
291
-
292
- stack.element( stackPtr ).assign( blasRoot );
293
- stackPtr.addAssign( 1 );
294
-
295
- } );
296
-
297
- } else {
298
-
299
- // No visibility buffer — push unconditionally (original behavior)
300
- If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
301
-
302
- stack.element( stackPtr ).assign( blasRoot );
303
- stackPtr.addAssign( 1 );
273
+ If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
304
274
 
305
- } );
275
+ stack.element( stackPtr ).assign( blasRoot );
276
+ stackPtr.addAssign( 1 );
306
277
 
307
- }
278
+ } );
308
279
 
309
280
  } );
310
281
 
@@ -466,35 +437,16 @@ export const traverseBVHShadow = Fn( ( [
466
437
 
467
438
  } ).Else( () => {
468
439
 
469
- // BLAS-pointer leaf (marker -2) — push BLAS root node onto stack
470
- // nodeData0: [blasRootNodeIndex, meshIndex, 0, -2]
440
+ // BLAS-pointer leaf (marker -2) — push BLAS root onto stack if mesh is visible
441
+ // nodeData0: [blasRootNodeIndex, meshIndex, visibility, -2]
471
442
  const blasRoot = int( nodeData0.x ).toVar();
472
443
 
473
- if ( _meshVisibilityBuffer ) {
474
-
475
- // Per-mesh visibility check — skip entire BLAS if mesh is hidden
476
- // getDatafromStorageBuffer( buffer, stride=1, sampleIndex=meshIdx, dataOffset=0 )
477
- const meshIdx = int( nodeData0.y ).toVar();
478
- const meshVis = getDatafromStorageBuffer( _meshVisibilityBuffer, int( 1 ), meshIdx, int( 0 ) ).x;
479
-
480
- If( meshVis.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
481
-
482
- stack.element( stackPtr ).assign( blasRoot );
483
- stackPtr.addAssign( 1 );
484
-
485
- } );
486
-
487
- } else {
444
+ If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
488
445
 
489
- // No visibility buffer — push unconditionally (original behavior)
490
- If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
491
-
492
- stack.element( stackPtr ).assign( blasRoot );
493
- stackPtr.addAssign( 1 );
494
-
495
- } );
446
+ stack.element( stackPtr ).assign( blasRoot );
447
+ stackPtr.addAssign( 1 );
496
448
 
497
- }
449
+ } );
498
450
 
499
451
  } );
500
452
 
@@ -13,7 +13,7 @@ import {
13
13
  If,
14
14
  } from 'three/tsl';
15
15
 
16
- import { struct } from './structProxy.js';
16
+ import { struct } from './patches.js';
17
17
 
18
18
  import { Ray, HitInfo, RayTracingMaterial, DotProducts } from './Struct.js';
19
19
  import { PI, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
@@ -1,6 +1,6 @@
1
1
  import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max } from 'three/tsl';
2
2
 
3
- import { struct } from './structProxy.js';
3
+ import { struct } from './patches.js';
4
4
  import { getDatafromStorageBuffer } from './Common.js';
5
5
  import { sampleDisplacementMap } from './TextureSampling.js';
6
6
 
@@ -27,7 +27,7 @@ import {
27
27
  atan,
28
28
  } from 'three/tsl';
29
29
 
30
- import { struct } from './structProxy.js';
30
+ import { struct } from './patches.js';
31
31
  import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT } from './Common.js';
32
32
  import { RandomValue } from './Random.js';
33
33
  import { calculateMaterialPDF } from './LightsSampling.js';
@@ -361,8 +361,10 @@ export const calculateEmissiveLightPdf = Fn( ( [
361
361
  // ================================================================================
362
362
 
363
363
  // Binary search in CDF for importance-weighted triangle selection
364
- // CDF values are stored in the .b channel of the emissive buffer
365
- const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveTriangleCount, rand ] ) => {
364
+ // CDF values are stored in the .b channel of the emissive buffer.
365
+ // `emissiveOffset` is the vec4-element offset into the packed light buffer
366
+ // where emissive entries start (0 if using a non-packed buffer).
367
+ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissiveTriangleCount, rand ] ) => {
366
368
 
367
369
  const lo = int( 0 ).toVar();
368
370
  const hi = emissiveTriangleCount.sub( 1 ).toVar();
@@ -370,7 +372,7 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveTriangleCount, r
370
372
  Loop( lo.lessThan( hi ), () => {
371
373
 
372
374
  const mid = lo.add( hi ).div( 2 ).toVar();
373
- const cdfVal = emissiveTriangleBuffer.element( mid.mul( EMISSIVE_STRIDE ) ).b;
375
+ const cdfVal = emissiveTriangleBuffer.element( emissiveOffset.add( mid.mul( int( EMISSIVE_STRIDE ) ) ) ).b;
374
376
 
375
377
  If( cdfVal.lessThan( rand ), () => {
376
378
 
@@ -388,11 +390,13 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveTriangleCount, r
388
390
 
389
391
  } );
390
392
 
391
- // Sample from emissive triangle index using CDF importance sampling
393
+ // Sample from emissive triangle index using CDF importance sampling.
394
+ // `emissiveTriangleBuffer` may be the shared packed light buffer; `emissiveVec4Offset`
395
+ // gives the vec4 offset where emissive entries begin.
392
396
  export const sampleEmissiveTriangle = Fn( ( [
393
397
  hitPoint, surfaceNormal, totalTriangleCount,
394
398
  rngState,
395
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
399
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
396
400
  triangleBuffer,
397
401
  ] ) => {
398
402
 
@@ -413,12 +417,12 @@ export const sampleEmissiveTriangle = Fn( ( [
413
417
 
414
418
  // CDF importance-weighted triangle selection (brighter triangles sampled more)
415
419
  const randEmissive = RandomValue( rngState );
416
- const emissiveIndex = binarySearchCDF( emissiveTriangleBuffer, emissiveTriangleCount, randEmissive ).toVar();
420
+ const emissiveIndex = binarySearchCDF( emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, randEmissive ).toVar();
417
421
 
418
- // Fetch emissive triangle data from storage buffer (2 vec4s per entry)
422
+ // Fetch emissive triangle data from packed light buffer (2 vec4s per entry)
419
423
  // vec4[0] = (triangleIndex, power, cdf, selectionPdf)
420
424
  // vec4[1] = (emission.r, emission.g, emission.b, area)
421
- const baseIdx = emissiveIndex.mul( EMISSIVE_STRIDE );
425
+ const baseIdx = emissiveVec4Offset.add( emissiveIndex.mul( int( EMISSIVE_STRIDE ) ) );
422
426
  const emissiveData0 = emissiveTriangleBuffer.element( baseIdx );
423
427
  const emissiveData1 = emissiveTriangleBuffer.element( baseIdx.add( 1 ) );
424
428
  const triangleIndex = int( emissiveData0.r );
@@ -534,7 +538,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
534
538
  hitPoint, normal, viewDir, material,
535
539
  totalTriangleCount, bounceIndex, rngState,
536
540
  emissiveBoost,
537
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
541
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
538
542
  triangleBuffer,
539
543
  // Callback functions to avoid circular deps
540
544
  traceShadowRayFn,
@@ -557,7 +561,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
557
561
  // Sample emissive triangle (CDF importance-weighted)
558
562
  const emissiveSample = EmissiveSample.wrap( sampleEmissiveTriangle(
559
563
  hitPoint, normal, totalTriangleCount, rngState,
560
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
564
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
561
565
  triangleBuffer,
562
566
  ) );
563
567
 
@@ -619,7 +623,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
619
623
  hitPoint, normal, viewDir, material,
620
624
  totalTriangleCount, bounceIndex, rngState,
621
625
  emissiveBoost,
622
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
626
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
623
627
  triangleBuffer,
624
628
  traceShadowRayFn,
625
629
  evaluateMaterialResponseFn,
@@ -630,7 +634,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
630
634
  hitPoint, normal, viewDir, material,
631
635
  totalTriangleCount, bounceIndex, rngState,
632
636
  emissiveBoost,
633
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
637
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
634
638
  triangleBuffer,
635
639
  traceShadowRayFn,
636
640
  evaluateMaterialResponseFn,
@@ -82,8 +82,7 @@ export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix,
82
82
  // Exact implementation from three-gpu-pathtracer
83
83
  export const sampleEquirectProbability = Fn( ( [
84
84
  environment,
85
- envMarginalWeights,
86
- envConditionalWeights,
85
+ envCDFBuffer,
87
86
  environmentMatrix,
88
87
  environmentIntensity,
89
88
  envTotalSum,
@@ -92,15 +91,19 @@ export const sampleEquirectProbability = Fn( ( [
92
91
  colorOutput
93
92
  ] ) => {
94
93
 
95
- // Sample marginal CDF for V coordinate (1D storage buffer, linear interpolation)
94
+ // Packed CDF layout: [marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats)]
95
+ // The conditional offset equals the marginal length, which is envResolution.y.
96
+ const condOffset = int( envResolution.y ).toVar();
97
+
98
+ // Sample marginal CDF for V coordinate (1D, linear interpolation)
96
99
  const marginalSize = envResolution.y;
97
100
  const mIdx = clamp( r.x.mul( marginalSize.sub( 1.0 ) ), 0.0, marginalSize.sub( 1.0 ) );
98
101
  const mI0 = int( floor( mIdx ) );
99
102
  const mI1 = min( mI0.add( 1 ), int( marginalSize ).sub( 1 ) );
100
103
  const mFrac = fract( mIdx );
101
- const v = mix( envMarginalWeights.element( mI0 ), envMarginalWeights.element( mI1 ), mFrac ).toVar();
104
+ const v = mix( envCDFBuffer.element( mI0 ), envCDFBuffer.element( mI1 ), mFrac ).toVar();
102
105
 
103
- // Sample conditional CDF for U coordinate (2D storage buffer, bilinear interpolation)
106
+ // Sample conditional CDF for U coordinate (2D grid, bilinear interpolation)
104
107
  const condW = envResolution.x;
105
108
  const condH = envResolution.y;
106
109
  const cxf = clamp( r.y.mul( condW.sub( 1.0 ) ), 0.0, condW.sub( 1.0 ) );
@@ -112,10 +115,10 @@ export const sampleEquirectProbability = Fn( ( [
112
115
  const fx = fract( cxf );
113
116
  const fy = fract( cyf );
114
117
  const condWi = int( condW );
115
- const v00 = envConditionalWeights.element( cy0.mul( condWi ).add( cx0 ) );
116
- const v10 = envConditionalWeights.element( cy0.mul( condWi ).add( cx1 ) );
117
- const v01 = envConditionalWeights.element( cy1.mul( condWi ).add( cx0 ) );
118
- const v11 = envConditionalWeights.element( cy1.mul( condWi ).add( cx1 ) );
118
+ const v00 = envCDFBuffer.element( condOffset.add( cy0.mul( condWi ).add( cx0 ) ) );
119
+ const v10 = envCDFBuffer.element( condOffset.add( cy0.mul( condWi ).add( cx1 ) ) );
120
+ const v01 = envCDFBuffer.element( condOffset.add( cy1.mul( condWi ).add( cx0 ) ) );
121
+ const v11 = envCDFBuffer.element( condOffset.add( cy1.mul( condWi ).add( cx1 ) ) );
119
122
  const u = mix( mix( v00, v10, fx ), mix( v01, v11, fx ), fy ).toVar();
120
123
 
121
124
  const uv = vec2( u, v ).toVar();
@@ -53,6 +53,7 @@ export const sampleLightBVHTriangle = Fn( ( [
53
53
  rngState,
54
54
  lbvhBuffer,
55
55
  emissiveTriangleBuffer,
56
+ emissiveVec4Offset,
56
57
  triangleBuffer,
57
58
  ] ) => {
58
59
 
@@ -185,7 +186,7 @@ export const sampleLightBVHTriangle = Fn( ( [
185
186
  Loop( { start: int( 0 ), end: emissiveCount }, ( { i } ) => {
186
187
 
187
188
  const entryIdx = emissiveStart.add( i );
188
- const baseIdx = entryIdx.mul( int( EMISSIVE_STRIDE ) );
189
+ const baseIdx = emissiveVec4Offset.add( entryIdx.mul( int( EMISSIVE_STRIDE ) ) );
189
190
  const emData0 = emissiveTriangleBuffer.element( baseIdx );
190
191
  const triPower = max( emData0.g, float( 0.0 ) );
191
192
  cumPower.addAssign( triPower );
@@ -204,7 +205,7 @@ export const sampleLightBVHTriangle = Fn( ( [
204
205
  selectionPdf.mulAssign( selectedPower.div( leafTotalPower ) );
205
206
 
206
207
  // Now sample the selected triangle (same path as flat CDF sampling)
207
- const baseIdx = selectedEmissiveIndex.mul( int( EMISSIVE_STRIDE ) );
208
+ const baseIdx = emissiveVec4Offset.add( selectedEmissiveIndex.mul( int( EMISSIVE_STRIDE ) ) );
208
209
  const emissiveData0 = emissiveTriangleBuffer.element( baseIdx );
209
210
  const emissiveData1 = emissiveTriangleBuffer.element( baseIdx.add( int( 1 ) ) );
210
211
 
@@ -13,7 +13,7 @@ import {
13
13
  abs,
14
14
  } from 'three/tsl';
15
15
 
16
- import { struct } from './structProxy.js';
16
+ import { struct } from './patches.js';
17
17
 
18
18
  // ================================================================================
19
19
  // LIGHT STRUCTURES
@@ -41,7 +41,7 @@ import { RandomValue } from './Random.js';
41
41
  import { getTransformedUV } from './TextureSampling.js';
42
42
 
43
43
  // Module-level state for alpha-cutout shadow testing.
44
- // Set by ShaderBuilder before graph construction (same pattern as _meshVisibilityBuffer in BVHTraversal.js).
44
+ // Set by ShaderBuilder before graph construction.
45
45
  let _shadowAlbedoMaps = null;
46
46
  let _enableAlphaShadows = null;
47
47
 
@@ -273,7 +273,6 @@ export const calculateIndirectLighting = Fn( ( [
273
273
  samplingInfo,
274
274
  // Environment resources
275
275
  envTexture, environmentIntensity, envMatrix,
276
- envMarginalWeights, envConditionalWeights,
277
276
  envTotalSum, envResolution,
278
277
  enableEnvironmentLight, useEnvMapIS,
279
278
  ] ) => {
@@ -934,7 +934,7 @@ export const calculateDirectLightingUnified = Fn( ( [
934
934
  materialBuffer,
935
935
  // Environment resources
936
936
  envTexture, environmentIntensity, envMatrix,
937
- envMarginalWeights, envConditionalWeights,
937
+ envCDFBuffer,
938
938
  envTotalSum, envResolution,
939
939
  enableEnvironmentLight,
940
940
  ] ) => {
@@ -1203,7 +1203,7 @@ export const calculateDirectLightingUnified = Fn( ( [
1203
1203
 
1204
1204
  // Sample direction + PDF + color from importance-sampled environment
1205
1205
  const envSampleResult = sampleEquirectProbability(
1206
- envTexture, envMarginalWeights, envConditionalWeights,
1206
+ envTexture, envCDFBuffer,
1207
1207
  envMatrix, environmentIntensity, envTotalSum, envResolution, envRandom, envColor
1208
1208
  ).toVar();
1209
1209
 
@@ -30,7 +30,7 @@ import {
30
30
  fract,
31
31
  } from 'three/tsl';
32
32
 
33
- import { struct } from './structProxy.js';
33
+ import { struct } from './patches.js';
34
34
  import { Ray, RayTracingMaterial, RenderState, HitInfo, DotProducts, DirectionSample } from './Struct.js';
35
35
  import { PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
36
36
  import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
@@ -137,14 +137,14 @@ export const pathTracerMain = ( params ) => {
137
137
  pointLightsBuffer, numPointLights,
138
138
  spotLightsBuffer, numSpotLights,
139
139
  envTexture, environmentIntensity, envMatrix,
140
- envMarginalWeights, envConditionalWeights,
140
+ envCDFBuffer,
141
141
  envTotalSum, envResolution,
142
142
  enableEnvironmentLight, useEnvMapIS,
143
143
  maxBounceCount, transmissiveBounces,
144
144
  showBackground, transparentBackground, backgroundIntensity,
145
145
  fireflyThreshold, globalIlluminationIntensity,
146
146
  totalTriangleCount, enableEmissiveTriangleSampling,
147
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
147
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
148
148
  lightBVHBuffer, lightBVHNodeCount,
149
149
  debugVisScale,
150
150
  enableAccumulation, hasPreviousAccumulated,
@@ -284,14 +284,14 @@ export const pathTracerMain = ( params ) => {
284
284
  pointLightsBuffer, numPointLights,
285
285
  spotLightsBuffer, numSpotLights,
286
286
  envTexture, environmentIntensity, envMatrix,
287
- envMarginalWeights, envConditionalWeights,
287
+ envCDFBuffer,
288
288
  envTotalSum, envResolution,
289
289
  enableEnvironmentLight, useEnvMapIS,
290
290
  maxBounceCount, transmissiveBounces,
291
291
  backgroundIntensity, showBackground, transparentBackground,
292
292
  fireflyThreshold, globalIlluminationIntensity,
293
293
  totalTriangleCount, enableEmissiveTriangleSampling,
294
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
294
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
295
295
  lightBVHBuffer, lightBVHNodeCount,
296
296
  pixelCoord, resolution, frame,
297
297
  ) );
@@ -41,7 +41,7 @@ import {
41
41
  sampler,
42
42
  } from 'three/tsl';
43
43
 
44
- import { struct } from './structProxy.js';
44
+ import { struct } from './patches.js';
45
45
 
46
46
  import {
47
47
  PI_INV,
@@ -590,7 +590,7 @@ export const Trace = Fn( ( [
590
590
  spotLightsBuffer, numSpotLights,
591
591
  // Environment
592
592
  envTexture, environmentIntensity, envMatrix,
593
- envMarginalWeights, envConditionalWeights,
593
+ envCDFBuffer,
594
594
  envTotalSum, envResolution,
595
595
  enableEnvironmentLight, useEnvMapIS,
596
596
  // Rendering parameters
@@ -598,7 +598,7 @@ export const Trace = Fn( ( [
598
598
  backgroundIntensity, showBackground, transparentBackground,
599
599
  fireflyThreshold, globalIlluminationIntensity,
600
600
  totalTriangleCount, enableEmissiveTriangleSampling,
601
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
601
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
602
602
  lightBVHBuffer, lightBVHNodeCount,
603
603
  // Per-pixel info
604
604
  pixelCoord, resolution, frame,
@@ -1013,7 +1013,7 @@ export const Trace = Fn( ( [
1013
1013
  triangleBuffer,
1014
1014
  materialBuffer,
1015
1015
  envTexture, environmentIntensity, envMatrix,
1016
- envMarginalWeights, envConditionalWeights,
1016
+ envCDFBuffer,
1017
1017
  envTotalSum, envResolution,
1018
1018
  enableEnvironmentLight,
1019
1019
  );
@@ -1040,6 +1040,7 @@ export const Trace = Fn( ( [
1040
1040
  rngState,
1041
1041
  lightBVHBuffer,
1042
1042
  emissiveTriangleBuffer,
1043
+ emissiveVec4Offset,
1043
1044
  triangleBuffer,
1044
1045
  ) );
1045
1046
 
@@ -1091,7 +1092,7 @@ export const Trace = Fn( ( [
1091
1092
  hitInfo.hitPoint, N, V, material,
1092
1093
  totalTriangleCount, bounceIndex, rngState,
1093
1094
  emissiveBoost,
1094
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
1095
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
1095
1096
  triangleBuffer,
1096
1097
  traceShadowRayWrapped,
1097
1098
  evaluateMaterialResponse,
@@ -1132,7 +1133,6 @@ export const Trace = Fn( ( [
1132
1133
  rngState,
1133
1134
  samplingInfo,
1134
1135
  envTexture, environmentIntensity, envMatrix,
1135
- envMarginalWeights, envConditionalWeights,
1136
1136
  envTotalSum, envResolution,
1137
1137
  enableEnvironmentLight, useEnvMapIS,
1138
1138
  ) );
package/src/TSL/Struct.js CHANGED
@@ -1,4 +1,4 @@
1
- import { struct } from './structProxy.js';
1
+ import { struct } from './patches.js';
2
2
 
3
3
  export const Ray = struct( {
4
4
  origin: 'vec3',
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Rayzee patches for Three.js / TSL.
3
+ *
4
+ * Side-effect on import: installs `WebGPUBackend.createNodeBuilder` override
5
+ * (restores r183 function-scoped `var` emission for compute shaders — prevents
6
+ * a register-allocation regression in the path tracer's hot loop).
7
+ *
8
+ * Export: `struct()` — drop-in replacement for TSL's `struct()` returning
9
+ * a proxy factory that supports GLSL-style dot-notation field access.
10
+ */
11
+
12
+ import { WebGPUBackend } from 'three/webgpu';
13
+ import { struct as _struct } from 'three/tsl';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // 1. WGSL global-variable promotion patch (compute-only)
17
+ // ---------------------------------------------------------------------------
18
+ // Three.js r184 introduced `WGSLNodeBuilder.allowGlobalVariables = true`, which
19
+ // emits `.toVar()` declarations at WGSL module scope as `var<private> name : T`
20
+ // instead of function-local `var name : T` inside `fn main()` (as r183 did).
21
+ //
22
+ // For compute shaders with hundreds of `.toVar()` calls in loops (e.g. the BVH
23
+ // traversal + BRDF path tracer), `var<private>` increases GPU register pressure
24
+ // because the Dawn/Chromium WGSL compiler cannot aggressively register-allocate
25
+ // variables with a stable per-invocation memory address. We measured a ~8% fps
26
+ // regression (120 → 110) on the path tracer after upgrading r183 → r184 that
27
+ // traced entirely to GPU execution, not CPU.
28
+ //
29
+ // `allowGlobalVariables` is ONLY consumed by the compute template
30
+ // (`_getWGSLComputeCode`). The vertex/fragment templates always emit
31
+ // `shaderData.vars` at module scope and REQUIRE `allowGlobalVariables=true`
32
+ // (emitting function-local `var` at module scope is invalid WGSL and crashes
33
+ // pipeline creation with "Invalid ShaderModule"). We install a per-instance
34
+ // accessor that returns `false` only when the builder is for a compute node
35
+ // (material === null) and `true` otherwise, so render pipelines keep r184
36
+ // behavior untouched.
37
+ //
38
+ // Relevant upstream lines:
39
+ // - `node_modules/three/src/renderers/webgpu/nodes/WGSLNodeBuilder.js:247`
40
+ // (`this.allowGlobalVariables = true`)
41
+ // - `...WGSLNodeBuilder.js:2458` (module-scope vars block)
42
+ // - `...WGSLNodeBuilder.js:2467` (function-body vars block)
43
+ //
44
+ // Revisit if upstream adds an official opt-out or fixes register pressure.
45
+
46
+ const _origCreateNodeBuilder = WebGPUBackend.prototype.createNodeBuilder;
47
+
48
+ WebGPUBackend.prototype.createNodeBuilder = function ( object, renderer ) {
49
+
50
+ const builder = _origCreateNodeBuilder.call( this, object, renderer );
51
+
52
+ Object.defineProperty( builder, 'allowGlobalVariables', {
53
+ get() {
54
+
55
+ return this.material !== null;
56
+
57
+ },
58
+ set() { /* ignore — the value is derived from material presence */ },
59
+ configurable: true,
60
+ } );
61
+
62
+ return builder;
63
+
64
+ };
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // 2. TSL struct proxy — enables GLSL-style dot-notation field access
68
+ // ---------------------------------------------------------------------------
69
+ // TSL structs require `.get('fieldName')` for member access, but GLSL-style
70
+ // dot notation (`.fieldName`) is more natural and matches ported code.
71
+ //
72
+ // This wraps TSL's `struct()` so that:
73
+ // - Direct construction: `MyStruct({...}).toVar('x')` → `.fieldName` works
74
+ // - Fn return values: `MyStruct.wrap(someFn(...))` → `.fieldName` works
75
+ //
76
+ // Property access for known struct member names is redirected to `.get('name')`.
77
+ // Swizzle properties (x, y, z, w, etc.), Node methods (.add, .assign, etc.), and
78
+ // other standard properties pass through to the underlying node unmodified.
79
+
80
+ function createStructProxy( node, memberSet ) {
81
+
82
+ return new Proxy( node, {
83
+
84
+ get( target, prop, receiver ) {
85
+
86
+ // Intercept known struct member names
87
+ if ( typeof prop === 'string' && memberSet.has( prop ) ) {
88
+
89
+ return target.get( prop );
90
+
91
+ }
92
+
93
+ const val = Reflect.get( target, prop, receiver );
94
+
95
+ // Intercept .toVar() to proxy-wrap the result
96
+ if ( prop === 'toVar' && typeof val === 'function' ) {
97
+
98
+ return ( ...args ) => createStructProxy( val.apply( target, args ), memberSet );
99
+
100
+ }
101
+
102
+ return val;
103
+
104
+ }
105
+
106
+ } );
107
+
108
+ }
109
+
110
+ /**
111
+ * Drop-in replacement for TSL's `struct()` that returns a proxy-enhanced factory.
112
+ *
113
+ * The returned factory:
114
+ * - Creates struct nodes where `.toVar()` results support dot-notation field access
115
+ * - Has `.wrap(node)` method to proxy-wrap Fn return values for field access
116
+ * - Has `.layout` and `.isStruct` matching the original TSL struct API
117
+ *
118
+ * @param {Object} members - Struct member layout (e.g., { didHit: 'bool', dst: 'float' })
119
+ * @param {string|null} name - Optional struct name
120
+ * @returns {Function} Enhanced struct factory
121
+ */
122
+ export function struct( members, name = null ) {
123
+
124
+ const factory = _struct( members, name );
125
+ const memberSet = new Set( Object.keys( members ) );
126
+
127
+ const wrappedFactory = ( ...args ) => {
128
+
129
+ const node = factory( ...args );
130
+ return createStructProxy( node, memberSet );
131
+
132
+ };
133
+
134
+ wrappedFactory.layout = factory.layout;
135
+ wrappedFactory.isStruct = true;
136
+
137
+ /**
138
+ * Wrap an existing node (e.g., Fn return value) with struct field access proxy.
139
+ * Usage: `const hit = HitInfo.wrap(traverseBVH(...).toVar('hit'));`
140
+ */
141
+ wrappedFactory.wrap = ( node ) => createStructProxy( node, memberSet );
142
+
143
+ return wrappedFactory;
144
+
145
+ }
package/src/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  // Patches (side-effect imports — must run before any StorageTexture usage)
9
- import './TSL/wgslGlobalVarsPatch.js';
9
+ import './TSL/patches.js';
10
10
 
11
11
  // Main application
12
12
  export { PathTracerApp } from './PathTracerApp.js';