rayzee 9.4.2 → 9.5.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 (144) hide show
  1. package/README.md +223 -21
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
  6. package/dist/ColorManagement-CheBCPaW.js +1488 -0
  7. package/dist/EngineDefaults-DH3bI5t-.js +844 -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-BnHghprG.js +97 -0
  12. package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
  13. package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
  14. package/dist/PathTracer-Ccl9KOuF.js +29729 -0
  15. package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
  16. package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
  17. package/dist/SolarLimb-ken-JbDK.js +721 -0
  18. package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
  19. package/dist/ViewTransforms-D0rTLBsX.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 +2266 -44079
  33. package/dist/rayzee.umd.js +485 -473
  34. package/dist/sceneCachePolicy-BmwYOYDu.js +183 -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/EngineDefaults.js +22 -8
  41. package/src/EngineEvents.js +2 -0
  42. package/src/EngineIssues.js +1 -0
  43. package/src/PathTracerApp.js +617 -4016
  44. package/src/Pipeline/PipelineContext.js +0 -4
  45. package/src/Platform.js +36 -0
  46. package/src/Processor/ArchiveCache.js +1 -1
  47. package/src/Processor/ArchiveImporter.js +1259 -0
  48. package/src/Processor/AssetLoader.js +322 -1746
  49. package/src/Processor/AtmosphereModel.js +1 -2
  50. package/src/Processor/BVHBuilder.js +4 -95
  51. package/src/Processor/EnvironmentExactTable.js +187 -0
  52. package/src/Processor/EquirectHDRInfo.js +30 -223
  53. package/src/Processor/FileFormats.js +116 -0
  54. package/src/Processor/GLTFDecoders.js +85 -0
  55. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  56. package/src/Processor/GeometryExtractor.js +12 -1
  57. package/src/Processor/IESParser.js +47 -35
  58. package/src/Processor/KernelManager.js +35 -2
  59. package/src/Processor/MaterialPacking.js +8 -7
  60. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  61. package/src/Processor/PBRT/PBRTParser.js +30 -17
  62. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  63. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  64. package/src/Processor/PBRT/PFM.js +59 -0
  65. package/src/Processor/PackedRayBuffer.js +116 -9
  66. package/src/Processor/ParallelBVHBuilder.js +3 -6
  67. package/src/Processor/PhysicalSky.js +5 -4
  68. package/src/Processor/PlatformImageLoader.js +50 -6
  69. package/src/Processor/QueueManager.js +12 -3
  70. package/src/Processor/ReinsertionOptimizer.js +2 -2
  71. package/src/Processor/SceneProcessor.js +15 -55
  72. package/src/Processor/ShaderBuilder.js +6 -16
  73. package/src/Processor/SolarLimb.js +3 -0
  74. package/src/Processor/TextureCreator.js +1 -1
  75. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  76. package/src/Processor/Workers/BVHWorker.js +5 -18
  77. package/src/Processor/Workers/CDFWorker.js +6 -183
  78. package/src/Processor/archiveFormats.js +7 -0
  79. package/src/Processor/utils.js +2 -1
  80. package/src/RayzeeRenderer.js +3902 -0
  81. package/src/RenderSettings.js +49 -41
  82. package/src/Stages/ASVGF.js +5 -7
  83. package/src/Stages/AutoExposure.js +3 -12
  84. package/src/Stages/Compositor.js +8 -23
  85. package/src/Stages/NRD.js +9 -2
  86. package/src/Stages/NormalDepth.js +9 -6
  87. package/src/Stages/PathTracer.js +254 -38
  88. package/src/Stages/PathTracerStage.js +11 -16
  89. package/src/Storage/BLASCache.js +1 -1
  90. package/src/Storage/CDFCache.js +18 -17
  91. package/src/Storage/DownloadCache.js +1 -1
  92. package/src/Storage/SceneGraphCodec.js +46 -3
  93. package/src/Storage/SpillStore.js +1 -1
  94. package/src/Storage/StorageManager.js +2 -4
  95. package/src/Storage/areas.js +4 -0
  96. package/src/TSL/BVHTraversal.js +62 -24
  97. package/src/TSL/Bidirectional.js +204 -0
  98. package/src/TSL/BidirectionalLamps.js +132 -0
  99. package/src/TSL/Clearcoat.js +15 -6
  100. package/src/TSL/Common.js +19 -14
  101. package/src/TSL/ConnectKernel.js +147 -0
  102. package/src/TSL/Debugger.js +1 -1
  103. package/src/TSL/Displacement.js +4 -3
  104. package/src/TSL/EmissiveSampling.js +64 -17
  105. package/src/TSL/Environment.js +77 -106
  106. package/src/TSL/EnvironmentCDF.js +85 -39
  107. package/src/TSL/ExtendKernel.js +8 -3
  108. package/src/TSL/FinalWriteKernel.js +4 -4
  109. package/src/TSL/GenerateKernel.js +18 -4
  110. package/src/TSL/HitFacet.js +35 -5
  111. package/src/TSL/LightBVHSampling.js +40 -9
  112. package/src/TSL/LightGenerateKernel.js +293 -0
  113. package/src/TSL/LightGuide.js +198 -0
  114. package/src/TSL/LightSplatKernel.js +165 -0
  115. package/src/TSL/LightsCore.js +13 -38
  116. package/src/TSL/LightsDirect.js +28 -29
  117. package/src/TSL/LightsIndirect.js +3 -1
  118. package/src/TSL/LightsSampling.js +90 -38
  119. package/src/TSL/MaterialEvaluation.js +113 -31
  120. package/src/TSL/MaterialProperties.js +44 -17
  121. package/src/TSL/MaterialTransmission.js +52 -47
  122. package/src/TSL/MergeKernel.js +218 -0
  123. package/src/TSL/PathTracerCore.js +65 -29
  124. package/src/TSL/Random.js +4 -0
  125. package/src/TSL/SceneResources.js +51 -0
  126. package/src/TSL/ShadeKernel.js +881 -103
  127. package/src/TSL/Struct.js +10 -0
  128. package/src/TSL/Sun.js +1 -1
  129. package/src/TSL/TextureSampling.js +65 -60
  130. package/src/addons/archives.js +14 -0
  131. package/src/addons/bidirectional.js +13 -0
  132. package/src/addons/color.js +32 -0
  133. package/src/addons/formats.js +16 -0
  134. package/src/addons/physicalSky.js +14 -0
  135. package/src/addons/storage.js +19 -0
  136. package/src/core.js +35 -0
  137. package/src/index.js +3 -1
  138. package/src/integrators/BidirectionalIntegrator.js +832 -0
  139. package/src/managers/EnvironmentManager.js +85 -33
  140. package/src/managers/MaterialDataManager.js +42 -0
  141. package/src/managers/UniformManager.js +0 -1
  142. package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
  143. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  144. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -1,8 +1,8 @@
1
- import { ENGINE_AREAS } from './StorageManager.js';
1
+ import { ENGINE_AREAS } from './areas.js';
2
2
  import { sharedStorage } from './shared.js';
3
3
  import { getAssetConfig } from '../AssetConfig.js';
4
4
 
5
- const FORMAT = 1;
5
+ const FORMAT = 5;
6
6
 
7
7
  /**
8
8
  * Where an environment texture came from, recorded by the loader so its sampling tables can be
@@ -25,7 +25,10 @@ function keyFor( texture ) {
25
25
 
26
26
  const area = () => sharedStorage( getAssetConfig().cacheNamespace )?.area( ENGINE_AREAS.CDF ) ?? null;
27
27
 
28
- /** @returns {Promise<?{marginalData: Float32Array, conditionalData: Float32Array, totalSum: number, compensationDelta: number, width: number, height: number}>} */
28
+ const ARRAYS = [ 'exactConditional', 'exactMarginal', 'exactRowGuide', 'exactMarginalGuide' ];
29
+ const FILES = ARRAYS.map( name => `${name}.f32` );
30
+
31
+ /** @returns {Promise<?Object>} the fields of an EquirectHDRInfo build */
29
32
  export async function loadCDF( texture ) {
30
33
 
31
34
  const key = keyFor( texture );
@@ -34,14 +37,11 @@ export async function loadCDF( texture ) {
34
37
 
35
38
  try {
36
39
 
37
- const [ marginal, conditional ] = await Promise.all( [ entry.file( 'marginal.f32' ), entry.file( 'conditional.f32' ) ] );
38
- if ( ! marginal || ! conditional ) return null;
39
- const { totalSum, compensationDelta, width, height } = entry.extra;
40
- return {
41
- marginalData: new Float32Array( await marginal.arrayBuffer() ),
42
- conditionalData: new Float32Array( await conditional.arrayBuffer() ),
43
- totalSum, compensationDelta, width, height,
44
- };
40
+ const files = await Promise.all( FILES.map( name => entry.file( name ) ) );
41
+ if ( files.some( f => ! f ) ) return null;
42
+ const arrays = await Promise.all( files.map( async f => new Float32Array( await f.arrayBuffer() ) ) );
43
+ const { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } = entry.extra;
44
+ return { ...Object.fromEntries( ARRAYS.map( ( name, i ) => [ name, arrays[ i ] ] ) ), totalSum, width, height, exactWidth, exactHeight, radianceIntegral };
45
45
 
46
46
  } finally {
47
47
 
@@ -51,20 +51,21 @@ export async function loadCDF( texture ) {
51
51
 
52
52
  }
53
53
 
54
- export async function saveCDF( texture, { marginalData, conditionalData, totalSum, compensationDelta, width, height } ) {
54
+ export async function saveCDF( texture, info ) {
55
55
 
56
+ const { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } = info;
57
+ const arrays = ARRAYS.map( name => info[ name ] );
56
58
  const key = keyFor( texture );
57
59
  const target = key ? area() : null;
58
- if ( ! target || ! marginalData || ! conditionalData ) return false;
60
+ if ( ! target || arrays.some( a => ! a ) ) return false;
59
61
 
60
- const writer = await target.create( key, { label: texture.name || 'environment', expectedBytes: marginalData.byteLength + conditionalData.byteLength } );
62
+ const writer = await target.create( key, { label: texture.name || 'environment', expectedBytes: arrays.reduce( ( n, a ) => n + a.byteLength, 0 ) } );
61
63
  if ( ! writer ) return false;
62
64
 
63
65
  try {
64
66
 
65
- await writer.write( 'marginal.f32', marginalData );
66
- await writer.write( 'conditional.f32', conditionalData );
67
- await writer.commit( { totalSum, compensationDelta, width, height } );
67
+ for ( let i = 0; i < FILES.length; i ++ ) await writer.write( FILES[ i ], arrays[ i ] );
68
+ await writer.commit( { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } );
68
69
  return true;
69
70
 
70
71
  } catch {
@@ -1,4 +1,4 @@
1
- import { ENGINE_AREAS } from './StorageManager.js';
1
+ import { ENGINE_AREAS } from './areas.js';
2
2
  import { sharedStorage } from './shared.js';
3
3
  import { getAssetConfig } from '../AssetConfig.js';
4
4
 
@@ -9,11 +9,13 @@
9
9
  */
10
10
 
11
11
  import {
12
- AnimationClip, BufferAttribute, BufferGeometry, DataTexture, Group, InstancedBufferAttribute, InstancedMesh,
13
- MaterialLoader, Mesh, Object3D, OrthographicCamera, PerspectiveCamera,
12
+ AnimationClip, BufferAttribute, BufferGeometry, Color, DataTexture, Group, InstancedBufferAttribute, InstancedMesh,
13
+ MaterialLoader, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, DirectionalLight, PointLight, SpotLight,
14
14
  } from 'three';
15
15
 
16
16
  export const SCENE_GRAPH_FORMAT = 2;
17
+ // Texture slots the engine reads off a material that three.js does not serialise.
18
+ const ENGINE_TEXTURES = [ 'diffuseTransmissionMap', 'diffuseTransmissionColorMap' ];
17
19
  export const ARCHIVE_PATH = '__rayzeeArchivePath';
18
20
  export const ARCHIVE_LOADER = '__rayzeeArchiveLoader';
19
21
 
@@ -28,6 +30,7 @@ const TEXTURE_PROPS = [
28
30
  'flipY', 'generateMipmaps', 'premultiplyAlpha', 'unpackAlignment', 'colorSpace',
29
31
  ];
30
32
 
33
+ const LIGHT_PROPS = [ 'intensity', 'distance', 'decay', 'angle', 'penumbra' ];
31
34
  const OBJECT_PROPS = [ 'name', 'visible', 'castShadow', 'receiveShadow', 'frustumCulled', 'renderOrder', 'matrixAutoUpdate' ];
32
35
 
33
36
  export class SceneGraphUnsupported extends Error {
@@ -48,6 +51,9 @@ function kindOf( object ) {
48
51
  if ( object.isMesh ) return 'Mesh';
49
52
  if ( object.isPerspectiveCamera ) return 'PerspectiveCamera';
50
53
  if ( object.isOrthographicCamera ) return 'OrthographicCamera';
54
+ if ( object.isDirectionalLight ) return 'DirectionalLight';
55
+ if ( object.isPointLight ) return 'PointLight';
56
+ if ( object.isSpotLight ) return 'SpotLight';
51
57
  if ( object.isLight || object.isPoints || object.isLine || object.isSprite || object.isLOD ) return null;
52
58
  if ( object.isGroup ) return 'Group';
53
59
  return object.constructor === Object3D ? 'Object3D' : null;
@@ -163,6 +169,9 @@ function encodeMaterial( material, meta ) {
163
169
  for ( const [ key, value ] of Object.entries( material ) ) if ( value?.isColor ) json.exactColors[ key ] = [ value.r, value.g, value.b ];
164
170
  // MaterialLoader sets `reflectivity` after `ior`, and that setter rewrites ior with rounding.
165
171
  if ( material.ior !== undefined ) json.exactIor = material.ior;
172
+ // Engine properties three.js does not know, so toJSON drops them.
173
+ if ( material.diffuseTransmission !== undefined ) json.diffuseTransmission = material.diffuseTransmission;
174
+ for ( const key of ENGINE_TEXTURES ) if ( material[ key ]?.isTexture ) ( json.engineTextures ??= {} )[ key ] = material[ key ].uuid;
166
175
  return json;
167
176
 
168
177
  }
@@ -234,6 +243,14 @@ export function encodeSceneGraph( root, { environment = null, animations = [], s
234
243
 
235
244
  }
236
245
 
246
+ if ( object.isLight ) {
247
+
248
+ node.color = object.color.toArray();
249
+ for ( const prop of LIGHT_PROPS ) if ( object[ prop ] !== undefined ) node[ prop ] = object[ prop ];
250
+ if ( object.target ) node.target = object.target.uuid;
251
+
252
+ }
253
+
237
254
  const index = nodes.push( node ) - 1;
238
255
  for ( const child of object.children ) visit( child, index );
239
256
 
@@ -419,8 +436,16 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
419
436
  for ( const json of manifest.materials ) {
420
437
 
421
438
  const material = materialLoader.parse( json );
422
- for ( const [ key, rgb ] of Object.entries( json.exactColors ?? {} ) ) material[ key ]?.setRGB( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
439
+ for ( const [ key, rgb ] of Object.entries( json.exactColors ?? {} ) ) {
440
+
441
+ if ( key === 'diffuseTransmissionColor' ) material[ key ] = new Color();
442
+ material[ key ]?.setRGB( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
443
+
444
+ }
445
+
423
446
  if ( json.exactIor !== undefined ) material.ior = json.exactIor;
447
+ if ( json.diffuseTransmission !== undefined ) material.diffuseTransmission = json.diffuseTransmission;
448
+ for ( const [ key, uuid ] of Object.entries( json.engineTextures ?? {} ) ) if ( textures[ uuid ] ) material[ key ] = textures[ uuid ];
424
449
  materials[ json.uuid ] = material;
425
450
 
426
451
  }
@@ -462,6 +487,9 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
462
487
  case 'PerspectiveCamera': object = new PerspectiveCamera(); break;
463
488
  case 'OrthographicCamera': object = new OrthographicCamera(); break;
464
489
  case 'Group': object = new Group(); break;
490
+ case 'DirectionalLight': object = new DirectionalLight(); break;
491
+ case 'PointLight': object = new PointLight(); break;
492
+ case 'SpotLight': object = new SpotLight(); break;
465
493
  default: object = new Object3D();
466
494
 
467
495
  }
@@ -487,11 +515,26 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
487
515
 
488
516
  }
489
517
 
518
+ if ( object.isLight ) {
519
+
520
+ object.color.fromArray( node.color );
521
+ for ( const prop of LIGHT_PROPS ) if ( node[ prop ] !== undefined ) object[ prop ] = node[ prop ];
522
+
523
+ }
524
+
490
525
  built.push( object );
491
526
  if ( node.parent >= 0 ) built[ node.parent ].add( object );
492
527
 
493
528
  }
494
529
 
530
+ // A light aims at its target, which is stored as an object of its own.
531
+ const byUuid = new Map( built.map( ( object ) => [ object.uuid, object ] ) );
532
+ manifest.nodes.forEach( ( node, i ) => {
533
+
534
+ if ( node.target && byUuid.has( node.target ) ) built[ i ].target = byUuid.get( node.target );
535
+
536
+ } );
537
+
495
538
  return {
496
539
  root: built[ 0 ],
497
540
  environment: manifest.environment ? textures[ manifest.environment ] : null,
@@ -1,4 +1,4 @@
1
- import { ENGINE_AREAS } from './StorageManager.js';
1
+ import { ENGINE_AREAS } from './areas.js';
2
2
 
3
3
  const WRITE_PIECE_BYTES = 32 * 1024 * 1024;
4
4
  const FILE = 'chunks.bin';
@@ -3,11 +3,9 @@ import { acquireLock, heldLockNames } from './locks.js';
3
3
  import { entryIdFor } from './identity.js';
4
4
  import { Emitter } from './events.js';
5
5
  import { InlineTransport } from './inlineTransport.js';
6
+ import { STORAGE_KIND, ENGINE_AREAS } from './areas.js';
6
7
 
7
- export const STORAGE_KIND = Object.freeze( { CACHE: 'cache', USER: 'user', SCRATCH: 'scratch' } );
8
-
9
- /** Areas the engine itself writes; hosts define their own with `defineArea`. */
10
- export const ENGINE_AREAS = Object.freeze( { DOWNLOADS: 'downloads', ARCHIVES: 'archives', SCENES: 'scenes', CDF: 'cdf', SPILL: 'spill' } );
8
+ export { STORAGE_KIND, ENGINE_AREAS };
11
9
 
12
10
  const META = 'meta.json';
13
11
  const META_VERSION = 1;
@@ -0,0 +1,4 @@
1
+ export const STORAGE_KIND = Object.freeze( { CACHE: 'cache', USER: 'user', SCRATCH: 'scratch' } );
2
+
3
+ /** Areas the engine itself writes; hosts define their own with `defineArea`. */
4
+ export const ENGINE_AREAS = Object.freeze( { DOWNLOADS: 'downloads', ARCHIVES: 'archives', SCENES: 'scenes', CDF: 'cdf', SPILL: 'spill' } );
@@ -1,3 +1,4 @@
1
+ import { sceneResources } from './SceneResources.js';
1
2
  import {
2
3
  Fn,
3
4
  wgslFn,
@@ -88,8 +89,10 @@ const toObjectDir = ( rows, d ) => vec3(
88
89
  // at shared triangle edges that Möller-Trumbore exhibits under FP32. Per-ray shears
89
90
  // are precomputed once via computeWoopFromInvDir; per-triangle test is FMA-friendly
90
91
  // 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 {
92
+ // guardT: reject t within its own rounding error. Closest-hit rays need it to leave a large triangle; shadow rays
93
+ // start lifted off their surface and skip it — its registers cost Shade's shadow traversal ~4 %.
94
+ const rayTriangle = ( name, guardT ) => wgslFn( `
95
+ fn ${ name }( rayOrigin: vec3f, rayDir: vec3f, pA: vec3f, pB: vec3f, pC: vec3f, closestHitDst: f32, woopParams: vec4f ) -> vec4f {
93
96
 
94
97
  // Returns vec4(t, u, v, hit) where hit > 0.5 means intersection.
95
98
  // woopParams: (Sx, Sy, Sz, bitcast<f32>(packed kx|ky<<2|kz<<4))
@@ -138,7 +141,19 @@ const RayTriangleGeometry = wgslFn( `
138
141
  let tSigned = T * detSign;
139
142
  let detAbs = abs( det );
140
143
 
141
- if ( tSigned > 0.0f && tSigned < closestHitDst * detAbs ) {
144
+ ${ guardT ? `
145
+ // t must clear its own rounding error (pbrt-v4 Triangle::Intersect, γn = n·2⁻²⁴ / (1 − n·2⁻²⁴)), or a
146
+ // ray leaving a large triangle can hit it again: its error grows with the distance to the vertices.
147
+ let maxZ = max( max( abs( Sz * Akz ), abs( Sz * Bkz ) ), abs( Sz * Ckz ) );
148
+ let maxX = max( max( abs( Ax ), abs( Bx ) ), abs( Cx ) );
149
+ let maxY = max( max( abs( Ay ), abs( By ) ), abs( Cy ) );
150
+ let maxE = max( max( abs( U ), abs( V ) ), abs( W ) );
151
+ let deltaX = 2.98023e-7f * ( maxX + maxZ );
152
+ let deltaY = 2.98023e-7f * ( maxY + maxZ );
153
+ let deltaE = 2.0f * ( 1.19209e-7f * maxX * maxY + deltaY * maxX + deltaX * maxY );
154
+ let errT = 3.0f * maxZ * ( 3.57628e-7f * maxE + deltaE );
155
+ ` : '' }
156
+ if ( tSigned > ${ guardT ? 'errT' : '0.0f' } && tSigned < closestHitDst * detAbs ) {
142
157
 
143
158
  // Match Möller-Trumbore convention: u = weight of B, v = weight of C.
144
159
  // In Woop's edge functions, U → weight of A, V → weight of B, W → weight of C.
@@ -156,6 +171,9 @@ const RayTriangleGeometry = wgslFn( `
156
171
  }
157
172
  ` );
158
173
 
174
+ const RayTriangleGeometry = rayTriangle( 'RayTriangleGeometry', true );
175
+ const RayTriangleGeometryShadow = rayTriangle( 'RayTriangleGeometryShadow', false );
176
+
159
177
  // Compute Woop ray-space transform (Woop 2013, §3.1) — runs once per ray and
160
178
  // amortizes across hundreds of triangle tests. Returns Sx/Sy/Sz shears plus the
161
179
  // permuted axis indices packed via bitcast into the .w slot.
@@ -517,12 +535,15 @@ export const traverseBVHDebug = /*@__PURE__*/ makeTraverseBVH( true );
517
535
  // SHADOW RAY TRAVERSAL (OPTIMIZED - early exit on any hit)
518
536
  // ================================================================================
519
537
 
520
- export const traverseBVHShadow = Fn( ( [
538
+ // cameraCulled: skip the faces a camera ray along −direction would cull, for a ray standing in for one.
539
+ const makeTraverseBVHShadow = ( cameraCulled ) => Fn( ( [
521
540
  ray,
522
541
  bvhBuffer,
523
542
  triangleBuffer,
524
543
  maxShadowDist,
525
- ] ) => {
544
+ ], builder ) => {
545
+
546
+ const { alphaShadows } = sceneResources( builder );
526
547
 
527
548
  const folded = bvhBuffer.value?.foldedLeaves === true;
528
549
  const isLeafTag = leafTagTest( folded );
@@ -572,36 +593,50 @@ export const traverseBVHShadow = Fn( ( [
572
593
  const pB = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 1 ).xyz );
573
594
  const pC = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 2 ).xyz );
574
595
 
575
- const triResult = RayTriangleGeometry( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
596
+ const triResult = RayTriangleGeometryShadow( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
576
597
 
577
598
  If( triResult.w.greaterThan( 0.5 ), () => {
578
599
 
579
600
  // Per-mesh visibility is handled at the BLAS-pointer level.
580
601
  const uvData2 = triangleRow( triangleBuffer, triIndex, 4 ).toVar();
581
602
 
582
- closestHit.didHit.assign( true );
583
- closestHit.dst.assign( triResult.x );
584
- closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
585
- closestHit.meshIndex.assign( int( uvData2.w ) );
586
- closestHit.instanceLeaf.assign( instLeaf );
603
+ const record = () => {
587
604
 
588
- // Hit point is cheap (origin + dir*t). Geometric normal is deferred
589
- // to traceShadowRay — only the transmission branch needs it, so we
590
- // skip the cross+normalize for the (much more common) opaque-blocker
591
- // and alpha-cutout paths. Normal stays vec3(0) from struct init.
592
- closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
605
+ closestHit.didHit.assign( true );
606
+ closestHit.dst.assign( triResult.x );
607
+ closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
608
+ closestHit.meshIndex.assign( int( uvData2.w ) );
609
+ closestHit.instanceLeaf.assign( instLeaf );
593
610
 
594
- closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
595
- closestHit.triangleIndex.assign( triIndex );
611
+ // Hit point is cheap (origin + dir*t). Geometric normal is deferred
612
+ // to traceShadowRay — only the transmission branch needs it, so we
613
+ // skip the cross+normalize for the (much more common) opaque-blocker
614
+ // and alpha-cutout paths. Normal stays vec3(0) from struct init.
615
+ closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
596
616
 
597
- // An opaque blocker settles the ray. A surface light passes through has to be
598
- // the nearest one, or the layers behind the first find are never counted.
599
- If( shadowFlagsSettle( uvData2.z ), () => {
617
+ closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
618
+ closestHit.triangleIndex.assign( triIndex );
600
619
 
601
- blocked.assign( true );
602
- Break();
620
+ // An opaque blocker settles the ray. A surface light passes through has to be
621
+ // the nearest one, or the layers behind the first find are never counted.
622
+ If( shadowFlagsSettle( uvData2.z, alphaShadows ), () => {
603
623
 
604
- } );
624
+ blocked.assign( true );
625
+ Break();
626
+
627
+ } );
628
+
629
+ };
630
+
631
+ if ( cameraCulled ) {
632
+
633
+ const w = float( 1.0 ).sub( triResult.y ).sub( triResult.z );
634
+ const n = normalize( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 0 ).w ).mul( w )
635
+ .add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 1 ).w ).mul( triResult.y ) )
636
+ .add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 2 ).w ).mul( triResult.z ) ) );
637
+ If( sideAccepts( triangleSide( uvData2.z ), rayDirection.dot( n ).negate() ), record );
638
+
639
+ } else record();
605
640
 
606
641
  } );
607
642
 
@@ -726,3 +761,6 @@ export const traverseBVHShadow = Fn( ( [
726
761
  return closestHit;
727
762
 
728
763
  } );
764
+
765
+ export const traverseBVHShadow = /*@__PURE__*/ makeTraverseBVHShadow( false );
766
+ 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 ) );