rayzee 5.4.3 → 5.5.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 +2180 -2307
- 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/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
package/package.json
CHANGED
package/src/Stages/PathTracer.js
CHANGED
|
@@ -2,9 +2,9 @@ import { storage } from 'three/tsl';
|
|
|
2
2
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
3
3
|
import {
|
|
4
4
|
NearestFilter, Vector2, Matrix4,
|
|
5
|
-
TextureLoader, RepeatWrapping
|
|
5
|
+
TextureLoader, RepeatWrapping
|
|
6
6
|
} from 'three';
|
|
7
|
-
import {
|
|
7
|
+
import { stbnScalarTextureNode, stbnVec2TextureNode } from '../TSL/Random.js';
|
|
8
8
|
|
|
9
9
|
// Pipeline system
|
|
10
10
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
@@ -26,8 +26,9 @@ import { LightSerializer } from '../Processor/LightSerializer';
|
|
|
26
26
|
// Constants
|
|
27
27
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
29
|
+
// STBN atlases - original source: https://github.com/NVIDIA-RTX/STBN/blob/main/Assets/STBN.zip
|
|
30
|
+
const stbnScalarAtlas = 'https://assets.rayzee.atulmourya.com/noise/stbn_scalar_atlas.png';
|
|
31
|
+
const stbnVec2Atlas = 'https://assets.rayzee.atulmourya.com/noise/stbn_vec2_atlas.png';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Data layout constants
|
|
@@ -190,8 +191,9 @@ export class PathTracer extends RenderStage {
|
|
|
190
191
|
this.spotLightsData = null;
|
|
191
192
|
this.areaLightsData = null;
|
|
192
193
|
|
|
193
|
-
//
|
|
194
|
-
this.
|
|
194
|
+
// STBN noise textures
|
|
195
|
+
this.stbnScalarTexture = null;
|
|
196
|
+
this.stbnVec2Texture = null;
|
|
195
197
|
|
|
196
198
|
// Packed light buffer — [lightBVH nodes (4 vec4s each) | emissive triangles (2 vec4s each)]
|
|
197
199
|
// emissiveVec4Offset uniform tracks the vec4-count offset where emissive data starts.
|
|
@@ -376,25 +378,38 @@ export class PathTracer extends RenderStage {
|
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
/**
|
|
379
|
-
*
|
|
381
|
+
* Load STBN (Spatiotemporal Blue Noise) atlas textures.
|
|
382
|
+
* Each atlas is 1024×1024: 8×8 grid of 128×128 tiles, 64 temporal slices.
|
|
380
383
|
*/
|
|
381
384
|
setupBlueNoise() {
|
|
382
385
|
|
|
383
386
|
const loader = new TextureLoader();
|
|
384
387
|
loader.setCrossOrigin( 'anonymous' );
|
|
385
|
-
loader.load( blueNoiseImage, ( texture ) => {
|
|
386
388
|
|
|
387
|
-
|
|
388
|
-
texture.magFilter = NearestFilter;
|
|
389
|
-
texture.wrapS = RepeatWrapping;
|
|
390
|
-
texture.wrapT = RepeatWrapping;
|
|
391
|
-
texture.type = FloatType;
|
|
392
|
-
texture.generateMipmaps = false;
|
|
389
|
+
const configure = ( tex ) => {
|
|
393
390
|
|
|
394
|
-
|
|
395
|
-
|
|
391
|
+
tex.minFilter = NearestFilter;
|
|
392
|
+
tex.magFilter = NearestFilter;
|
|
393
|
+
tex.wrapS = RepeatWrapping;
|
|
394
|
+
tex.wrapT = RepeatWrapping;
|
|
395
|
+
tex.generateMipmaps = false;
|
|
396
|
+
return tex;
|
|
396
397
|
|
|
397
|
-
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
loader.load( stbnScalarAtlas, ( tex ) => {
|
|
401
|
+
|
|
402
|
+
this.stbnScalarTexture = configure( tex );
|
|
403
|
+
stbnScalarTextureNode.value = tex;
|
|
404
|
+
console.log( `PathTracer: STBN scalar atlas loaded ${tex.image.width}x${tex.image.height}` );
|
|
405
|
+
|
|
406
|
+
} );
|
|
407
|
+
|
|
408
|
+
loader.load( stbnVec2Atlas, ( tex ) => {
|
|
409
|
+
|
|
410
|
+
this.stbnVec2Texture = configure( tex );
|
|
411
|
+
stbnVec2TextureNode.value = tex;
|
|
412
|
+
console.log( `PathTracer: STBN vec2 atlas loaded ${tex.image.width}x${tex.image.height}` );
|
|
398
413
|
|
|
399
414
|
} );
|
|
400
415
|
|
|
@@ -1521,9 +1536,9 @@ export class PathTracer extends RenderStage {
|
|
|
1521
1536
|
|
|
1522
1537
|
setBlueNoiseTexture( tex ) {
|
|
1523
1538
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
if ( tex )
|
|
1539
|
+
// Legacy API — sets the scalar STBN atlas texture
|
|
1540
|
+
this.stbnScalarTexture = tex;
|
|
1541
|
+
if ( tex ) stbnScalarTextureNode.value = tex;
|
|
1527
1542
|
|
|
1528
1543
|
}
|
|
1529
1544
|
|
|
@@ -1676,7 +1691,8 @@ export class PathTracer extends RenderStage {
|
|
|
1676
1691
|
this.storageTextures?.dispose();
|
|
1677
1692
|
|
|
1678
1693
|
// Dispose textures
|
|
1679
|
-
this.
|
|
1694
|
+
this.stbnScalarTexture?.dispose();
|
|
1695
|
+
this.stbnVec2Texture?.dispose();
|
|
1680
1696
|
this.placeholderTexture?.dispose();
|
|
1681
1697
|
|
|
1682
1698
|
// Clear data references
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -28,12 +28,11 @@ import {
|
|
|
28
28
|
texture,
|
|
29
29
|
} from 'three/tsl';
|
|
30
30
|
|
|
31
|
-
import { Ray, ShadowMaterial, HitInfo
|
|
31
|
+
import { Ray, ShadowMaterial, HitInfo } from './Struct.js';
|
|
32
32
|
import { PI, TWO_PI, EPSILON, REC709_LUMINANCE_COEFFICIENTS, powerHeuristic, getShadowMaterial, getDatafromStorageBuffer } from './Common.js';
|
|
33
33
|
import { fresnelSchlickFloat } from './Fresnel.js';
|
|
34
34
|
import { iorToFresnel0 } from './Fresnel.js';
|
|
35
35
|
import {
|
|
36
|
-
DirectionalLight, AreaLight, PointLight, SpotLight,
|
|
37
36
|
sampleCone, intersectAreaLight,
|
|
38
37
|
} from './LightsCore.js';
|
|
39
38
|
import { calculateBeerLawAbsorption, calculateShadowTransmittance } from './MaterialTransmission.js';
|
|
@@ -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
|
|