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/dist/rayzee.es.js +3855 -3065
- package/dist/rayzee.umd.js +83 -109
- package/package.json +1 -1
- package/src/EngineDefaults.js +4 -3
- package/src/PathTracerApp.js +7 -0
- package/src/Pipeline/BindingAudit.js +178 -0
- package/src/Pipeline/RenderPipeline.js +5 -2
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/PackedRayBuffer.js +33 -8
- package/src/Stages/ASVGF.js +10 -10
- package/src/Stages/BilateralFilter.js +70 -11
- package/src/Stages/EdgeFilter.js +44 -6
- package/src/Stages/NormalDepth.js +10 -2
- package/src/Stages/PathTracer.js +7 -3
- package/src/Stages/PathTracerStage.js +15 -46
- package/src/TSL/Clearcoat.js +29 -40
- package/src/TSL/Common.js +1 -47
- package/src/TSL/EmissiveSampling.js +47 -50
- package/src/TSL/LightBVHSampling.js +16 -12
- package/src/TSL/LightsCore.js +3 -2
- package/src/TSL/LightsDirect.js +4 -4
- package/src/TSL/LightsIndirect.js +36 -355
- package/src/TSL/LightsSampling.js +153 -386
- package/src/TSL/MaterialEvaluation.js +44 -71
- package/src/TSL/MaterialProperties.js +209 -176
- package/src/TSL/MaterialTransmission.js +17 -32
- package/src/TSL/PathTracerCore.js +51 -66
- package/src/TSL/Random.js +202 -132
- package/src/TSL/ShadeKernel.js +89 -56
- package/src/TSL/Struct.js +6 -38
- package/src/TSL/Subsurface.js +13 -8
- package/src/index.js +5 -0
- package/src/managers/EnvironmentManager.js +0 -5
- package/src/managers/UniformManager.js +4 -1
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
249
|
-
v ^= v
|
|
250
|
-
v
|
|
251
|
-
v ^= v
|
|
252
|
-
v
|
|
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
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
for ( var i = 0; i < 32; i++ ) {
|
|
272
|
+
let r0 = reverseBits( index );
|
|
266
273
|
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
280
|
-
|
|
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
|
-
`, [
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
`, [
|
|
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 —
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
413
|
+
result.w.assign( haltonScrambled( { index: int( frame ), base: int( 7 ), scramble } ) );
|
|
382
414
|
|
|
383
415
|
} );
|
|
384
416
|
|
|
385
|
-
} ).
|
|
417
|
+
} ).Else( () => {
|
|
386
418
|
|
|
387
|
-
// Sobol —
|
|
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(
|
|
422
|
+
const seed = pcgHash( { state: pixelHash.bitXor( uint( bounceIndex ).mul( uint( 0x517cc1b7 ) ) ).bitXor( uint( sampleIndex ).mul( uint( 0x2545f491 ) ) ) } ).toVar();
|
|
390
423
|
|
|
391
|
-
|
|
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(
|
|
431
|
+
result.y.assign( xy.y );
|
|
396
432
|
|
|
397
433
|
} );
|
|
398
434
|
|
|
399
435
|
If( dimensions.greaterThan( int( 2 ) ), () => {
|
|
400
436
|
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
420
|
-
result.x.assign( _sample.x );
|
|
421
|
-
result.y.assign( _sample.y );
|
|
442
|
+
result.w.assign( zw.y );
|
|
422
443
|
|
|
423
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
const jitter = vec2(
|
|
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
|
-
|
|
482
|
-
jitter.assign( sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ) );
|
|
483
|
-
|
|
484
|
-
} ).Else( () => {
|
|
563
|
+
If( totalRays.greaterThan( int( 4 ) ), () => {
|
|
485
564
|
|
|
486
|
-
|
|
487
|
-
|
|
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
|
|