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
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Pure TSL: Fn(), If(), Loop(), .toVar(), .assign() — NO wgslFn()
|
|
5
5
|
*
|
|
6
6
|
* Direct lighting combines:
|
|
7
|
-
* -
|
|
7
|
+
* - Discrete light NEE + BSDF-hit, both evaluated per vertex (area, point, spot, directional)
|
|
8
8
|
* - Deterministic environment NEE (always runs, two-strategy Veach MIS with implicit miss)
|
|
9
9
|
*
|
|
10
10
|
* Contains:
|
|
11
11
|
* - sampleRectAreaLight — rectangle area light sampling
|
|
12
12
|
* - sampleSpotLightWithRadius — spot light sampling with radius
|
|
13
13
|
* - samplePointLightWithAttenuation — point light sampling with attenuation
|
|
14
|
-
* - sampleLightWithImportance — importance-weighted light selection (
|
|
14
|
+
* - sampleLightWithImportance — importance-weighted light selection (reservoir)
|
|
15
15
|
* - calculateMaterialPDF — material PDF for MIS
|
|
16
16
|
* - calculateDirectLightingUnified — unified direct lighting (main entry)
|
|
17
17
|
*/
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
sphQuadSolidAngle,
|
|
62
62
|
} from './LightsCore.js';
|
|
63
63
|
|
|
64
|
-
import {
|
|
64
|
+
import { DotProducts, DirectLightingDual } from './Struct.js';
|
|
65
65
|
import {
|
|
66
66
|
calculateDirectionalLightImportance,
|
|
67
67
|
estimateLightImportance,
|
|
@@ -72,10 +72,9 @@ import {
|
|
|
72
72
|
|
|
73
73
|
import { traverseBVHShadow } from './BVHTraversal.js';
|
|
74
74
|
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
75
|
+
import { calculateBSDFSamplingPDFFromMaterial } from './MaterialProperties.js';
|
|
76
|
+
import { getRandomSample1D, getRandomSample2D, SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
77
77
|
import {
|
|
78
|
-
selectOptimalMISStrategy,
|
|
79
78
|
PI,
|
|
80
79
|
PI_INV,
|
|
81
80
|
EPSILON,
|
|
@@ -223,6 +222,7 @@ export const sampleRectAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
|
|
|
223
222
|
distance: ls_distance,
|
|
224
223
|
pdf: ls_pdf,
|
|
225
224
|
lightType: ls_lightType,
|
|
225
|
+
selectionTotalWeight: float( 0.0 ),
|
|
226
226
|
} );
|
|
227
227
|
|
|
228
228
|
} );
|
|
@@ -281,6 +281,7 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectio
|
|
|
281
281
|
distance: ls_distance,
|
|
282
282
|
pdf: ls_pdf,
|
|
283
283
|
lightType: ls_lightType,
|
|
284
|
+
selectionTotalWeight: float( 0.0 ),
|
|
284
285
|
} );
|
|
285
286
|
|
|
286
287
|
} );
|
|
@@ -324,6 +325,7 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
324
325
|
distance: ls_distance,
|
|
325
326
|
pdf: ls_pdf,
|
|
326
327
|
lightType: ls_lightType,
|
|
328
|
+
selectionTotalWeight: float( 0.0 ),
|
|
327
329
|
} );
|
|
328
330
|
|
|
329
331
|
} );
|
|
@@ -340,6 +342,7 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
340
342
|
// unbiased; PDF = winnerImportance / totalWeight, identical to the CDF form.
|
|
341
343
|
export const sampleLightWithImportance = Fn( ( [
|
|
342
344
|
rayOrigin, normal, material, randomSeed, bounceIndex, rngState,
|
|
345
|
+
pixelCoord, resolution, frame, dimBase,
|
|
343
346
|
// Light buffers + counts
|
|
344
347
|
directionalLightsBuffer, numDirectionalLights,
|
|
345
348
|
areaLightsBuffer, numAreaLights,
|
|
@@ -354,12 +357,19 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
354
357
|
const r_distance = float( 0.0 ).toVar();
|
|
355
358
|
const r_pdf = float( 0.0 ).toVar();
|
|
356
359
|
const r_lightType = int( LIGHT_TYPE_POINT ).toVar();
|
|
360
|
+
const totalWeight = float( 0.0 ).toVar();
|
|
357
361
|
|
|
358
362
|
const totalLights = numDirectionalLights.add( numAreaLights ).add( numPointLights ).add( numSpotLights ).toVar();
|
|
359
363
|
|
|
360
364
|
If( totalLights.greaterThan( int( 0 ) ), () => {
|
|
361
365
|
|
|
362
|
-
|
|
366
|
+
// One sampler dimension per light in the scene: the reservoir's replacement tests must
|
|
367
|
+
// be mutually independent, and a per-type block hands light 16 of one type the same
|
|
368
|
+
// variate as light 0 of the next.
|
|
369
|
+
const dimDir = int( SAMPLER_DIM_AUX_BASE + 64 ).toVar();
|
|
370
|
+
const dimArea = dimDir.add( numDirectionalLights ).toVar();
|
|
371
|
+
const dimPoint = dimArea.add( numAreaLights ).toVar();
|
|
372
|
+
const dimSpot = dimPoint.add( numPointLights ).toVar();
|
|
363
373
|
|
|
364
374
|
// Reservoir state: winning light's type/index/importance.
|
|
365
375
|
const selectedType = int( - 1 ).toVar(); // 0=dir, 1=area, 2=point, 3=spot
|
|
@@ -374,20 +384,18 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
374
384
|
|
|
375
385
|
Loop( { start: int( 0 ), end: numDirectionalLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
376
386
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
} );
|
|
387
|
+
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, i ) );
|
|
388
|
+
const importance = calculateDirectionalLightImportance( light, normal, material, bounceIndex ).toVar();
|
|
389
|
+
totalWeight.addAssign( importance );
|
|
390
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
391
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
392
|
+
dimDir.add( i ), rngState, resolution, frame
|
|
393
|
+
).mul( totalWeight ).lessThan( importance )
|
|
394
|
+
), () => {
|
|
395
|
+
|
|
396
|
+
selectedType.assign( 0 );
|
|
397
|
+
selectedIdx.assign( i );
|
|
398
|
+
selectedImportance.assign( importance );
|
|
391
399
|
|
|
392
400
|
} );
|
|
393
401
|
|
|
@@ -399,20 +407,18 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
399
407
|
|
|
400
408
|
Loop( { start: int( 0 ), end: numAreaLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
401
409
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
} );
|
|
410
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
411
|
+
const importance = select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, rayOrigin, normal, material ), float( 0.0 ) ).toVar();
|
|
412
|
+
totalWeight.addAssign( importance );
|
|
413
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
414
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
415
|
+
dimArea.add( i ), rngState, resolution, frame
|
|
416
|
+
).mul( totalWeight ).lessThan( importance )
|
|
417
|
+
), () => {
|
|
418
|
+
|
|
419
|
+
selectedType.assign( 1 );
|
|
420
|
+
selectedIdx.assign( i );
|
|
421
|
+
selectedImportance.assign( importance );
|
|
416
422
|
|
|
417
423
|
} );
|
|
418
424
|
|
|
@@ -424,20 +430,18 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
424
430
|
|
|
425
431
|
Loop( { start: int( 0 ), end: numPointLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
426
432
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
} );
|
|
433
|
+
const light = PointLight.wrap( getPointLight( pointLightsBuffer, i ) );
|
|
434
|
+
const importance = calculatePointLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
435
|
+
totalWeight.addAssign( importance );
|
|
436
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
437
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
438
|
+
dimPoint.add( i ), rngState, resolution, frame
|
|
439
|
+
).mul( totalWeight ).lessThan( importance )
|
|
440
|
+
), () => {
|
|
441
|
+
|
|
442
|
+
selectedType.assign( 2 );
|
|
443
|
+
selectedIdx.assign( i );
|
|
444
|
+
selectedImportance.assign( importance );
|
|
441
445
|
|
|
442
446
|
} );
|
|
443
447
|
|
|
@@ -449,20 +453,18 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
449
453
|
|
|
450
454
|
Loop( { start: int( 0 ), end: numSpotLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
451
455
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
} );
|
|
456
|
+
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, i ) );
|
|
457
|
+
const importance = calculateSpotLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
458
|
+
totalWeight.addAssign( importance );
|
|
459
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
460
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
461
|
+
dimSpot.add( i ), rngState, resolution, frame
|
|
462
|
+
).mul( totalWeight ).lessThan( importance )
|
|
463
|
+
), () => {
|
|
464
|
+
|
|
465
|
+
selectedType.assign( 3 );
|
|
466
|
+
selectedIdx.assign( i );
|
|
467
|
+
selectedImportance.assign( importance );
|
|
466
468
|
|
|
467
469
|
} );
|
|
468
470
|
|
|
@@ -518,7 +520,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
518
520
|
|
|
519
521
|
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
520
522
|
|
|
521
|
-
const uv = vec2( randomSeed.y,
|
|
523
|
+
const uv = vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ).toVar();
|
|
522
524
|
const areaSample = LightSample.wrap( sampleRectAreaLight( light, rayOrigin, uv, lightSelectionPdf ) );
|
|
523
525
|
r_valid.assign( areaSample.valid );
|
|
524
526
|
r_direction.assign( areaSample.direction );
|
|
@@ -607,7 +609,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
607
609
|
const cosHalfAngle = cos( light.angle.mul( 0.5 ) ).toVar();
|
|
608
610
|
const cosTheta = mix( cosHalfAngle, float( 1.0 ), randomSeed.y ).toVar();
|
|
609
611
|
const sinTheta = sqrt( max( float( 0.0 ), float( 1.0 ).sub( cosTheta.mul( cosTheta ) ) ) ).toVar();
|
|
610
|
-
const phi = float( TWO_PI ).mul(
|
|
612
|
+
const phi = float( TWO_PI ).mul( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 14 ) ), rngState, resolution, frame ) ).toVar();
|
|
611
613
|
|
|
612
614
|
const w = normalize( light.direction ).toVar();
|
|
613
615
|
const u = normalize( cross(
|
|
@@ -639,7 +641,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
639
641
|
If( selectedType.equal( int( 1 ) ).and( selectedIdx.greaterThanEqual( int( 0 ) ) ), () => {
|
|
640
642
|
|
|
641
643
|
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, selectedIdx ) );
|
|
642
|
-
const uv = vec2( randomSeed.y,
|
|
644
|
+
const uv = vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ).toVar();
|
|
643
645
|
const areaSample = LightSample.wrap( sampleRectAreaLight( light, rayOrigin, uv, pdf ) );
|
|
644
646
|
r_valid.assign( areaSample.valid );
|
|
645
647
|
r_direction.assign( areaSample.direction );
|
|
@@ -689,6 +691,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
689
691
|
distance: r_distance,
|
|
690
692
|
pdf: r_pdf,
|
|
691
693
|
lightType: r_lightType,
|
|
694
|
+
selectionTotalWeight: totalWeight,
|
|
692
695
|
} );
|
|
693
696
|
|
|
694
697
|
} );
|
|
@@ -700,66 +703,14 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
700
703
|
// PDF computation given precomputed dot products. Use this when the caller
|
|
701
704
|
// already has dots from a paired evaluateMaterialResponseFromDots invocation
|
|
702
705
|
// to avoid recomputing H + dots.
|
|
706
|
+
// BSDF-sampling density for MIS — must be the exact function the sampler draws from, or the two
|
|
707
|
+
// strategies' weights stop summing to 1.
|
|
703
708
|
export const calculateMaterialPDFFromDots = Fn( ( [ material, dots ] ) => {
|
|
704
709
|
|
|
705
|
-
|
|
706
|
-
const NoL = dots.NoL.toVar();
|
|
707
|
-
const NoH = dots.NoH;
|
|
708
|
-
|
|
709
|
-
// Calculate lobe weights
|
|
710
|
-
const diffuseWeight = float( 1.0 ).sub( material.metalness ).mul(
|
|
711
|
-
float( 1.0 ).sub( material.transmission )
|
|
712
|
-
).toVar();
|
|
713
|
-
|
|
714
|
-
const specularWeight = float( 1.0 ).sub(
|
|
715
|
-
diffuseWeight.mul( float( 1.0 ).sub( material.metalness ) )
|
|
716
|
-
).toVar();
|
|
717
|
-
|
|
718
|
-
const totalWeight = diffuseWeight.add( specularWeight ).toVar();
|
|
719
|
-
|
|
720
|
-
const pdf = float( 0.0 ).toVar();
|
|
721
|
-
|
|
722
|
-
If( totalWeight.greaterThan( 0.0 ), () => {
|
|
723
|
-
|
|
724
|
-
// Guard division
|
|
725
|
-
const invTotalWeight = float( 1.0 ).div( max( totalWeight, 1e-10 ) ).toVar();
|
|
726
|
-
diffuseWeight.mulAssign( invTotalWeight );
|
|
727
|
-
specularWeight.mulAssign( invTotalWeight );
|
|
728
|
-
|
|
729
|
-
// Diffuse PDF (cosine-weighted hemisphere)
|
|
730
|
-
If( diffuseWeight.greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ), () => {
|
|
731
|
-
|
|
732
|
-
pdf.addAssign( diffuseWeight.mul( NoL ).mul( PI_INV ) );
|
|
733
|
-
|
|
734
|
-
} );
|
|
735
|
-
|
|
736
|
-
// Specular PDF (VNDF sampling used in path tracer)
|
|
737
|
-
If( specularWeight.greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ), () => {
|
|
738
|
-
|
|
739
|
-
const roughness = max( material.roughness, 0.02 );
|
|
740
|
-
const specPdf = float( 0.0 ).toVar();
|
|
741
|
-
If( material.anisotropy.greaterThan( 0.0 ), () => {
|
|
742
|
-
|
|
743
|
-
const a = computeAnisoAlphas( material.roughness, material.anisotropy );
|
|
744
|
-
specPdf.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ) );
|
|
745
|
-
|
|
746
|
-
} ).Else( () => {
|
|
747
|
-
|
|
748
|
-
specPdf.assign( calculateVNDFPDF( NoH, NoV, roughness ) );
|
|
749
|
-
|
|
750
|
-
} );
|
|
751
|
-
pdf.addAssign( specularWeight.mul( specPdf ) );
|
|
752
|
-
|
|
753
|
-
} );
|
|
754
|
-
|
|
755
|
-
} );
|
|
756
|
-
|
|
757
|
-
return max( pdf, 1e-8 );
|
|
710
|
+
return max( calculateBSDFSamplingPDFFromMaterial( material, dots ), 1e-8 );
|
|
758
711
|
|
|
759
712
|
} );
|
|
760
713
|
|
|
761
|
-
// Wrapper that computes dots internally. Use this when the caller doesn't
|
|
762
|
-
// already have dots; otherwise prefer calculateMaterialPDFFromDots.
|
|
763
714
|
export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material ] ) => {
|
|
764
715
|
|
|
765
716
|
const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, lightDir, material ) );
|
|
@@ -767,86 +718,6 @@ export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material
|
|
|
767
718
|
|
|
768
719
|
} );
|
|
769
720
|
|
|
770
|
-
// Total light-selection weight at a shading point, replicating the NEE reservoir's
|
|
771
|
-
// importance accumulation (same per-type importance fns, same per-type 16-light cap + order).
|
|
772
|
-
// Used to reconstruct the exact NEE selection pdf for MIS on the BSDF-hit path —
|
|
773
|
-
// the analogue of Cycles' light_tree_pdf re-walk. Without it, the BSDF-hit MIS
|
|
774
|
-
// partner assumes uniform 1/N selection, which disagrees with the importance-
|
|
775
|
-
// weighted NEE selection and biases the power-heuristic weights.
|
|
776
|
-
export const computeTotalLightImportance = Fn( ( [
|
|
777
|
-
rayOrigin, normal, material, bounceIndex,
|
|
778
|
-
directionalLightsBuffer, numDirectionalLights,
|
|
779
|
-
areaLightsBuffer, numAreaLights,
|
|
780
|
-
pointLightsBuffer, numPointLights,
|
|
781
|
-
spotLightsBuffer, numSpotLights,
|
|
782
|
-
] ) => {
|
|
783
|
-
|
|
784
|
-
const totalWeight = float( 0.0 ).toVar();
|
|
785
|
-
|
|
786
|
-
If( numDirectionalLights.greaterThan( int( 0 ) ), () => {
|
|
787
|
-
|
|
788
|
-
Loop( { start: int( 0 ), end: numDirectionalLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
789
|
-
|
|
790
|
-
If( i.lessThan( int( 16 ) ), () => {
|
|
791
|
-
|
|
792
|
-
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, i ) );
|
|
793
|
-
totalWeight.addAssign( calculateDirectionalLightImportance( light, normal, material, bounceIndex ) );
|
|
794
|
-
|
|
795
|
-
} );
|
|
796
|
-
|
|
797
|
-
} );
|
|
798
|
-
|
|
799
|
-
} );
|
|
800
|
-
|
|
801
|
-
If( numAreaLights.greaterThan( int( 0 ) ), () => {
|
|
802
|
-
|
|
803
|
-
Loop( { start: int( 0 ), end: numAreaLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
804
|
-
|
|
805
|
-
If( i.lessThan( int( 16 ) ), () => {
|
|
806
|
-
|
|
807
|
-
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
808
|
-
totalWeight.addAssign( select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, rayOrigin, normal, material ), float( 0.0 ) ) );
|
|
809
|
-
|
|
810
|
-
} );
|
|
811
|
-
|
|
812
|
-
} );
|
|
813
|
-
|
|
814
|
-
} );
|
|
815
|
-
|
|
816
|
-
If( numPointLights.greaterThan( int( 0 ) ), () => {
|
|
817
|
-
|
|
818
|
-
Loop( { start: int( 0 ), end: numPointLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
819
|
-
|
|
820
|
-
If( i.lessThan( int( 16 ) ), () => {
|
|
821
|
-
|
|
822
|
-
const light = PointLight.wrap( getPointLight( pointLightsBuffer, i ) );
|
|
823
|
-
totalWeight.addAssign( calculatePointLightImportance( light, rayOrigin, normal, material ) );
|
|
824
|
-
|
|
825
|
-
} );
|
|
826
|
-
|
|
827
|
-
} );
|
|
828
|
-
|
|
829
|
-
} );
|
|
830
|
-
|
|
831
|
-
If( numSpotLights.greaterThan( int( 0 ) ), () => {
|
|
832
|
-
|
|
833
|
-
Loop( { start: int( 0 ), end: numSpotLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
834
|
-
|
|
835
|
-
If( i.lessThan( int( 16 ) ), () => {
|
|
836
|
-
|
|
837
|
-
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, i ) );
|
|
838
|
-
totalWeight.addAssign( calculateSpotLightImportance( light, rayOrigin, normal, material ) );
|
|
839
|
-
|
|
840
|
-
} );
|
|
841
|
-
|
|
842
|
-
} );
|
|
843
|
-
|
|
844
|
-
} );
|
|
845
|
-
|
|
846
|
-
return totalWeight;
|
|
847
|
-
|
|
848
|
-
} );
|
|
849
|
-
|
|
850
721
|
// =============================================================================
|
|
851
722
|
// Unified Direct Lighting System
|
|
852
723
|
// =============================================================================
|
|
@@ -861,6 +732,9 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
861
732
|
brdfSampleDirection, brdfSamplePdf, brdfSampleValue,
|
|
862
733
|
// Tracing context
|
|
863
734
|
bounceIndex, rngState,
|
|
735
|
+
// Sampler context — NEE's continuous variates come off the sampler keyed on
|
|
736
|
+
// (pixel, dimension, frame). dimBase is this bounce's block; see Random.js budget.
|
|
737
|
+
pixelCoord, resolution, frame, dimBase,
|
|
864
738
|
// Light data
|
|
865
739
|
directionalLightsBuffer, numDirectionalLights,
|
|
866
740
|
areaLightsBuffer, numAreaLights,
|
|
@@ -893,101 +767,40 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
893
767
|
// Early exit for highly emissive surfaces
|
|
894
768
|
If( material.emissiveIntensity.lessThanEqual( 10.0 ), () => {
|
|
895
769
|
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
material.roughness, material.metalness, bounceIndex, currentThroughput
|
|
900
|
-
) );
|
|
901
|
-
|
|
902
|
-
// Extract MIS fields to mutable variables
|
|
903
|
-
// (env is handled deterministically below, not part of stochastic selection)
|
|
904
|
-
const useBRDFSampling = misResult.useBRDFSampling.toVar();
|
|
905
|
-
const useLightSampling = misResult.useLightSampling.toVar();
|
|
906
|
-
const brdfWeight = misResult.brdfWeight.toVar();
|
|
907
|
-
const lightWeight = misResult.lightWeight.toVar();
|
|
908
|
-
|
|
909
|
-
// Adaptive light processing
|
|
770
|
+
// NEE and BSDF-hit both run at every vertex, one sample each, so the MIS weights are the
|
|
771
|
+
// plain power heuristic over the raw densities. Picking one strategy stochastically and
|
|
772
|
+
// dividing by the selection probability was unbiased but cost 3× RMSE on an area light.
|
|
910
773
|
const totalLights = numDirectionalLights.add( numAreaLights ).add( numPointLights ).add( numSpotLights ).toVar();
|
|
911
|
-
|
|
912
|
-
const importanceThreshold = float( 0.001 ).mul( float( 1.0 ).add( float( bounceIndex ).mul( 0.5 ) ) ).toVar();
|
|
913
|
-
|
|
914
|
-
// Check if discrete lights exist
|
|
915
774
|
const hasDiscreteLights = totalLights.greaterThan( int( 0 ) ).toVar();
|
|
916
775
|
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
If( useLightSampling.and( hasDiscreteLights ), () => {
|
|
921
|
-
|
|
922
|
-
totalSamplingWeight.addAssign( lightWeight );
|
|
923
|
-
|
|
924
|
-
} );
|
|
925
|
-
|
|
926
|
-
If( useBRDFSampling, () => {
|
|
927
|
-
|
|
928
|
-
totalSamplingWeight.addAssign( brdfWeight );
|
|
929
|
-
|
|
930
|
-
} );
|
|
931
|
-
|
|
932
|
-
If( totalSamplingWeight.lessThanEqual( 0.0 ), () => {
|
|
933
|
-
|
|
934
|
-
totalSamplingWeight.assign( 1.0 );
|
|
935
|
-
useBRDFSampling.assign( tslBool( true ) );
|
|
936
|
-
brdfWeight.assign( 1.0 );
|
|
937
|
-
|
|
938
|
-
} );
|
|
939
|
-
|
|
940
|
-
const stratRand1 = RandomValue( rngState ).toVar();
|
|
941
|
-
const stratRand2 = RandomValue( rngState ).toVar();
|
|
942
|
-
|
|
943
|
-
// Determine sampling technique: stochastic {lights, BRDF}
|
|
944
|
-
const rand = stratRand1;
|
|
945
|
-
const sampleLights = tslBool( false ).toVar();
|
|
946
|
-
const sampleBRDF = tslBool( false ).toVar();
|
|
947
|
-
|
|
948
|
-
// Calculate effective weights for probability (only include light weight if lights exist)
|
|
949
|
-
const effectiveLightWeight = select( hasDiscreteLights, lightWeight, float( 0.0 ) );
|
|
950
|
-
// Guard division
|
|
951
|
-
const invTotalSamplingWeight = float( 1.0 ).div( max( totalSamplingWeight, 1e-10 ) );
|
|
952
|
-
const cumulativeLight = effectiveLightWeight.mul( invTotalSamplingWeight );
|
|
953
|
-
|
|
954
|
-
If( rand.lessThan( cumulativeLight ).and( useLightSampling ).and( hasDiscreteLights ), () => {
|
|
955
|
-
|
|
956
|
-
sampleLights.assign( tslBool( true ) );
|
|
957
|
-
|
|
958
|
-
} ).ElseIf( useBRDFSampling, () => {
|
|
959
|
-
|
|
960
|
-
sampleBRDF.assign( tslBool( true ) );
|
|
961
|
-
|
|
962
|
-
} ).ElseIf( hasDiscreteLights, () => {
|
|
963
|
-
|
|
964
|
-
// Fallback to light sampling only if lights exist
|
|
965
|
-
sampleLights.assign( tslBool( true ) );
|
|
966
|
-
|
|
967
|
-
} );
|
|
776
|
+
// Reservoir denominator from the NEE walk above; the BSDF-hit branch below reuses it
|
|
777
|
+
// rather than re-walking every light buffer to rebuild the same sum.
|
|
778
|
+
const neeTotalWeight = float( 0.0 ).toVar();
|
|
968
779
|
|
|
969
780
|
// =====================================================================
|
|
970
781
|
// LIGHT SAMPLING PATH
|
|
971
782
|
// =====================================================================
|
|
972
783
|
|
|
973
|
-
If(
|
|
784
|
+
If( hasDiscreteLights, () => {
|
|
974
785
|
|
|
975
786
|
// Importance-weighted light sampling
|
|
976
|
-
const lightRandom =
|
|
787
|
+
const lightRandom = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 2 ) ), rngState, resolution, frame ).toVar();
|
|
977
788
|
const lightSample = LightSample.wrap( sampleLightWithImportance(
|
|
978
789
|
rayOrigin, hitNormal, material, lightRandom, bounceIndex, rngState,
|
|
790
|
+
pixelCoord, resolution, frame, dimBase,
|
|
979
791
|
directionalLightsBuffer, numDirectionalLights,
|
|
980
792
|
areaLightsBuffer, numAreaLights,
|
|
981
793
|
pointLightsBuffer, numPointLights,
|
|
982
794
|
spotLightsBuffer, numSpotLights,
|
|
983
795
|
) );
|
|
984
796
|
|
|
797
|
+
neeTotalWeight.assign( lightSample.selectionTotalWeight );
|
|
798
|
+
|
|
985
799
|
If( lightSample.valid.and( lightSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
986
800
|
|
|
987
801
|
const NoL = max( float( 0.0 ), dot( hitNormal, lightSample.direction ) ).toVar();
|
|
988
|
-
const lightImportance = lightSample.emission.x.add( lightSample.emission.y ).add( lightSample.emission.z );
|
|
989
802
|
|
|
990
|
-
If( NoL.greaterThan( 0.0 ).and(
|
|
803
|
+
If( NoL.greaterThan( 0.0 ).and( isDirectionValid( { direction: lightSample.direction, surfaceNormal: hitNormal } ) ), () => {
|
|
991
804
|
|
|
992
805
|
const shadowDistance = min( lightSample.distance.sub( 0.001 ), float( 1000.0 ) );
|
|
993
806
|
const visibility = shadow( rayOrigin, lightSample.direction, shadowDistance );
|
|
@@ -1000,28 +813,19 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1000
813
|
const brdfValue = evaluateMaterialResponseFromDots( material, sharedDots );
|
|
1001
814
|
const bPdf = calculateMaterialPDFFromDots( material, sharedDots ).toVar();
|
|
1002
815
|
|
|
816
|
+
// Power heuristic only for area lights — they are the only type the BRDF
|
|
817
|
+
// path can intersect, so elsewhere MIS would just delete energy.
|
|
1003
818
|
const misW = float( 1.0 ).toVar();
|
|
1004
819
|
|
|
1005
|
-
If( bPdf.greaterThan( 0.0 ).and(
|
|
1006
|
-
|
|
1007
|
-
const lightPdfWeighted = lightSample.pdf.mul( lightWeight );
|
|
1008
|
-
const brdfPdfWeighted = bPdf.mul( brdfWeight );
|
|
1009
|
-
|
|
1010
|
-
// Apply power heuristic only for area lights — the BRDF path can
|
|
1011
|
-
// intersect area lights, so both strategies contribute and MIS is valid.
|
|
1012
|
-
// Point/spot/directional lights are delta or non-intersectable by the
|
|
1013
|
-
// BRDF path, so MIS would only reduce energy without compensation.
|
|
1014
|
-
If( lightSample.lightType.equal( int( LIGHT_TYPE_AREA ) ), () => {
|
|
820
|
+
If( bPdf.greaterThan( 0.0 ).and( lightSample.lightType.equal( int( LIGHT_TYPE_AREA ) ) ), () => {
|
|
1015
821
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
} );
|
|
822
|
+
misW.assign( powerHeuristic( { pdf1: lightSample.pdf, pdf2: bPdf } ) );
|
|
1019
823
|
|
|
1020
824
|
} );
|
|
1021
825
|
|
|
1022
826
|
// Base contribution WITHOUT visibility; shadowed = base × visibility (identical to the
|
|
1023
827
|
// pre-dual-sum math), unoccluded = base × 1 (shadow-catcher reference only).
|
|
1024
|
-
const baseContribution = lightSample.emission.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightSample.pdf, 1e-10 ) )
|
|
828
|
+
const baseContribution = lightSample.emission.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightSample.pdf, 1e-10 ) );
|
|
1025
829
|
totalContribution.addAssign( baseContribution.mul( visibility ) );
|
|
1026
830
|
If( wantUnoccluded, () => {
|
|
1027
831
|
|
|
@@ -1041,129 +845,94 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1041
845
|
// BRDF SAMPLING PATH
|
|
1042
846
|
// =====================================================================
|
|
1043
847
|
|
|
1044
|
-
If(
|
|
1045
|
-
|
|
1046
|
-
If( brdfSamplePdf.greaterThan( 0.0 ).and( useBRDFSampling ), () => {
|
|
1047
|
-
|
|
1048
|
-
const NoL = max( float( 0.0 ), dot( hitNormal, brdfSampleDirection ) ).toVar();
|
|
1049
|
-
|
|
1050
|
-
If( NoL.greaterThan( 0.0 ).and( isDirectionValid( { direction: brdfSampleDirection, surfaceNormal: hitNormal } ) ), () => {
|
|
1051
|
-
|
|
1052
|
-
// Check intersection with area lights
|
|
1053
|
-
If( numAreaLights.greaterThan( int( 0 ) ), () => {
|
|
1054
|
-
|
|
1055
|
-
const foundIntersection = tslBool( false ).toVar();
|
|
1056
|
-
const maxImportance = float( 0.0 ).toVar();
|
|
1057
|
-
const maxImportanceLight = int( - 1 ).toVar();
|
|
1058
|
-
|
|
1059
|
-
// Track best match (no early break)
|
|
1060
|
-
Loop( { start: int( 0 ), end: numAreaLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
1061
|
-
|
|
1062
|
-
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
1063
|
-
|
|
1064
|
-
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
848
|
+
If( brdfSamplePdf.greaterThan( 0.0 ).and( numAreaLights.greaterThan( int( 0 ) ) ), () => {
|
|
1065
849
|
|
|
1066
|
-
|
|
850
|
+
const NoL = max( float( 0.0 ), dot( hitNormal, brdfSampleDirection ) ).toVar();
|
|
1067
851
|
|
|
1068
|
-
|
|
852
|
+
If( NoL.greaterThan( 0.0 ).and( isDirectionValid( { direction: brdfSampleDirection, surfaceNormal: hitNormal } ) ), () => {
|
|
1069
853
|
|
|
1070
|
-
|
|
854
|
+
// Nearest hit — the ray stops at the first emitter, so a brighter one behind it is
|
|
855
|
+
// the wrong emitter and the wrong pdf. No importance gate: skipping a light drops
|
|
856
|
+
// energy the NEE partner's MIS weight already counted on, and estimateLightImportance
|
|
857
|
+
// reads zero across the terminator band of a large light (its CENTRE is below the
|
|
858
|
+
// horizon while half of it is still visible).
|
|
859
|
+
const nearestT = float( 1e30 ).toVar();
|
|
860
|
+
const nearestLight = int( - 1 ).toVar();
|
|
1071
861
|
|
|
1072
|
-
|
|
862
|
+
Loop( { start: int( 0 ), end: numAreaLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
1073
863
|
|
|
1074
|
-
|
|
864
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
1075
865
|
|
|
1076
|
-
|
|
1077
|
-
maxImportanceLight.assign( i );
|
|
866
|
+
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
1078
867
|
|
|
1079
|
-
|
|
1080
|
-
foundIntersection.assign( tslBool( true ) );
|
|
868
|
+
const hitDistance = intersectAreaLight( light, rayOrigin, brdfSampleDirection ).toVar();
|
|
1081
869
|
|
|
1082
|
-
|
|
870
|
+
If( hitDistance.greaterThan( 0.0 ).and( hitDistance.lessThan( nearestT ) ), () => {
|
|
1083
871
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
} );
|
|
872
|
+
nearestT.assign( hitDistance );
|
|
873
|
+
nearestLight.assign( i );
|
|
1087
874
|
|
|
1088
875
|
} );
|
|
1089
876
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, maxImportanceLight ) );
|
|
1093
|
-
const hitDistance = intersectAreaLight( light, rayOrigin, brdfSampleDirection ).toVar();
|
|
1094
|
-
|
|
1095
|
-
If( hitDistance.greaterThan( 0.0 ), () => {
|
|
1096
|
-
|
|
1097
|
-
const shadowDistance = min( hitDistance.sub( 0.001 ), float( 1000.0 ) );
|
|
1098
|
-
const visibility = shadow( rayOrigin, brdfSampleDirection, shadowDistance );
|
|
1099
|
-
|
|
1100
|
-
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
1101
|
-
|
|
1102
|
-
const lightFacing = max( float( 0.0 ), dot( brdfSampleDirection, light.normal ).negate() ).toVar();
|
|
1103
|
-
|
|
1104
|
-
If( lightFacing.greaterThan( 0.0 ), () => {
|
|
1105
|
-
|
|
1106
|
-
const lightDistSq = hitDistance.mul( hitDistance );
|
|
1107
|
-
|
|
1108
|
-
// Directional pdf must match the NEE sampler: 1/S (spherical-rect)
|
|
1109
|
-
// for rectangles, dist²/(area·cos) for disk/ellipse + degenerate rects.
|
|
1110
|
-
const corner = light.position.sub( light.u ).sub( light.v );
|
|
1111
|
-
const sAngle = sphQuadSolidAngle( rayOrigin, corner, light.u.mul( 2.0 ), light.v.mul( 2.0 ) ).toVar();
|
|
1112
|
-
const areaPdf = lightDistSq.div( max( light.area.mul( lightFacing ), EPSILON ) );
|
|
1113
|
-
const dirPdf = select(
|
|
1114
|
-
light.shape.lessThan( 0.5 ).and( sAngle.greaterThan( 1e-5 ) ),
|
|
1115
|
-
float( 1.0 ).div( max( sAngle, 1e-10 ) ),
|
|
1116
|
-
areaPdf,
|
|
1117
|
-
).toVar();
|
|
1118
|
-
|
|
1119
|
-
// Selection pdf consistent with the importance-weighted NEE reservoir
|
|
1120
|
-
// (Cycles light_tree_pdf re-walk). Falls back to uniform 1/N only when
|
|
1121
|
-
// total importance is zero, matching the NEE fallback.
|
|
1122
|
-
const selImp = estimateLightImportance( light, rayOrigin, hitNormal, material );
|
|
1123
|
-
const totalImp = computeTotalLightImportance(
|
|
1124
|
-
rayOrigin, hitNormal, material, bounceIndex,
|
|
1125
|
-
directionalLightsBuffer, numDirectionalLights,
|
|
1126
|
-
areaLightsBuffer, numAreaLights,
|
|
1127
|
-
pointLightsBuffer, numPointLights,
|
|
1128
|
-
spotLightsBuffer, numSpotLights,
|
|
1129
|
-
).toVar();
|
|
1130
|
-
const selPdf = select(
|
|
1131
|
-
totalImp.greaterThan( 0.0 ),
|
|
1132
|
-
selImp.div( max( totalImp, 1e-10 ) ),
|
|
1133
|
-
float( 1.0 ).div( max( float( totalLights ), 1.0 ) ),
|
|
1134
|
-
);
|
|
1135
|
-
const lightPdf = dirPdf.mul( selPdf ).toVar();
|
|
1136
|
-
|
|
1137
|
-
const brdfPdfWeighted = brdfSamplePdf.mul( brdfWeight );
|
|
1138
|
-
const lightPdfWeighted = lightPdf.mul( lightWeight );
|
|
1139
|
-
const misW = powerHeuristic( { pdf1: brdfPdfWeighted, pdf2: lightPdfWeighted } ).toVar();
|
|
877
|
+
} );
|
|
1140
878
|
|
|
1141
|
-
|
|
1142
|
-
// Base contribution WITHOUT visibility (see discrete-light site).
|
|
1143
|
-
const baseContribution = lightEmission.mul( brdfSampleValue ).mul( NoL ).mul( misW ).div( max( brdfSamplePdf, 1e-10 ) ).mul( totalSamplingWeight ).div( max( brdfWeight, 1e-10 ) );
|
|
1144
|
-
totalContribution.addAssign( baseContribution.mul( visibility ) );
|
|
1145
|
-
If( wantUnoccluded, () => {
|
|
879
|
+
} );
|
|
1146
880
|
|
|
1147
|
-
|
|
881
|
+
If( nearestLight.greaterThanEqual( int( 0 ) ), () => {
|
|
1148
882
|
|
|
1149
|
-
|
|
883
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, nearestLight ) );
|
|
884
|
+
const shadowDistance = min( nearestT.sub( 0.001 ), float( 1000.0 ) );
|
|
885
|
+
const visibility = shadow( rayOrigin, brdfSampleDirection, shadowDistance );
|
|
1150
886
|
|
|
1151
|
-
|
|
887
|
+
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
1152
888
|
|
|
1153
|
-
|
|
889
|
+
const lightFacing = max( float( 0.0 ), dot( brdfSampleDirection, light.normal ).negate() ).toVar();
|
|
890
|
+
|
|
891
|
+
If( lightFacing.greaterThan( 0.0 ), () => {
|
|
892
|
+
|
|
893
|
+
// Directional pdf must match the NEE sampler: 1/S (spherical-rect) for
|
|
894
|
+
// rectangles, dist²/(area·cos) for disk/ellipse + degenerate rects.
|
|
895
|
+
const corner = light.position.sub( light.u ).sub( light.v );
|
|
896
|
+
const sAngle = sphQuadSolidAngle( rayOrigin, corner, light.u.mul( 2.0 ), light.v.mul( 2.0 ) ).toVar();
|
|
897
|
+
const areaPdf = nearestT.mul( nearestT ).div( max( light.area.mul( lightFacing ), EPSILON ) );
|
|
898
|
+
const dirPdf = select(
|
|
899
|
+
light.shape.lessThan( 0.5 ).and( sAngle.greaterThan( 1e-5 ) ),
|
|
900
|
+
float( 1.0 ).div( max( sAngle, 1e-10 ) ),
|
|
901
|
+
areaPdf,
|
|
902
|
+
).toVar();
|
|
903
|
+
|
|
904
|
+
// The NEE partner's selection pdf, rebuilt from the reservoir denominator it
|
|
905
|
+
// already accumulated. Uniform 1/N only when total importance is zero, matching
|
|
906
|
+
// the reservoir's own fallback; 0 for a light NEE can never pick, which hands
|
|
907
|
+
// this strategy the full MIS weight — as it must.
|
|
908
|
+
const selImp = estimateLightImportance( light, rayOrigin, hitNormal, material );
|
|
909
|
+
const selPdf = select(
|
|
910
|
+
neeTotalWeight.greaterThan( 0.0 ),
|
|
911
|
+
selImp.div( max( neeTotalWeight, 1e-10 ) ),
|
|
912
|
+
float( 1.0 ).div( max( float( totalLights ), 1.0 ) ),
|
|
913
|
+
);
|
|
914
|
+
const lightPdf = dirPdf.mul( selPdf ).toVar();
|
|
915
|
+
const misW = powerHeuristic( { pdf1: brdfSamplePdf, pdf2: lightPdf } ).toVar();
|
|
916
|
+
|
|
917
|
+
const lightEmission = areaLightRadiance( light ).mul( areaLightSpreadAttenuation( lightFacing, light.spread ) );
|
|
918
|
+
// Base contribution WITHOUT visibility (see discrete-light site).
|
|
919
|
+
const baseContribution = lightEmission.mul( brdfSampleValue ).mul( NoL ).mul( misW ).div( max( brdfSamplePdf, 1e-10 ) );
|
|
920
|
+
totalContribution.addAssign( baseContribution.mul( visibility ) );
|
|
921
|
+
If( wantUnoccluded, () => {
|
|
922
|
+
|
|
923
|
+
unoccludedContribution.addAssign( baseContribution );
|
|
1154
924
|
|
|
1155
925
|
} );
|
|
1156
926
|
|
|
1157
927
|
} );
|
|
1158
928
|
|
|
1159
|
-
} );
|
|
929
|
+
} );
|
|
1160
930
|
|
|
1161
931
|
} );
|
|
1162
932
|
|
|
1163
933
|
} );
|
|
1164
934
|
|
|
1165
935
|
} );
|
|
1166
|
-
|
|
1167
936
|
// =====================================================================
|
|
1168
937
|
// DETERMINISTIC ENVIRONMENT NEE
|
|
1169
938
|
// Always runs (not stochastic) — forms a two-strategy Veach MIS
|
|
@@ -1172,9 +941,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1172
941
|
|
|
1173
942
|
If( enableEnvironmentLight, () => {
|
|
1174
943
|
|
|
1175
|
-
const
|
|
1176
|
-
const env_r2 = RandomValue( rngState ).toVar();
|
|
1177
|
-
const envRandom = vec2( env_r1, env_r2 ).toVar();
|
|
944
|
+
const envRandom = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame ).toVar();
|
|
1178
945
|
const envColor = vec3( 0.0 ).toVar();
|
|
1179
946
|
|
|
1180
947
|
// Sample direction + PDF + color from importance-sampled environment
|