rayzee 10.0.0 → 11.0.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-BRQvLggb.js +4300 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -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-BCXylThH.js} +8 -6
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
- package/dist/PathTracer-CQPW06Mm.js +29971 -0
- package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
- package/dist/RayzeeRenderer-COpwotpU.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-BZw7TiDp.js +75 -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-BSSsKY98.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 +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- 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 +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -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/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 +3899 -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 +338 -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 +204 -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 +147 -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 +165 -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 +218 -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 +964 -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 +833 -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
|
@@ -35,8 +35,10 @@ import {
|
|
|
35
35
|
length,
|
|
36
36
|
mix,
|
|
37
37
|
select,
|
|
38
|
+
min,
|
|
38
39
|
If,
|
|
39
40
|
Loop,
|
|
41
|
+
sampler,
|
|
40
42
|
} from 'three/tsl';
|
|
41
43
|
|
|
42
44
|
import {
|
|
@@ -71,8 +73,8 @@ import {
|
|
|
71
73
|
} from './LightsDirect.js';
|
|
72
74
|
|
|
73
75
|
import { traverseBVHShadow } from './BVHTraversal.js';
|
|
74
|
-
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
75
|
-
import { calculateBSDFSamplingPDFFromMaterial } from './MaterialProperties.js';
|
|
76
|
+
import { evaluateMaterialResponseFromDots, evaluateDiffuseTransmission } from './MaterialEvaluation.js';
|
|
77
|
+
import { calculateBSDFSamplingPDFFromMaterial, diffuseTransmissionWeight, diffuseTransmissionPdf } from './MaterialProperties.js';
|
|
76
78
|
import { getRandomSample1D, getRandomSample2D, SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
77
79
|
import {
|
|
78
80
|
PI,
|
|
@@ -83,12 +85,12 @@ import {
|
|
|
83
85
|
computeDotProductsAniso,
|
|
84
86
|
offsetRayOrigin,
|
|
85
87
|
SHADOW_END,
|
|
88
|
+
REC709_LUMINANCE_COEFFICIENTS,
|
|
86
89
|
} from './Common.js';
|
|
87
|
-
import {
|
|
88
|
-
sampleEquirectProbability,
|
|
89
|
-
} from './Environment.js';
|
|
90
|
+
import { sampleEnvironment, sampleEnvironmentExact } from './Environment.js';
|
|
90
91
|
import { shadowTerminatorOrigin } from './ShadowTerminator.js';
|
|
91
92
|
import { sunRadianceToward, sampleSunDisc } from './Sun.js';
|
|
93
|
+
import { VISIBILITY_KIND, visibilityShare, recordVisibility } from './LightVisibility.js';
|
|
92
94
|
|
|
93
95
|
const TWO_PI = 2.0 * PI;
|
|
94
96
|
|
|
@@ -757,17 +759,114 @@ export const calculateMaterialPDFFromDots = Fn( ( [ material, dots ] ) => {
|
|
|
757
759
|
|
|
758
760
|
export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material ] ) => {
|
|
759
761
|
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
+
const pdf = float( 0.0 ).toVar();
|
|
763
|
+
|
|
764
|
+
If( dot( normal, lightDir ).lessThan( 0.0 ).and( material.diffuseTransmission.greaterThan( 0.0 ) ), () => {
|
|
765
|
+
|
|
766
|
+
pdf.assign( max( diffuseTransmissionPdf( diffuseTransmissionWeight( material ), dot( normal, lightDir ).negate() ), 1e-8 ) );
|
|
767
|
+
|
|
768
|
+
} ).Else( () => {
|
|
769
|
+
|
|
770
|
+
const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, lightDir, material ) );
|
|
771
|
+
pdf.assign( calculateMaterialPDFFromDots( material, dots ) );
|
|
772
|
+
|
|
773
|
+
} );
|
|
774
|
+
|
|
775
|
+
return pdf;
|
|
776
|
+
|
|
777
|
+
} );
|
|
778
|
+
|
|
779
|
+
// Fewer shadow rays a hit (setting shadowRays). Each light kind offers its sample unshadowed; `variant`:
|
|
780
|
+
// 'one' — keeps one with chance ∝ luminance × √(its kind's learned visibility), traced and divided by that chance;
|
|
781
|
+
// 'two' — always traces the strongest as well, and picks one of the rest.
|
|
782
|
+
const ONE_BELOW = 0.99999994;
|
|
762
783
|
|
|
784
|
+
const slotOf = () => ( {
|
|
785
|
+
kind: int( 0 ).toVar(), weight: float( 0.0 ).toVar(), value: vec3( 0.0 ).toVar(),
|
|
786
|
+
origin: vec3( 0.0 ).toVar(), direction: vec3( 0.0, 1.0, 0.0 ).toVar(), distance: float( 0.0 ).toVar(),
|
|
763
787
|
} );
|
|
764
788
|
|
|
789
|
+
const FIELDS = [ 'kind', 'weight', 'value', 'origin', 'direction', 'distance' ];
|
|
790
|
+
const assignSlot = ( slot, from ) => FIELDS.forEach( ( f ) => slot[ f ].assign( from[ f ] ) );
|
|
791
|
+
|
|
792
|
+
// `learned` (LightVisibility.js) gives `share( kind )` and `record( kind, visible )`.
|
|
793
|
+
export const lightPick = ( u0, learned = null, variant = 'one' ) => {
|
|
794
|
+
|
|
795
|
+
const u = float( u0 ).toVar();
|
|
796
|
+
const total = float( 0.0 ).toVar();
|
|
797
|
+
const kept = slotOf();
|
|
798
|
+
const best = variant === 'two' ? slotOf() : null;
|
|
799
|
+
|
|
800
|
+
const keep = ( c ) => {
|
|
801
|
+
|
|
802
|
+
If( c.weight.greaterThan( 0.0 ), () => {
|
|
803
|
+
|
|
804
|
+
total.addAssign( c.weight );
|
|
805
|
+
const p = c.weight.div( total ).toVar();
|
|
806
|
+
If( u.lessThan( p ), () => {
|
|
807
|
+
|
|
808
|
+
u.assign( min( u.div( p ), ONE_BELOW ) );
|
|
809
|
+
assignSlot( kept, c );
|
|
810
|
+
|
|
811
|
+
} ).Else( () => {
|
|
812
|
+
|
|
813
|
+
u.assign( min( u.sub( p ).div( float( 1.0 ).sub( p ) ), ONE_BELOW ) );
|
|
814
|
+
|
|
815
|
+
} );
|
|
816
|
+
|
|
817
|
+
} );
|
|
818
|
+
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const offer = ( contribution, from, dir, maxDist, lightKind = 0 ) => {
|
|
822
|
+
|
|
823
|
+
const luminance = dot( contribution, REC709_LUMINANCE_COEFFICIENTS );
|
|
824
|
+
const c = {
|
|
825
|
+
kind: int( lightKind ), value: contribution, origin: from, direction: dir, distance: maxDist,
|
|
826
|
+
weight: ( learned ? luminance.mul( learned.share( lightKind ) ) : luminance ).toVar(),
|
|
827
|
+
};
|
|
828
|
+
if ( ! best ) return keep( c );
|
|
829
|
+
|
|
830
|
+
const better = c.weight.greaterThan( best.weight ).toVar();
|
|
831
|
+
const loser = Object.fromEntries( FIELDS.map( ( f ) => [ f, select( better, best[ f ], c[ f ] ).toVar() ] ) );
|
|
832
|
+
keep( loser );
|
|
833
|
+
If( better, () => assignSlot( best, c ) );
|
|
834
|
+
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// The direct light, tracing what the variant keeps through `shadow( origin, direction, distance )`.
|
|
838
|
+
const trace = ( shadow ) => {
|
|
839
|
+
|
|
840
|
+
const result = vec3( 0.0 ).toVar();
|
|
841
|
+
Loop( { start: int( 0 ), end: int( best ? 2 : 1 ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
842
|
+
|
|
843
|
+
const isBest = best ? i.equal( int( 0 ) ) : tslBool( false );
|
|
844
|
+
const pickOf = ( f ) => ( best ? select( isBest, best[ f ], kept[ f ] ) : kept[ f ] );
|
|
845
|
+
const weight = pickOf( 'weight' ).toVar();
|
|
846
|
+
If( weight.greaterThan( 0.0 ), () => {
|
|
847
|
+
|
|
848
|
+
const visibility = shadow( pickOf( 'origin' ), pickOf( 'direction' ), pickOf( 'distance' ) ).toVar();
|
|
849
|
+
if ( learned ) learned.record( pickOf( 'kind' ), visibility.greaterThan( 0.0 ) );
|
|
850
|
+
result.addAssign( pickOf( 'value' ).mul( visibility ).mul( select( isBest, float( 1.0 ), total.div( weight ) ) ) );
|
|
851
|
+
|
|
852
|
+
} );
|
|
853
|
+
|
|
854
|
+
} );
|
|
855
|
+
return result;
|
|
856
|
+
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
return { offer, total, trace };
|
|
860
|
+
|
|
861
|
+
};
|
|
862
|
+
|
|
765
863
|
// =============================================================================
|
|
766
864
|
// Unified Direct Lighting System
|
|
767
865
|
// =============================================================================
|
|
768
866
|
|
|
769
|
-
// Optimized direct lighting function with importance-based sampling and better MIS
|
|
770
|
-
|
|
867
|
+
// Optimized direct lighting function with importance-based sampling and better MIS. `throughSurfaces` (compile
|
|
868
|
+
// time) adds light from behind a surface that passes it through diffusely; without it the code is exactly as before.
|
|
869
|
+
const makeDirectLighting = ( throughSurfaces, picked = null ) => Fn( ( [
|
|
771
870
|
// Surface hit data
|
|
772
871
|
hitPoint, hitNormal, geomNormal, material,
|
|
773
872
|
// View direction
|
|
@@ -791,7 +890,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
791
890
|
// Environment resources
|
|
792
891
|
envTexture, environmentIntensity, envMatrix,
|
|
793
892
|
envCDFTexture,
|
|
794
|
-
envTotalSum,
|
|
893
|
+
envTotalSum, envResolution,
|
|
795
894
|
enableEnvironmentLight,
|
|
796
895
|
// Shadow catcher: when true, also accumulate the unoccluded (visibility=1) reference
|
|
797
896
|
// at every light/env site so the caller can form a shadow ratio. Dead path otherwise.
|
|
@@ -801,6 +900,9 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
801
900
|
terminatorLift, facetNormal, terminatorCutoff,
|
|
802
901
|
// The physical sky's sun (Sun.js); its BSDF-sampled partner is the miss branch in ShadeKernel.
|
|
803
902
|
hasSun, sunDirection, sunRadiance, sunParams,
|
|
903
|
+
// picked: the caller's emitter sample, unshadowed, and the learned visibility (LightVisibility.js) at this point
|
|
904
|
+
emissiveValue, emissiveOrigin, emissiveDirection, emissiveDistance,
|
|
905
|
+
visibilityCounters, visibilityCell,
|
|
804
906
|
] ) => {
|
|
805
907
|
|
|
806
908
|
const totalContribution = vec3( 0.0 ).toVar();
|
|
@@ -817,6 +919,46 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
817
919
|
traceShadowRay( origin, dir, maxDist, traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer )
|
|
818
920
|
);
|
|
819
921
|
|
|
922
|
+
// Light from behind a surface that passes light through diffusely: its shadow ray leaves from the other side,
|
|
923
|
+
// and the BSDF is the transmission lobe.
|
|
924
|
+
const throughSurface = L => ( throughSurfaces
|
|
925
|
+
? material.diffuseTransmission.greaterThan( 0.0 ).and( dot( hitNormal, L ).lessThan( 0.0 ) ).and( dot( L, geomNormal ).lessThan( 0.0 ) )
|
|
926
|
+
: tslBool( false ) );
|
|
927
|
+
const backOrigin = throughSurfaces ? offsetRayOrigin( hitPoint, facetNormal.negate() ) : null;
|
|
928
|
+
const inFront = L => dot( hitNormal, L ).greaterThan( 0.0 ).and( isDirectionValid( { direction: L, surfaceNormal: geomNormal } ) );
|
|
929
|
+
const reachesSurface = ( L, back ) => ( throughSurfaces ? back.or( inFront( L ) ) : inFront( L ) );
|
|
930
|
+
const cosineTo = L => ( throughSurfaces ? abs( dot( hitNormal, L ) ) : max( float( 0.0 ), dot( hitNormal, L ) ) );
|
|
931
|
+
const originFor = ( back, front ) => ( throughSurfaces ? select( back, backOrigin, front ) : front );
|
|
932
|
+
const reflection = L => {
|
|
933
|
+
|
|
934
|
+
// Share H + dot products between BRDF eval and PDF — otherwise each
|
|
935
|
+
// would recompute normalize(V+L) + 5 dot products independently.
|
|
936
|
+
const dots = DotProducts.wrap( computeDotProductsAniso( hitNormal, viewDir, L, material ) );
|
|
937
|
+
return { value: evaluateMaterialResponseFromDots( material, dots ), pdf: calculateMaterialPDFFromDots( material, dots ).toVar() };
|
|
938
|
+
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
const bsdfToward = ( L, back ) => {
|
|
942
|
+
|
|
943
|
+
if ( ! throughSurfaces ) return reflection( L );
|
|
944
|
+
const value = vec3( 0.0 ).toVar();
|
|
945
|
+
const pdf = float( 0.0 ).toVar();
|
|
946
|
+
If( back, () => {
|
|
947
|
+
|
|
948
|
+
value.assign( evaluateDiffuseTransmission( material, max( dot( hitNormal, viewDir ), 0.001 ) ) );
|
|
949
|
+
pdf.assign( max( diffuseTransmissionPdf( diffuseTransmissionWeight( material ), dot( hitNormal, L ).negate() ), 1e-8 ) );
|
|
950
|
+
|
|
951
|
+
} ).Else( () => {
|
|
952
|
+
|
|
953
|
+
const r = reflection( L );
|
|
954
|
+
value.assign( r.value );
|
|
955
|
+
pdf.assign( r.pdf );
|
|
956
|
+
|
|
957
|
+
} );
|
|
958
|
+
return { value, pdf };
|
|
959
|
+
|
|
960
|
+
};
|
|
961
|
+
|
|
820
962
|
|
|
821
963
|
// NEE and BSDF-hit both run at every vertex, one sample each, so the MIS weights are the
|
|
822
964
|
// plain power heuristic over the raw densities. Picking one strategy stochastically and
|
|
@@ -828,6 +970,13 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
828
970
|
// rather than re-walking every light buffer to rebuild the same sum.
|
|
829
971
|
const neeTotalWeight = float( 0.0 ).toVar();
|
|
830
972
|
|
|
973
|
+
const learned = picked ? {
|
|
974
|
+
share: ( kind ) => visibilityShare( visibilityCounters, visibilityCell, kind ),
|
|
975
|
+
record: ( kind, visible ) => recordVisibility( visibilityCounters, visibilityCell, kind, visible ),
|
|
976
|
+
} : null;
|
|
977
|
+
const pick = picked ? lightPick( getRandomSample1D( pixelCoord, int( 0 ), dimBase, rngState, resolution, frame ), learned, picked ) : null;
|
|
978
|
+
if ( pick ) pick.offer( emissiveValue, emissiveOrigin, emissiveDirection, emissiveDistance, VISIBILITY_KIND.EMITTERS );
|
|
979
|
+
|
|
831
980
|
// =====================================================================
|
|
832
981
|
// LIGHT SAMPLING PATH
|
|
833
982
|
// =====================================================================
|
|
@@ -849,24 +998,19 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
849
998
|
|
|
850
999
|
If( lightSample.valid.and( lightSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
851
1000
|
|
|
852
|
-
const
|
|
1001
|
+
const back = throughSurface( lightSample.direction ).toVar();
|
|
1002
|
+
const NoL = cosineTo( lightSample.direction ).toVar();
|
|
853
1003
|
|
|
854
|
-
If(
|
|
1004
|
+
If( reachesSurface( lightSample.direction, back ), () => {
|
|
855
1005
|
|
|
856
1006
|
// Light geometry is measured from the hit point; only the visibility ray starts on the lifted
|
|
857
1007
|
// origin, aimed at the sampled point and stopped a relative hair short of it.
|
|
858
|
-
const lightOrigin = lightShadowOrigin( lightSample.direction ).toVar();
|
|
1008
|
+
const lightOrigin = originFor( back, lightShadowOrigin( lightSample.direction ) ).toVar();
|
|
859
1009
|
const toSample = hitPoint.add( lightSample.direction.mul( lightSample.distance ) ).sub( lightOrigin ).toVar();
|
|
860
1010
|
const shadowDistance = length( toSample ).toVar();
|
|
861
|
-
const
|
|
1011
|
+
const lampLight = () => {
|
|
862
1012
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
// Share H + dot products between BRDF eval and PDF — otherwise each
|
|
866
|
-
// would recompute normalize(V+L) + 5 dot products independently.
|
|
867
|
-
const sharedDots = DotProducts.wrap( computeDotProductsAniso( hitNormal, viewDir, lightSample.direction, material ) );
|
|
868
|
-
const brdfValue = evaluateMaterialResponseFromDots( material, sharedDots );
|
|
869
|
-
const bPdf = calculateMaterialPDFFromDots( material, sharedDots ).toVar();
|
|
1013
|
+
const { value: brdfValue, pdf: bPdf } = bsdfToward( lightSample.direction, back );
|
|
870
1014
|
|
|
871
1015
|
// Power heuristic only for area lights — they are the only type the BRDF
|
|
872
1016
|
// path can intersect, so elsewhere MIS would just delete energy.
|
|
@@ -878,17 +1022,33 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
878
1022
|
|
|
879
1023
|
} );
|
|
880
1024
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1025
|
+
return lightSample.emission.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightSample.pdf, 1e-10 ) );
|
|
1026
|
+
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
if ( pick ) {
|
|
1030
|
+
|
|
1031
|
+
pick.offer( lampLight(), lightOrigin, toSample.div( shadowDistance ), shadowDistance.mul( SHADOW_END ), VISIBILITY_KIND.LAMPS );
|
|
1032
|
+
|
|
1033
|
+
} else {
|
|
1034
|
+
|
|
1035
|
+
const visibility = shadow( lightOrigin, toSample.div( shadowDistance ), shadowDistance.mul( SHADOW_END ) );
|
|
1036
|
+
|
|
1037
|
+
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
1038
|
+
|
|
1039
|
+
// Base contribution WITHOUT visibility; shadowed = base × visibility (identical to the
|
|
1040
|
+
// pre-dual-sum math), unoccluded = base × 1 (shadow-catcher reference only).
|
|
1041
|
+
const baseContribution = lampLight();
|
|
1042
|
+
totalContribution.addAssign( baseContribution.mul( visibility ) );
|
|
1043
|
+
If( wantUnoccluded, () => {
|
|
1044
|
+
|
|
1045
|
+
unoccludedContribution.addAssign( baseContribution );
|
|
886
1046
|
|
|
887
|
-
|
|
1047
|
+
} );
|
|
888
1048
|
|
|
889
1049
|
} );
|
|
890
1050
|
|
|
891
|
-
}
|
|
1051
|
+
}
|
|
892
1052
|
|
|
893
1053
|
} );
|
|
894
1054
|
|
|
@@ -902,9 +1062,10 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
902
1062
|
|
|
903
1063
|
If( brdfSamplePdf.greaterThan( 0.0 ).and( numAreaLights.greaterThan( int( 0 ) ) ), () => {
|
|
904
1064
|
|
|
905
|
-
const
|
|
1065
|
+
const back = throughSurface( brdfSampleDirection ).toVar();
|
|
1066
|
+
const NoL = cosineTo( brdfSampleDirection ).toVar();
|
|
906
1067
|
|
|
907
|
-
If(
|
|
1068
|
+
If( reachesSurface( brdfSampleDirection, back ), () => {
|
|
908
1069
|
|
|
909
1070
|
// Nearest hit — the ray stops at the first emitter, so a brighter one behind it is
|
|
910
1071
|
// the wrong emitter and the wrong pdf. No importance gate: skipping a light drops
|
|
@@ -936,9 +1097,10 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
936
1097
|
If( nearestLight.greaterThanEqual( int( 0 ) ), () => {
|
|
937
1098
|
|
|
938
1099
|
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, nearestLight ) );
|
|
939
|
-
const
|
|
1100
|
+
const hitOrigin = originFor( back, rayOrigin ).toVar();
|
|
1101
|
+
const toLight = hitPoint.add( brdfSampleDirection.mul( nearestT ) ).sub( hitOrigin ).toVar();
|
|
940
1102
|
const shadowDistance = length( toLight ).toVar();
|
|
941
|
-
const visibility = shadow(
|
|
1103
|
+
const visibility = shadow( hitOrigin, toLight.div( shadowDistance ), shadowDistance.mul( SHADOW_END ) );
|
|
942
1104
|
|
|
943
1105
|
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
944
1106
|
|
|
@@ -1011,33 +1173,29 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1011
1173
|
radiance.assign( sunRadianceToward( direction, sunDirection, sunRadiance, sunParams ).mul( environmentIntensity ) );
|
|
1012
1174
|
lightPdf.assign( select( radiance.x.add( radiance.y ).add( radiance.z ).greaterThan( 0.0 ), float( 1.0 ).div( sunParams.y ), float( 0.0 ) ) );
|
|
1013
1175
|
|
|
1014
|
-
} ).
|
|
1176
|
+
} ).ElseIf( envTotalSum.greaterThan( 0.0 ), () => {
|
|
1015
1177
|
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
lightPdf.assign( envSampleResult.w );
|
|
1178
|
+
// Drawn from the table its density is read back from; a total of 0 is no table, or a black map.
|
|
1179
|
+
const drawn = sampleEnvironmentExact( envCDFTexture, envMatrix, envResolution,
|
|
1180
|
+
getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame ) );
|
|
1181
|
+
direction.assign( drawn.direction );
|
|
1182
|
+
radiance.assign( sampleEnvironment( {
|
|
1183
|
+
tex: envTexture, samp: sampler( envTexture ), direction, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight: float( 1.0 ),
|
|
1184
|
+
} ).xyz );
|
|
1185
|
+
lightPdf.assign( drawn.pdf );
|
|
1025
1186
|
|
|
1026
1187
|
} );
|
|
1027
1188
|
|
|
1028
1189
|
If( lightPdf.greaterThan( 0.0 ), () => {
|
|
1029
1190
|
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
If( NoL.greaterThan( 0.0 ).and( isDirectionValid( { direction, surfaceNormal: geomNormal } ) ), () => {
|
|
1191
|
+
const back = throughSurface( direction ).toVar();
|
|
1192
|
+
const NoL = cosineTo( direction ).toVar();
|
|
1033
1193
|
|
|
1034
|
-
|
|
1194
|
+
If( reachesSurface( direction, back ), () => {
|
|
1035
1195
|
|
|
1036
|
-
|
|
1196
|
+
const skyLight = () => {
|
|
1037
1197
|
|
|
1038
|
-
const
|
|
1039
|
-
const brdfValue = evaluateMaterialResponseFromDots( material, envDots );
|
|
1040
|
-
const bPdf = calculateMaterialPDFFromDots( material, envDots ).toVar();
|
|
1198
|
+
const { value: brdfValue, pdf: bPdf } = bsdfToward( direction, back );
|
|
1041
1199
|
|
|
1042
1200
|
// The map pairs with the balance heuristic — optimal for its MIS-compensated pdf
|
|
1043
1201
|
// (Karlík et al. 2019); the sun, a small bright source, with the power heuristic.
|
|
@@ -1047,8 +1205,24 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1047
1205
|
float( 1.0 )
|
|
1048
1206
|
).toVar();
|
|
1049
1207
|
|
|
1208
|
+
return radiance.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightPdf, 1e-10 ) );
|
|
1209
|
+
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
if ( pick ) {
|
|
1213
|
+
|
|
1214
|
+
pick.offer( skyLight(), originFor( back, lightShadowOrigin( direction ) ), direction, float( 1e20 ),
|
|
1215
|
+
select( isSun, int( VISIBILITY_KIND.SUN ), int( VISIBILITY_KIND.ENVIRONMENT ) ) );
|
|
1216
|
+
return;
|
|
1217
|
+
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const visibility = shadow( originFor( back, lightShadowOrigin( direction ) ), direction, float( 1e20 ) );
|
|
1221
|
+
|
|
1222
|
+
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
1223
|
+
|
|
1050
1224
|
// Base contribution WITHOUT visibility (deterministic estimator; no stochastic scaling).
|
|
1051
|
-
const baseContribution =
|
|
1225
|
+
const baseContribution = skyLight();
|
|
1052
1226
|
totalContribution.addAssign( baseContribution.mul( visibility ) );
|
|
1053
1227
|
If( wantUnoccluded, () => {
|
|
1054
1228
|
|
|
@@ -1066,6 +1240,8 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1066
1240
|
|
|
1067
1241
|
} );
|
|
1068
1242
|
|
|
1243
|
+
if ( pick ) totalContribution.addAssign( pick.trace( shadow ) );
|
|
1244
|
+
|
|
1069
1245
|
// EMISSIVE TRIANGLE DIRECT LIGHTING
|
|
1070
1246
|
// NOTE: Emissive triangle sampling is handled separately in pathtracer_core.fs
|
|
1071
1247
|
// to bypass firefly suppression. Do not add it here to avoid double-counting.
|
|
@@ -1073,3 +1249,15 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1073
1249
|
return DirectLightingDual( { shadowed: totalContribution, unoccluded: unoccludedContribution } );
|
|
1074
1250
|
|
|
1075
1251
|
} );
|
|
1252
|
+
|
|
1253
|
+
export const calculateDirectLightingUnified = /*@__PURE__*/ makeDirectLighting( false );
|
|
1254
|
+
export const calculateDirectLightingThroughSurfaces = /*@__PURE__*/ makeDirectLighting( true );
|
|
1255
|
+
const pickedLighting = new Map();
|
|
1256
|
+
/** The direct light with fewer shadow rays a hit; `variant` as lightPick takes it. */
|
|
1257
|
+
export const calculateDirectLightingPicked = ( throughSurfaces, variant ) => {
|
|
1258
|
+
|
|
1259
|
+
const key = `${throughSurfaces}:${variant}`;
|
|
1260
|
+
if ( ! pickedLighting.has( key ) ) pickedLighting.set( key, makeDirectLighting( throughSurfaces, variant ) );
|
|
1261
|
+
return pickedLighting.get( key );
|
|
1262
|
+
|
|
1263
|
+
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Fn, float, vec3,
|
|
2
|
+
Fn, float, vec3, dot,
|
|
3
3
|
If, max, min, clamp, mix
|
|
4
4
|
} from 'three/tsl';
|
|
5
5
|
|
|
6
6
|
import { DotProducts, DFGResult, BaseFresnel } from './Struct.js';
|
|
7
7
|
import { PI_INV, MIN_CLEARCOAT_ROUGHNESS, computeDotProductsAniso } from './Common.js';
|
|
8
8
|
import { fresnelSchlick, fresnelDielectric, dielectricFresnelWeight } from './Fresnel.js';
|
|
9
|
+
import { materialLayers } from './SceneResources.js';
|
|
9
10
|
import {
|
|
10
11
|
DistributionGGX, SheenDistribution, VisibilitySheen, VisibilityGGXSmithCorrelated,
|
|
11
12
|
sheenDirectionalAlbedo, evaluateSpecularDFG, baseFresnelParams,
|
|
@@ -21,27 +22,32 @@ import { evalIridescence } from './MaterialProperties.js';
|
|
|
21
22
|
// Main Material Response Evaluation
|
|
22
23
|
// -----------------------------------------------------------------------------
|
|
23
24
|
|
|
25
|
+
// A rough plain dielectric: its BSDF is a bare Lambert term (no specular lobe, no DFG budget).
|
|
26
|
+
const isLambertian = ( material ) => material.roughness.greaterThan( 0.98 )
|
|
27
|
+
.and( material.metalness.lessThan( 0.02 ) )
|
|
28
|
+
.and( material.transmission.equal( 0.0 ) )
|
|
29
|
+
.and( material.clearcoat.equal( 0.0 ) )
|
|
30
|
+
.and( material.sheen.equal( 0.0 ) )
|
|
31
|
+
.and( material.iridescence.equal( 0.0 ) );
|
|
32
|
+
|
|
24
33
|
// Body of evaluateMaterialResponse taking precomputed dot products. Callers
|
|
25
34
|
// that also need calculateMaterialPDF for the same (V, L, N) should share dots
|
|
26
35
|
// to save one computeDotProducts call.
|
|
27
36
|
// Roughness 0 marks an exact mirror (ShadeKernel): its delta lobe is left out of this BSDF, and
|
|
28
37
|
// deltaOnly returns that lobe's reflectance instead, under the same sheen and coat attenuation.
|
|
29
|
-
const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] ) => {
|
|
38
|
+
const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ], builder ) => {
|
|
30
39
|
|
|
40
|
+
const layers = materialLayers( builder );
|
|
31
41
|
const result = vec3( 0.0 ).toVar();
|
|
32
42
|
|
|
33
43
|
// Early exit for purely diffuse materials (skip if iridescent)
|
|
34
44
|
// `sheen` is in the guard because this fast path returns a bare Lambert term: without it a
|
|
35
45
|
// rough sheen material lost its sheen lobe entirely — silently, since the fast path is only
|
|
36
46
|
// taken above roughness 0.98.
|
|
37
|
-
If( material
|
|
38
|
-
.and( material.metalness.lessThan( 0.02 ) )
|
|
39
|
-
.and( material.transmission.equal( 0.0 ) )
|
|
40
|
-
.and( material.clearcoat.equal( 0.0 ) )
|
|
41
|
-
.and( material.sheen.equal( 0.0 ) )
|
|
42
|
-
.and( material.iridescence.equal( 0.0 ) ), () => {
|
|
47
|
+
If( isLambertian( material ), () => {
|
|
43
48
|
|
|
44
|
-
result.assign( material.color.rgb.mul( float( 1.0 ).sub( material.metalness ) )
|
|
49
|
+
result.assign( material.color.rgb.mul( float( 1.0 ).sub( material.metalness ) )
|
|
50
|
+
.mul( float( 1.0 ).sub( material.diffuseTransmission ) ).mul( PI_INV ) );
|
|
45
51
|
|
|
46
52
|
} ).Else( () => {
|
|
47
53
|
|
|
@@ -51,7 +57,7 @@ const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] )
|
|
|
51
57
|
|
|
52
58
|
// Modify material color for dispersive materials to enhance color separation
|
|
53
59
|
const materialColor = material.color.rgb.toVar();
|
|
54
|
-
If( material.dispersion.greaterThan( 0.0 ).and( material.transmission.greaterThan( 0.5 ) ), () => {
|
|
60
|
+
if ( layers.dispersion ) If( material.dispersion.greaterThan( 0.0 ).and( material.transmission.greaterThan( 0.5 ) ), () => {
|
|
55
61
|
|
|
56
62
|
// For highly dispersive transmissive materials, boost color saturation
|
|
57
63
|
const dispersionEffect = clamp( material.dispersion.mul( 0.1 ), 0.0, 0.8 );
|
|
@@ -67,7 +73,7 @@ const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] )
|
|
|
67
73
|
} );
|
|
68
74
|
|
|
69
75
|
// Add iridescence effect if enabled
|
|
70
|
-
If( material.iridescence.greaterThan( 0.0 ), () => {
|
|
76
|
+
if ( layers.iridescence ) If( material.iridescence.greaterThan( 0.0 ), () => {
|
|
71
77
|
|
|
72
78
|
// Per glTF KHR_materials_iridescence spec: use max thickness when no texture
|
|
73
79
|
const thickness = material.iridescenceThicknessRange.y;
|
|
@@ -78,29 +84,29 @@ const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] )
|
|
|
78
84
|
|
|
79
85
|
// Dielectric on the exact Fresnel curve, metal and iridescence on Schlick.
|
|
80
86
|
const Fd = mix( bf.f0, vec3( bf.f90 ), dielectricFresnelWeight( dots.VoH, bf.eta ) );
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
fresnelSchlick( dots.VoH, iridF ),
|
|
84
|
-
material.iridescence,
|
|
85
|
-
).toVar();
|
|
87
|
+
const Fbase = mix( Fd, fresnelSchlick( dots.VoH, bf.F0m ), material.metalness );
|
|
88
|
+
const F = ( layers.iridescence ? mix( Fbase, fresnelSchlick( dots.VoH, iridF ), material.iridescence ) : Fbase ).toVar();
|
|
86
89
|
|
|
87
90
|
// Single-scatter specular BRDF (anisotropic when material.anisotropy > 0; the aniso
|
|
88
91
|
// visibility term already carries the 1/(4·NoV·NoL) denominator)
|
|
89
92
|
const specularSS = vec3( 0.0 ).toVar();
|
|
90
|
-
|
|
93
|
+
const isotropic = () => {
|
|
94
|
+
|
|
95
|
+
const D = DistributionGGX( dots.NoH, material.roughness );
|
|
96
|
+
const Vis = VisibilityGGXSmithCorrelated( dots.NoV, dots.NoL, material.roughness );
|
|
97
|
+
specularSS.assign( D.mul( Vis ).mul( F ) );
|
|
98
|
+
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if ( layers.anisotropy ) If( material.anisotropy.greaterThan( 0.0 ), () => {
|
|
91
102
|
|
|
92
103
|
const a = computeAnisoAlphas( material.roughness, material.anisotropy );
|
|
93
104
|
const Da = DistributionGGXAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH );
|
|
94
105
|
const Va = VisibilityGGXAniso( a.x, a.y, dots.ToV, dots.BoV, dots.ToL, dots.BoL, dots.NoV, dots.NoL );
|
|
95
106
|
specularSS.assign( F.mul( Da.mul( Va ) ) );
|
|
96
107
|
|
|
97
|
-
} ).ElseIf( material.roughness.greaterThan( 0.0 ),
|
|
98
|
-
|
|
99
|
-
const D = DistributionGGX( dots.NoH, material.roughness );
|
|
100
|
-
const Vis = VisibilityGGXSmithCorrelated( dots.NoV, dots.NoL, material.roughness );
|
|
101
|
-
specularSS.assign( D.mul( Vis ).mul( F ) );
|
|
102
|
-
|
|
103
|
-
} );
|
|
108
|
+
} ).ElseIf( material.roughness.greaterThan( 0.0 ), isotropic );
|
|
109
|
+
else If( material.roughness.greaterThan( 0.0 ), isotropic );
|
|
104
110
|
|
|
105
111
|
// Compensation factor and total directional albedo from the same table fetch.
|
|
106
112
|
const dfg = DFGResult.wrap( evaluateSpecularDFG(
|
|
@@ -112,15 +118,17 @@ const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] )
|
|
|
112
118
|
// Diffuse energy budget from hemisphere-integrated specular albedo (includes multiscatter)
|
|
113
119
|
// Transmission removes energy from diffuse just as metalness does — KHR_materials_transmission
|
|
114
120
|
// defines transmission as replacing the diffuse component.
|
|
121
|
+
// Diffuse transmission takes its share of the diffuse lobe to the other side (evaluateDiffuseTransmission).
|
|
115
122
|
const kD = vec3( 1.0 ).sub( dfg.E_total )
|
|
116
123
|
.mul( float( 1.0 ).sub( material.metalness ) )
|
|
117
|
-
.mul( float( 1.0 ).sub( material.transmission ) )
|
|
124
|
+
.mul( float( 1.0 ).sub( material.transmission ) )
|
|
125
|
+
.mul( float( 1.0 ).sub( material.diffuseTransmission ) );
|
|
118
126
|
const diffuse = deltaOnly ? vec3( 0.0 ) : kD.mul( materialColor ).mul( PI_INV );
|
|
119
127
|
|
|
120
128
|
const baseLayer = diffuse.add( specular ).toVar();
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
If( material.sheen.greaterThan( 0.0 ), () => {
|
|
130
|
+
if ( ! layers.sheen ) result.assign( baseLayer );
|
|
131
|
+
else If( material.sheen.greaterThan( 0.0 ), () => {
|
|
124
132
|
|
|
125
133
|
// D · V, not · NoL — callers apply the cosine when they integrate.
|
|
126
134
|
const sheenDist = SheenDistribution( dots.NoH, material.sheenRoughness );
|
|
@@ -143,7 +151,7 @@ const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] )
|
|
|
143
151
|
|
|
144
152
|
// The coat lives here, not in a separate layered-BRDF function: every strategy has to
|
|
145
153
|
// evaluate the same integrand or MIS is biased however good the weights are.
|
|
146
|
-
If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
154
|
+
if ( layers.clearcoat ) If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
147
155
|
|
|
148
156
|
const ccRoughness = max( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS );
|
|
149
157
|
const ccF0 = vec3( 0.04 ); // IOR 1.5
|
|
@@ -176,12 +184,86 @@ const makeEvaluateMaterialResponse = ( deltaOnly ) => Fn( ( [ material, dots ] )
|
|
|
176
184
|
export const evaluateMaterialResponseFromDots = /*@__PURE__*/ makeEvaluateMaterialResponse( false );
|
|
177
185
|
export const evaluateSpecularDeltaFromDots = /*@__PURE__*/ makeEvaluateMaterialResponse( true );
|
|
178
186
|
|
|
187
|
+
/**
|
|
188
|
+
* KHR_materials_diffuse_transmission's BTDF, for light arriving from the far side of a thin surface: the diffuse
|
|
189
|
+
* budget the reflected diffuse lobe has (what the specular lobes leave, less metal and specular transmission) × the
|
|
190
|
+
* transmitted share × its colour / π, under the same sheen and coat attenuation. Zero when the material has none.
|
|
191
|
+
*/
|
|
192
|
+
export const evaluateDiffuseTransmission = Fn( ( [ material, NoV ], builder ) => {
|
|
193
|
+
|
|
194
|
+
const layers = materialLayers( builder );
|
|
195
|
+
const result = vec3( 0.0 ).toVar();
|
|
196
|
+
|
|
197
|
+
if ( layers.diffuseTransmission ) If( material.diffuseTransmission.greaterThan( 0.0 ), () => {
|
|
198
|
+
|
|
199
|
+
const kD = vec3( float( 1.0 ).sub( material.metalness ).mul( float( 1.0 ).sub( material.transmission ) ) ).toVar();
|
|
200
|
+
|
|
201
|
+
If( isLambertian( material ).not(), () => {
|
|
202
|
+
|
|
203
|
+
const bf = BaseFresnel.wrap( baseFresnelParams( material, material.color.rgb ) );
|
|
204
|
+
const F0 = bf.F0.toVar();
|
|
205
|
+
const iridF = vec3( 0.0 ).toVar();
|
|
206
|
+
if ( layers.iridescence ) If( material.iridescence.greaterThan( 0.0 ), () => {
|
|
207
|
+
|
|
208
|
+
// No half vector across the surface: the iridescent F0 at normal incidence to the view.
|
|
209
|
+
iridF.assign( evalIridescence( float( 1.0 ), material.iridescenceIOR, NoV, material.iridescenceThicknessRange.y, F0 ) );
|
|
210
|
+
F0.assign( mix( F0, iridF, material.iridescence ) );
|
|
211
|
+
|
|
212
|
+
} );
|
|
213
|
+
const dfg = DFGResult.wrap( evaluateSpecularDFG(
|
|
214
|
+
bf.f0, bf.f90, bf.eta, bf.F0m, material.metalness, iridF, material.iridescence, F0,
|
|
215
|
+
NoV, material.roughness,
|
|
216
|
+
) );
|
|
217
|
+
kD.mulAssign( vec3( 1.0 ).sub( dfg.E_total ) );
|
|
218
|
+
|
|
219
|
+
} );
|
|
220
|
+
|
|
221
|
+
result.assign( kD.mul( material.diffuseTransmission ).mul( material.diffuseTransmissionColor ).mul( PI_INV ) );
|
|
222
|
+
|
|
223
|
+
if ( layers.sheen ) If( material.sheen.greaterThan( 0.0 ), () => {
|
|
224
|
+
|
|
225
|
+
const sheenE = sheenDirectionalAlbedo( NoV, material.sheenRoughness );
|
|
226
|
+
result.mulAssign( vec3( 1.0 ).sub( clamp( material.sheenColor.mul( material.sheen ).mul( sheenE ), vec3( 0.0 ), vec3( 1.0 ) ) ) );
|
|
227
|
+
|
|
228
|
+
} );
|
|
229
|
+
|
|
230
|
+
if ( layers.clearcoat ) If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
231
|
+
|
|
232
|
+
const ccDfg = DFGResult.wrap( evaluateSpecularDFG(
|
|
233
|
+
vec3( 0.04 ), float( 1.0 ), float( 1.5 ), vec3( 0.0 ), float( 0.0 ), vec3( 0.0 ), float( 0.0 ), vec3( 0.04 ),
|
|
234
|
+
NoV, max( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS ),
|
|
235
|
+
) );
|
|
236
|
+
result.mulAssign( vec3( 1.0 ).sub( ccDfg.E_total.mul( material.clearcoat ) ) );
|
|
237
|
+
|
|
238
|
+
} );
|
|
239
|
+
|
|
240
|
+
} );
|
|
241
|
+
|
|
242
|
+
return result;
|
|
243
|
+
|
|
244
|
+
} );
|
|
245
|
+
|
|
179
246
|
// Wrapper that computes dot products internally. Use this when you don't already
|
|
180
247
|
// have dots; otherwise prefer evaluateMaterialResponseFromDots to share the work.
|
|
181
|
-
|
|
248
|
+
// L below N reaches only the diffuse transmission lobe.
|
|
249
|
+
export const evaluateMaterialResponse = Fn( ( [ V, L, N, material ], builder ) => {
|
|
250
|
+
|
|
251
|
+
const result = vec3( 0.0 ).toVar();
|
|
252
|
+
const reflected = () => {
|
|
182
253
|
|
|
183
|
-
|
|
184
|
-
|
|
254
|
+
const dots = DotProducts.wrap( computeDotProductsAniso( N, V, L, material ) );
|
|
255
|
+
result.assign( evaluateMaterialResponseFromDots( material, dots ) );
|
|
256
|
+
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if ( ! materialLayers( builder ).diffuseTransmission ) reflected();
|
|
260
|
+
else If( dot( N, L ).lessThan( 0.0 ).and( material.diffuseTransmission.greaterThan( 0.0 ) ), () => {
|
|
261
|
+
|
|
262
|
+
result.assign( evaluateDiffuseTransmission( material, max( dot( N, V ), 0.001 ) ) );
|
|
263
|
+
|
|
264
|
+
} ).Else( reflected );
|
|
265
|
+
|
|
266
|
+
return result;
|
|
185
267
|
|
|
186
268
|
} );
|
|
187
269
|
|