rayzee 5.4.3 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rayzee.es.js +1614 -1720
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +46 -52
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Stages/AutoExposure.js +160 -86
- package/src/Stages/PathTracer.js +37 -21
- package/src/TSL/LightsDirect.js +1 -2
- package/src/TSL/MaterialProperties.js +7 -99
- package/src/TSL/MaterialSampling.js +5 -16
- package/src/TSL/PathTracerCore.js +1 -14
- package/src/TSL/Random.js +62 -135
- package/src/TSL/Struct.js +6 -28
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MaterialClassification,
|
|
6
6
|
MaterialCache,
|
|
7
7
|
ImportanceSamplingInfo,
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
} from './Struct.js';
|
|
10
10
|
|
|
11
11
|
import {
|
|
@@ -394,22 +394,13 @@ export const getImportanceSamplingInfo = Fn( ( [
|
|
|
394
394
|
const tempMaxSheenColor = max( material.sheenColor.r, max( material.sheenColor.g, material.sheenColor.b ) );
|
|
395
395
|
|
|
396
396
|
const tempCache = MaterialCache( {
|
|
397
|
+
F0: dielectricF0( material.ior ),
|
|
397
398
|
NoV: float( 0.5 ),
|
|
399
|
+
diffuseColor: material.color.rgb,
|
|
398
400
|
isPurelyDiffuse: false,
|
|
399
|
-
isMetallic: mc.isMetallic,
|
|
400
|
-
hasSpecialFeatures: false,
|
|
401
401
|
alpha: material.roughness.mul( material.roughness ),
|
|
402
|
-
alpha2: material.roughness.mul( material.roughness ).mul( material.roughness ).mul( material.roughness ),
|
|
403
402
|
k: material.roughness.add( 1.0 ).mul( material.roughness.add( 1.0 ) ).div( 8.0 ),
|
|
404
|
-
|
|
405
|
-
diffuseColor: material.color.rgb,
|
|
406
|
-
specularColor: material.color.rgb,
|
|
407
|
-
tsAlbedo: material.color,
|
|
408
|
-
tsEmissive: material.emissive,
|
|
409
|
-
tsMetalness: material.metalness,
|
|
410
|
-
tsRoughness: material.roughness,
|
|
411
|
-
tsNormal: vec3( 0.0, 1.0, 0.0 ),
|
|
412
|
-
tsHasTextures: false,
|
|
403
|
+
alpha2: material.roughness.mul( material.roughness ).mul( material.roughness ).mul( material.roughness ),
|
|
413
404
|
invRoughness: tempInvRoughness,
|
|
414
405
|
metalFactor: tempMetalFactor,
|
|
415
406
|
iorFactor: tempIorFactor,
|
|
@@ -500,13 +491,6 @@ export const createMaterialCache = Fn( ( [ N, V, material, samples, mc ] ) => {
|
|
|
500
491
|
.and( material.clearcoat.equal( 0.0 ) )
|
|
501
492
|
.toVar();
|
|
502
493
|
|
|
503
|
-
const isMetallic = mc.isMetallic.toVar();
|
|
504
|
-
|
|
505
|
-
const hasSpecialFeatures = mc.isTransmissive.or( mc.hasClearcoat )
|
|
506
|
-
.or( material.sheen.greaterThan( 0.0 ) )
|
|
507
|
-
.or( material.iridescence.greaterThan( 0.0 ) )
|
|
508
|
-
.toVar();
|
|
509
|
-
|
|
510
494
|
const alpha = samples.roughness.mul( samples.roughness ).toVar();
|
|
511
495
|
const alpha2 = alpha.mul( alpha ).toVar();
|
|
512
496
|
const r = samples.roughness.add( 1.0 );
|
|
@@ -515,7 +499,6 @@ export const createMaterialCache = Fn( ( [ N, V, material, samples, mc ] ) => {
|
|
|
515
499
|
const baseF0 = dielectricF0( material.ior ).mul( material.specularColor );
|
|
516
500
|
const F0 = mix( baseF0, samples.albedo.rgb, samples.metalness ).mul( material.specularIntensity ).toVar();
|
|
517
501
|
const diffuseColor = samples.albedo.rgb.mul( float( 1.0 ).sub( samples.metalness ) ).toVar();
|
|
518
|
-
const specularColor = samples.albedo.rgb.toVar();
|
|
519
502
|
|
|
520
503
|
const invRoughness = float( 1.0 ).sub( samples.roughness ).toVar();
|
|
521
504
|
const metalFactor = float( 0.5 ).add( float( 0.5 ).mul( samples.metalness ) ).toVar();
|
|
@@ -523,89 +506,13 @@ export const createMaterialCache = Fn( ( [ N, V, material, samples, mc ] ) => {
|
|
|
523
506
|
const maxSheenColor = max( material.sheenColor.r, max( material.sheenColor.g, material.sheenColor.b ) ).toVar();
|
|
524
507
|
|
|
525
508
|
return MaterialCache( {
|
|
526
|
-
NoV,
|
|
527
|
-
isPurelyDiffuse,
|
|
528
|
-
isMetallic,
|
|
529
|
-
hasSpecialFeatures,
|
|
530
|
-
alpha,
|
|
531
|
-
alpha2,
|
|
532
|
-
k,
|
|
533
509
|
F0,
|
|
534
|
-
diffuseColor,
|
|
535
|
-
specularColor,
|
|
536
|
-
tsAlbedo: samples.albedo,
|
|
537
|
-
tsEmissive: samples.emissive,
|
|
538
|
-
tsMetalness: samples.metalness,
|
|
539
|
-
tsRoughness: samples.roughness,
|
|
540
|
-
tsNormal: samples.normal,
|
|
541
|
-
tsHasTextures: samples.hasTextures,
|
|
542
|
-
invRoughness,
|
|
543
|
-
metalFactor,
|
|
544
|
-
iorFactor,
|
|
545
|
-
maxSheenColor,
|
|
546
|
-
} );
|
|
547
|
-
|
|
548
|
-
} );
|
|
549
|
-
|
|
550
|
-
export const createMaterialCacheLegacy = Fn( ( [ N, V, material ] ) => {
|
|
551
|
-
|
|
552
|
-
const NoV = max( dot( N, V ), 0.001 ).toVar();
|
|
553
|
-
|
|
554
|
-
const isPurelyDiffuse = material.roughness.greaterThan( 0.98 )
|
|
555
|
-
.and( material.metalness.lessThan( 0.02 ) )
|
|
556
|
-
.and( material.transmission.equal( 0.0 ) )
|
|
557
|
-
.and( material.clearcoat.equal( 0.0 ) )
|
|
558
|
-
.toVar();
|
|
559
|
-
|
|
560
|
-
const isMetallic = material.metalness.greaterThan( 0.7 ).toVar();
|
|
561
|
-
|
|
562
|
-
const hasSpecialFeatures = material.transmission.greaterThan( 0.0 )
|
|
563
|
-
.or( material.clearcoat.greaterThan( 0.0 ) )
|
|
564
|
-
.or( material.sheen.greaterThan( 0.0 ) )
|
|
565
|
-
.or( material.iridescence.greaterThan( 0.0 ) )
|
|
566
|
-
.toVar();
|
|
567
|
-
|
|
568
|
-
const alpha = material.roughness.mul( material.roughness ).toVar();
|
|
569
|
-
const alpha2 = alpha.mul( alpha ).toVar();
|
|
570
|
-
const r = material.roughness.add( 1.0 );
|
|
571
|
-
const k = r.mul( r ).div( 8.0 ).toVar();
|
|
572
|
-
|
|
573
|
-
const baseF0 = dielectricF0( material.ior ).mul( material.specularColor );
|
|
574
|
-
const F0 = mix( baseF0, material.color.rgb, material.metalness ).mul( material.specularIntensity ).toVar();
|
|
575
|
-
const diffuseColor = material.color.rgb.mul( float( 1.0 ).sub( material.metalness ) ).toVar();
|
|
576
|
-
const specularColor = material.color.rgb.toVar();
|
|
577
|
-
|
|
578
|
-
const dummySamples = MaterialSamples( {
|
|
579
|
-
albedo: material.color,
|
|
580
|
-
emissive: material.emissive.mul( material.emissiveIntensity ),
|
|
581
|
-
metalness: material.metalness,
|
|
582
|
-
roughness: material.roughness,
|
|
583
|
-
normal: N,
|
|
584
|
-
hasTextures: false,
|
|
585
|
-
} );
|
|
586
|
-
|
|
587
|
-
const invRoughness = float( 1.0 ).sub( material.roughness ).toVar();
|
|
588
|
-
const metalFactor = float( 0.5 ).add( float( 0.5 ).mul( material.metalness ) ).toVar();
|
|
589
|
-
const iorFactor = min( float( 2.0 ).div( material.ior ), 1.0 ).toVar();
|
|
590
|
-
const maxSheenColor = max( material.sheenColor.r, max( material.sheenColor.g, material.sheenColor.b ) ).toVar();
|
|
591
|
-
|
|
592
|
-
return MaterialCache( {
|
|
593
510
|
NoV,
|
|
511
|
+
diffuseColor,
|
|
594
512
|
isPurelyDiffuse,
|
|
595
|
-
isMetallic,
|
|
596
|
-
hasSpecialFeatures,
|
|
597
513
|
alpha,
|
|
598
|
-
alpha2,
|
|
599
514
|
k,
|
|
600
|
-
|
|
601
|
-
diffuseColor,
|
|
602
|
-
specularColor,
|
|
603
|
-
tsAlbedo: dummySamples.albedo,
|
|
604
|
-
tsEmissive: dummySamples.emissive,
|
|
605
|
-
tsMetalness: dummySamples.metalness,
|
|
606
|
-
tsRoughness: dummySamples.roughness,
|
|
607
|
-
tsNormal: dummySamples.normal,
|
|
608
|
-
tsHasTextures: dummySamples.hasTextures,
|
|
515
|
+
alpha2,
|
|
609
516
|
invRoughness,
|
|
610
517
|
metalFactor,
|
|
611
518
|
iorFactor,
|
|
@@ -613,3 +520,4 @@ export const createMaterialCacheLegacy = Fn( ( [ N, V, material ] ) => {
|
|
|
613
520
|
} );
|
|
614
521
|
|
|
615
522
|
} );
|
|
523
|
+
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Fn, wgslFn, float, vec3,
|
|
3
|
-
If, max, min, abs, normalize, reflect, refract, dot, pow
|
|
2
|
+
Fn, wgslFn, float, vec3, If, max, min, abs, normalize, reflect, refract, dot, pow
|
|
4
3
|
} from 'three/tsl';
|
|
5
4
|
|
|
6
5
|
import {
|
|
@@ -8,8 +7,7 @@ import {
|
|
|
8
7
|
} from './Struct.js';
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
|
-
PI, PI_INV, MIN_PDF,
|
|
12
|
-
classifyMaterial, square,
|
|
10
|
+
PI, PI_INV, MIN_PDF, classifyMaterial,
|
|
13
11
|
} from './Common.js';
|
|
14
12
|
import { dielectricF0 } from './Fresnel.js';
|
|
15
13
|
|
|
@@ -116,22 +114,13 @@ export const calculateSamplingWeights = Fn( ( [ V, N, material ] ) => {
|
|
|
116
114
|
|
|
117
115
|
// Create temporary cache for calculations
|
|
118
116
|
const tempCache = MaterialCache( {
|
|
117
|
+
F0: dielectricF0( material.ior ),
|
|
119
118
|
NoV: float( 0.5 ),
|
|
119
|
+
diffuseColor: material.color.rgb,
|
|
120
120
|
isPurelyDiffuse: false,
|
|
121
|
-
isMetallic: mc.isMetallic,
|
|
122
|
-
hasSpecialFeatures: false,
|
|
123
121
|
alpha: material.roughness.mul( material.roughness ),
|
|
124
|
-
alpha2: material.roughness.mul( material.roughness ).mul( material.roughness ).mul( material.roughness ),
|
|
125
122
|
k: material.roughness.add( 1.0 ).mul( material.roughness.add( 1.0 ) ).div( 8.0 ),
|
|
126
|
-
|
|
127
|
-
diffuseColor: material.color.rgb,
|
|
128
|
-
specularColor: material.color.rgb,
|
|
129
|
-
tsAlbedo: material.color, // placeholder
|
|
130
|
-
tsEmissive: vec3( 0.0 ),
|
|
131
|
-
tsMetalness: float( 0.0 ),
|
|
132
|
-
tsRoughness: material.roughness,
|
|
133
|
-
tsNormal: vec3( 0.0, 1.0, 0.0 ),
|
|
134
|
-
tsHasTextures: false,
|
|
123
|
+
alpha2: material.roughness.mul( material.roughness ).mul( material.roughness ).mul( material.roughness ),
|
|
135
124
|
invRoughness: tempInvRoughness,
|
|
136
125
|
metalFactor: tempMetalFactor,
|
|
137
126
|
iorFactor: tempIorFactor,
|
|
@@ -184,19 +184,10 @@ export const generateSampledDirection = Fn( ( [
|
|
|
184
184
|
F0: dielectricF0( material.ior ),
|
|
185
185
|
NoV: float( 1.0 ),
|
|
186
186
|
diffuseColor: vec3( 0.0 ),
|
|
187
|
-
specularColor: vec3( 0.0 ),
|
|
188
|
-
isMetallic: false,
|
|
189
187
|
isPurelyDiffuse: false,
|
|
190
|
-
hasSpecialFeatures: false,
|
|
191
188
|
alpha: float( 0.0 ),
|
|
192
189
|
k: float( 0.0 ),
|
|
193
190
|
alpha2: float( 0.0 ),
|
|
194
|
-
tsAlbedo: vec4( 0.0 ),
|
|
195
|
-
tsEmissive: vec3( 0.0 ),
|
|
196
|
-
tsMetalness: float( 0.0 ),
|
|
197
|
-
tsRoughness: float( 0.0 ),
|
|
198
|
-
tsNormal: vec3( 0.0 ),
|
|
199
|
-
tsHasTextures: false,
|
|
200
191
|
invRoughness: float( 1.0 ).sub( material.roughness ),
|
|
201
192
|
metalFactor: float( 0.5 ).add( float( 0.5 ).mul( material.metalness ) ),
|
|
202
193
|
iorFactor: min( float( 2.0 ).div( material.ior ), 1.0 ),
|
|
@@ -655,12 +646,8 @@ export const Trace = Fn( ( [
|
|
|
655
646
|
// Cached material cache
|
|
656
647
|
const psCachedMaterialCache = MaterialCache( {
|
|
657
648
|
F0: vec3( 0.04 ), NoV: float( 1.0 ),
|
|
658
|
-
diffuseColor: vec3( 0.0 ),
|
|
659
|
-
isMetallic: false, isPurelyDiffuse: false, hasSpecialFeatures: false,
|
|
649
|
+
diffuseColor: vec3( 0.0 ), isPurelyDiffuse: false,
|
|
660
650
|
alpha: float( 0.0 ), k: float( 0.0 ), alpha2: float( 0.0 ),
|
|
661
|
-
tsAlbedo: vec4( 0.0 ), tsEmissive: vec3( 0.0 ),
|
|
662
|
-
tsMetalness: float( 0.0 ), tsRoughness: float( 0.0 ),
|
|
663
|
-
tsNormal: vec3( 0.0 ), tsHasTextures: false,
|
|
664
651
|
invRoughness: float( 1.0 ), metalFactor: float( 0.5 ),
|
|
665
652
|
iorFactor: float( 1.0 ), maxSheenColor: float( 0.0 ),
|
|
666
653
|
} ).toVar();
|
package/src/TSL/Random.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Three.js Transpiler r182
|
|
2
2
|
|
|
3
|
-
import { uniform, texture,
|
|
3
|
+
import { uniform, texture, float, If, wgslFn, uint, TWO_PI, cos, sin, vec2, sqrt, fract, mod, ivec2, select, int, vec4, mix } from 'three/tsl';
|
|
4
4
|
import { DataTexture, FloatType } from 'three';
|
|
5
5
|
|
|
6
6
|
// -----------------------------------------------------------------------------
|
|
@@ -12,20 +12,30 @@ const samplingTechnique = samplingTechniqueUniform;
|
|
|
12
12
|
|
|
13
13
|
// 0: PCG, 1: Halton, 2: Sobol, 3: Blue Noise
|
|
14
14
|
|
|
15
|
-
// 1x1 placeholder — real texture assigned later via
|
|
15
|
+
// 1x1 placeholder — real texture assigned later via .value = ...
|
|
16
16
|
const _placeholderData = new Float32Array( [ 0.5, 0.5, 0.5, 1.0 ] );
|
|
17
|
-
const _placeholderTex = new DataTexture( _placeholderData, 1, 1 );
|
|
18
|
-
_placeholderTex.type = FloatType;
|
|
19
|
-
_placeholderTex.needsUpdate = true;
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const _placeholderScalar = new DataTexture( _placeholderData, 1, 1 );
|
|
19
|
+
_placeholderScalar.type = FloatType;
|
|
20
|
+
_placeholderScalar.needsUpdate = true;
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
const _placeholderVec2 = new DataTexture( new Float32Array( [ 0.5, 0.5, 0.0, 1.0 ] ), 1, 1 );
|
|
23
|
+
_placeholderVec2.type = FloatType;
|
|
24
|
+
_placeholderVec2.needsUpdate = true;
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// STBN (Spatiotemporal Blue Noise) atlas textures — Heitz 2019
|
|
27
|
+
// Each atlas: 1024×1024, 8×8 grid of 128×128 tiles, 64 temporal slices
|
|
28
|
+
// Scalar atlas: single-channel (R) — optimal for 1D decisions (RR, lobe selection)
|
|
29
|
+
// Vec2 atlas: two-channel (R,G) — decorrelated 2D pairs (direction sampling Xi)
|
|
30
|
+
export const stbnScalarTextureNode = texture( _placeholderScalar );
|
|
31
|
+
stbnScalarTextureNode.setUpdateMatrix( false );
|
|
32
|
+
|
|
33
|
+
export const stbnVec2TextureNode = texture( _placeholderVec2 );
|
|
34
|
+
stbnVec2TextureNode.setUpdateMatrix( false );
|
|
35
|
+
|
|
36
|
+
// R2 quasi-random sequence constants (Roberts 2018) — optimal 2D additive offsets
|
|
37
|
+
const R2_A1 = float( 0.7548776662466927 );
|
|
38
|
+
const R2_A2 = float( 0.5698402909980532 );
|
|
29
39
|
|
|
30
40
|
// Sobol sequence direction vectors using function lookup for compatibility
|
|
31
41
|
|
|
@@ -160,126 +170,49 @@ export const RandomPointInCircle = ( rngState ) => {
|
|
|
160
170
|
};
|
|
161
171
|
|
|
162
172
|
// -----------------------------------------------------------------------------
|
|
163
|
-
//
|
|
173
|
+
// STBN atlas sampling — proper spatiotemporal blue noise
|
|
164
174
|
// -----------------------------------------------------------------------------
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
fn cranleyPatterson2D( p: vec2f, offset: vec2f ) -> vec2f {
|
|
169
|
-
|
|
170
|
-
return fract( p + offset );
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
` );
|
|
174
|
-
|
|
175
|
-
// Improved blue noise sampling that properly uses all parameters
|
|
176
|
-
|
|
177
|
-
export const sampleBlueNoiseRaw = /*@__PURE__*/ Fn( ( [ pixelCoords, sampleIndex, bounceIndex, frame ] ) => {
|
|
178
|
-
|
|
179
|
-
// Create dimension-specific offsets using golden ratio
|
|
180
|
-
|
|
181
|
-
const dimensionOffset = vec2( fract( float( sampleIndex ).mul( INV_PHI ) ), fract( float( bounceIndex ).mul( INV_PHI2 ) ) );
|
|
182
|
-
|
|
183
|
-
// Frame-based decorrelation with better hash
|
|
184
|
-
|
|
185
|
-
const frameHash = wang_hash( { seed: pcgHash( { state: uint( frame ) } ) } );
|
|
186
|
-
const frameOffset = vec2( float( frameHash.bitAnd( 0xFFFF ) ).div( 65536.0 ), float( frameHash.shiftRight( 16 ).bitAnd( 0xFFFF ) ).div( 65536.0 ) );
|
|
187
|
-
|
|
188
|
-
// Scale offsets to texture size
|
|
189
|
-
|
|
190
|
-
const scaledDimOffset = dimensionOffset.mul( vec2( blueNoiseTextureSize ) );
|
|
191
|
-
const scaledFrameOffset = frameOffset.mul( vec2( blueNoiseTextureSize ) );
|
|
192
|
-
|
|
193
|
-
// Combine all offsets with proper toroidal wrapping
|
|
194
|
-
|
|
195
|
-
const coords = mod( pixelCoords.add( scaledDimOffset ).add( scaledFrameOffset ), vec2( blueNoiseTextureSize ) );
|
|
196
|
-
|
|
197
|
-
// Ensure positive coordinates and fetch
|
|
198
|
-
// .load() → textureLoad() in WGSL: exact integer texel fetch, no filtering (≡ GLSL texelFetch)
|
|
199
|
-
|
|
200
|
-
const texCoord = ivec2( floor( coords ) );
|
|
201
|
-
|
|
202
|
-
return blueNoiseTextureNode.load( texCoord );
|
|
203
|
-
|
|
204
|
-
}, { pixelCoords: 'vec2', sampleIndex: 'int', bounceIndex: 'int', frame: 'int', return: 'vec4' } );
|
|
205
|
-
|
|
206
|
-
// Get 2D blue noise sample with dimension offset
|
|
207
|
-
|
|
208
|
-
export const sampleBlueNoise2D = /*@__PURE__*/ Fn( ( [ pixelCoords, sampleIndex, dimensionBase, frame ] ) => {
|
|
209
|
-
|
|
210
|
-
// For 2D sampling, we need to carefully select components to maintain blue noise properties
|
|
211
|
-
|
|
212
|
-
const noise = sampleBlueNoiseRaw( pixelCoords, sampleIndex, dimensionBase.div( int( 2 ) ), frame );
|
|
213
|
-
|
|
214
|
-
// Use different component pairs based on dimension
|
|
215
|
-
|
|
216
|
-
const pairIndex = mod( dimensionBase.div( int( 2 ) ), int( 6 ) );
|
|
175
|
+
// Atlas layout: 8×8 grid of 128×128 tiles = 1024×1024 texture.
|
|
176
|
+
// Temporal axis: frame % 64 selects tile (true STBN temporal decorrelation).
|
|
177
|
+
// Spatial decorrelation: R2 quasi-random offset keyed on dimension + sample index.
|
|
217
178
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Switch( pairIndex )
|
|
221
|
-
.Case( 0, () => {
|
|
222
|
-
|
|
223
|
-
result.assign( noise.xy );
|
|
224
|
-
|
|
225
|
-
} ).Case( 1, () => {
|
|
226
|
-
|
|
227
|
-
result.assign( noise.zw );
|
|
228
|
-
|
|
229
|
-
} ).Case( 2, () => {
|
|
230
|
-
|
|
231
|
-
result.assign( noise.xz );
|
|
179
|
+
const computeSTBNAtlasCoord = ( pixelCoords, sampleIndex, dimensionIndex, frame ) => {
|
|
232
180
|
|
|
233
|
-
|
|
181
|
+
// Temporal slice — true STBN temporal axis
|
|
182
|
+
const slice = uint( frame ).bitAnd( uint( 63 ) ); // frame % 64
|
|
234
183
|
|
|
235
|
-
|
|
184
|
+
// R2 quasi-random spatial offset for per-dimension/per-sample decorrelation
|
|
185
|
+
const n = float( dimensionIndex ).add( float( sampleIndex ).mul( 7.0 ) );
|
|
186
|
+
const offsetX = int( fract( n.mul( R2_A1 ).add( 0.5 ) ).mul( 128.0 ) );
|
|
187
|
+
const offsetY = int( fract( n.mul( R2_A2 ).add( 0.5 ) ).mul( 128.0 ) );
|
|
236
188
|
|
|
237
|
-
|
|
189
|
+
// Pixel within 128×128 tile (toroidal wrap via bitmask)
|
|
190
|
+
const px = int( pixelCoords.x ).add( offsetX ).bitAnd( int( 127 ) );
|
|
191
|
+
const py = int( pixelCoords.y ).add( offsetY ).bitAnd( int( 127 ) );
|
|
238
192
|
|
|
239
|
-
|
|
193
|
+
// Atlas tile position from slice index
|
|
194
|
+
const tileCol = int( slice ).bitAnd( int( 7 ) ); // slice % 8
|
|
195
|
+
const tileRow = int( slice ).shiftRight( int( 3 ) ); // slice / 8
|
|
240
196
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
result.assign( noise.yz );
|
|
244
|
-
|
|
245
|
-
} ).Default( () => {
|
|
246
|
-
|
|
247
|
-
result.assign( noise.xy );
|
|
248
|
-
|
|
249
|
-
} );
|
|
250
|
-
|
|
251
|
-
return result;
|
|
197
|
+
return ivec2( tileCol.mul( int( 128 ) ).add( px ), tileRow.mul( int( 128 ) ).add( py ) );
|
|
252
198
|
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Progressive blue noise sampling for temporal accumulation
|
|
256
|
-
|
|
257
|
-
export const sampleProgressiveBlueNoise = /*@__PURE__*/ Fn( ( [ pixelCoords, currentSample, maxSamples, frame ] ) => {
|
|
258
|
-
|
|
259
|
-
// Determine which "slice" of the blue noise we're in
|
|
260
|
-
|
|
261
|
-
const progress = float( currentSample ).div( max( 1.0, float( maxSamples ) ) );
|
|
262
|
-
const temporalSlice = int( progress.mul( 16.0 ) );
|
|
263
|
-
|
|
264
|
-
// 16 temporal slices
|
|
265
|
-
// Use different regions of blue noise for different sample counts
|
|
266
|
-
|
|
267
|
-
const sliceOffset = vec2( float( mod( temporalSlice, int( 4 ) ) ).mul( 0.25 ), float( temporalSlice.div( int( 4 ) ) ).mul( 0.25 ) );
|
|
199
|
+
};
|
|
268
200
|
|
|
269
|
-
|
|
201
|
+
// Sample 1D scalar STBN value in [0,1]
|
|
202
|
+
export const sampleSTBNScalar = ( pixelCoords, sampleIndex, dimensionIndex, frame ) => {
|
|
270
203
|
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
const noise = sampleBlueNoiseRaw( coords, currentSample, int( 0 ), frame );
|
|
204
|
+
const coord = computeSTBNAtlasCoord( pixelCoords, sampleIndex, dimensionIndex, frame );
|
|
205
|
+
return stbnScalarTextureNode.load( coord ).x;
|
|
274
206
|
|
|
275
|
-
|
|
207
|
+
};
|
|
276
208
|
|
|
277
|
-
|
|
278
|
-
|
|
209
|
+
// Sample decorrelated 2D STBN pair in [0,1]²
|
|
210
|
+
export const sampleSTBN2D = ( pixelCoords, sampleIndex, dimensionPairIndex, frame ) => {
|
|
279
211
|
|
|
280
|
-
|
|
212
|
+
const coord = computeSTBNAtlasCoord( pixelCoords, sampleIndex, dimensionPairIndex, frame );
|
|
213
|
+
return stbnVec2TextureNode.load( coord ).xy;
|
|
281
214
|
|
|
282
|
-
}
|
|
215
|
+
};
|
|
283
216
|
|
|
284
217
|
// -----------------------------------------------------------------------------
|
|
285
218
|
// Low-discrepancy sequence generators
|
|
@@ -490,19 +423,20 @@ export const getRandomSampleND = ( pixelCoord, sampleIndex, bounceIndex, rngStat
|
|
|
490
423
|
|
|
491
424
|
} ).Else( () => {
|
|
492
425
|
|
|
493
|
-
// Blue Noise (technique 3)
|
|
494
|
-
|
|
426
|
+
// STBN — Spatiotemporal Blue Noise (technique 3)
|
|
427
|
+
// Each bounce uses a block of 4 dimension indices for decorrelation
|
|
428
|
+
const dimBase = bounceIndex.mul( int( 4 ) );
|
|
495
429
|
|
|
496
430
|
If( dimensions.lessThanEqual( int( 2 ) ), () => {
|
|
497
431
|
|
|
498
|
-
const _sample =
|
|
432
|
+
const _sample = sampleSTBN2D( pixelCoord, sampleIndex, dimBase, frame );
|
|
499
433
|
result.x.assign( _sample.x );
|
|
500
434
|
result.y.assign( _sample.y );
|
|
501
435
|
|
|
502
436
|
} ).Else( () => {
|
|
503
437
|
|
|
504
|
-
const _sample1 =
|
|
505
|
-
const _sample2 =
|
|
438
|
+
const _sample1 = sampleSTBN2D( pixelCoord, sampleIndex, dimBase, frame );
|
|
439
|
+
const _sample2 = sampleSTBN2D( pixelCoord, sampleIndex, dimBase.add( int( 1 ) ), frame );
|
|
506
440
|
result.assign( vec4( _sample1, _sample2 ) );
|
|
507
441
|
|
|
508
442
|
} );
|
|
@@ -551,33 +485,26 @@ export const getStratifiedSample = ( pixelCoord, rayIndex, totalRays, rngState,
|
|
|
551
485
|
|
|
552
486
|
const strataPos = vec2( float( sx ), float( sy ) ).div( vec2( float( strataX ), float( strataY ) ) );
|
|
553
487
|
|
|
554
|
-
//
|
|
488
|
+
// Jitter via STBN or fast RNG fallback
|
|
555
489
|
|
|
556
490
|
const jitter = vec2( 0.0 ).toVar();
|
|
557
491
|
|
|
558
492
|
If( samplingTechnique.greaterThanEqual( int( 3 ) ), () => {
|
|
559
493
|
|
|
560
|
-
//
|
|
561
|
-
|
|
562
|
-
jitter.assign( sampleProgressiveBlueNoise( pixelCoord, rayIndex, totalRays, frame ) );
|
|
494
|
+
// STBN — true spatiotemporal blue noise jitter
|
|
495
|
+
jitter.assign( sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ) );
|
|
563
496
|
|
|
564
497
|
} ).Else( () => {
|
|
565
498
|
|
|
566
|
-
//
|
|
567
|
-
// .toVar() on each call to capture value before next state advance
|
|
568
|
-
|
|
499
|
+
// Fast RNG with subtle STBN influence for better convergence
|
|
569
500
|
const j1 = RandomValueFast( rngState ).toVar();
|
|
570
501
|
const j2 = RandomValueFast( rngState ).toVar();
|
|
571
502
|
jitter.assign( vec2( j1, j2 ) );
|
|
572
503
|
|
|
573
|
-
// Add subtle blue noise influence even for non-blue-noise techniques
|
|
574
|
-
|
|
575
504
|
If( totalRays.greaterThan( int( 4 ) ), () => {
|
|
576
505
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const blueNoiseInfluence = sampleBlueNoise2D( pixelCoord, rayIndex, int( 0 ), frame ).mul( 0.1 );
|
|
580
|
-
jitter.assign( mix( jitter, blueNoiseInfluence, 0.2 ) );
|
|
506
|
+
const stbnInfluence = sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ).mul( 0.1 );
|
|
507
|
+
jitter.assign( mix( jitter, stbnInfluence, 0.2 ) );
|
|
581
508
|
|
|
582
509
|
} );
|
|
583
510
|
|
package/src/TSL/Struct.js
CHANGED
|
@@ -172,47 +172,25 @@ export const UVCache = struct( {
|
|
|
172
172
|
allSameUV: 'bool',
|
|
173
173
|
} );
|
|
174
174
|
|
|
175
|
-
//
|
|
175
|
+
// Material cache — precomputed BRDF terms for the current surface hit.
|
|
176
|
+
// Fields are split into two groups:
|
|
177
|
+
// 1. BRDF evaluation: F0, NoV, diffuseColor, isPurelyDiffuse, alpha, k, alpha2
|
|
178
|
+
// 2. BRDF weight calc: invRoughness, metalFactor, iorFactor, maxSheenColor
|
|
176
179
|
export const MaterialCache = struct( {
|
|
177
180
|
F0: 'vec3', // Base reflectance
|
|
178
181
|
NoV: 'float', // Normal dot View
|
|
179
|
-
diffuseColor: 'vec3', // Precomputed diffuse color
|
|
180
|
-
specularColor: 'vec3', // Precomputed specular color
|
|
181
|
-
isMetallic: 'bool', // metalness > 0.7
|
|
182
|
+
diffuseColor: 'vec3', // Precomputed diffuse color (only for isPurelyDiffuse fast path)
|
|
182
183
|
isPurelyDiffuse: 'bool', // Optimized path flag
|
|
183
|
-
hasSpecialFeatures: 'bool', // Has transmission, clearcoat, etc.
|
|
184
184
|
alpha: 'float', // roughness squared
|
|
185
185
|
k: 'float', // Geometry term constant
|
|
186
186
|
alpha2: 'float', // roughness to the fourth power
|
|
187
|
-
//
|
|
188
|
-
tsAlbedo: 'vec4',
|
|
189
|
-
tsEmissive: 'vec3',
|
|
190
|
-
tsMetalness: 'float',
|
|
191
|
-
tsRoughness: 'float',
|
|
192
|
-
tsNormal: 'vec3',
|
|
193
|
-
tsHasTextures: 'bool',
|
|
194
|
-
|
|
195
|
-
// BRDF optimization: precomputed shared values
|
|
187
|
+
// BRDF weight calculation: precomputed shared values
|
|
196
188
|
invRoughness: 'float', // 1.0 - roughness
|
|
197
189
|
metalFactor: 'float', // 0.5 + 0.5 * metalness
|
|
198
190
|
iorFactor: 'float', // min(2.0 / ior, 1.0)
|
|
199
191
|
maxSheenColor: 'float', // max component of sheen color
|
|
200
192
|
} );
|
|
201
193
|
|
|
202
|
-
// Update PathState to include texture samples
|
|
203
|
-
export const PathState = struct( {
|
|
204
|
-
brdfWeights: BRDFWeights, // Cached BRDF weights
|
|
205
|
-
samplingInfo: ImportanceSamplingInfo, // Cached importance sampling info
|
|
206
|
-
materialCache: MaterialCache, // Cached material properties
|
|
207
|
-
materialClass: MaterialClassification, // Cached material classification
|
|
208
|
-
weightsComputed: 'bool', // Flag to track if weights are computed
|
|
209
|
-
texturesLoaded: 'bool', // Flag to track if textures are loaded
|
|
210
|
-
classificationCached: 'bool', // Flag for material classification
|
|
211
|
-
materialCacheCached: 'bool', // Flag for material cache creation
|
|
212
|
-
pathImportance: 'float', // Cached path importance estimate
|
|
213
|
-
lastMaterialIndex: 'int', // Track material changes to preserve cache
|
|
214
|
-
} );
|
|
215
|
-
|
|
216
194
|
export const SamplingStrategyWeights = struct( {
|
|
217
195
|
envWeight: 'float',
|
|
218
196
|
specularWeight: 'float',
|