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.
Files changed (173) hide show
  1. package/README.md +252 -44
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-BRQvLggb.js +4300 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -0
  6. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  7. package/dist/ColorManagement-oeEk7oSu.js +1488 -0
  8. package/dist/EngineEvents-Ci8dVWCP.js +73 -0
  9. package/dist/EngineIssues-CIZnXHsF.js +72 -0
  10. package/dist/FileFormats-sPGJeCRc.js +102 -0
  11. package/dist/Logger-dDDGj9Av.js +97 -0
  12. package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BCXylThH.js} +8 -6
  13. package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
  14. package/dist/PathTracer-CQPW06Mm.js +29971 -0
  15. package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
  16. package/dist/RayzeeRenderer-COpwotpU.js +3551 -0
  17. package/dist/SolarLimb-CPaeZVwz.js +721 -0
  18. package/dist/ToneMapGPU-CjU3osDd.js +177 -0
  19. package/dist/ViewTransforms-CYN7bq2w.js +255 -0
  20. package/dist/WorkingMatrix-C6y233hT.js +27 -0
  21. package/dist/ZipReader-gDSWr0I5.js +98 -0
  22. package/dist/addons/archives.es.js +3 -0
  23. package/dist/addons/bidirectional.es.js +2 -0
  24. package/dist/addons/color.es.js +3 -0
  25. package/dist/addons/formats.es.js +2 -0
  26. package/dist/addons/physical-sky.es.js +2 -0
  27. package/dist/addons/storage.es.js +3 -0
  28. package/dist/archiveFormats-BZw7TiDp.js +75 -0
  29. package/dist/identity-DrQsGT2o.js +39 -0
  30. package/dist/openStorage-DwcBRUj7.js +820 -0
  31. package/dist/rayzee-core.es.js +12 -0
  32. package/dist/rayzee.es.js +2551 -44024
  33. package/dist/rayzee.umd.js +528 -487
  34. package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
  35. package/dist/shared-BGBcSxBQ.js +17 -0
  36. package/package.json +23 -2
  37. package/src/Color/ActiveColor.js +34 -0
  38. package/src/Color/BasicColor.js +147 -0
  39. package/src/Color/ColorManagement.js +7 -31
  40. package/src/Color/ViewTransforms.js +5 -1
  41. package/src/EngineDefaults.js +62 -1029
  42. package/src/EngineEvents.js +2 -0
  43. package/src/EngineIssues.js +2 -0
  44. package/src/Headless.js +6 -5
  45. package/src/Passes/AIUpscaler.js +58 -141
  46. package/src/Passes/OIDNDenoiser.js +92 -65
  47. package/src/Passes/OIDNTemporalHistory.js +1 -1
  48. package/src/PathTracerApp.js +620 -4026
  49. package/src/Pipeline/PipelineContext.js +0 -4
  50. package/src/Platform.js +36 -0
  51. package/src/Processor/ArchiveCache.js +1 -1
  52. package/src/Processor/ArchiveImporter.js +1259 -0
  53. package/src/Processor/AssetLoader.js +327 -1763
  54. package/src/Processor/AtmosphereModel.js +1 -2
  55. package/src/Processor/BVHBuilder.js +14 -126
  56. package/src/Processor/BVHLeafFold.js +4 -6
  57. package/src/Processor/BVHRefitter.js +67 -46
  58. package/src/Processor/BufferLayout.js +291 -0
  59. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  60. package/src/Processor/EnvironmentExactTable.js +187 -0
  61. package/src/Processor/EquirectHDRInfo.js +30 -223
  62. package/src/Processor/FileFormats.js +116 -0
  63. package/src/Processor/GLTFDecoders.js +85 -0
  64. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  65. package/src/Processor/GeometryExtractor.js +15 -5
  66. package/src/Processor/HostMemory.js +7 -1
  67. package/src/Processor/IESParser.js +47 -35
  68. package/src/Processor/InstanceTable.js +1 -1
  69. package/src/Processor/KernelManager.js +35 -2
  70. package/src/Processor/MaterialPacking.js +10 -8
  71. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  72. package/src/Processor/PBRT/PBRTParser.js +30 -17
  73. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  74. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  75. package/src/Processor/PBRT/PFM.js +59 -0
  76. package/src/Processor/PackedRayBuffer.js +116 -9
  77. package/src/Processor/ParallelBVHBuilder.js +3 -6
  78. package/src/Processor/PhysicalSky.js +5 -4
  79. package/src/Processor/PlatformImageLoader.js +50 -6
  80. package/src/Processor/QueueManager.js +39 -5
  81. package/src/Processor/ReinsertionOptimizer.js +2 -2
  82. package/src/Processor/SceneProcessor.js +53 -62
  83. package/src/Processor/ShaderBuilder.js +6 -16
  84. package/src/Processor/SimpleSky.js +1 -62
  85. package/src/Processor/SolarLimb.js +3 -0
  86. package/src/Processor/SplitBLAS.js +1 -1
  87. package/src/Processor/StorageTexturePool.js +22 -1
  88. package/src/Processor/TLASBuilder.js +1 -3
  89. package/src/Processor/TextureBuckets.js +237 -0
  90. package/src/Processor/TextureCreator.js +181 -8
  91. package/src/Processor/ToneMapGPU.js +64 -15
  92. package/src/Processor/Workers/BVHRefitWorker.js +5 -2
  93. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  94. package/src/Processor/Workers/BVHWorker.js +5 -18
  95. package/src/Processor/Workers/CDFWorker.js +6 -183
  96. package/src/Processor/Workers/TexturesWorker.js +103 -18
  97. package/src/Processor/archiveFormats.js +7 -0
  98. package/src/Processor/utils.js +2 -1
  99. package/src/RayzeeRenderer.js +3899 -0
  100. package/src/RenderSettings.js +68 -62
  101. package/src/Stages/ASVGF.js +13 -14
  102. package/src/Stages/AutoExposure.js +17 -17
  103. package/src/Stages/BilateralFilter.js +2 -1
  104. package/src/Stages/Compositor.js +8 -23
  105. package/src/Stages/DenoiserSettings.js +182 -0
  106. package/src/Stages/EdgeFilter.js +2 -1
  107. package/src/Stages/MotionVector.js +1 -1
  108. package/src/Stages/NRD.js +11 -5
  109. package/src/Stages/NormalDepth.js +12 -8
  110. package/src/Stages/PathTracer.js +338 -43
  111. package/src/Stages/PathTracerStage.js +69 -66
  112. package/src/Stages/Variance.js +1 -1
  113. package/src/Storage/BLASCache.js +1 -1
  114. package/src/Storage/CDFCache.js +18 -17
  115. package/src/Storage/DownloadCache.js +1 -1
  116. package/src/Storage/SceneGraphCodec.js +46 -3
  117. package/src/Storage/SpillStore.js +1 -1
  118. package/src/Storage/StorageManager.js +45 -9
  119. package/src/Storage/areas.js +4 -0
  120. package/src/TSL/BVHTraversal.js +50 -25
  121. package/src/TSL/Bidirectional.js +204 -0
  122. package/src/TSL/BidirectionalLamps.js +132 -0
  123. package/src/TSL/Clearcoat.js +15 -6
  124. package/src/TSL/Common.js +20 -15
  125. package/src/TSL/ConnectKernel.js +147 -0
  126. package/src/TSL/Debugger.js +1 -1
  127. package/src/TSL/Displacement.js +4 -3
  128. package/src/TSL/EmissiveSampling.js +65 -18
  129. package/src/TSL/Environment.js +77 -106
  130. package/src/TSL/EnvironmentCDF.js +85 -39
  131. package/src/TSL/ExtendKernel.js +8 -3
  132. package/src/TSL/FinalWriteKernel.js +4 -4
  133. package/src/TSL/GenerateKernel.js +18 -4
  134. package/src/TSL/HitFacet.js +35 -5
  135. package/src/TSL/LightBVHSampling.js +40 -9
  136. package/src/TSL/LightGenerateKernel.js +293 -0
  137. package/src/TSL/LightGuide.js +198 -0
  138. package/src/TSL/LightSplatKernel.js +165 -0
  139. package/src/TSL/LightVisibility.js +111 -0
  140. package/src/TSL/LightsCore.js +13 -38
  141. package/src/TSL/LightsDirect.js +28 -29
  142. package/src/TSL/LightsIndirect.js +3 -1
  143. package/src/TSL/LightsSampling.js +239 -51
  144. package/src/TSL/MaterialEvaluation.js +113 -31
  145. package/src/TSL/MaterialProperties.js +44 -17
  146. package/src/TSL/MaterialTransmission.js +52 -47
  147. package/src/TSL/MergeKernel.js +218 -0
  148. package/src/TSL/PathTracerCore.js +65 -29
  149. package/src/TSL/Random.js +5 -1
  150. package/src/TSL/SceneResources.js +51 -0
  151. package/src/TSL/ShadeKernel.js +964 -101
  152. package/src/TSL/Struct.js +10 -0
  153. package/src/TSL/Sun.js +1 -1
  154. package/src/TSL/TextureSampling.js +66 -61
  155. package/src/addons/archives.js +14 -0
  156. package/src/addons/bidirectional.js +13 -0
  157. package/src/addons/color.js +32 -0
  158. package/src/addons/formats.js +16 -0
  159. package/src/addons/physicalSky.js +14 -0
  160. package/src/addons/storage.js +19 -0
  161. package/src/core.js +31 -0
  162. package/src/index.js +13 -23
  163. package/src/integrators/BidirectionalIntegrator.js +833 -0
  164. package/src/managers/AnimationManager.js +88 -6
  165. package/src/managers/CameraManager.js +103 -46
  166. package/src/managers/DenoisingManager.js +14 -10
  167. package/src/managers/EnvironmentManager.js +125 -103
  168. package/src/managers/GoboManager.js +9 -0
  169. package/src/managers/MaterialDataManager.js +48 -37
  170. package/src/managers/UniformManager.js +32 -10
  171. package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
  172. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  173. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -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 { BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX, TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TLAS_LEAF_IDENTITY, MATERIAL_SIDE_CUTAWAY } from '../EngineDefaults.js';
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
- const RayTriangleGeometry = wgslFn( `
92
- fn RayTriangleGeometry( rayOrigin: vec3f, rayDir: vec3f, pA: vec3f, pB: vec3f, pC: vec3f, closestHitDst: f32, woopParams: vec4f ) -> vec4f {
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
- if ( tSigned > 0.0f && tSigned < closestHitDst * detAbs ) {
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, 3 cutaway (front, every ray).
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 ) ).or( side.equal( int( MATERIAL_SIDE_CUTAWAY ) ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
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.and( side.notEqual( int( MATERIAL_SIDE_CUTAWAY ) ) ).or( sideAccepts( side, rayDotNormal ) ), () => {
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
- export const traverseBVHShadow = Fn( ( [
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 = RayTriangleGeometry( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
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
- // A cutaway face is see-through from behind for shadow rays too.
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,204 @@
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
+ // Shade leaves diffuse transmission out in this integrator (its strategies do not cross surfaces yet).
160
+ material.diffuseTransmission.assign( 0.0 );
161
+
162
+ const samples = MaterialSamples.wrap( sampleAllMaterialTextures( material, uv, N, vec4( 0.0 ) ) ).toVar();
163
+ const rawRough = samples.roughness.toVar();
164
+
165
+ material.color.assign( samples.albedo );
166
+ material.metalness.assign( samples.metalness.clamp( 0.0, 1.0 ) );
167
+ material.roughness.assign( samples.roughness.clamp( MIN_ROUGHNESS, 1.0 ) );
168
+ material.sheenRoughness.assign( material.sheenRoughness.clamp( 0.05, 1.0 ) );
169
+
170
+ const extUV = getTransformedUV( { uv, transform: material.albedoTransform } ).toVar();
171
+
172
+ If( material.anisotropyMapIndex.greaterThanEqual( int( 0 ) ), () => {
173
+
174
+ const aniso = processAnisotropyMap( material, extUV ).toVar();
175
+ material.anisotropy.assign( aniso.x );
176
+ material.anisotropyRotation.assign( aniso.y );
177
+
178
+ } );
179
+
180
+ const ext = ExtMapResult.wrap( applyExtensionMaps( material, extUV ) ).toVar();
181
+ material.transmission.assign( ext.transmission );
182
+ material.clearcoat.assign( ext.clearcoat );
183
+ material.clearcoatRoughness.assign( ext.clearcoatRoughness );
184
+ material.sheenColor.assign( ext.sheenColor );
185
+ material.sheenRoughness.assign( ext.sheenRoughness );
186
+ material.iridescence.assign( ext.iridescence );
187
+ material.iridescenceThicknessRange.assign( vec2( material.iridescenceThicknessRange.x, ext.iridescenceThickness ) );
188
+ material.specularIntensity.assign( ext.specularIntensity );
189
+ material.specularColor.assign( ext.specularColor );
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 light end's cosine with the shading-normal correction (Veach 5.3.2): |V·N| |d·Ng| / |V·Ng|.
203
+ export const lightEndCosine = ( V, N, facetN, toOther ) =>
204
+ 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
+ };
@@ -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
- const pdf = max( calculateBSDFSamplingPDF( material, weights, dots ), 0.001 );
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 { MATERIAL_DATA_LAYOUT, TRI_BLOCKER_SHIFT, TRI_BLOCKER_ALPHA_SHIFT } from '../EngineDefaults.js';
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
- // Runtime uniform that toggles alpha-cutout shadows; set by ShaderBuilder before the kernels build.
616
- let _enableAlphaShadows = null;
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 = _enableAlphaShadows ? _enableAlphaShadows.equal( int( 0 ) ) : tslBool( true );
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
  };