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,6 +1,6 @@
1
- import { Fn, wgslFn, vec2, vec3, vec4, ivec2, float, int, If, texture, dot, sin, sqrt, floor, fract, min, max, mix, clamp } from 'three/tsl';
1
+ import { Fn, wgslFn, vec2, vec3, ivec2, float, int, If, Loop, sin, sqrt, min, max, clamp, select } from 'three/tsl';
2
2
 
3
- import { REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
3
+ import { EXACT_TABLE_MAX_WIDTH, GUIDES_PER_ENTRY } from '../Processor/EnvironmentExactTable.js';
4
4
 
5
5
  // Convert direction to UV coordinates for equirectangular map
6
6
  // Exact implementation from three-gpu-pathtracer
@@ -28,125 +28,96 @@ export const equirectUvToDirection = /*@__PURE__*/ wgslFn( `
28
28
  }
29
29
  ` );
30
30
 
31
- // Evaluate PDF for a given direction (for MIS)
32
- // Returns vec4(color.rgb, pdf) since TSL cannot use inout params
33
- // Uses MIS-compensated PDF (Karlík et al. 2019): max(0, lum - delta) / compensatedTotalSum
34
- export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix, envTotalSum, envCompensationDelta, envResolution ] ) => {
31
+ // The table (EnvironmentExactTable.js packExactTable): texel ( x, y ) is row y's entry x — its running sum, the one
32
+ // below it, and the guides of steps 2x and 2x + 1 — and texel ( w, y ) the rows' entry y likewise. Sizes as exactTableSize.
33
+ const exactTable = ( cdfTexture, envResolution ) => {
35
34
 
36
- const result = vec4( 0.0 ).toVar();
35
+ const W = int( envResolution.x );
36
+ const H = int( envResolution.y );
37
+ const k = max( W.add( int( EXACT_TABLE_MAX_WIDTH - 1 ) ).div( int( EXACT_TABLE_MAX_WIDTH ) ), int( 1 ) );
38
+ const w = W.add( k ).sub( int( 1 ) ).div( k ).toVar();
39
+ const h = H.add( k ).sub( int( 1 ) ).div( k ).toVar();
40
+ return { w, h, entry: ( x, y ) => cdfTexture.load( ivec2( x, y ) ) };
37
41
 
38
- If( envTotalSum.equal( 0.0 ), () => {
42
+ };
39
43
 
40
- // Exclude black environments from MIS
41
- result.assign( vec4( 0.0 ) );
44
+ // The first of `count` entries whose running sum is above target: the guide of target's step names it at once unless
45
+ // the step holds more than one entry boundary, or f32 rounding of target × steps picked the step beside it; then a
46
+ // search from one entry below the guide to the guide two steps on. Returns the entry with its sum and the one below.
47
+ const invert = ( count, entryAt, target ) => {
42
48
 
43
- } ).Else( () => {
49
+ const steps = count.mul( int( GUIDES_PER_ENTRY ) ).toVar();
50
+ const step = clamp( int( target.mul( float( steps ) ) ), int( 0 ), steps.sub( int( 1 ) ) ).toVar();
51
+ const odd = step.bitAnd( int( 1 ) ).equal( int( 1 ) ).toVar();
52
+ const guides = entryAt( step.shiftRight( int( 1 ) ) ).toVar();
53
+ const index = int( select( odd, guides.w, guides.z ) ).toVar();
54
+ const e = entryAt( index ).toVar();
55
+ If( target.lessThan( e.y ).or( target.greaterThanEqual( e.x ).and( index.lessThan( count.sub( int( 1 ) ) ) ) ), () => {
44
56
 
45
- const uv = equirectDirectionToUv( { direction, environmentMatrix } ).toVar();
46
- const color = texture( environment, uv, 0 ).rgb.toVar();
57
+ const lo = max( index.sub( int( 1 ) ), int( 0 ) ).toVar();
58
+ const ahead = entryAt( step.shiftRight( int( 1 ) ).add( int( 1 ) ) );
59
+ const hi = select( step.add( int( 2 ) ).lessThan( steps ), int( select( odd, ahead.w, ahead.z ) ), count.sub( int( 1 ) ) ).toVar();
60
+ Loop( lo.lessThan( hi ), () => {
47
61
 
48
- // sin(theta) matches the CDF's solid-angle weighting (lum * sinTheta)
49
- const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
50
- const lum = dot( color, REC709_LUMINANCE_COEFFICIENTS );
51
- const weightedLum = lum.mul( sinTheta );
52
- // MIS Compensation: subtract delta to match the sharpened CDF
53
- const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
54
- const pdf = compensatedWeight.div( envTotalSum );
62
+ const mid = lo.add( hi ).div( 2 ).toVar();
63
+ If( entryAt( mid ).x.lessThanEqual( target ), () => {
55
64
 
56
- // Inline equirectDirectionPdf using the uv + sinTheta already in scope —
57
- // the helper would otherwise re-derive uv via atan2+acos and recompute sin.
58
- const dirPdf = sinTheta.greaterThan( 0.0 ).select(
59
- float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
60
- float( 0.0 )
61
- );
62
- const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
65
+ lo.assign( mid.add( 1 ) );
63
66
 
64
- result.assign( vec4( color, finalPdf ) );
67
+ } ).Else( () => {
68
+
69
+ hi.assign( mid );
70
+
71
+ } );
72
+
73
+ } );
74
+ index.assign( lo );
75
+ e.assign( entryAt( lo ) );
65
76
 
66
77
  } );
78
+ return { index, top: e.x, bottom: e.y };
67
79
 
68
- return result;
80
+ };
69
81
 
70
- } );
82
+ // Cell density over uv, per steradian at v.
83
+ const cellPdf = ( share, w, h, v ) => {
71
84
 
72
- // Sample environment map using importance sampling
73
- // Returns vec4(direction.xyz, pdf). Optionally writes sampled color to colorOutput.
74
- // Exact implementation from three-gpu-pathtracer
75
- export const sampleEquirectProbability = Fn( ( [
76
- environment,
77
- envCDFTexture,
78
- environmentMatrix,
79
- environmentIntensity,
80
- envTotalSum,
81
- envCompensationDelta,
82
- envResolution,
83
- r,
84
- colorOutput
85
- ] ) => {
86
-
87
- // CDF texture layout: (W+1)×H R32F — conditional[cy*W+cx] at texel (cx,cy); marginal[cy] at column W.
88
- const cdfMarginalCol = int( envResolution.x ).toVar();
89
-
90
- // Sample marginal CDF for V coordinate (1D, linear interpolation)
91
- const marginalSize = envResolution.y;
92
- const mIdx = clamp( r.x.mul( marginalSize.sub( 1.0 ) ), 0.0, marginalSize.sub( 1.0 ) );
93
- const mI0 = int( floor( mIdx ) );
94
- const mI1 = min( mI0.add( 1 ), int( marginalSize ).sub( 1 ) );
95
- const mFrac = fract( mIdx );
96
- const v = mix(
97
- envCDFTexture.load( ivec2( cdfMarginalCol, mI0 ) ).x,
98
- envCDFTexture.load( ivec2( cdfMarginalCol, mI1 ) ).x,
99
- mFrac,
100
- ).toVar();
101
-
102
- // Sample conditional CDF for U coordinate (2D grid, bilinear interpolation)
103
- const condW = envResolution.x;
104
- const condH = envResolution.y;
105
- const cxf = clamp( r.y.mul( condW.sub( 1.0 ) ), 0.0, condW.sub( 1.0 ) );
106
- const cyf = clamp( v.mul( condH.sub( 1.0 ) ), 0.0, condH.sub( 1.0 ) );
107
- const cx0 = int( floor( cxf ) );
108
- const cy0 = int( floor( cyf ) );
109
- const cx1 = min( cx0.add( 1 ), int( condW ).sub( 1 ) );
110
- const cy1 = min( cy0.add( 1 ), int( condH ).sub( 1 ) );
111
- const fx = fract( cxf );
112
- const fy = fract( cyf );
113
- const v00 = envCDFTexture.load( ivec2( cx0, cy0 ) ).x;
114
- const v10 = envCDFTexture.load( ivec2( cx1, cy0 ) ).x;
115
- const v01 = envCDFTexture.load( ivec2( cx0, cy1 ) ).x;
116
- const v11 = envCDFTexture.load( ivec2( cx1, cy1 ) ).x;
117
- const u = mix( mix( v00, v10, fx ), mix( v01, v11, fx ), fy ).toVar();
118
-
119
- const uv = vec2( u, v ).toVar();
120
-
121
- // Convert UV to direction
122
- const direction = equirectUvToDirection( { uv, environmentMatrix } ).toVar();
123
-
124
- // Sample color
125
- const color = texture( environment, uv, 0 ).rgb.mul( environmentIntensity ).toVar();
126
-
127
- // Write color to output parameter (avoids redundant CDF texture lookups)
128
- colorOutput.assign( color );
129
-
130
- // Calculate PDF — sin(theta) weighting + MIS Compensation (Karlík et al. 2019)
131
- const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
132
- const lum = dot( color.div( environmentIntensity ), REC709_LUMINANCE_COEFFICIENTS );
133
- const weightedLum = lum.mul( sinTheta );
134
- const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
135
- const pdf = compensatedWeight.div( envTotalSum );
136
-
137
- // Inline equirectDirectionPdf — uv + sinTheta are already in scope, so we
138
- // skip the helper's redundant uv-from-direction + sin recompute.
139
- const dirPdf = sinTheta.greaterThan( 0.0 ).select(
140
- float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
141
- float( 0.0 )
142
- );
143
- const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
144
-
145
- return vec4( direction, finalPdf );
85
+ const sinTheta = sin( v.mul( Math.PI ) );
86
+ return select( sinTheta.greaterThan( 0.0 ).and( share.greaterThan( 0.0 ) ),
87
+ share.mul( float( w ) ).mul( float( h ) ).div( max( sinTheta.mul( 2 * Math.PI * Math.PI ), 1e-30 ) ), float( 0.0 ) );
146
88
 
147
- } );
89
+ };
90
+
91
+ /** A direction drawn exactly from the table, and its density per steradian. */
92
+ export function sampleEnvironmentExact( cdfTexture, environmentMatrix, envResolution, xi ) {
93
+
94
+ const { w, h, entry } = exactTable( cdfTexture, envResolution );
95
+ const row = invert( h, ( i ) => entry( w, i ), xi.y );
96
+ const y = row.index.toVar();
97
+ const cell = invert( w, ( i ) => entry( i, y ), xi.x );
98
+ const pRow = row.top.sub( row.bottom ).toVar();
99
+ const pCell = cell.top.sub( cell.bottom ).toVar();
100
+
101
+ const fv = clamp( xi.y.sub( row.bottom ).div( max( pRow, 1e-30 ) ), 0.0, 0.99999994 );
102
+ const fu = clamp( xi.x.sub( cell.bottom ).div( max( pCell, 1e-30 ) ), 0.0, 0.99999994 );
103
+ const uv = vec2( float( cell.index ).add( fu ).div( float( w ) ), float( y ).add( fv ).div( float( h ) ) ).toVar();
104
+
105
+ return { direction: equirectUvToDirection( { uv, environmentMatrix } ), pdf: cellPdf( pRow.mul( pCell ), w, h, uv.y ) };
106
+
107
+ }
108
+
109
+ /** sampleEnvironmentExact's density per steradian for `direction`. */
110
+ export function environmentPdfExact( cdfTexture, environmentMatrix, envResolution, direction ) {
111
+
112
+ const { w, h, entry } = exactTable( cdfTexture, envResolution );
113
+ const uv = equirectDirectionToUv( { direction, environmentMatrix } ).toVar();
114
+ const x = clamp( int( uv.x.mul( float( w ) ) ), int( 0 ), w.sub( int( 1 ) ) ).toVar();
115
+ const y = clamp( int( uv.y.mul( float( h ) ) ), int( 0 ), h.sub( int( 1 ) ) ).toVar();
116
+ const rowEntry = entry( w, y ).toVar();
117
+ const cellEntry = entry( x, y ).toVar();
118
+ return cellPdf( rowEntry.x.sub( rowEntry.y ).mul( cellEntry.x.sub( cellEntry.y ) ), w, h, uv.y );
148
119
 
149
- // Note: powerHeuristic() is defined in Common.js
120
+ }
150
121
 
151
122
  // Simple environment lookup (no importance sampling) — native WGSL
152
123
  export const sampleEnvironment = /*@__PURE__*/ wgslFn( `
@@ -2,11 +2,11 @@ import { Fn, float, int, uint, vec2, vec4, If, Loop, Return, instanceIndex, max,
2
2
 
3
3
  import { REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
4
4
 
5
- /** Floats per row of the packed table: width + 1 texels, padded to the 256 bytes a buffer→texture copy needs. */
6
- export const cdfRowStride = width => Math.ceil( ( width + 1 ) * 4 / 256 ) * 64;
5
+ /** Floats per row of the packed table: width + 1 RGBA texels, padded to the 256 bytes a buffer→texture copy needs. */
6
+ export const cdfRowStride = width => Math.ceil( ( width + 1 ) * 16 / 256 ) * 64;
7
7
 
8
- // First index in [0, n) whose value is ≥ target, n − 1 when none is — as the CPU builder's search.
9
- function lowerBound( n, valueAt, target ) {
8
+ // First index in [0, n) whose value is above target, n − 1 when none is — as the CPU builder's cutpoints.
9
+ function firstAbove( n, valueAt, target ) {
10
10
 
11
11
  const lo = int( 0 ).toVar();
12
12
  const hi = int( n - 1 ).toVar();
@@ -15,7 +15,7 @@ function lowerBound( n, valueAt, target ) {
15
15
  If( lo.lessThan( hi ), () => {
16
16
 
17
17
  const mid = lo.add( hi ).div( 2 ).toVar();
18
- If( valueAt( mid ).lessThan( target ), () => {
18
+ If( valueAt( mid ).lessThanEqual( target ), () => {
19
19
 
20
20
  lo.assign( mid.add( 1 ) );
21
21
 
@@ -36,13 +36,16 @@ function lowerBound( n, valueAt, target ) {
36
36
  // against the row total it ends on.
37
37
 
38
38
  /**
39
- * The GPU twin of `EquirectHDRInfo.computeCDF`, into the packed (width + 1) × height table the
40
- * sampler reads. `stats[ 1 ]` ends as ( totalSum, compensationDelta, compensated, 0 ).
39
+ * The GPU twin of `buildExactEnvironmentTable`, in packExactTable's (width + 1) × height RGBA layout (one cell a
40
+ * texel, compensated by the same mean, without its neighbour weighting: the sky has no sharp texel).
41
+ * `stats[ 1 ]` ends as ( totalSum, compensationDelta, compensated, 0 ).
41
42
  * @returns {Array} kernels, dispatched in order
42
43
  */
43
44
  export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf, width, height } ) {
44
45
 
45
46
  const stride = cdfRowStride( width );
47
+ // Channel c of texel ( x, y ).
48
+ const at = ( x, y, c ) => uint( y ).mul( uint( stride ) ).add( uint( x ).mul( uint( 4 ) ) ).add( uint( c ) );
46
49
  const sinTheta = y => sin( float( y ).add( 0.5 ).mul( Math.PI / height ) );
47
50
  const weight = ( y, x, sinT ) => dot( pixels.element( y.mul( width ).add( x ) ).xyz, REC709_LUMINANCE_COEFFICIENTS ).mul( sinT );
48
51
  const firstThreadOnly = () => If( instanceIndex.greaterThan( uint( 0 ) ), () => {
@@ -109,7 +112,7 @@ export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf,
109
112
 
110
113
  } )().compute( height, [ 64 ] );
111
114
 
112
- const marginal = Fn( () => {
115
+ const totals = Fn( () => {
113
116
 
114
117
  firstThreadOnly();
115
118
  const compensatedTotal = float( 0 ).toVar();
@@ -118,59 +121,102 @@ export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf,
118
121
  compensatedTotal.addAssign( rows.element( i ).y );
119
122
 
120
123
  } );
121
- const useCompensated = compensatedTotal.greaterThan( 0 ).toVar();
124
+ const s = stats.element( 0 ).toVar();
125
+ stats.element( 1 ).assign( select( compensatedTotal.greaterThan( 0 ), vec4( compensatedTotal, s.y, 1, 0 ), vec4( s.x, 0, 0, 0 ) ) );
122
126
 
123
- const cumulative = float( 0 ).toVar();
124
- Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
127
+ } )().compute( 1, [ 1 ] );
128
+
129
+ // buildExactEnvironmentTable's floor: a share of the mean weight in every cell.
130
+ const exactFloor = () => stats.element( 0 ).x.mul( 1e-4 / ( width * height ) );
125
131
 
126
- const r = rows.element( i ).toVar();
127
- cumulative.addAssign( select( useCompensated, r.y, r.x ) );
128
- rows.element( i ).assign( vec4( r.x, r.y, cumulative, 0 ) );
132
+ const exactRows = Fn( () => {
133
+
134
+ const y = instanceIndex;
135
+ If( y.greaterThanEqual( uint( height ) ), () => {
136
+
137
+ Return();
129
138
 
130
139
  } );
131
140
 
132
- const s = stats.element( 0 ).toVar();
133
- stats.element( 1 ).assign( select( useCompensated, vec4( compensatedTotal, s.y, 1, 0 ), vec4( s.x, 0, 0, 0 ) ) );
141
+ const floor = exactFloor().toVar();
142
+ const compensated = stats.element( 1 ).z.greaterThan( 0 ).toVar();
143
+ const r = rows.element( y ).toVar();
144
+ const rowSum = select( compensated, r.y, r.x ).add( floor.mul( width ) ).toVar();
145
+ const below = float( 0 ).toVar();
146
+ Loop( { start: int( 0 ), end: int( width ), type: 'int', condition: '<' }, ( { i } ) => {
147
+
148
+ const p = prefix.element( y.mul( width ).add( uint( i ) ) ).toVar();
149
+ const c = select( compensated, p.y, p.x ).add( floor.mul( float( i ).add( 1 ) ) ).div( max( rowSum, 1e-30 ) );
150
+ const sum = select( rowSum.greaterThan( 0 ), select( i.equal( int( width - 1 ) ), float( 1 ), c ), float( 0 ) ).toVar();
151
+ cdf.element( at( i, y, 0 ) ).assign( sum );
152
+ cdf.element( at( i, y, 1 ) ).assign( below );
153
+ below.assign( sum );
154
+
155
+ } );
156
+
157
+ } )().compute( height, [ 64 ] );
158
+
159
+ const exactMarginal = Fn( () => {
134
160
 
135
- Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<', name: 'row' }, ( { row } ) => {
161
+ firstThreadOnly();
162
+ const floor = exactFloor().mul( width ).toVar();
163
+ const compensated = stats.element( 1 ).z.greaterThan( 0 ).toVar();
164
+ const rowSum = ( i ) => {
136
165
 
137
- const k = select(
138
- cumulative.greaterThan( 0 ),
139
- float( lowerBound( height, j => rows.element( j ).z, float( row ).add( 1 ).div( height ).mul( cumulative ) ) ),
140
- float( height - 1 ),
141
- );
142
- cdf.element( uint( row ).mul( stride ).add( width ) ).assign( k.add( 0.5 ).div( height ) );
166
+ const r = rows.element( i ); return select( compensated, r.y, r.x ).add( floor );
167
+
168
+ };
169
+
170
+ const total = float( 0 ).toVar();
171
+ Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
172
+
173
+ total.addAssign( rowSum( i ) );
174
+
175
+ } );
176
+ const cumulative = float( 0 ).toVar();
177
+ const below = float( 0 ).toVar();
178
+ Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
179
+
180
+ cumulative.addAssign( rowSum( i ) );
181
+ const c = select( i.equal( int( height - 1 ) ), float( 1 ), cumulative.div( max( total, 1e-30 ) ) );
182
+ const sum = select( total.greaterThan( 0 ), c, float( 0 ) ).toVar();
183
+ cdf.element( at( width, i, 0 ) ).assign( sum );
184
+ cdf.element( at( width, i, 1 ) ).assign( below );
185
+ below.assign( sum );
143
186
 
144
187
  } );
145
188
 
146
189
  } )().compute( 1, [ 1 ] );
147
190
 
148
- const conditional = Fn( () => {
191
+ // GUIDES_PER_ENTRY (2) guides an entry, in its texel's z and w: the rows' in columns [0, width), the marginal's in
192
+ // column width.
193
+ const rowGuides = 2 * width * height;
194
+ const guides = Fn( () => {
149
195
 
150
196
  const idx = instanceIndex;
151
- If( idx.greaterThanEqual( uint( width * height ) ), () => {
197
+ If( idx.greaterThanEqual( uint( rowGuides + 2 * height ) ), () => {
152
198
 
153
199
  Return();
154
200
 
155
201
  } );
156
202
 
157
- const x = idx.mod( width );
158
- const y = idx.div( width );
159
- const useCompensated = stats.element( 1 ).z.greaterThan( 0 ).toVar();
160
- const r = rows.element( y ).toVar();
161
- const rowTotal = select( useCompensated, r.y, r.x ).toVar();
162
- const base = y.mul( width ).toVar();
163
- const k = lowerBound( width, j => {
203
+ If( idx.lessThan( uint( rowGuides ) ), () => {
204
+
205
+ const g = idx.mod( 2 * width ).toVar();
206
+ const y = idx.div( 2 * width ).toVar();
207
+ const x = firstAbove( width, j => cdf.element( at( j, y, 0 ) ), float( g ).div( 2 * width ) );
208
+ cdf.element( at( g.shiftRight( uint( 1 ) ), y, uint( 2 ).add( g.bitAnd( uint( 1 ) ) ) ) ).assign( float( x ) );
164
209
 
165
- const p = prefix.element( base.add( uint( j ) ) );
166
- return select( useCompensated, p.y, p.x );
210
+ } ).Else( () => {
167
211
 
168
- }, float( x ).add( 1 ).div( width ).mul( rowTotal ) );
169
- const column = select( rowTotal.greaterThan( 0 ), float( k ), float( width - 1 ) );
170
- cdf.element( y.mul( stride ).add( x ) ).assign( column.add( 0.5 ).div( width ) );
212
+ const g = idx.sub( uint( rowGuides ) ).toVar();
213
+ const y = firstAbove( height, j => cdf.element( at( width, j, 0 ) ), float( g ).div( 2 * height ) );
214
+ cdf.element( at( width, g.shiftRight( uint( 1 ) ), uint( 2 ).add( g.bitAnd( uint( 1 ) ) ) ) ).assign( float( y ) );
215
+
216
+ } );
171
217
 
172
- } )().compute( width * height, [ 64 ] );
218
+ } )().compute( rowGuides + 2 * height, [ 64 ] );
173
219
 
174
- return [ rowTotals, mean, prefixSums, marginal, conditional ];
220
+ return [ rowTotals, mean, prefixSums, totals, exactRows, exactMarginal, guides ];
175
221
 
176
222
  }
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import {
6
- Fn, uint, int,
7
- If,
6
+ Fn, uint, int, float,
7
+ If, dot, abs, select,
8
8
  instanceIndex,
9
9
  atomicLoad,
10
10
  Return,
@@ -76,9 +76,14 @@ export function buildExtendKernel( params ) {
76
76
  didHit: hitInfo.didHit, liftEnabled: shadowTerminatorOffset.greaterThan( 0.0 ),
77
77
  } );
78
78
 
79
+ // origin + t · direction sits off a large triangle by t's own error, which grows with the triangle's size: the
80
+ // distance stored is moved along the ray onto the triangle's plane (not where it grazes the plane).
81
+ const cosToPlane = dot( direction, facet.faceN ).toVar();
82
+ const planeDst = hitInfo.dst.add( select( abs( cosToPlane ).greaterThan( 1e-3 ), facet.surfaceOffset.div( cosToPlane ), float( 0.0 ) ) );
83
+
79
84
  writeHitPacked(
80
85
  hitBufferRW, rayID,
81
- hitInfo.dst,
86
+ planeDst,
82
87
  uint( hitInfo.triangleIndex ),
83
88
  hitInfo.uv.x, hitInfo.uv.y,
84
89
  hitInfo.normal,
@@ -46,7 +46,7 @@ export function buildFinalWriteKernel( params ) {
46
46
  visMode,
47
47
  // Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. Gated by a live uniform (1 = denoiser
48
48
  // on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
49
- auxGBufferEnabled,
49
+ auxGBufferEnabled, gBuffer = true, // compiled in only when requested (PathTracer.requestOutput( 'gBuffer' ))
50
50
  // Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
51
51
  // OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
52
52
  cleanAuxNormalEnabled,
@@ -98,7 +98,7 @@ export function buildFinalWriteKernel( params ) {
98
98
  const finalAlbedo = vec4( 0.0 ).xyz.toVar();
99
99
  // Albedo .w carries the hit distance — OIDN reads albedo as 3 channels, so it is free.
100
100
  const finalHitDist = float( 0.0 ).toVar();
101
- If( auxOn, () => {
101
+ if ( gBuffer ) If( auxOn, () => {
102
102
 
103
103
  const gbuf = readGBuffer( gBufferRO, rayID );
104
104
  finalNormalDepth.assign( gbDecodeNormalDepth( gbuf ) );
@@ -118,7 +118,7 @@ export function buildFinalWriteKernel( params ) {
118
118
 
119
119
  // Frozen pixels pass prev colour through unchanged (stale sample); active pixels accumulate.
120
120
  finalColor.assign( select( wasFrozen, prevAccumSample.xyz, mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) ) );
121
- If( auxOn.and( hasPreviousAux ), () => {
121
+ if ( gBuffer ) If( auxOn.and( hasPreviousAux ), () => {
122
122
 
123
123
  // Albedo averages cleanly (it's a colour); so does the normalized hit distance in .w.
124
124
  const prevAlbedoSample = texture( prevAlbedoTexture, prevUV, 0 ).toVar();
@@ -253,7 +253,7 @@ export function buildFinalWriteKernel( params ) {
253
253
 
254
254
  const uintCoord = uvec2( uint( gx ), uint( gy ) );
255
255
  textureStore( writeColorTex, uintCoord, vec4( finalColor, outputAlpha ) ).toWriteOnly();
256
- If( auxOn, () => {
256
+ if ( gBuffer ) If( auxOn, () => {
257
257
 
258
258
  textureStore( writeNDTex, uintCoord, finalNormalDepth ).toWriteOnly();
259
259
  textureStore( writeAlbedoTex, uintCoord, vec4( finalAlbedo, finalHitDist ) ).toWriteOnly();
@@ -13,7 +13,7 @@
13
13
  import {
14
14
  Fn, float, vec2, vec3, vec4, int, uint,
15
15
  If, instanceIndex, atomicLoad,
16
- localId, workgroupId,
16
+ localId, workgroupId, dot, max, select,
17
17
  } from 'three/tsl';
18
18
 
19
19
  import {
@@ -28,7 +28,7 @@ import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
28
28
  import {
29
29
  writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf,
30
30
  writeRayRadiance, writeGBuffer,
31
- writeMediumStack, writeFeatureThroughput, writeRngState,
31
+ writeMediumStack, writeFeatureThroughput, writeRngState, writeRngMis,
32
32
  } from '../Processor/PackedRayBuffer.js';
33
33
 
34
34
  const WG_SIZE = 16;
@@ -45,8 +45,10 @@ export function buildGenerateKernel( params ) {
45
45
  chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
46
46
  transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
47
47
  auxGBufferEnabled, // live uniform: 1 = init the per-pixel G-buffer (denoiser on), 0 = skip it
48
+ gBuffer = true, // compiled in only when requested (PathTracer.requestOutput( 'gBuffer' ))
48
49
  // listDriven: 1D dispatch over the active-pixel list (activeIndicesRO[tid] = LOCAL slot) instead of 2D.
49
50
  listDriven = false, activeIndicesRO = null, counters = null,
51
+ bidirectional: bdpt = null,
50
52
  } = params;
51
53
 
52
54
  const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
@@ -89,7 +91,7 @@ export function buildGenerateKernel( params ) {
89
91
  // tell subject from background. Output is unaffected: FinalWrite forces alpha 1 when transparent-bg is off.
90
92
  writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), float( 1.0 ) ) );
91
93
 
92
- If( auxOn, () => {
94
+ if ( gBuffer ) If( auxOn, () => {
93
95
 
94
96
  // default: normal +Z, depth 1 (far), black albedo (background/miss). Per-CHUNK G-buffer → LOCAL slot.
95
97
  writeGBuffer( gBufferRW, rayID, vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ), float( 0.0 ) );
@@ -100,7 +102,19 @@ export function buildGenerateKernel( params ) {
100
102
 
101
103
  writeMediumStack( rayBufferRW, rayID, uint( 0 ), uint( transmissiveBounces ), float( 1.0 ), float( 1.0 ), float( 1.0 ) );
102
104
 
103
- writeRngState( hitBufferRW, rayID, seed );
105
+ if ( bdpt ) {
106
+
107
+ // Georgiev (31): light tracing's N_L samples over this ray's density 1 / (A_pixel cos³θ).
108
+ const cosCamera = max( dot( ray.direction, bdpt.cameraForward ), 1e-4 );
109
+ const dVCM = select( bdpt.lightTrace.greaterThan( uint( 0 ) ),
110
+ bdpt.lib.mis( float( bdpt.lightPaths ).mul( bdpt.pixelArea ).mul( cosCamera ).mul( cosCamera ).mul( cosCamera ) ), float( 0.0 ) );
111
+ writeRngMis( hitBufferRW, rayID, seed, dVCM, float( 0.0 ) );
112
+
113
+ } else {
114
+
115
+ writeRngState( hitBufferRW, rayID, seed );
116
+
117
+ }
104
118
 
105
119
  };
106
120
 
@@ -15,18 +15,20 @@ import {
15
15
  packHalf2x16, unpackHalf2x16,
16
16
  } from 'three/tsl';
17
17
  import {
18
- unpackTriangleNormal, triangleRow, instanceRows, instanceFaceNormalToWorld,
18
+ unpackTriangleNormal, triangleRow, instanceRows, instanceFaceNormalToWorld, instancePointToWorld,
19
19
  } from './Common.js';
20
20
  import { shadowTerminatorLift } from './ShadowTerminator.js';
21
21
 
22
22
  /**
23
- * @returns {{ faceN: Node, liftScale: Node }} the facet normal on the viewer's side (the
24
- * interpolated one for a degenerate triangle), and the terminator lift (0 unless `liftEnabled`)
23
+ * @returns {{ faceN: Node, liftScale: Node, surfaceOffset: Node }} the facet normal on the viewer's side (the
24
+ * interpolated one for a degenerate triangle), the terminator lift (0 unless `liftEnabled`), and how far along
25
+ * faceN `hitPoint` lies from the triangle's plane (ExtendKernel moves the stored distance onto it)
25
26
  */
26
27
  export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hitPoint, smoothNormal, viewDir, didHit, liftEnabled } ) {
27
28
 
28
29
  const faceN = vec3( 0.0, 0.0, 1.0 ).toVar();
29
30
  const liftScale = float( 0.0 ).toVar();
31
+ const surfaceOffset = float( 0.0 ).toVar();
30
32
 
31
33
  If( didHit, () => {
32
34
 
@@ -47,9 +49,18 @@ export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hit
47
49
 
48
50
  } );
49
51
 
50
- const n = select( dot( face, face ).greaterThan( 0.0 ), normalize( face ), smoothNormal ).toVar();
52
+ const degenerate = dot( face, face ).lessThanEqual( 0.0 ).toVar();
53
+ const n = select( degenerate, smoothNormal, normalize( face ) ).toVar();
51
54
  faceN.assign( select( dot( n, viewDir ).lessThan( 0.0 ), n.negate(), n ) );
52
55
 
56
+ const corner = V0.toVar();
57
+ If( instanced, () => {
58
+
59
+ corner.assign( instancePointToWorld( instanceRows( bvhBuffer, instanceLeaf ), V0 ) );
60
+
61
+ } );
62
+ surfaceOffset.assign( select( degenerate, float( 0.0 ), dot( corner.sub( hitPoint ), faceN ) ) );
63
+
53
64
  If( liftEnabled, () => {
54
65
 
55
66
  liftScale.assign( shadowTerminatorLift( {
@@ -62,7 +73,26 @@ export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hit
62
73
 
63
74
  } );
64
75
 
65
- return { faceN, liftScale };
76
+ return { faceN, liftScale, surfaceOffset };
77
+
78
+ }
79
+
80
+ /**
81
+ * The triangle's unit winding normal in world space: what the traversal's front/back test sees (a mirrored
82
+ * placement keeps its object-space sense). The interpolated normal can face the other way near a coarse mesh's
83
+ * silhouette. Unit length because sideAccepts has a threshold: a raw cross product of a small triangle is under it.
84
+ */
85
+ export function windingNormal( triangleBuffer, bvhBuffer, triIdx, instanceLeaf ) {
86
+
87
+ const V0 = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 0 ).xyz ).toVar();
88
+ const local = cross( uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 1 ).xyz ).sub( V0 ), uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 2 ).xyz ).sub( V0 ) ).toVar();
89
+ const n = local.toVar();
90
+ If( instanceLeaf.greaterThanEqual( int( 0 ) ), () => {
91
+
92
+ n.assign( instanceFaceNormalToWorld( instanceRows( bvhBuffer, instanceLeaf ), local ) );
93
+
94
+ } );
95
+ return normalize( n );
66
96
 
67
97
  }
68
98