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
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sceneResources } from './SceneResources.js';
|
|
1
2
|
import {
|
|
2
3
|
Fn,
|
|
3
4
|
wgslFn,
|
|
@@ -24,7 +25,10 @@ import {
|
|
|
24
25
|
bool as tslBool,
|
|
25
26
|
} from 'three/tsl';
|
|
26
27
|
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX,
|
|
30
|
+
TLAS_LEAF_IDENTITY,
|
|
31
|
+
} from '../Processor/BufferLayout.js';
|
|
28
32
|
import { HitInfo } from './Struct.js';
|
|
29
33
|
import {
|
|
30
34
|
getDatafromStorageBuffer, instanceRows, instanceNormalToWorld, unpackTriangleNormal, triangleRow, shadowFlagsSettle
|
|
@@ -88,8 +92,10 @@ const toObjectDir = ( rows, d ) => vec3(
|
|
|
88
92
|
// at shared triangle edges that Möller-Trumbore exhibits under FP32. Per-ray shears
|
|
89
93
|
// are precomputed once via computeWoopFromInvDir; per-triangle test is FMA-friendly
|
|
90
94
|
// and uses sign-aware depth comparison so it works for any det orientation.
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
// guardT: reject t within its own rounding error. Closest-hit rays need it to leave a large triangle; shadow rays
|
|
96
|
+
// start lifted off their surface and skip it — its registers cost Shade's shadow traversal ~4 %.
|
|
97
|
+
const rayTriangle = ( name, guardT ) => wgslFn( `
|
|
98
|
+
fn ${ name }( rayOrigin: vec3f, rayDir: vec3f, pA: vec3f, pB: vec3f, pC: vec3f, closestHitDst: f32, woopParams: vec4f ) -> vec4f {
|
|
93
99
|
|
|
94
100
|
// Returns vec4(t, u, v, hit) where hit > 0.5 means intersection.
|
|
95
101
|
// woopParams: (Sx, Sy, Sz, bitcast<f32>(packed kx|ky<<2|kz<<4))
|
|
@@ -138,7 +144,19 @@ const RayTriangleGeometry = wgslFn( `
|
|
|
138
144
|
let tSigned = T * detSign;
|
|
139
145
|
let detAbs = abs( det );
|
|
140
146
|
|
|
141
|
-
|
|
147
|
+
${ guardT ? `
|
|
148
|
+
// t must clear its own rounding error (pbrt-v4 Triangle::Intersect, γn = n·2⁻²⁴ / (1 − n·2⁻²⁴)), or a
|
|
149
|
+
// ray leaving a large triangle can hit it again: its error grows with the distance to the vertices.
|
|
150
|
+
let maxZ = max( max( abs( Sz * Akz ), abs( Sz * Bkz ) ), abs( Sz * Ckz ) );
|
|
151
|
+
let maxX = max( max( abs( Ax ), abs( Bx ) ), abs( Cx ) );
|
|
152
|
+
let maxY = max( max( abs( Ay ), abs( By ) ), abs( Cy ) );
|
|
153
|
+
let maxE = max( max( abs( U ), abs( V ) ), abs( W ) );
|
|
154
|
+
let deltaX = 2.98023e-7f * ( maxX + maxZ );
|
|
155
|
+
let deltaY = 2.98023e-7f * ( maxY + maxZ );
|
|
156
|
+
let deltaE = 2.0f * ( 1.19209e-7f * maxX * maxY + deltaY * maxX + deltaX * maxY );
|
|
157
|
+
let errT = 3.0f * maxZ * ( 3.57628e-7f * maxE + deltaE );
|
|
158
|
+
` : '' }
|
|
159
|
+
if ( tSigned > ${ guardT ? 'errT' : '0.0f' } && tSigned < closestHitDst * detAbs ) {
|
|
142
160
|
|
|
143
161
|
// Match Möller-Trumbore convention: u = weight of B, v = weight of C.
|
|
144
162
|
// In Woop's edge functions, U → weight of A, V → weight of B, W → weight of C.
|
|
@@ -156,6 +174,9 @@ const RayTriangleGeometry = wgslFn( `
|
|
|
156
174
|
}
|
|
157
175
|
` );
|
|
158
176
|
|
|
177
|
+
const RayTriangleGeometry = rayTriangle( 'RayTriangleGeometry', true );
|
|
178
|
+
const RayTriangleGeometryShadow = rayTriangle( 'RayTriangleGeometryShadow', false );
|
|
179
|
+
|
|
159
180
|
// Compute Woop ray-space transform (Woop 2013, §3.1) — runs once per ray and
|
|
160
181
|
// amortizes across hundreds of triangle tests. Returns Sx/Sy/Sz shears plus the
|
|
161
182
|
// permuted axis indices packed via bitcast into the .w slot.
|
|
@@ -213,12 +234,12 @@ const fastRayAABBDst = wgslFn( `
|
|
|
213
234
|
// MAIN BVH TRAVERSAL
|
|
214
235
|
// ================================================================================
|
|
215
236
|
|
|
216
|
-
// Side from a triangle's flags word (row 4 .z): 0 front, 1 back, 2 double
|
|
237
|
+
// Side from a triangle's flags word (row 4 .z): 0 front, 1 back, 2 double.
|
|
217
238
|
export const triangleSide = ( flags ) => int( flags.shiftRight( uint( TRI_SIDE_SHIFT ) ).bitAnd( uint( 3 ) ) );
|
|
218
239
|
|
|
219
240
|
// Whether a face of this side is seen by a ray; rayDotNormal < 0 is a front hit.
|
|
220
241
|
export const sideAccepts = ( side, rayDotNormal ) => side.equal( int( 2 ) )
|
|
221
|
-
.or( side.equal( int( 0 ) ).
|
|
242
|
+
.or( side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
|
|
222
243
|
.or( side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) ) );
|
|
223
244
|
|
|
224
245
|
// Factory: the boxTests/triTests debug counters are compiled OUT of the hot closest-hit
|
|
@@ -324,7 +345,7 @@ const makeTraverseBVH = ( trackStats ) => Fn( ( [
|
|
|
324
345
|
);
|
|
325
346
|
|
|
326
347
|
// Side culling (inline; per-mesh visibility is at the BLAS-pointer level).
|
|
327
|
-
If( eitherSide.
|
|
348
|
+
If( eitherSide.or( sideAccepts( side, rayDotNormal ) ), () => {
|
|
328
349
|
|
|
329
350
|
closestHit.didHit.assign( true );
|
|
330
351
|
closestHit.dst.assign( t );
|
|
@@ -517,12 +538,15 @@ export const traverseBVHDebug = /*@__PURE__*/ makeTraverseBVH( true );
|
|
|
517
538
|
// SHADOW RAY TRAVERSAL (OPTIMIZED - early exit on any hit)
|
|
518
539
|
// ================================================================================
|
|
519
540
|
|
|
520
|
-
|
|
541
|
+
// cameraCulled: skip the faces a camera ray along −direction would cull, for a ray standing in for one.
|
|
542
|
+
const makeTraverseBVHShadow = ( cameraCulled ) => Fn( ( [
|
|
521
543
|
ray,
|
|
522
544
|
bvhBuffer,
|
|
523
545
|
triangleBuffer,
|
|
524
546
|
maxShadowDist,
|
|
525
|
-
] ) => {
|
|
547
|
+
], builder ) => {
|
|
548
|
+
|
|
549
|
+
const { alphaShadows } = sceneResources( builder );
|
|
526
550
|
|
|
527
551
|
const folded = bvhBuffer.value?.foldedLeaves === true;
|
|
528
552
|
const isLeafTag = leafTagTest( folded );
|
|
@@ -572,26 +596,14 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
572
596
|
const pB = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 1 ).xyz );
|
|
573
597
|
const pC = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 2 ).xyz );
|
|
574
598
|
|
|
575
|
-
const triResult =
|
|
599
|
+
const triResult = RayTriangleGeometryShadow( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
|
|
576
600
|
|
|
577
601
|
If( triResult.w.greaterThan( 0.5 ), () => {
|
|
578
602
|
|
|
579
603
|
// Per-mesh visibility is handled at the BLAS-pointer level.
|
|
580
604
|
const uvData2 = triangleRow( triangleBuffer, triIndex, 4 ).toVar();
|
|
581
605
|
|
|
582
|
-
|
|
583
|
-
const seen = tslBool( true ).toVar();
|
|
584
|
-
If( triangleSide( uvData2.z ).equal( int( MATERIAL_SIDE_CUTAWAY ) ), () => {
|
|
585
|
-
|
|
586
|
-
const w = float( 1.0 ).sub( triResult.y ).sub( triResult.z );
|
|
587
|
-
const n = unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 0 ).w ).mul( w )
|
|
588
|
-
.add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 1 ).w ).mul( triResult.y ) )
|
|
589
|
-
.add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 2 ).w ).mul( triResult.z ) );
|
|
590
|
-
seen.assign( rayDirection.dot( normalize( n ) ).lessThan( - 0.0001 ) );
|
|
591
|
-
|
|
592
|
-
} );
|
|
593
|
-
|
|
594
|
-
If( seen, () => {
|
|
606
|
+
const record = () => {
|
|
595
607
|
|
|
596
608
|
closestHit.didHit.assign( true );
|
|
597
609
|
closestHit.dst.assign( triResult.x );
|
|
@@ -610,14 +622,24 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
610
622
|
|
|
611
623
|
// An opaque blocker settles the ray. A surface light passes through has to be
|
|
612
624
|
// the nearest one, or the layers behind the first find are never counted.
|
|
613
|
-
If( shadowFlagsSettle( uvData2.z ), () => {
|
|
625
|
+
If( shadowFlagsSettle( uvData2.z, alphaShadows ), () => {
|
|
614
626
|
|
|
615
627
|
blocked.assign( true );
|
|
616
628
|
Break();
|
|
617
629
|
|
|
618
630
|
} );
|
|
619
631
|
|
|
620
|
-
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
if ( cameraCulled ) {
|
|
635
|
+
|
|
636
|
+
const w = float( 1.0 ).sub( triResult.y ).sub( triResult.z );
|
|
637
|
+
const n = normalize( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 0 ).w ).mul( w )
|
|
638
|
+
.add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 1 ).w ).mul( triResult.y ) )
|
|
639
|
+
.add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 2 ).w ).mul( triResult.z ) ) );
|
|
640
|
+
If( sideAccepts( triangleSide( uvData2.z ), rayDirection.dot( n ).negate() ), record );
|
|
641
|
+
|
|
642
|
+
} else record();
|
|
621
643
|
|
|
622
644
|
} );
|
|
623
645
|
|
|
@@ -742,3 +764,6 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
742
764
|
return closestHit;
|
|
743
765
|
|
|
744
766
|
} );
|
|
767
|
+
|
|
768
|
+
export const traverseBVHShadow = /*@__PURE__*/ makeTraverseBVHShadow( false );
|
|
769
|
+
export const traverseBVHShadowCameraCulled = /*@__PURE__*/ makeTraverseBVHShadow( true );
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bidirectional path tracing — shared by the light pass, ConnectKernel and LightSplatKernel.
|
|
3
|
+
* MIS is Georgiev 2012's dVCM/dVC recursion; every density comes from calculateMaterialPDF, both ways
|
|
4
|
+
* round, so every strategy evaluates the same weights. With vertex merging (integrator 'vcm') a state also
|
|
5
|
+
* carries `vm`, η² at its vertex (η = πr² · light paths, r that vertex's merge radius): Georgiev's dVM there is
|
|
6
|
+
* dVC / η², so it is never stored.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
float, vec2, vec4, int, uint, If, max, select, abs, dot, length,
|
|
11
|
+
} from 'three/tsl';
|
|
12
|
+
|
|
13
|
+
import { getMaterial, MIN_ROUGHNESS, REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
|
|
14
|
+
import { RayTracingMaterial, MaterialSamples, ExtMapResult } from './Struct.js';
|
|
15
|
+
import { sampleAllMaterialTextures, processAnisotropyMap, applyExtensionMaps, getTransformedUV } from './TextureSampling.js';
|
|
16
|
+
import { SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
17
|
+
|
|
18
|
+
// 1D dimensions of a camera vertex's bounce block (Random.js budget) picking its connection.
|
|
19
|
+
export const DIM_CONNECT_PATH = 16;
|
|
20
|
+
export const DIM_CONNECT_VERTEX = 17;
|
|
21
|
+
// Emission: 1D triangle pick, 2D point, 2D direction, 1D side, 1D sun or emitter, 2D sun direction, 2D disc point.
|
|
22
|
+
export const DIM_EMIT = SAMPLER_DIM_AUX_BASE + 2048;
|
|
23
|
+
|
|
24
|
+
// Light paths draw at pixels far below the image, so their sequences never repeat a camera path's.
|
|
25
|
+
export const LIGHT_PIXEL_ROW_OFFSET = 65536;
|
|
26
|
+
|
|
27
|
+
// Light tracing adds into u32 per channel: value · SPLAT_SCALE, rounded stochastically.
|
|
28
|
+
export const SPLAT_SCALE = 16384;
|
|
29
|
+
export const SPLAT_MAX = 4096;
|
|
30
|
+
|
|
31
|
+
// Pending-connection tags set the high bit: a light path's data in that lane is a positive float's bits.
|
|
32
|
+
export const PASS_TAG_BIT = 0x80000000;
|
|
33
|
+
|
|
34
|
+
const MIS_MAX = 1e30;
|
|
35
|
+
|
|
36
|
+
const finite = ( x ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, MIS_MAX );
|
|
37
|
+
|
|
38
|
+
export const lightPathPixel = ( index, resolution ) => {
|
|
39
|
+
|
|
40
|
+
const w = uint( resolution.x );
|
|
41
|
+
return vec2( float( uint( index ).mod( w ) ).add( 0.5 ), float( uint( index ).div( w ).add( uint( LIGHT_PIXEL_ROW_OFFSET ) ) ).add( 0.5 ) );
|
|
42
|
+
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// The power heuristic (β = 2). The recursion stores every density ratio already raised to it.
|
|
46
|
+
export const mis = ( x ) => x.mul( x );
|
|
47
|
+
|
|
48
|
+
// MIS recursion: `state` holds two .toVar()s, { dVCM, dVC }, updated in place, and `vm` when merging.
|
|
49
|
+
|
|
50
|
+
/** Arriving at a vertex `dist` from the last one, at |cos| `cosIn` to its facet. */
|
|
51
|
+
export const misOnHit = ( state, dist, cosIn ) => {
|
|
52
|
+
|
|
53
|
+
const c = max( cosIn, 1e-6 );
|
|
54
|
+
state.dVCM.assign( finite( state.dVCM.mul( mis( dist.mul( dist ).div( c ) ) ) ) );
|
|
55
|
+
state.dVC.assign( finite( state.dVC.div( mis( c ) ) ) );
|
|
56
|
+
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Leaving through a delta lobe, a refraction or a subsurface boundary: nothing can connect here. */
|
|
60
|
+
export const misOnSpecular = ( state, cosOut ) => {
|
|
61
|
+
|
|
62
|
+
state.dVC.assign( finite( state.dVC.mul( mis( cosOut ) ) ) );
|
|
63
|
+
state.dVCM.assign( 0.0 );
|
|
64
|
+
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Leaving by a BSDF sample; densities are solid angle, forward the sampler's and reverse its partner's. */
|
|
68
|
+
export const misOnScatter = ( state, cosOut, pdfForward, pdfReverse ) => {
|
|
69
|
+
|
|
70
|
+
const p = max( pdfForward, 1e-12 );
|
|
71
|
+
const partial = state.dVC.mul( mis( pdfReverse ) ).add( state.dVCM );
|
|
72
|
+
state.dVC.assign( finite( mis( cosOut.div( p ) ).mul( finite( state.vm ? partial.add( state.vm ) : partial ) ) ) );
|
|
73
|
+
state.dVCM.assign( finite( mis( float( 1.0 ).div( p ) ) ) );
|
|
74
|
+
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Georgiev (40)-(46): `lead` is the other side's density of reaching this end, `reverse` this end's density back.
|
|
78
|
+
export const misPartial = ( lead, state, reverse ) => {
|
|
79
|
+
|
|
80
|
+
const partial = state.dVCM.add( state.dVC.mul( mis( reverse ) ) );
|
|
81
|
+
return finite( finite( mis( lead ) ).mul( finite( state.vm ? partial.add( state.vm ) : partial ) ) );
|
|
82
|
+
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Vertex merging's radius at p: a few pixels' footprint seen from the camera (`mergeConst` + `mergeSlope` · distance),
|
|
86
|
+
// at least `mergeMin`. A function of position alone, so both subpaths weigh a merge at any vertex alike.
|
|
87
|
+
export const mergeRadiusAt = ( bdpt, p ) =>
|
|
88
|
+
max( bdpt.mergeConst.add( bdpt.mergeSlope.mul( length( p.sub( bdpt.cameraPosition ) ) ) ), bdpt.mergeMin );
|
|
89
|
+
|
|
90
|
+
// η = πr² · light paths: a merge's density over a connection's, there.
|
|
91
|
+
export const mergeEta = ( bdpt, radius ) => radius.mul( radius ).mul( Math.PI ).mul( float( bdpt.lightPaths ) );
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* η² at p as the weights take it, a state's `vm` (Georgiev's vmFactor, per vertex): η scaled by `mergeTrust`. Any
|
|
95
|
+
* density the strategies agree on still sums their weights to one; trust below 1 hands light other strategies can
|
|
96
|
+
* also reach back to them, unblurred, and leaves light only a merge reaches (a caustic in a mirror) all to merging.
|
|
97
|
+
*/
|
|
98
|
+
export const mergeVmAt = ( bdpt, p ) => {
|
|
99
|
+
|
|
100
|
+
const eta = mergeEta( bdpt, mergeRadiusAt( bdpt, p ) ).mul( bdpt.mergeTrust );
|
|
101
|
+
return eta.mul( eta );
|
|
102
|
+
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Georgiev (38)-(39), a merge at a camera vertex: each side's sum, `density` the camera vertex's BSDF density
|
|
106
|
+
// (of the light's arrival direction for the light side, back along its own for the camera side); `vc` = 1 / η².
|
|
107
|
+
export const misMergePartial = ( state, density, vc ) => finite( finite( state.dVCM.add( state.dVC.mul( mis( density ) ) ) ).mul( vc ) );
|
|
108
|
+
|
|
109
|
+
export const misWeight = ( wLight, wCamera ) => float( 1.0 ).div( float( 1.0 ).add( finite( wLight ) ).add( finite( wCamera ) ) );
|
|
110
|
+
|
|
111
|
+
// Verification: a view keeps one strategy, MIS-weighted, or alone at full weight (+ STRATEGY_ALONE).
|
|
112
|
+
export const STRATEGY = { ALL: 0, HIT: 1, NEE: 2, CONNECT: 3, LIGHT_TRACE: 4, MERGE: 5 };
|
|
113
|
+
export const STRATEGY_ALONE = 8;
|
|
114
|
+
|
|
115
|
+
export const strategyWeight = ( view, strategy, weight, alone = float( 1.0 ) ) =>
|
|
116
|
+
select( view.equal( uint( STRATEGY.ALL ) ).or( view.equal( uint( strategy ) ) ), weight,
|
|
117
|
+
select( view.equal( uint( strategy + STRATEGY_ALONE ) ), alone, float( 0.0 ) ) );
|
|
118
|
+
|
|
119
|
+
// Chance a light path leaves this side of a triangle toward `cosToward` (cosine to the winding normal).
|
|
120
|
+
export const emitterSideProbability = ( side, cosToward ) => select( side.equal( int( 2 ) ), float( 0.5 ),
|
|
121
|
+
select( side.equal( int( 1 ) ), select( cosToward.lessThan( 0.0 ), float( 1.0 ), float( 0.0 ) ),
|
|
122
|
+
select( cosToward.greaterThan( 0.0 ), float( 1.0 ), float( 0.0 ) ) ) );
|
|
123
|
+
|
|
124
|
+
/** Area density of a light path's start on a triangle with this emission — power over total, per area. */
|
|
125
|
+
export const emitterAreaPdf = ( emission, totalPower, share = 1 ) =>
|
|
126
|
+
max( dot( emission, REC709_LUMINANCE_COEFFICIENTS ), 0.0 ).div( max( totalPower, 1e-10 ) ).mul( share );
|
|
127
|
+
|
|
128
|
+
// The sun as a light subpath start: a direction over its disc, then a point on the scene's bounding disc at `discPdf`
|
|
129
|
+
// (LightGuide.js guidedDiscPdf).
|
|
130
|
+
export const sunEmissionPdf = ( bdpt, sunParams, discPdf ) => bdpt.sunPick.mul( discPdf ).div( max( sunParams.y, 1e-30 ) );
|
|
131
|
+
|
|
132
|
+
// Where a light subpath starts, in its pick table's order (`sourceCdf`, a running sum); each lamp list follows
|
|
133
|
+
// at `sourceOffsets[ type ]`, by LIGHT_TYPE. A light path's origin code is its triangle, or −1 − its source.
|
|
134
|
+
export const SOURCE = { SUN: 0, EMITTERS: 1, ENVIRONMENT: 2, LAMPS: 3 };
|
|
135
|
+
|
|
136
|
+
export const sourcePick = ( bdpt, source ) => {
|
|
137
|
+
|
|
138
|
+
const s = int( source );
|
|
139
|
+
return bdpt.sourceCdf.element( s ).sub( select( s.greaterThan( int( 0 ) ), bdpt.sourceCdf.element( max( s.sub( int( 1 ) ), int( 0 ) ) ), float( 0.0 ) ) );
|
|
140
|
+
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const lampSource = ( bdpt, type, index ) => select( type.equal( int( 0 ) ), int( bdpt.sourceOffsets[ 0 ] ),
|
|
144
|
+
select( type.equal( int( 1 ) ), int( bdpt.sourceOffsets[ 1 ] ), select( type.equal( int( 2 ) ), int( bdpt.sourceOffsets[ 2 ] ), int( bdpt.sourceOffsets[ 3 ] ) ) ) ).add( index );
|
|
145
|
+
|
|
146
|
+
export const sourceLampType = ( bdpt, source ) => select( source.lessThan( int( bdpt.sourceOffsets[ 1 ] ) ), int( 0 ),
|
|
147
|
+
select( source.lessThan( int( bdpt.sourceOffsets[ 2 ] ) ), int( 1 ), select( source.lessThan( int( bdpt.sourceOffsets[ 3 ] ) ), int( 2 ), int( 3 ) ) ) );
|
|
148
|
+
|
|
149
|
+
export const sourceLampIndex = ( bdpt, source, type ) => source.sub( select( type.equal( int( 0 ) ), int( bdpt.sourceOffsets[ 0 ] ),
|
|
150
|
+
select( type.equal( int( 1 ) ), int( bdpt.sourceOffsets[ 1 ] ), select( type.equal( int( 2 ) ), int( bdpt.sourceOffsets[ 2 ] ), int( bdpt.sourceOffsets[ 3 ] ) ) ) ) );
|
|
151
|
+
|
|
152
|
+
// The material at a stored vertex, folded as ShadeKernel folds it; its shading normal is already stored.
|
|
153
|
+
export function resolveSurfaceMaterial( materialIndex, uv, N, materialBuffer ) {
|
|
154
|
+
|
|
155
|
+
const material = RayTracingMaterial.wrap( getMaterial( int( materialIndex ), materialBuffer ) ).toVar();
|
|
156
|
+
material.normalMapIndex.assign( int( - 1 ) );
|
|
157
|
+
material.bumpMapIndex.assign( int( - 1 ) );
|
|
158
|
+
material.displacementMapIndex.assign( int( - 1 ) );
|
|
159
|
+
|
|
160
|
+
const samples = MaterialSamples.wrap( sampleAllMaterialTextures( material, uv, N, vec4( 0.0 ) ) ).toVar();
|
|
161
|
+
const rawRough = samples.roughness.toVar();
|
|
162
|
+
|
|
163
|
+
material.color.assign( samples.albedo );
|
|
164
|
+
material.metalness.assign( samples.metalness.clamp( 0.0, 1.0 ) );
|
|
165
|
+
material.roughness.assign( samples.roughness.clamp( MIN_ROUGHNESS, 1.0 ) );
|
|
166
|
+
material.sheenRoughness.assign( material.sheenRoughness.clamp( 0.05, 1.0 ) );
|
|
167
|
+
|
|
168
|
+
const extUV = getTransformedUV( { uv, transform: material.albedoTransform } ).toVar();
|
|
169
|
+
|
|
170
|
+
If( material.anisotropyMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
171
|
+
|
|
172
|
+
const aniso = processAnisotropyMap( material, extUV ).toVar();
|
|
173
|
+
material.anisotropy.assign( aniso.x );
|
|
174
|
+
material.anisotropyRotation.assign( aniso.y );
|
|
175
|
+
|
|
176
|
+
} );
|
|
177
|
+
|
|
178
|
+
const ext = ExtMapResult.wrap( applyExtensionMaps( material, extUV ) ).toVar();
|
|
179
|
+
material.transmission.assign( ext.transmission );
|
|
180
|
+
material.clearcoat.assign( ext.clearcoat );
|
|
181
|
+
material.clearcoatRoughness.assign( ext.clearcoatRoughness );
|
|
182
|
+
material.sheenColor.assign( ext.sheenColor );
|
|
183
|
+
material.sheenRoughness.assign( ext.sheenRoughness );
|
|
184
|
+
material.iridescence.assign( ext.iridescence );
|
|
185
|
+
material.iridescenceThicknessRange.assign( vec2( material.iridescenceThicknessRange.x, ext.iridescenceThickness ) );
|
|
186
|
+
material.specularIntensity.assign( ext.specularIntensity );
|
|
187
|
+
material.specularColor.assign( ext.specularColor );
|
|
188
|
+
material.diffuseTransmission.assign( ext.diffuseTransmission );
|
|
189
|
+
material.diffuseTransmissionColor.assign( ext.diffuseTransmissionColor );
|
|
190
|
+
|
|
191
|
+
If( rawRough.lessThan( MIN_ROUGHNESS ).and( material.anisotropy.equal( 0.0 ) ).and( material.clearcoat.equal( 0.0 ) )
|
|
192
|
+
.and( material.transmission.equal( 0.0 ) ).and( material.subsurface.equal( 0.0 ) ), () => {
|
|
193
|
+
|
|
194
|
+
material.roughness.assign( 0.0 );
|
|
195
|
+
|
|
196
|
+
} );
|
|
197
|
+
|
|
198
|
+
return material;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The normal of the frame with `toward` on top: a surface that passes light through diffusely is seen from either side.
|
|
203
|
+
export const facingSide = ( N, toward, material ) => select(
|
|
204
|
+
material.diffuseTransmission.greaterThan( 0.0 ).and( dot( N, toward ).lessThan( 0.0 ) ), N.negate(), N );
|
|
205
|
+
|
|
206
|
+
// The light end's cosine with the shading-normal correction (Veach 5.3.2): |V·N| |d·Ng| / |V·Ng|.
|
|
207
|
+
export const lightEndCosine = ( V, N, facetN, toOther ) =>
|
|
208
|
+
abs( dot( V, N ) ).mul( abs( dot( toOther, facetN ) ) ).div( max( abs( dot( V, facetN ) ), 1e-4 ) );
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The analytic lights as bidirectional sources: the camera side's lamp choice, made as the path tracer
|
|
3
|
+
* makes it, and the densities a light path needs to weigh the strategies against it.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { float, int, If, Loop, select, cos, max, min } from 'three/tsl';
|
|
7
|
+
|
|
8
|
+
import { struct } from './patches.js';
|
|
9
|
+
import {
|
|
10
|
+
DirectionalLight, AreaLight, PointLight, SpotLight,
|
|
11
|
+
getDirectionalLight, getAreaLight, getPointLight, getSpotLight,
|
|
12
|
+
LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_AREA, LIGHT_TYPE_POINT, sphQuadSolidAngle,
|
|
13
|
+
} from './LightsCore.js';
|
|
14
|
+
import {
|
|
15
|
+
calculateDirectionalLightImportance, estimateLightImportance, calculatePointLightImportance, calculateSpotLightImportance,
|
|
16
|
+
} from './LightsDirect.js';
|
|
17
|
+
import { SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
18
|
+
import { PI } from './Common.js';
|
|
19
|
+
|
|
20
|
+
export const LampPick = struct( { kind: 'int', index: 'int', pdf: 'float', total: 'float' } );
|
|
21
|
+
|
|
22
|
+
/** @param lamps { directional, numDirectional, area, numArea, point, numPoint, spot, numSpot } */
|
|
23
|
+
export const lampCount = ( lamps ) => lamps.numDirectional.add( lamps.numArea ).add( lamps.numPoint ).add( lamps.numSpot );
|
|
24
|
+
|
|
25
|
+
export const lampTypeAt = ( lamps, flat ) => select( flat.lessThan( lamps.numDirectional ), int( LIGHT_TYPE_DIRECTIONAL ),
|
|
26
|
+
select( flat.lessThan( lamps.numDirectional.add( lamps.numArea ) ), int( LIGHT_TYPE_AREA ),
|
|
27
|
+
select( flat.lessThan( lamps.numDirectional.add( lamps.numArea ).add( lamps.numPoint ) ), int( LIGHT_TYPE_POINT ), int( 3 ) ) ) );
|
|
28
|
+
|
|
29
|
+
const typeStart = ( lamps, type ) => select( type.equal( int( LIGHT_TYPE_DIRECTIONAL ) ), int( 0 ),
|
|
30
|
+
select( type.equal( int( LIGHT_TYPE_AREA ) ), lamps.numDirectional,
|
|
31
|
+
select( type.equal( int( LIGHT_TYPE_POINT ) ), lamps.numDirectional.add( lamps.numArea ),
|
|
32
|
+
lamps.numDirectional.add( lamps.numArea ).add( lamps.numPoint ) ) ) );
|
|
33
|
+
|
|
34
|
+
// The path tracer's importance (LightsDirect.js) without its bounce-depth factor, which a light path cannot know.
|
|
35
|
+
const walkLamps = ( lamps, hitPoint, N, material, visit ) => {
|
|
36
|
+
|
|
37
|
+
const each = ( type, count, read ) => If( count.greaterThan( int( 0 ) ), () => {
|
|
38
|
+
|
|
39
|
+
Loop( { start: int( 0 ), end: count, type: 'int', condition: '<' }, ( { i } ) => {
|
|
40
|
+
|
|
41
|
+
visit( int( type ), i, read( i ).toVar() );
|
|
42
|
+
|
|
43
|
+
} );
|
|
44
|
+
|
|
45
|
+
} );
|
|
46
|
+
each( LIGHT_TYPE_DIRECTIONAL, lamps.numDirectional, ( i ) =>
|
|
47
|
+
calculateDirectionalLightImportance( DirectionalLight.wrap( getDirectionalLight( lamps.directional, i ) ), N, material, int( 0 ) ) );
|
|
48
|
+
each( LIGHT_TYPE_AREA, lamps.numArea, ( i ) => {
|
|
49
|
+
|
|
50
|
+
const light = AreaLight.wrap( getAreaLight( lamps.area, i ) );
|
|
51
|
+
return select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, hitPoint, N, material ), float( 0.0 ) );
|
|
52
|
+
|
|
53
|
+
} );
|
|
54
|
+
each( LIGHT_TYPE_POINT, lamps.numPoint, ( i ) =>
|
|
55
|
+
calculatePointLightImportance( PointLight.wrap( getPointLight( lamps.point, i ) ), hitPoint, N, material ) );
|
|
56
|
+
each( 3, lamps.numSpot, ( i ) =>
|
|
57
|
+
calculateSpotLightImportance( SpotLight.wrap( getSpotLight( lamps.spot, i ) ), hitPoint, N, material ) );
|
|
58
|
+
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* One lamp, as the path tracer's reservoir picks it, on the same dimensions; uniform when nothing has importance.
|
|
63
|
+
* `sample1D( dim )` draws a 1D variate, `u` is the fallback's.
|
|
64
|
+
*/
|
|
65
|
+
export function pickLamp( lamps, hitPoint, N, material, u, sample1D ) {
|
|
66
|
+
|
|
67
|
+
const type = int( - 1 ).toVar();
|
|
68
|
+
const index = int( - 1 ).toVar();
|
|
69
|
+
const chosen = float( 0.0 ).toVar();
|
|
70
|
+
const total = float( 0.0 ).toVar();
|
|
71
|
+
walkLamps( lamps, hitPoint, N, material, ( t, i, w ) => {
|
|
72
|
+
|
|
73
|
+
total.addAssign( w );
|
|
74
|
+
If( w.greaterThan( 0.0 ).and( sample1D( int( SAMPLER_DIM_AUX_BASE + 64 ).add( typeStart( lamps, t ) ).add( i ) ).mul( total ).lessThan( w ) ), () => {
|
|
75
|
+
|
|
76
|
+
type.assign( t );
|
|
77
|
+
index.assign( i );
|
|
78
|
+
chosen.assign( w );
|
|
79
|
+
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
const count = lampCount( lamps ).toVar();
|
|
85
|
+
const pdf = chosen.div( max( total, 1e-10 ) ).toVar();
|
|
86
|
+
If( total.lessThanEqual( 0.0 ), () => {
|
|
87
|
+
|
|
88
|
+
const flat = min( int( u.mul( float( count ) ) ), count.sub( int( 1 ) ) ).toVar();
|
|
89
|
+
type.assign( lampTypeAt( lamps, flat ) );
|
|
90
|
+
index.assign( flat.sub( typeStart( lamps, type ) ) );
|
|
91
|
+
pdf.assign( float( 1.0 ).div( max( float( count ), 1.0 ) ) );
|
|
92
|
+
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
return LampPick( { kind: type, index, pdf, total } );
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The chance `pickLamp` at this point takes lamp (type, index). */
|
|
100
|
+
export function lampPickPdf( lamps, type, index, hitPoint, N, material ) {
|
|
101
|
+
|
|
102
|
+
const total = float( 0.0 ).toVar();
|
|
103
|
+
const own = float( 0.0 ).toVar();
|
|
104
|
+
walkLamps( lamps, hitPoint, N, material, ( t, i, w ) => {
|
|
105
|
+
|
|
106
|
+
total.addAssign( w );
|
|
107
|
+
If( t.equal( type ).and( i.equal( index ) ), () => {
|
|
108
|
+
|
|
109
|
+
own.assign( w );
|
|
110
|
+
|
|
111
|
+
} );
|
|
112
|
+
|
|
113
|
+
} );
|
|
114
|
+
return select( total.greaterThan( 0.0 ), own.div( total ), float( 1.0 ).div( max( float( lampCount( lamps ) ), 1.0 ) ) );
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Solid angle of a spot's cone, over which its light paths leave uniformly.
|
|
119
|
+
export const spotConeSolidAngle = ( light ) => float( 2 * PI ).mul( max( float( 1.0 ).sub( cos( light.angle ) ), 1e-10 ) );
|
|
120
|
+
|
|
121
|
+
// A soft directional light's cone (angle = angular diameter), as sampleDirectionalLight computes it.
|
|
122
|
+
export const directionalConeSolidAngle = ( light ) => float( 2 * PI ).mul( max( float( 1.0 ).sub( cos( light.angle.mul( 0.5 ) ) ), 1e-10 ) );
|
|
123
|
+
|
|
124
|
+
/** Area-light NEE's solid-angle density for any point of the light, seen from `from` (sampleRectAreaLight). */
|
|
125
|
+
export const areaLightDirectPdfW = ( light, from, dist, cosLight ) => {
|
|
126
|
+
|
|
127
|
+
const corner = light.position.sub( light.u ).sub( light.v );
|
|
128
|
+
const solidAngle = sphQuadSolidAngle( from, corner, light.u.mul( 2.0 ), light.v.mul( 2.0 ) ).toVar();
|
|
129
|
+
return select( light.shape.lessThan( 0.5 ).and( solidAngle.greaterThan( 1e-5 ) ), float( 1.0 ).div( solidAngle ),
|
|
130
|
+
dist.mul( dist ).div( max( light.area.mul( max( cosLight, 0.001 ) ), 1e-10 ) ) );
|
|
131
|
+
|
|
132
|
+
};
|
package/src/TSL/Clearcoat.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
reflect,
|
|
8
8
|
max,
|
|
9
9
|
If,
|
|
10
|
+
bool as tslBool,
|
|
10
11
|
} from 'three/tsl';
|
|
11
12
|
|
|
12
13
|
import { struct } from './patches.js';
|
|
@@ -15,15 +16,16 @@ import { AnisoFrame, DotProducts,
|
|
|
15
16
|
BRDFWeights,
|
|
16
17
|
} from './Struct.js';
|
|
17
18
|
import { MIN_CLEARCOAT_ROUGHNESS, computeDotProductsAniso, anisoTangentFrame, constructTBN } from './Common.js';
|
|
18
|
-
import { computeAnisoAlphas, calculateBRDFWeightsFromMaterial, calculateBSDFSamplingPDF } from './MaterialProperties.js';
|
|
19
|
+
import { computeAnisoAlphas, calculateBRDFWeightsFromMaterial, calculateBSDFSamplingPDF, diffuseTransmissionPdf } from './MaterialProperties.js';
|
|
19
20
|
import { ImportanceSampleCosine, sampleGGXVNDF, sampleGGXVNDFAniso } from './MaterialSampling.js';
|
|
20
|
-
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
21
|
+
import { evaluateMaterialResponseFromDots, evaluateDiffuseTransmission } from './MaterialEvaluation.js';
|
|
21
22
|
import { getRandomSample1D } from './Random.js';
|
|
22
23
|
|
|
23
24
|
export const ClearcoatResult = struct( {
|
|
24
25
|
brdf: 'vec3',
|
|
25
26
|
L: 'vec3',
|
|
26
27
|
pdf: 'float',
|
|
28
|
+
isDiffuseTransmission: 'bool',
|
|
27
29
|
} );
|
|
28
30
|
|
|
29
31
|
// Improved clearcoat sampling function
|
|
@@ -55,6 +57,7 @@ export const sampleClearcoat = Fn( ( [
|
|
|
55
57
|
|
|
56
58
|
const L = vec3( 0.0 ).toVar();
|
|
57
59
|
const H = vec3( 0.0 ).toVar();
|
|
60
|
+
const transmitted = tslBool( false ).toVar();
|
|
58
61
|
|
|
59
62
|
If( rand.lessThan( clearcoatWeight ), () => {
|
|
60
63
|
|
|
@@ -83,6 +86,11 @@ export const sampleClearcoat = Fn( ( [
|
|
|
83
86
|
} );
|
|
84
87
|
L.assign( reflect( V.negate(), H ) );
|
|
85
88
|
|
|
89
|
+
} ).ElseIf( rand.lessThan( clearcoatWeight.add( specularWeight ).add( weights.diffuseTransmission ) ), () => {
|
|
90
|
+
|
|
91
|
+
L.assign( ImportanceSampleCosine( { N: N.negate(), xi: randomSample } ) );
|
|
92
|
+
transmitted.assign( true );
|
|
93
|
+
|
|
86
94
|
} ).Else( () => {
|
|
87
95
|
|
|
88
96
|
// Sample diffuse
|
|
@@ -94,16 +102,17 @@ export const sampleClearcoat = Fn( ( [
|
|
|
94
102
|
// Calculate dot products (aniso-aware: also projects onto the anisotropy frame)
|
|
95
103
|
const dots = DotProducts.wrap( computeDotProductsAniso( N, V, L, material ) );
|
|
96
104
|
|
|
97
|
-
// One density for every MIS site — see calculateBSDFSamplingPDF.
|
|
98
|
-
|
|
105
|
+
// One density for every MIS site — see calculateBSDFSamplingPDF. Below the surface only the
|
|
106
|
+
// diffuse transmission lobe draws.
|
|
107
|
+
const pdf = max( transmitted.select( diffuseTransmissionPdf( weights.diffuseTransmission, dot( N, L ).negate() ), calculateBSDFSamplingPDF( material, weights, dots ) ), 0.001 );
|
|
99
108
|
|
|
100
109
|
// Evaluate complete BRDF
|
|
101
110
|
// The same BRDF every other site evaluates — see the clearcoat note in MaterialEvaluation.
|
|
102
|
-
const brdf = evaluateMaterialResponseFromDots( material, dots );
|
|
111
|
+
const brdf = transmitted.select( evaluateDiffuseTransmission( material, max( dot( N, V ), 0.001 ) ), evaluateMaterialResponseFromDots( material, dots ) );
|
|
103
112
|
|
|
104
113
|
// Return brdf, L direction, and pdf packed together
|
|
105
114
|
// Caller needs L and pdf - return as struct-like output
|
|
106
115
|
// We pack: result.xyz = brdf, result.w = pdf, L stored in separate output
|
|
107
|
-
return ClearcoatResult( { brdf, L, pdf } );
|
|
116
|
+
return ClearcoatResult( { brdf, L, pdf, isDiffuseTransmission: transmitted } );
|
|
108
117
|
|
|
109
118
|
} );
|
package/src/TSL/Common.js
CHANGED
|
@@ -39,7 +39,7 @@ export const FP16_MAX = 65504.0;
|
|
|
39
39
|
export const sanitize1 = ( x, ceiling = 1e7 ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, ceiling );
|
|
40
40
|
export const sanitizeRGB = ( c, ceiling = 1e7 ) =>
|
|
41
41
|
vec3( sanitize1( c.x, ceiling ), sanitize1( c.y, ceiling ), sanitize1( c.z, ceiling ) );
|
|
42
|
-
import {
|
|
42
|
+
import { TRI_BLOCKER_SHIFT, TRI_BLOCKER_ALPHA_SHIFT, MATERIAL_DATA_LAYOUT } from '../Processor/BufferLayout.js';
|
|
43
43
|
|
|
44
44
|
export const MATERIAL_SLOTS = MATERIAL_DATA_LAYOUT.SLOTS_PER_MATERIAL;
|
|
45
45
|
export const MATERIAL_SLOT = MATERIAL_DATA_LAYOUT.SLOT;
|
|
@@ -432,6 +432,13 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
|
|
|
432
432
|
const data30 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_A ), int( MATERIAL_SLOTS ) ).toVar();
|
|
433
433
|
const data31 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_B ), int( MATERIAL_SLOTS ) ).toVar();
|
|
434
434
|
const data32 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_C ), int( MATERIAL_SLOTS ) ).toVar();
|
|
435
|
+
// Only a material with diffuse transmission has maps for it.
|
|
436
|
+
const data33 = vec4( - 1.0, - 1.0, 0.0, 0.0 ).toVar();
|
|
437
|
+
If( data5.a.greaterThan( 0.0 ), () => {
|
|
438
|
+
|
|
439
|
+
data33.assign( getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_D ), int( MATERIAL_SLOTS ) ) );
|
|
440
|
+
|
|
441
|
+
} );
|
|
435
442
|
|
|
436
443
|
return RayTracingMaterial( {
|
|
437
444
|
color: vec4( data0.rgb, 1.0 ),
|
|
@@ -470,6 +477,10 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
|
|
|
470
477
|
iridescenceThicknessMapIndex: int( data31.b ),
|
|
471
478
|
specularIntensityMapIndex: int( data31.a ),
|
|
472
479
|
specularColorMapIndex: int( data32.r ),
|
|
480
|
+
diffuseTransmission: data5.a,
|
|
481
|
+
diffuseTransmissionColor: data32.gba,
|
|
482
|
+
diffuseTransmissionMapIndex: int( data33.r ),
|
|
483
|
+
diffuseTransmissionColorMapIndex: int( data33.g ),
|
|
473
484
|
albedoMapIndex: int( data8.r ),
|
|
474
485
|
normalMapIndex: int( data8.g ),
|
|
475
486
|
roughnessMapIndex: int( data8.b ),
|
|
@@ -569,6 +580,10 @@ export const diffuseGroundMaterial = Fn( () => {
|
|
|
569
580
|
iridescenceThicknessMapIndex: int( - 1 ),
|
|
570
581
|
specularIntensityMapIndex: int( - 1 ),
|
|
571
582
|
specularColorMapIndex: int( - 1 ),
|
|
583
|
+
diffuseTransmission: float( 0.0 ),
|
|
584
|
+
diffuseTransmissionColor: vec3( 1.0 ),
|
|
585
|
+
diffuseTransmissionMapIndex: int( - 1 ),
|
|
586
|
+
diffuseTransmissionColorMapIndex: int( - 1 ),
|
|
572
587
|
} );
|
|
573
588
|
|
|
574
589
|
} );
|
|
@@ -612,22 +627,12 @@ export const getShadowMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
|
|
|
612
627
|
* rides along in the rest — no second binding, which matters on a backend that
|
|
613
628
|
* allows ten storage buffers per stage.
|
|
614
629
|
*/
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
export function setAlphaShadowsUniform( node ) {
|
|
619
|
-
|
|
620
|
-
_enableAlphaShadows = node;
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
export const getAlphaShadowsUniform = () => _enableAlphaShadows;
|
|
625
|
-
|
|
626
|
-
// Does a hit on a triangle with these flags settle the shadow ray without a material fetch?
|
|
627
|
-
export const shadowFlagsSettle = ( flags ) => {
|
|
630
|
+
// Does a hit on a triangle with these flags settle the shadow ray without a material fetch? `alphaShadows` is the
|
|
631
|
+
// kernel's switch (SceneResources); null means alpha is never tested.
|
|
632
|
+
export const shadowFlagsSettle = ( flags, alphaShadows ) => {
|
|
628
633
|
|
|
629
634
|
const bit = ( shift ) => flags.shiftRight( uint( shift ) ).bitAnd( uint( 1 ) ).equal( uint( 1 ) );
|
|
630
|
-
const alphaOff =
|
|
635
|
+
const alphaOff = alphaShadows ? alphaShadows.equal( int( 0 ) ) : tslBool( true );
|
|
631
636
|
return bit( TRI_BLOCKER_SHIFT ).or( bit( TRI_BLOCKER_ALPHA_SHIFT ).and( alphaOff ) );
|
|
632
637
|
|
|
633
638
|
};
|