rayzee 10.0.0 → 11.1.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/README.md +252 -44
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-Hn35o6Z5.js +5145 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -0
- package/dist/BufferLayout-D_IBo2ci.js +150 -0
- package/dist/ColorManagement-oeEk7oSu.js +1488 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-dDDGj9Av.js +97 -0
- package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BzNpiVHZ.js} +8 -6
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-Bk7bHg2B.js} +55 -120
- package/dist/PathTracer-72XufKAr.js +29976 -0
- package/dist/PhysicalSky-BgjdV88k.js +1219 -0
- package/dist/RayzeeRenderer-1QRO97eq.js +3551 -0
- package/dist/SolarLimb-CPaeZVwz.js +721 -0
- package/dist/ToneMapGPU-CjU3osDd.js +177 -0
- package/dist/ViewTransforms-CYN7bq2w.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-tnAoUL1j.js +94 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2551 -44024
- package/dist/rayzee.umd.js +528 -487
- package/dist/sceneCachePolicy-C67jbci-.js +443 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/Color/ViewTransforms.js +5 -1
- package/src/EngineDefaults.js +62 -1029
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +2 -0
- package/src/Headless.js +6 -5
- package/src/Passes/AIUpscaler.js +58 -141
- package/src/Passes/OIDNDenoiser.js +92 -65
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +620 -4026
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +20 -7
- package/src/Processor/ArchiveImporter.js +1265 -0
- package/src/Processor/ArchiveReader.js +22 -4
- package/src/Processor/AssetLoader.js +327 -1763
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +14 -126
- package/src/Processor/BVHLeafFold.js +4 -6
- package/src/Processor/BVHRefitter.js +67 -46
- package/src/Processor/BufferLayout.js +291 -0
- package/src/Processor/EmissiveTriangleBuilder.js +1 -1
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +15 -5
- package/src/Processor/HostMemory.js +7 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/InstanceTable.js +1 -1
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +10 -8
- package/src/Processor/PBRT/PBRTMaterials.js +254 -42
- package/src/Processor/PBRT/PBRTParser.js +49 -19
- package/src/Processor/PBRT/PBRTScattering.js +93 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +1050 -75
- package/src/Processor/PBRT/PBRTTextureBake.js +179 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +39 -5
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneMetadata.js +30 -5
- package/src/Processor/SceneProcessor.js +53 -62
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/SplitBLAS.js +1 -1
- package/src/Processor/StorageTexturePool.js +22 -1
- package/src/Processor/TLASBuilder.js +1 -3
- package/src/Processor/TextureBuckets.js +237 -0
- package/src/Processor/TextureCreator.js +181 -8
- package/src/Processor/ToneMapGPU.js +64 -15
- package/src/Processor/Workers/BVHRefitWorker.js +5 -2
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/Workers/TexturesWorker.js +103 -18
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3900 -0
- package/src/RenderSettings.js +68 -62
- package/src/Stages/ASVGF.js +13 -14
- package/src/Stages/AutoExposure.js +17 -17
- package/src/Stages/BilateralFilter.js +2 -1
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/DenoiserSettings.js +182 -0
- package/src/Stages/EdgeFilter.js +2 -1
- package/src/Stages/MotionVector.js +1 -1
- package/src/Stages/NRD.js +11 -5
- package/src/Stages/NormalDepth.js +12 -8
- package/src/Stages/PathTracer.js +336 -43
- package/src/Stages/PathTracerStage.js +69 -66
- package/src/Stages/Variance.js +1 -1
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +45 -9
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +50 -25
- package/src/TSL/Bidirectional.js +208 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +20 -15
- package/src/TSL/ConnectKernel.js +167 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +65 -18
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +180 -0
- package/src/TSL/LightVisibility.js +111 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +239 -51
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +224 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +5 -1
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +985 -101
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +66 -61
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +31 -0
- package/src/index.js +13 -23
- package/src/integrators/BidirectionalIntegrator.js +838 -0
- package/src/managers/AnimationManager.js +88 -6
- package/src/managers/CameraManager.js +103 -46
- package/src/managers/DenoisingManager.js +14 -10
- package/src/managers/EnvironmentManager.js +125 -103
- package/src/managers/GoboManager.js +9 -0
- package/src/managers/MaterialDataManager.js +48 -37
- package/src/managers/UniformManager.js +32 -10
- package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -6,6 +6,7 @@ import { BRDFWeights, MaterialCache, MaterialClassification, DFGResult, BaseFres
|
|
|
6
6
|
import { PI, PI_INV, TWO_PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, MAX_ROUGHNESS, XYZ_TO_REC709, square, classifyMaterial } from './Common.js';
|
|
7
7
|
|
|
8
8
|
import { fresnelSchlickFloat, fresnel0ToIor, iorToFresnel0Vec3, iorToFresnel0, dielectricF0 } from './Fresnel.js';
|
|
9
|
+
import { materialLayers } from './SceneResources.js';
|
|
9
10
|
|
|
10
11
|
// -----------------------------------------------------------------------------
|
|
11
12
|
// Microfacet Distribution Functions
|
|
@@ -407,8 +408,9 @@ export const evalIridescence = Fn( ( [ outsideIOR, eta2, cosTheta1, thinFilmThic
|
|
|
407
408
|
// BRDF Weight Calculation
|
|
408
409
|
// -----------------------------------------------------------------------------
|
|
409
410
|
|
|
410
|
-
export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
411
|
+
export const calculateBRDFWeights = Fn( ( [ material, mc, cache ], builder ) => {
|
|
411
412
|
|
|
413
|
+
const layers = materialLayers( builder );
|
|
412
414
|
// Use precomputed values from cache
|
|
413
415
|
const invRoughness = cache.invRoughness;
|
|
414
416
|
const metalFactor = cache.metalFactor;
|
|
@@ -438,10 +440,10 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
|
438
440
|
const diffuse = float( 1.0 ).sub( baseSpecularWeight )
|
|
439
441
|
.mul( float( 1.0 ).sub( material.metalness ) )
|
|
440
442
|
.mul( float( 1.0 ).sub( material.transmission ) ).toVar();
|
|
441
|
-
const sheen = material.sheen.mul( cache.maxSheenColor ).toVar();
|
|
443
|
+
const sheen = ( layers.sheen ? material.sheen.mul( cache.maxSheenColor ) : float( 0.0 ) ).toVar();
|
|
442
444
|
|
|
443
445
|
const clearcoat = float( 0.0 ).toVar();
|
|
444
|
-
If( mc.hasClearcoat, () => {
|
|
446
|
+
if ( layers.clearcoat ) If( mc.hasClearcoat, () => {
|
|
445
447
|
|
|
446
448
|
clearcoat.assign( material.clearcoat.mul( invRoughness ).mul( 0.4 ) );
|
|
447
449
|
|
|
@@ -471,17 +473,22 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
|
471
473
|
// Iridescence: shifts energy from diffuse to specular since it modifies specular F0
|
|
472
474
|
// This preserves the total weight (no inflation) while increasing specular importance
|
|
473
475
|
const iridescenceBase = invRoughness.mul( mc.isSmooth.select( float( 0.6 ), float( 0.5 ) ) );
|
|
474
|
-
|
|
476
|
+
// Off, the weight is 0 but the shift is kept: min( 0, diffuse ) still moves a negative diffuse weight to specular.
|
|
477
|
+
const iridescenceWeight = layers.iridescence ? material.iridescence.mul( iridescenceBase )
|
|
475
478
|
.mul( float( 0.5 ).add( float( 0.5 ).mul(
|
|
476
479
|
material.iridescenceThicknessRange.y.sub( material.iridescenceThicknessRange.x ).div( 1000.0 )
|
|
477
480
|
) ) )
|
|
478
|
-
.mul( float( 0.5 ).add( float( 0.5 ).mul( material.iridescenceIOR.div( 2.0 ) ) ) );
|
|
481
|
+
.mul( float( 0.5 ).add( float( 0.5 ).mul( material.iridescenceIOR.div( 2.0 ) ) ) ) : float( 0.0 );
|
|
479
482
|
const iridescenceShift = min( iridescenceWeight, diffuse );
|
|
480
483
|
specular.addAssign( iridescenceShift );
|
|
481
484
|
diffuse.subAssign( iridescenceShift );
|
|
482
485
|
|
|
486
|
+
// The diffuse lobe's transmitted share is drawn on the other side, in proportion.
|
|
487
|
+
const diffuseTransmission = ( layers.diffuseTransmission ? diffuse.mul( material.diffuseTransmission ) : float( 0.0 ) ).toVar();
|
|
488
|
+
diffuse.subAssign( diffuseTransmission );
|
|
489
|
+
|
|
483
490
|
// Single normalization pass
|
|
484
|
-
const total = specular.add( diffuse ).add( sheen ).add( clearcoat ).add( transmission );
|
|
491
|
+
const total = specular.add( diffuse ).add( sheen ).add( clearcoat ).add( transmission ).add( diffuseTransmission );
|
|
485
492
|
const invTotal = float( 1.0 ).div( max( total, 0.001 ) );
|
|
486
493
|
|
|
487
494
|
return BRDFWeights( {
|
|
@@ -491,6 +498,7 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
|
491
498
|
clearcoat: clearcoat.mul( invTotal ),
|
|
492
499
|
transmission: transmission.mul( invTotal ),
|
|
493
500
|
iridescence: float( 0.0 ),
|
|
501
|
+
diffuseTransmission: diffuseTransmission.mul( invTotal ),
|
|
494
502
|
} );
|
|
495
503
|
|
|
496
504
|
} );
|
|
@@ -508,8 +516,9 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
|
508
516
|
//
|
|
509
517
|
// Transmission is absent because it only produces NoL < 0. `weights` is normalised over all
|
|
510
518
|
// lobes including transmission, so omitting its term is exact, not an approximation.
|
|
511
|
-
export const calculateBSDFSamplingPDF = Fn( ( [ material, weights, dots ] ) => {
|
|
519
|
+
export const calculateBSDFSamplingPDF = Fn( ( [ material, weights, dots ], builder ) => {
|
|
512
520
|
|
|
521
|
+
const layers = materialLayers( builder );
|
|
513
522
|
const pdf = float( 0.0 ).toVar();
|
|
514
523
|
|
|
515
524
|
If( dots.NoL.greaterThan( 0.0 ), () => {
|
|
@@ -517,21 +526,24 @@ export const calculateBSDFSamplingPDF = Fn( ( [ material, weights, dots ] ) => {
|
|
|
517
526
|
pdf.addAssign( weights.diffuse.mul( dots.NoL ).mul( PI_INV ) );
|
|
518
527
|
|
|
519
528
|
const specPdf = float( 0.0 ).toVar();
|
|
520
|
-
|
|
529
|
+
// Raw roughness, not clamped: sampleGGXVNDF is called with material.roughness, and a
|
|
530
|
+
// clamp here would report a density the sampler never drew from.
|
|
531
|
+
const isotropic = () => {
|
|
521
532
|
|
|
522
|
-
|
|
523
|
-
specPdf.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ) );
|
|
533
|
+
specPdf.assign( calculateVNDFPDF( dots.NoH, dots.NoV, material.roughness ) );
|
|
524
534
|
|
|
525
|
-
}
|
|
535
|
+
};
|
|
526
536
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
specPdf.assign( calculateVNDFPDF( dots.NoH, dots.NoV, material.roughness ) );
|
|
537
|
+
if ( ! layers.anisotropy ) isotropic();
|
|
538
|
+
else If( material.anisotropy.greaterThan( 0.0 ), () => {
|
|
530
539
|
|
|
531
|
-
|
|
540
|
+
const a = computeAnisoAlphas( material.roughness, material.anisotropy );
|
|
541
|
+
specPdf.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ) );
|
|
542
|
+
|
|
543
|
+
} ).Else( isotropic );
|
|
532
544
|
pdf.addAssign( weights.specular.mul( specPdf ) );
|
|
533
545
|
|
|
534
|
-
If( weights.sheen.greaterThan( 0.0 ), () => {
|
|
546
|
+
if ( layers.sheen ) If( weights.sheen.greaterThan( 0.0 ), () => {
|
|
535
547
|
|
|
536
548
|
const sheenPdf = SheenDistribution( dots.NoH, material.sheenRoughness )
|
|
537
549
|
.mul( dots.NoH ).div( max( float( 4.0 ).mul( dots.VoH ), EPSILON ) );
|
|
@@ -539,7 +551,7 @@ export const calculateBSDFSamplingPDF = Fn( ( [ material, weights, dots ] ) => {
|
|
|
539
551
|
|
|
540
552
|
} );
|
|
541
553
|
|
|
542
|
-
If( weights.clearcoat.greaterThan( 0.0 ), () => {
|
|
554
|
+
if ( layers.clearcoat ) If( weights.clearcoat.greaterThan( 0.0 ), () => {
|
|
543
555
|
|
|
544
556
|
const ccRoughness = clamp( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS, MAX_ROUGHNESS );
|
|
545
557
|
pdf.addAssign( weights.clearcoat.mul( calculateVNDFPDF( dots.NoH, dots.NoV, ccRoughness ) ) );
|
|
@@ -552,6 +564,21 @@ export const calculateBSDFSamplingPDF = Fn( ( [ material, weights, dots ] ) => {
|
|
|
552
564
|
|
|
553
565
|
} );
|
|
554
566
|
|
|
567
|
+
// The sampling density below the surface (cosBelow = −N·L > 0): only the diffuse transmission lobe draws there,
|
|
568
|
+
// chosen with probability `weight` (BRDFWeights.diffuseTransmission).
|
|
569
|
+
export const diffuseTransmissionPdf = Fn( ( [ weight, cosBelow ] ) => {
|
|
570
|
+
|
|
571
|
+
return weight.mul( cosBelow ).mul( PI_INV );
|
|
572
|
+
|
|
573
|
+
} );
|
|
574
|
+
|
|
575
|
+
// The diffuse transmission lobe's selection probability, for samplers outside the classify-once path.
|
|
576
|
+
export const diffuseTransmissionWeight = Fn( ( [ material ] ) => {
|
|
577
|
+
|
|
578
|
+
return BRDFWeights.wrap( calculateBRDFWeightsFromMaterial( material ) ).diffuseTransmission;
|
|
579
|
+
|
|
580
|
+
} );
|
|
581
|
+
|
|
555
582
|
// Selection probabilities from `material` alone, for samplers outside the wavefront's
|
|
556
583
|
// classify-once path. Must be the same weights whose density calculateBSDFSamplingPDF reports.
|
|
557
584
|
export const calculateBRDFWeightsFromMaterial = Fn( ( [ material ] ) => {
|
|
@@ -10,6 +10,7 @@ import { DistributionGGX } from './MaterialProperties.js';
|
|
|
10
10
|
import { ImportanceSampleGGX } from './MaterialSampling.js';
|
|
11
11
|
import { getRandomSample1D, getRandomSample2D, pcgHash } from './Random.js';
|
|
12
12
|
import { handleSubsurfaceEntry, SubsurfaceEntryResult } from './Subsurface.js';
|
|
13
|
+
import { materialLayers } from './SceneResources.js';
|
|
13
14
|
|
|
14
15
|
// ================================================================================
|
|
15
16
|
// STRUCTS (local to transmission)
|
|
@@ -158,8 +159,9 @@ export const calculateBeerLawAbsorption = /*@__PURE__*/ wgslFn( `
|
|
|
158
159
|
export const sampleMicrofacetTransmission = Fn( ( [
|
|
159
160
|
V, N, ior, roughness, entering, dispersion, xi, rngState, pathWavelength,
|
|
160
161
|
pixelCoord, resolution, frame, dimBase,
|
|
161
|
-
] ) => {
|
|
162
|
+
], builder ) => {
|
|
162
163
|
|
|
164
|
+
const layers = materialLayers( builder );
|
|
163
165
|
const result = MicrofacetTransmissionResult( {
|
|
164
166
|
direction: vec3( 0.0 ),
|
|
165
167
|
halfVector: vec3( 0.0 ),
|
|
@@ -169,48 +171,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
169
171
|
pathWavelength: pathWavelength,
|
|
170
172
|
} ).toVar();
|
|
171
173
|
|
|
172
|
-
|
|
173
|
-
If( roughness.lessThanEqual( 0.05 ).and( dispersion.greaterThan( 0.0 ) ), () => {
|
|
174
|
-
|
|
175
|
-
result.halfVector.assign( N );
|
|
176
|
-
result.didReflect.assign( false );
|
|
177
|
-
|
|
178
|
-
const eta = ior;
|
|
179
|
-
const etaRatio = select( entering, float( 1.0 ).div( eta ), eta ).toVar();
|
|
180
|
-
|
|
181
|
-
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
182
|
-
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
183
|
-
|
|
184
|
-
const lockedIOR = iorFromWavelength( ior, dispersion, pathWavelength );
|
|
185
|
-
etaRatio.assign( select( entering, float( 1.0 ).div( lockedIOR ), lockedIOR ) );
|
|
186
|
-
|
|
187
|
-
} ).Else( () => {
|
|
188
|
-
|
|
189
|
-
const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 5 ) ), rngState, resolution, frame ) ) );
|
|
190
|
-
etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
|
|
191
|
-
result.colorWeight.assign( spectralSample.colorWeight );
|
|
192
|
-
result.pathWavelength.assign( spectralSample.wavelength );
|
|
193
|
-
|
|
194
|
-
} );
|
|
195
|
-
|
|
196
|
-
// Perfect refraction using surface normal
|
|
197
|
-
const refractDir = refract( V.negate(), N, etaRatio ).toVar();
|
|
198
|
-
|
|
199
|
-
// Check for total internal reflection
|
|
200
|
-
If( dot( refractDir, refractDir ).lessThan( 0.001 ), () => {
|
|
201
|
-
|
|
202
|
-
result.direction.assign( reflect( V.negate(), N ) );
|
|
203
|
-
result.didReflect.assign( true );
|
|
204
|
-
result.pdf.assign( 1.0 );
|
|
205
|
-
|
|
206
|
-
} ).Else( () => {
|
|
207
|
-
|
|
208
|
-
result.direction.assign( refractDir );
|
|
209
|
-
result.pdf.assign( 1.0 );
|
|
210
|
-
|
|
211
|
-
} );
|
|
212
|
-
|
|
213
|
-
} ).Else( () => {
|
|
174
|
+
const microfacet = () => {
|
|
214
175
|
|
|
215
176
|
// Use minimum roughness to avoid numerical issues for rough surfaces
|
|
216
177
|
const transmissionRoughness = max( MIN_ROUGHNESS, roughness );
|
|
@@ -223,7 +184,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
223
184
|
const etaRatio = select( entering, float( 1.0 ).div( ior ), ior ).toVar();
|
|
224
185
|
|
|
225
186
|
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
226
|
-
If( dispersion.greaterThan( 0.0 ), () => {
|
|
187
|
+
if ( layers.dispersion ) If( dispersion.greaterThan( 0.0 ), () => {
|
|
227
188
|
|
|
228
189
|
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
229
190
|
|
|
@@ -278,7 +239,51 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
278
239
|
|
|
279
240
|
} );
|
|
280
241
|
|
|
281
|
-
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// For smooth surfaces with dispersion, use perfect refraction with spectral IOR
|
|
245
|
+
if ( ! layers.dispersion ) microfacet();
|
|
246
|
+
else If( roughness.lessThanEqual( 0.05 ).and( dispersion.greaterThan( 0.0 ) ), () => {
|
|
247
|
+
|
|
248
|
+
result.halfVector.assign( N );
|
|
249
|
+
result.didReflect.assign( false );
|
|
250
|
+
|
|
251
|
+
const eta = ior;
|
|
252
|
+
const etaRatio = select( entering, float( 1.0 ).div( eta ), eta ).toVar();
|
|
253
|
+
|
|
254
|
+
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
255
|
+
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
256
|
+
|
|
257
|
+
const lockedIOR = iorFromWavelength( ior, dispersion, pathWavelength );
|
|
258
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( lockedIOR ), lockedIOR ) );
|
|
259
|
+
|
|
260
|
+
} ).Else( () => {
|
|
261
|
+
|
|
262
|
+
const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 5 ) ), rngState, resolution, frame ) ) );
|
|
263
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
|
|
264
|
+
result.colorWeight.assign( spectralSample.colorWeight );
|
|
265
|
+
result.pathWavelength.assign( spectralSample.wavelength );
|
|
266
|
+
|
|
267
|
+
} );
|
|
268
|
+
|
|
269
|
+
// Perfect refraction using surface normal
|
|
270
|
+
const refractDir = refract( V.negate(), N, etaRatio ).toVar();
|
|
271
|
+
|
|
272
|
+
// Check for total internal reflection
|
|
273
|
+
If( dot( refractDir, refractDir ).lessThan( 0.001 ), () => {
|
|
274
|
+
|
|
275
|
+
result.direction.assign( reflect( V.negate(), N ) );
|
|
276
|
+
result.didReflect.assign( true );
|
|
277
|
+
result.pdf.assign( 1.0 );
|
|
278
|
+
|
|
279
|
+
} ).Else( () => {
|
|
280
|
+
|
|
281
|
+
result.direction.assign( refractDir );
|
|
282
|
+
result.pdf.assign( 1.0 );
|
|
283
|
+
|
|
284
|
+
} );
|
|
285
|
+
|
|
286
|
+
} ).Else( microfacet );
|
|
282
287
|
|
|
283
288
|
return result;
|
|
284
289
|
|
|
@@ -430,7 +435,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
430
435
|
transmissiveTraversals,
|
|
431
436
|
currentMediumIOR, previousMediumIOR,
|
|
432
437
|
pathWavelength,
|
|
433
|
-
] ) => {
|
|
438
|
+
], builder ) => {
|
|
434
439
|
|
|
435
440
|
const result = MaterialInteractionResult( {
|
|
436
441
|
continueRay: false,
|
|
@@ -534,7 +539,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
534
539
|
|
|
535
540
|
// Subsurface (independent of transmission; works at transmission==0). Entry is a lottery
|
|
536
541
|
// (prob = weight) so 1-weight falls through to the opaque BRDF; exit is deterministic.
|
|
537
|
-
If( handled.not().and( material.subsurface.greaterThan( 0.0 ) ), () => {
|
|
542
|
+
if ( materialLayers( builder ).subsurface ) If( handled.not().and( material.subsurface.greaterThan( 0.0 ) ), () => {
|
|
538
543
|
|
|
539
544
|
const entering = dot( ray.direction, normal ).lessThan( 0.0 );
|
|
540
545
|
const doEnter = entering.not().or( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 8 ) ), rngState, resolution, frame ).lessThan( material.subsurface ) );
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MergeKernel.js — vertex merging (Georgiev et al. 2012, "Light Transport Simulation with Vertex Connection and
|
|
3
|
+
* Merging"): each camera vertex Shade left pending gathers the light vertices within the merge radius, as photon
|
|
4
|
+
* mapping does, MIS-weighted against every other strategy. It is the one strategy for light no path can connect:
|
|
5
|
+
* a lamp behind glass seen in a mirror, a caustic seen through water.
|
|
6
|
+
*
|
|
7
|
+
* The radius is a few pixels' footprint where the camera vertex is (Bidirectional.js mergeRadiusAt), so it grows
|
|
8
|
+
* with the distance from the camera. The light vertex cache is filed by that radius in shells, each a hash grid of
|
|
9
|
+
* cells two of its largest radii wide: one list a cell, threaded through the cached records' spare lane (`next`),
|
|
10
|
+
* built after the light pass by mergeInsert. A gather searches the one or two shells its sphere reaches, 2³ cells each.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Fn, float, vec3, vec4, int, uint, ivec3, If, Loop, instanceIndex, atomicLoad, atomicStore, atomicFunc, Return, dot, floor, max, min, select,
|
|
15
|
+
length, log2, exp2, Break, abs,
|
|
16
|
+
} from 'three/tsl';
|
|
17
|
+
|
|
18
|
+
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
19
|
+
import { calculateMaterialPDF } from './LightsSampling.js';
|
|
20
|
+
import { luminance } from './Common.js';
|
|
21
|
+
import { regularizePathContribution } from './PathTracerCore.js';
|
|
22
|
+
import { resolveSurfaceMaterial, misMergePartial, misWeight, strategyWeight, mergeRadiusAt, mergeEta, facingSide, STRATEGY } from './Bidirectional.js';
|
|
23
|
+
import { COUNTER } from '../Processor/QueueManager.js';
|
|
24
|
+
import {
|
|
25
|
+
readRayRadiance, writeRayRadiance, pendingVertex, cachedVertex, readVertexTag, readVertexRecord, readVertexPosition,
|
|
26
|
+
readVertexNext, writeVertexNext, readLightPathLength,
|
|
27
|
+
} from '../Processor/PackedRayBuffer.js';
|
|
28
|
+
|
|
29
|
+
const WG_SIZE = 256;
|
|
30
|
+
export const MERGE_EMPTY = 0xFFFFFFFF;
|
|
31
|
+
// A cell's list is walked at most this far: a bound on the work, never reached at the radius the radius scale gives.
|
|
32
|
+
const MAX_CELL_STEPS = 1024;
|
|
33
|
+
|
|
34
|
+
// Shells of radius ratio 1.25: a sphere whose radius changes by (1 ± slope) across it spans at most two.
|
|
35
|
+
export const MERGE_SHELL_RATIO = 1.25;
|
|
36
|
+
const LOG2_RATIO = Math.log2( MERGE_SHELL_RATIO );
|
|
37
|
+
|
|
38
|
+
const shellOf = ( bdpt, radius ) => int( max( floor( log2( radius.div( bdpt.mergeMin ) ).div( LOG2_RATIO ) ), 0.0 ) );
|
|
39
|
+
|
|
40
|
+
// Twice the largest radius whose sphere can reach the shell (its own largest, over 1 − slope).
|
|
41
|
+
const shellCell = ( bdpt, shell ) => bdpt.mergeMin.mul( exp2( float( shell.add( int( 1 ) ) ).mul( LOG2_RATIO ) ) ).mul( 2.0 )
|
|
42
|
+
.div( max( float( 1.0 ).sub( bdpt.mergeSlope ), 0.5 ) );
|
|
43
|
+
|
|
44
|
+
const cellHash = ( shell, cell, hashMask ) => uint( cell.x ).mul( uint( 73856093 ) )
|
|
45
|
+
.bitXor( uint( cell.y ).mul( uint( 19349663 ) ) )
|
|
46
|
+
.bitXor( uint( cell.z ).mul( uint( 83492791 ) ) )
|
|
47
|
+
.bitXor( uint( shell ).mul( uint( 2654435761 ) ) )
|
|
48
|
+
.bitAnd( hashMask );
|
|
49
|
+
|
|
50
|
+
// A light vertex's shell and cell.
|
|
51
|
+
const filing = ( bdpt, p ) => {
|
|
52
|
+
|
|
53
|
+
const shell = shellOf( bdpt, mergeRadiusAt( bdpt, p ) ).toVar();
|
|
54
|
+
return { shell, cell: ivec3( floor( p.div( shellCell( bdpt, shell ) ) ) ).toVar() };
|
|
55
|
+
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Empties every list head. */
|
|
59
|
+
export function buildMergeClearKernel( { head, hashSize } ) {
|
|
60
|
+
|
|
61
|
+
return Fn( () => {
|
|
62
|
+
|
|
63
|
+
If( instanceIndex.lessThan( uint( hashSize ) ), () => {
|
|
64
|
+
|
|
65
|
+
atomicStore( head.element( instanceIndex ), uint( MERGE_EMPTY ) );
|
|
66
|
+
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
} )().compute( Math.ceil( hashSize / WG_SIZE ) * WG_SIZE, [ WG_SIZE ] );
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Files each light vertex of this frame at the head of its cell's list. */
|
|
74
|
+
export function buildMergeInsertKernel( { hitBufferRW, head, bidirectional: bdpt } ) {
|
|
75
|
+
|
|
76
|
+
const { lightPaths, slotsPerPath, lightTag, hashMask } = bdpt;
|
|
77
|
+
|
|
78
|
+
return Fn( () => {
|
|
79
|
+
|
|
80
|
+
const slot = instanceIndex.toVar();
|
|
81
|
+
If( slot.greaterThanEqual( lightPaths.mul( slotsPerPath ) ), () => {
|
|
82
|
+
|
|
83
|
+
Return();
|
|
84
|
+
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
const depth = slot.mod( slotsPerPath ).toVar();
|
|
88
|
+
If( depth.greaterThanEqual( readLightPathLength( hitBufferRW, cachedVertex( slot.sub( depth ) ), lightTag ) ), () => {
|
|
89
|
+
|
|
90
|
+
Return();
|
|
91
|
+
|
|
92
|
+
} );
|
|
93
|
+
|
|
94
|
+
const at = cachedVertex( slot );
|
|
95
|
+
const { shell, cell } = filing( bdpt, readVertexPosition( hitBufferRW, at ) );
|
|
96
|
+
const bucket = cellHash( shell, cell, hashMask );
|
|
97
|
+
writeVertexNext( hitBufferRW, at, atomicFunc( 'atomicExchange', head.element( bucket ), slot ) );
|
|
98
|
+
|
|
99
|
+
} );
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildMergeKernel( params ) {
|
|
104
|
+
|
|
105
|
+
const {
|
|
106
|
+
rayBufferRW, hitBufferRO, activeIndicesRO, counters, head, materialBuffer, bidirectional: bdpt,
|
|
107
|
+
maxBounceCount, globalIlluminationIntensity, fireflyThreshold, accumFrame,
|
|
108
|
+
// Some material passes light through diffusely: light arriving from behind its surface merges too.
|
|
109
|
+
diffuseTransmission = true,
|
|
110
|
+
} = params;
|
|
111
|
+
const { passTag, slotsPerPath, strategyView, hashMask } = bdpt;
|
|
112
|
+
|
|
113
|
+
return Fn( () => {
|
|
114
|
+
|
|
115
|
+
const tid = instanceIndex;
|
|
116
|
+
If( tid.greaterThanEqual( atomicLoad( counters.element( uint( COUNTER.ENTERING_COUNT ) ) ) ), () => {
|
|
117
|
+
|
|
118
|
+
Return();
|
|
119
|
+
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
const rayID = activeIndicesRO.element( tid ).toVar();
|
|
123
|
+
const pending = pendingVertex( rayID );
|
|
124
|
+
If( readVertexTag( hitBufferRO, pending ).notEqual( passTag ), () => {
|
|
125
|
+
|
|
126
|
+
Return();
|
|
127
|
+
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
const cam = readVertexRecord( hitBufferRO, pending );
|
|
131
|
+
const cameraDepth = int( cam.extra ).toVar();
|
|
132
|
+
// A merged path of k scattering vertices: the camera's cameraDepth + 1 and the light's l share one.
|
|
133
|
+
const lightBudget = int( maxBounceCount ).add( int( 1 ) ).sub( cameraDepth ).toVar();
|
|
134
|
+
const material = resolveSurfaceMaterial( cam.materialIndex, cam.uv, cam.N, materialBuffer );
|
|
135
|
+
const gathered = vec3( 0.0 ).toVar();
|
|
136
|
+
|
|
137
|
+
const radius = mergeRadiusAt( bdpt, cam.position ).toVar();
|
|
138
|
+
const radius2 = radius.mul( radius ).toVar();
|
|
139
|
+
const eta = mergeEta( bdpt, radius ).toVar();
|
|
140
|
+
const trusted = eta.mul( bdpt.mergeTrust );
|
|
141
|
+
const vc = float( 1.0 ).div( max( trusted.mul( trusted ), 1e-30 ) ).toVar();
|
|
142
|
+
// The shells the sphere reaches: its radius at its nearest and farthest points from the camera.
|
|
143
|
+
const distance = length( cam.position.sub( bdpt.cameraPosition ) ).toVar();
|
|
144
|
+
const nearShell = shellOf( bdpt, max( bdpt.mergeConst.add( bdpt.mergeSlope.mul( max( distance.sub( radius ), 0.0 ) ) ), bdpt.mergeMin ) ).toVar();
|
|
145
|
+
const farShell = min( shellOf( bdpt, bdpt.mergeConst.add( bdpt.mergeSlope.mul( distance.add( radius ) ) ) ), nearShell.add( int( 1 ) ) ).toVar();
|
|
146
|
+
|
|
147
|
+
// Per shell, the 2 × 2 × 2 cells the sphere can reach, the cells being at least two radii wide.
|
|
148
|
+
Loop( { start: int( 0 ), end: int( 16 ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
149
|
+
|
|
150
|
+
const shell = nearShell.add( i.shiftRight( int( 3 ) ) ).toVar();
|
|
151
|
+
If( shell.greaterThan( farShell ), () => {
|
|
152
|
+
|
|
153
|
+
Break();
|
|
154
|
+
|
|
155
|
+
} );
|
|
156
|
+
const cellSize = shellCell( bdpt, shell ).toVar();
|
|
157
|
+
const corner = i.bitAnd( int( 7 ) );
|
|
158
|
+
const cell = ivec3( floor( cam.position.div( cellSize ).sub( 0.5 ) ) )
|
|
159
|
+
.add( ivec3( corner.bitAnd( int( 1 ) ), corner.shiftRight( int( 1 ) ).bitAnd( int( 1 ) ), corner.shiftRight( int( 2 ) ) ) ).toVar();
|
|
160
|
+
const slot = atomicLoad( head.element( cellHash( shell, cell, hashMask ) ) ).toVar();
|
|
161
|
+
const steps = int( 0 ).toVar();
|
|
162
|
+
|
|
163
|
+
Loop( slot.notEqual( uint( MERGE_EMPTY ) ).and( steps.lessThan( int( MAX_CELL_STEPS ) ) ), () => {
|
|
164
|
+
|
|
165
|
+
const at = cachedVertex( slot );
|
|
166
|
+
const position = readVertexPosition( hitBufferRO, at ).toVar();
|
|
167
|
+
const offset = position.sub( cam.position );
|
|
168
|
+
const l = int( slot.mod( slotsPerPath ) ).add( int( 1 ) ).toVar();
|
|
169
|
+
If( dot( offset, offset ).lessThan( radius2 ).and( l.lessThanEqual( lightBudget ) ), () => {
|
|
170
|
+
|
|
171
|
+
// Its own shell and cell only: two lists sharing a bucket (a hash collision) must not count it twice.
|
|
172
|
+
const own = filing( bdpt, position );
|
|
173
|
+
If( own.shell.equal( shell ).and( own.cell.x.equal( cell.x ) ).and( own.cell.y.equal( cell.y ) ).and( own.cell.z.equal( cell.z ) ), () => {
|
|
174
|
+
|
|
175
|
+
const light = readVertexRecord( hitBufferRO, at );
|
|
176
|
+
const cosShading = dot( light.V, cam.N ).toVar();
|
|
177
|
+
const cosFacet = dot( light.V, cam.facetN ).toVar();
|
|
178
|
+
// Light arriving on this side, or from behind a surface that passes it through diffusely. A corner's
|
|
179
|
+
// other wall stays in: its light vertices stand in for the part of the sphere past the corner, which
|
|
180
|
+
// the density divides by too.
|
|
181
|
+
const onThisSide = cosShading.greaterThan( 0.0 ).and( cosFacet.greaterThan( 0.0 ) );
|
|
182
|
+
const throughSurface = material.diffuseTransmission.greaterThan( 0.0 ).and( cosShading.lessThan( 0.0 ) ).and( cosFacet.lessThan( 0.0 ) );
|
|
183
|
+
If( diffuseTransmission ? onThisSide.or( throughSurface ) : onThisSide, () => {
|
|
184
|
+
|
|
185
|
+
const f = evaluateMaterialResponse( cam.V, light.V, cam.N, material );
|
|
186
|
+
const wLight = misMergePartial( light, calculateMaterialPDF( cam.V, light.V, cam.N, material ), vc );
|
|
187
|
+
const reverseN = diffuseTransmission ? facingSide( cam.N, light.V, material ) : cam.N;
|
|
188
|
+
const wCamera = misMergePartial( cam, calculateMaterialPDF( light.V, cam.V, reverseN, material ), vc );
|
|
189
|
+
const scattering = cameraDepth.add( l );
|
|
190
|
+
// Alone, a path of k scattering vertices is reached by k merges.
|
|
191
|
+
const weight = strategyWeight( strategyView, STRATEGY.MERGE, misWeight( wLight, wCamera ), float( 1.0 ).div( float( scattering ) ) );
|
|
192
|
+
const gi = select( scattering.greaterThan( int( 1 ) ), globalIlluminationIntensity, float( 1.0 ) );
|
|
193
|
+
// The photon's flux is per unit area; the BSDF wants it per projected solid angle (Veach 5.3.2).
|
|
194
|
+
const correction = diffuseTransmission ? abs( cosShading ).div( max( abs( cosFacet ), 1e-4 ) ) : cosShading.div( max( cosFacet, 1e-4 ) );
|
|
195
|
+
gathered.addAssign( regularizePathContribution(
|
|
196
|
+
cam.throughput.mul( f ).mul( light.throughput ).mul( weight.mul( correction ).div( eta ).mul( gi ) ),
|
|
197
|
+
float( scattering.sub( int( 1 ) ) ), fireflyThreshold, int( accumFrame ),
|
|
198
|
+
) );
|
|
199
|
+
|
|
200
|
+
} );
|
|
201
|
+
|
|
202
|
+
} );
|
|
203
|
+
|
|
204
|
+
} );
|
|
205
|
+
|
|
206
|
+
slot.assign( readVertexNext( hitBufferRO, at ) );
|
|
207
|
+
steps.addAssign( int( 1 ) );
|
|
208
|
+
|
|
209
|
+
} );
|
|
210
|
+
|
|
211
|
+
} );
|
|
212
|
+
|
|
213
|
+
If( luminance( gathered ).greaterThan( 0.0 ), () => {
|
|
214
|
+
|
|
215
|
+
const radiance = readRayRadiance( rayBufferRW, rayID ).toVar();
|
|
216
|
+
writeRayRadiance( rayBufferRW, rayID, vec4( radiance.xyz.add( gathered ), radiance.w ) );
|
|
217
|
+
|
|
218
|
+
} );
|
|
219
|
+
|
|
220
|
+
} );
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export { WG_SIZE as MERGE_WG_SIZE };
|