rayzee 7.20.2 → 7.21.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.
package/src/TSL/Random.js CHANGED
@@ -10,7 +10,7 @@ import { DataTexture, FloatType } from 'three';
10
10
  export const samplingTechniqueUniform = uniform( 0, 'int' );
11
11
  const samplingTechnique = samplingTechniqueUniform;
12
12
 
13
- // 0: PCG, 1: Halton, 2: Sobol, 3: Blue Noise
13
+ // 0: PCG, 1: Halton, 2: Owen-scrambled Sobol (default; anything higher falls back to it)
14
14
 
15
15
  // 1x1 placeholder — real texture assigned later via .value = ...
16
16
  const _placeholderData = new Float32Array( [ 0.5, 0.5, 0.5, 1.0 ] );
@@ -37,50 +37,6 @@ stbnVec2TextureNode.setUpdateMatrix( false );
37
37
  const R2_A1 = float( 0.7548776662466927 );
38
38
  const R2_A2 = float( 0.5698402909980532 );
39
39
 
40
- // Sobol sequence direction vectors using function lookup for compatibility
41
-
42
- // Sobol direction vectors for first dimension — exact port of GLSL
43
- export const getSobolDirectionVector = /*@__PURE__*/ wgslFn( `
44
- fn getSobolDirectionVector( index: i32 ) -> u32 {
45
-
46
- switch ( index ) {
47
- case 0: { return 2147483648u; }
48
- case 1: { return 1073741824u; }
49
- case 2: { return 536870912u; }
50
- case 3: { return 268435456u; }
51
- case 4: { return 134217728u; }
52
- case 5: { return 67108864u; }
53
- case 6: { return 33554432u; }
54
- case 7: { return 16777216u; }
55
- case 8: { return 8388608u; }
56
- case 9: { return 4194304u; }
57
- case 10: { return 2097152u; }
58
- case 11: { return 1048576u; }
59
- case 12: { return 524288u; }
60
- case 13: { return 262144u; }
61
- case 14: { return 131072u; }
62
- case 15: { return 65536u; }
63
- case 16: { return 32768u; }
64
- case 17: { return 16384u; }
65
- case 18: { return 8192u; }
66
- case 19: { return 4096u; }
67
- case 20: { return 2048u; }
68
- case 21: { return 1024u; }
69
- case 22: { return 512u; }
70
- case 23: { return 256u; }
71
- case 24: { return 128u; }
72
- case 25: { return 64u; }
73
- case 26: { return 32u; }
74
- case 27: { return 16u; }
75
- case 28: { return 8u; }
76
- case 29: { return 4u; }
77
- case 30: { return 2u; }
78
- default: { return 1u; }
79
- }
80
-
81
- }
82
- ` );
83
-
84
40
  // -----------------------------------------------------------------------------
85
41
  // Basic random number generation
86
42
  // -----------------------------------------------------------------------------
@@ -155,7 +111,56 @@ export const RandomPointInCircle = ( rngState ) => {
155
111
  };
156
112
 
157
113
  // -----------------------------------------------------------------------------
158
- // STBN atlas sampling — proper spatiotemporal blue noise
114
+ // Sampler dimension allocation
115
+ // -----------------------------------------------------------------------------
116
+ // DIMENSION BUDGET — bounce b owns [b*DIMS_PER_BOUNCE, b*DIMS_PER_BOUNCE + 15]. The index keys
117
+ // the Owen scramble seed, so two draws sharing one index at the same bounce get the SAME variate:
118
+ // perfectly correlated decisions, not independent ones. The 1D and 2D allocations are separate
119
+ // namespaces (different seed derivations), so a collision only matters within one.
120
+ //
121
+ // Every stochastic decision on the shading path draws from here rather than the per-pixel PCG
122
+ // stream. A low-discrepancy sequence only stratifies the dimensions it actually covers; a draw
123
+ // left on PCG contributes white-noise error that the rest of the path cannot compensate for.
124
+ //
125
+ // 2D +0 BSDF direction xi (ShadeKernel)
126
+ // +1 environment NEE (LightsSampling)
127
+ // +2 discrete light position (LightsSampling)
128
+ // +3 emissive-triangle NEE (EmissiveSampling / LightBVHSampling — the two are
129
+ // mutually exclusive at runtime, so they share the index)
130
+ // +4 microfacet transmission dir (MaterialTransmission)
131
+ // +5 subsurface reflect/refract (Subsurface)
132
+ // +6 subsurface HG scatter dir (ShadeKernel)
133
+ // +7 ground-catcher cosine BSDF (ShadeKernel)
134
+ // +8 indirect-strategy direction (LightsIndirect)
135
+ // 1D +0 (free — was the {lights,BRDF} stochastic strategy pick)
136
+ // +1 emissive-triangle pick (EmissiveSampling / LightBVHSampling)
137
+ // +2 BSDF lobe selection (ShadeKernel)
138
+ // +3 Russian roulette (PathTracerCore)
139
+ // +4 transmission reflect/refract (MaterialTransmission)
140
+ // +5 dispersion wavelength (MaterialTransmission)
141
+ // +6 alpha cutout test (MaterialTransmission)
142
+ // +7 stochastic transmission test (MaterialTransmission)
143
+ // +8 subsurface entry lottery (MaterialTransmission)
144
+ // +9 subsurface collision dist (Subsurface)
145
+ // +10 subsurface channel pick (Subsurface)
146
+ // +11 subsurface reflect/refract (Subsurface)
147
+ // +12 subsurface walk RR (ShadeKernel)
148
+ // +13 clearcoat lobe (Clearcoat)
149
+ // +14 area/spot light phi (LightsSampling)
150
+ // +15 light uv second component (LightsSampling)
151
+ //
152
+ // Stride is 32 rather than the ~16 in use so a new call site does not force a renumbering.
153
+ // The index only keys a scramble seed, so spare slots cost nothing.
154
+ //
155
+ // Draws whose COUNT varies per pixel cannot sit in a per-bounce block without overflowing into
156
+ // the next bounce's dimensions, so they live above AUX_BASE:
157
+ // AUX_BASE + 0..63 env-backdrop blur taps (ShadeKernel)
158
+ // AUX_BASE + 64.. light reservoir, one dimension per scene light (LightsSampling)
159
+ export const SAMPLER_DIMS_PER_BOUNCE = 32;
160
+ export const SAMPLER_DIM_AUX_BASE = 4096;
161
+
162
+ // -----------------------------------------------------------------------------
163
+ // STBN atlas sampling — spatiotemporal blue noise
159
164
  // -----------------------------------------------------------------------------
160
165
  // Atlas layout: 8×8 grid of 128×128 tiles = 1024×1024 texture.
161
166
  // Temporal axis: frame % 64 selects tile (true STBN temporal decorrelation).
@@ -237,61 +242,86 @@ export const haltonScrambled = /*@__PURE__*/ wgslFn( `
237
242
  }
238
243
  ` );
239
244
 
240
- // Owen scrambling for Sobol sequence
241
-
245
+ // Owen scrambling — Burley 2020 "Practical Hash-based Owen Scrambling".
246
+ //
247
+ // The reverseBits sandwich is required, not decoration: Owen scrambling permutes a digit from the
248
+ // digits ABOVE it, so the permutation must run on the bit-reversed value. A plain hash randomises
249
+ // the point but destroys the stratification Sobol' exists to provide.
242
250
  export const owen_scramble = /*@__PURE__*/ wgslFn( `
243
251
  fn owen_scramble( x: u32, seed: u32 ) -> u32 {
244
252
 
245
- var v = x;
246
- v ^= v * 0x3d20adeau;
253
+ var v = reverseBits( x );
247
254
  v += seed;
248
- v *= ( seed >> 16u ) | 1u;
249
- v ^= v >> 15u;
250
- v *= 0x5851f42du;
251
- v ^= v >> 12u;
252
- v *= 0x4c957f2du;
253
- v ^= v >> 18u;
254
- return v;
255
+ v ^= v * 0x6c50b47cu;
256
+ v ^= v * 0xb82f1e52u;
257
+ v ^= v * 0xc7afe638u;
258
+ v ^= v * 0x8d22f6e6u;
259
+ return reverseBits( v );
255
260
 
256
261
  }
257
262
  ` );
258
263
 
259
- // Owen-scrambled Sobol sequence
260
-
261
- export const owen_scrambled_sobol = /*@__PURE__*/ wgslFn( `
262
- fn owen_scrambled_sobol( index: u32, dimension: u32, seed: u32 ) -> f32 {
264
+ // Sobol' direction vectors for the first two dimensions, advanced together so a 2D point costs
265
+ // one pass. dim 0 is van der Corput (a bit reversal); dim 1 is the primitive polynomial x+1,
266
+ // whose matrix rows are Pascal's triangle mod 2 — by Lucas' theorem row k selects the submasks of
267
+ // k, making the transform a 5-step subset-XOR butterfly rather than a loop over set bits.
268
+ // Together they form a (0,2)-sequence.
269
+ export const sobol2D = /*@__PURE__*/ wgslFn( `
270
+ fn sobol2D( index: u32 ) -> vec2u {
263
271
 
264
- var result = 0u;
265
- for ( var i = 0; i < 32; i++ ) {
272
+ let r0 = reverseBits( index );
266
273
 
267
- if ( ( index & ( 1u << u32( i ) ) ) != 0u ) {
274
+ var x = r0;
275
+ x ^= ( x << 1u ) & 0xaaaaaaaau;
276
+ x ^= ( x << 2u ) & 0xccccccccu;
277
+ x ^= ( x << 4u ) & 0xf0f0f0f0u;
278
+ x ^= ( x << 8u ) & 0xff00ff00u;
279
+ x ^= ( x << 16u ) & 0xffff0000u;
268
280
 
269
- result ^= getSobolDirectionVector( i );
281
+ return vec2u( r0, x );
270
282
 
271
- }
283
+ }
284
+ ` );
272
285
 
273
- }
286
+ // Padded Owen-scrambled Sobol for a STANDALONE 1D dimension.
287
+ //
288
+ // The index shuffle is what actually decorrelates dimensions. A scramble seed does not: Owen
289
+ // scrambling either preserves or flips each digit, so two dimensions built from the same
290
+ // direction vectors come out ~±1 correlated whatever seed they are given (measured cross-
291
+ // dimension mean |corr| 0.74, max 0.99). Along a multi-bounce path that turns "independent"
292
+ // per-bounce decisions into effectively one repeated variate — which showed up as a persistent
293
+ // 1% energy error on dispersion-glass, where it biased the reflect/refract choice at every
294
+ // interface the same way. Shuffling the index instead permutes WHICH sample each dimension
295
+ // takes, dropping cross-dimension |corr| to 0.05 while leaving each dimension's own
296
+ // stratification intact (a permutation of 0..2^k-1 is the same point set).
297
+ export const owen_scrambled_sobol = /*@__PURE__*/ wgslFn( `
298
+ fn owen_scrambled_sobol( index: u32, dimension: u32, seed: u32 ) -> f32 {
274
299
 
275
- // Mix dimension into seed for inter-dimensional decorrelation
276
- // (Van der Corput base is shared; Owen scrambling with distinct seeds
277
- // produces decorrelated sequences across dimensions)
278
300
  let dimSeed = seed ^ ( dimension * 0x9e3779b9u + 0x6a09e667u );
279
- result = owen_scramble( result, dimSeed );
280
- return f32( result ) / 4294967296.0f;
301
+ let shuffled = owen_scramble( index, dimSeed ^ 0xa511e9b3u );
302
+ let pair = sobol2D( shuffled );
303
+ let raw = select( pair.x, pair.y, ( dimension & 1u ) != 0u );
304
+ return f32( owen_scramble( raw, dimSeed ) ) / 4294967296.0f;
281
305
 
282
306
  }
283
- `, [ getSobolDirectionVector, owen_scramble ] );
307
+ `, [ sobol2D, owen_scramble ] );
284
308
 
309
+ // A 2D pair must shuffle the index ONCE and take both Sobol' components from that single
310
+ // shuffled index — that is what keeps the pair a (0,2)-net. Shuffling each component
311
+ // independently (i.e. calling the 1D routine twice) destroys it: measured 982/2304 elementary
312
+ // intervals wrong at 256 points, versus 0/2304 with the shared shuffle.
285
313
  export const owen_scrambled_sobol2D = /*@__PURE__*/ wgslFn( `
286
314
  fn owen_scrambled_sobol2D( index: u32, seed: u32 ) -> vec2f {
287
315
 
316
+ let shuffled = owen_scramble( index, seed ^ 0xa511e9b3u );
317
+ let pair = sobol2D( shuffled );
288
318
  return vec2f(
289
- owen_scrambled_sobol( index, 0u, seed ),
290
- owen_scrambled_sobol( index, 1u, seed )
319
+ f32( owen_scramble( pair.x, seed ^ 0x68bc21ebu ) ) / 4294967296.0f,
320
+ f32( owen_scramble( pair.y, seed ^ 0x02e5be93u ) ) / 4294967296.0f
291
321
  );
292
322
 
293
323
  }
294
- `, [ owen_scrambled_sobol ] );
324
+ `, [ sobol2D, owen_scramble ] );
295
325
 
296
326
  // -----------------------------------------------------------------------------
297
327
  // Multi-dimensional sampling interface
@@ -358,73 +388,60 @@ export const getRandomSampleND = ( pixelCoord, sampleIndex, bounceIndex, rngStat
358
388
 
359
389
  } ).ElseIf( technique.equal( int( 1 ) ), () => {
360
390
 
361
- // Halton — mix frame + bounceIndex into scramble for temporal and per-bounce decorrelation
391
+ // Halton — the accumulation frame is the sequence INDEX, not part of the scramble. Folding
392
+ // frame into the scramble (as this did) re-randomises point 0 every frame and never walks the
393
+ // sequence, which is a hash, not low-discrepancy sampling. The scramble is what separates pixels.
362
394
  const pixelHash = uint( pixelCoord.x ).add( uint( pixelCoord.y ).mul( uint( resolution.x ) ) );
363
- const scramble = pcgHash( { state: pixelHash.bitXor( frame.mul( uint( 0x9e3779b9 ) ) ).bitXor( uint( bounceIndex ).mul( uint( 0x517cc1b7 ) ) ) } ).toVar();
395
+ const scramble = pcgHash( { state: pixelHash.bitXor( uint( bounceIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
364
396
 
365
- result.x.assign( haltonScrambled( { index: sampleIndex, base: int( 2 ), scramble } ) );
397
+ result.x.assign( haltonScrambled( { index: int( frame ), base: int( 2 ), scramble } ) );
366
398
 
367
399
  If( dimensions.greaterThan( int( 1 ) ), () => {
368
400
 
369
- result.y.assign( haltonScrambled( { index: sampleIndex, base: int( 3 ), scramble } ) );
401
+ result.y.assign( haltonScrambled( { index: int( frame ), base: int( 3 ), scramble } ) );
370
402
 
371
403
  } );
372
404
 
373
405
  If( dimensions.greaterThan( int( 2 ) ), () => {
374
406
 
375
- result.z.assign( haltonScrambled( { index: sampleIndex, base: int( 5 ), scramble } ) );
407
+ result.z.assign( haltonScrambled( { index: int( frame ), base: int( 5 ), scramble } ) );
376
408
 
377
409
  } );
378
410
 
379
411
  If( dimensions.greaterThan( int( 3 ) ), () => {
380
412
 
381
- result.w.assign( haltonScrambled( { index: sampleIndex, base: int( 7 ), scramble } ) );
413
+ result.w.assign( haltonScrambled( { index: int( frame ), base: int( 7 ), scramble } ) );
382
414
 
383
415
  } );
384
416
 
385
- } ).ElseIf( technique.equal( int( 2 ) ), () => {
417
+ } ).Else( () => {
386
418
 
387
- // Sobol — mix frame + bounceIndex into seed for temporal and per-bounce decorrelation
419
+ // Sobol — frame is the sequence INDEX; the per-pixel Owen seed must stay frame-independent
420
+ // or the sequence never advances. See the Halton note above.
388
421
  const pixelHash = uint( pixelCoord.x ).add( uint( pixelCoord.y ).mul( uint( resolution.x ) ) );
389
- const seed = pcgHash( { state: pixelHash.bitXor( frame.mul( uint( 0x9e3779b9 ) ) ).bitXor( uint( bounceIndex ).mul( uint( 0x517cc1b7 ) ) ) } ).toVar();
422
+ const seed = pcgHash( { state: pixelHash.bitXor( uint( bounceIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
390
423
 
391
- result.x.assign( owen_scrambled_sobol( { index: uint( sampleIndex ), dimension: uint( 0 ), seed } ) );
424
+ // Drawn as PAIRS, not four padded 1D dimensions — only a shared index shuffle keeps
425
+ // (x,y) and (z,w) proper 2D nets. See owen_scrambled_sobol2D.
426
+ const xy = owen_scrambled_sobol2D( { index: uint( frame ), seed } ).toVar();
427
+ result.x.assign( xy.x );
392
428
 
393
429
  If( dimensions.greaterThan( int( 1 ) ), () => {
394
430
 
395
- result.y.assign( owen_scrambled_sobol( { index: uint( sampleIndex ), dimension: uint( 1 ), seed } ) );
431
+ result.y.assign( xy.y );
396
432
 
397
433
  } );
398
434
 
399
435
  If( dimensions.greaterThan( int( 2 ) ), () => {
400
436
 
401
- result.z.assign( owen_scrambled_sobol( { index: uint( sampleIndex ), dimension: uint( 2 ), seed } ) );
402
-
403
- } );
437
+ const zw = owen_scrambled_sobol2D( { index: uint( frame ), seed: seed.bitXor( uint( 0x734f6b19 ) ) } ).toVar();
438
+ result.z.assign( zw.x );
404
439
 
405
- If( dimensions.greaterThan( int( 3 ) ), () => {
406
-
407
- result.w.assign( owen_scrambled_sobol( { index: uint( sampleIndex ), dimension: uint( 3 ), seed } ) );
408
-
409
- } );
410
-
411
- } ).Else( () => {
412
-
413
- // STBN — Spatiotemporal Blue Noise (technique 3)
414
- // Each bounce uses a block of 4 dimension indices for decorrelation
415
- const dimBase = bounceIndex.mul( int( 4 ) );
416
-
417
- If( dimensions.lessThanEqual( int( 2 ) ), () => {
440
+ If( dimensions.greaterThan( int( 3 ) ), () => {
418
441
 
419
- const _sample = sampleSTBN2D( pixelCoord, sampleIndex, dimBase, frame );
420
- result.x.assign( _sample.x );
421
- result.y.assign( _sample.y );
442
+ result.w.assign( zw.y );
422
443
 
423
- } ).Else( () => {
424
-
425
- const _sample1 = sampleSTBN2D( pixelCoord, sampleIndex, dimBase, frame );
426
- const _sample2 = sampleSTBN2D( pixelCoord, sampleIndex, dimBase.add( int( 1 ) ), frame );
427
- result.assign( vec4( _sample1, _sample2 ) );
444
+ } );
428
445
 
429
446
  } );
430
447
 
@@ -447,7 +464,74 @@ export const getRandomSample = ( pixelCoord, sampleIndex, bounceIndex, rngState,
447
464
 
448
465
  };
449
466
 
450
- // Get stratified sample with proper blue noise support
467
+ // 2D pair on an explicitly chosen dimension. getRandomSample derives its dimension from
468
+ // bounceIndex, which is fine for the one BSDF sample per bounce but cannot express the several
469
+ // independent 2D draws NEE needs at the same bounce.
470
+
471
+ export const getRandomSample2D = ( pixelCoord, sampleIndex, dimensionIndex, rngState, resolution, frame ) => {
472
+
473
+ const result = vec2( 0.0 ).toVar();
474
+
475
+ If( samplingTechnique.equal( int( 0 ) ), () => {
476
+
477
+ // per-component .toVar(): vec2( RandomValue, RandomValue ) collapses to u==v
478
+ const u1 = RandomValue( rngState ).toVar();
479
+ const u2 = RandomValue( rngState ).toVar();
480
+ result.assign( vec2( u1, u2 ) );
481
+
482
+ } ).ElseIf( samplingTechnique.equal( int( 1 ) ), () => {
483
+
484
+ const pixelHash = uint( pixelCoord.x ).add( uint( pixelCoord.y ).mul( uint( resolution.x ) ) );
485
+ const scramble = pcgHash( { state: pixelHash.bitXor( uint( dimensionIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
486
+ result.assign( vec2(
487
+ haltonScrambled( { index: int( frame ), base: int( 2 ), scramble } ),
488
+ haltonScrambled( { index: int( frame ), base: int( 3 ), scramble } ),
489
+ ) );
490
+
491
+ } ).Else( () => {
492
+
493
+ const pixelHash = uint( pixelCoord.x ).add( uint( pixelCoord.y ).mul( uint( resolution.x ) ) );
494
+ const seed = pcgHash( { state: pixelHash.bitXor( uint( dimensionIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
495
+ result.assign( owen_scrambled_sobol2D( { index: uint( frame ), seed } ) );
496
+
497
+ } );
498
+
499
+ return result;
500
+
501
+ };
502
+
503
+ // Single scalar variate on its own dimension — for discrete choices that must stay independent
504
+ // of the 2D pair driving direction sampling. `dimensionIndex` keys the scramble seed, so callers
505
+ // must keep it distinct from the other 1D dimensions in use at the same bounce.
506
+
507
+ export const getRandomSample1D = ( pixelCoord, sampleIndex, dimensionIndex, rngState, resolution, frame ) => {
508
+
509
+ const result = float( 0.0 ).toVar();
510
+
511
+ If( samplingTechnique.equal( int( 0 ) ), () => {
512
+
513
+ result.assign( RandomValue( rngState ) );
514
+
515
+ } ).ElseIf( samplingTechnique.equal( int( 1 ) ), () => {
516
+
517
+ const pixelHash = uint( pixelCoord.x ).add( uint( pixelCoord.y ).mul( uint( resolution.x ) ) );
518
+ const scramble = pcgHash( { state: pixelHash.bitXor( uint( dimensionIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
519
+ result.assign( haltonScrambled( { index: int( frame ), base: int( 2 ), scramble } ) );
520
+
521
+ } ).Else( () => {
522
+
523
+ const pixelHash = uint( pixelCoord.x ).add( uint( pixelCoord.y ).mul( uint( resolution.x ) ) );
524
+ const seed = pcgHash( { state: pixelHash.bitXor( uint( dimensionIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
525
+ result.assign( owen_scrambled_sobol( { index: uint( frame ), dimension: uint( 0 ), seed } ) );
526
+
527
+ } );
528
+
529
+ return result;
530
+
531
+ };
532
+
533
+ // Stratified sample. Both call sites pass totalRays = 1, so only the first branch is live;
534
+ // the strata path below is kept for the multi-ray-per-pixel mode that was removed.
451
535
 
452
536
  export const getStratifiedSample = ( pixelCoord, rayIndex, totalRays, rngState, resolution, frame ) => {
453
537
 
@@ -472,28 +556,14 @@ export const getStratifiedSample = ( pixelCoord, rayIndex, totalRays, rngState,
472
556
 
473
557
  const strataPos = vec2( float( sx ), float( sy ) ).div( vec2( float( strataX ), float( strataY ) ) );
474
558
 
475
- // Jitter via STBN or fast RNG fallback
476
-
477
- const jitter = vec2( 0.0 ).toVar();
478
-
479
- If( samplingTechnique.greaterThanEqual( int( 3 ) ), () => {
559
+ const j1 = RandomValueFast( rngState ).toVar();
560
+ const j2 = RandomValueFast( rngState ).toVar();
561
+ const jitter = vec2( j1, j2 ).toVar();
480
562
 
481
- // STBN — true spatiotemporal blue noise jitter
482
- jitter.assign( sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ) );
483
-
484
- } ).Else( () => {
563
+ If( totalRays.greaterThan( int( 4 ) ), () => {
485
564
 
486
- // Fast RNG with subtle STBN influence for better convergence
487
- const j1 = RandomValueFast( rngState ).toVar();
488
- const j2 = RandomValueFast( rngState ).toVar();
489
- jitter.assign( vec2( j1, j2 ) );
490
-
491
- If( totalRays.greaterThan( int( 4 ) ), () => {
492
-
493
- const stbnInfluence = sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ).mul( 0.1 );
494
- jitter.assign( mix( jitter, stbnInfluence, 0.2 ) );
495
-
496
- } );
565
+ const stbnInfluence = sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ).mul( 0.1 );
566
+ jitter.assign( mix( jitter, stbnInfluence, 0.2 ) );
497
567
 
498
568
  } );
499
569