rayzee 10.0.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +252 -44
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-Hn35o6Z5.js +5145 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -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-BzNpiVHZ.js} +8 -6
  13. package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-Bk7bHg2B.js} +55 -120
  14. package/dist/PathTracer-72XufKAr.js +29976 -0
  15. package/dist/PhysicalSky-BgjdV88k.js +1219 -0
  16. package/dist/RayzeeRenderer-1QRO97eq.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-tnAoUL1j.js +94 -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-C67jbci-.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 +20 -7
  52. package/src/Processor/ArchiveImporter.js +1265 -0
  53. package/src/Processor/ArchiveReader.js +22 -4
  54. package/src/Processor/AssetLoader.js +327 -1763
  55. package/src/Processor/AtmosphereModel.js +1 -2
  56. package/src/Processor/BVHBuilder.js +14 -126
  57. package/src/Processor/BVHLeafFold.js +4 -6
  58. package/src/Processor/BVHRefitter.js +67 -46
  59. package/src/Processor/BufferLayout.js +291 -0
  60. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  61. package/src/Processor/EnvironmentExactTable.js +187 -0
  62. package/src/Processor/EquirectHDRInfo.js +30 -223
  63. package/src/Processor/FileFormats.js +116 -0
  64. package/src/Processor/GLTFDecoders.js +85 -0
  65. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  66. package/src/Processor/GeometryExtractor.js +15 -5
  67. package/src/Processor/HostMemory.js +7 -1
  68. package/src/Processor/IESParser.js +47 -35
  69. package/src/Processor/InstanceTable.js +1 -1
  70. package/src/Processor/KernelManager.js +35 -2
  71. package/src/Processor/MaterialPacking.js +10 -8
  72. package/src/Processor/PBRT/PBRTMaterials.js +254 -42
  73. package/src/Processor/PBRT/PBRTParser.js +49 -19
  74. package/src/Processor/PBRT/PBRTScattering.js +93 -0
  75. package/src/Processor/PBRT/PBRTSceneBuilder.js +1050 -75
  76. package/src/Processor/PBRT/PBRTTextureBake.js +179 -0
  77. package/src/Processor/PBRT/PFM.js +59 -0
  78. package/src/Processor/PackedRayBuffer.js +116 -9
  79. package/src/Processor/ParallelBVHBuilder.js +3 -6
  80. package/src/Processor/PhysicalSky.js +5 -4
  81. package/src/Processor/PlatformImageLoader.js +50 -6
  82. package/src/Processor/QueueManager.js +39 -5
  83. package/src/Processor/ReinsertionOptimizer.js +2 -2
  84. package/src/Processor/SceneMetadata.js +30 -5
  85. package/src/Processor/SceneProcessor.js +53 -62
  86. package/src/Processor/ShaderBuilder.js +6 -16
  87. package/src/Processor/SimpleSky.js +1 -62
  88. package/src/Processor/SolarLimb.js +3 -0
  89. package/src/Processor/SplitBLAS.js +1 -1
  90. package/src/Processor/StorageTexturePool.js +22 -1
  91. package/src/Processor/TLASBuilder.js +1 -3
  92. package/src/Processor/TextureBuckets.js +237 -0
  93. package/src/Processor/TextureCreator.js +181 -8
  94. package/src/Processor/ToneMapGPU.js +64 -15
  95. package/src/Processor/Workers/BVHRefitWorker.js +5 -2
  96. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  97. package/src/Processor/Workers/BVHWorker.js +5 -18
  98. package/src/Processor/Workers/CDFWorker.js +6 -183
  99. package/src/Processor/Workers/TexturesWorker.js +103 -18
  100. package/src/Processor/archiveFormats.js +7 -0
  101. package/src/Processor/utils.js +2 -1
  102. package/src/RayzeeRenderer.js +3900 -0
  103. package/src/RenderSettings.js +68 -62
  104. package/src/Stages/ASVGF.js +13 -14
  105. package/src/Stages/AutoExposure.js +17 -17
  106. package/src/Stages/BilateralFilter.js +2 -1
  107. package/src/Stages/Compositor.js +8 -23
  108. package/src/Stages/DenoiserSettings.js +182 -0
  109. package/src/Stages/EdgeFilter.js +2 -1
  110. package/src/Stages/MotionVector.js +1 -1
  111. package/src/Stages/NRD.js +11 -5
  112. package/src/Stages/NormalDepth.js +12 -8
  113. package/src/Stages/PathTracer.js +336 -43
  114. package/src/Stages/PathTracerStage.js +69 -66
  115. package/src/Stages/Variance.js +1 -1
  116. package/src/Storage/BLASCache.js +1 -1
  117. package/src/Storage/CDFCache.js +18 -17
  118. package/src/Storage/DownloadCache.js +1 -1
  119. package/src/Storage/SceneGraphCodec.js +46 -3
  120. package/src/Storage/SpillStore.js +1 -1
  121. package/src/Storage/StorageManager.js +45 -9
  122. package/src/Storage/areas.js +4 -0
  123. package/src/TSL/BVHTraversal.js +50 -25
  124. package/src/TSL/Bidirectional.js +208 -0
  125. package/src/TSL/BidirectionalLamps.js +132 -0
  126. package/src/TSL/Clearcoat.js +15 -6
  127. package/src/TSL/Common.js +20 -15
  128. package/src/TSL/ConnectKernel.js +167 -0
  129. package/src/TSL/Debugger.js +1 -1
  130. package/src/TSL/Displacement.js +4 -3
  131. package/src/TSL/EmissiveSampling.js +65 -18
  132. package/src/TSL/Environment.js +77 -106
  133. package/src/TSL/EnvironmentCDF.js +85 -39
  134. package/src/TSL/ExtendKernel.js +8 -3
  135. package/src/TSL/FinalWriteKernel.js +4 -4
  136. package/src/TSL/GenerateKernel.js +18 -4
  137. package/src/TSL/HitFacet.js +35 -5
  138. package/src/TSL/LightBVHSampling.js +40 -9
  139. package/src/TSL/LightGenerateKernel.js +293 -0
  140. package/src/TSL/LightGuide.js +198 -0
  141. package/src/TSL/LightSplatKernel.js +180 -0
  142. package/src/TSL/LightVisibility.js +111 -0
  143. package/src/TSL/LightsCore.js +13 -38
  144. package/src/TSL/LightsDirect.js +28 -29
  145. package/src/TSL/LightsIndirect.js +3 -1
  146. package/src/TSL/LightsSampling.js +239 -51
  147. package/src/TSL/MaterialEvaluation.js +113 -31
  148. package/src/TSL/MaterialProperties.js +44 -17
  149. package/src/TSL/MaterialTransmission.js +52 -47
  150. package/src/TSL/MergeKernel.js +224 -0
  151. package/src/TSL/PathTracerCore.js +65 -29
  152. package/src/TSL/Random.js +5 -1
  153. package/src/TSL/SceneResources.js +51 -0
  154. package/src/TSL/ShadeKernel.js +985 -101
  155. package/src/TSL/Struct.js +10 -0
  156. package/src/TSL/Sun.js +1 -1
  157. package/src/TSL/TextureSampling.js +66 -61
  158. package/src/addons/archives.js +14 -0
  159. package/src/addons/bidirectional.js +13 -0
  160. package/src/addons/color.js +32 -0
  161. package/src/addons/formats.js +16 -0
  162. package/src/addons/physicalSky.js +14 -0
  163. package/src/addons/storage.js +19 -0
  164. package/src/core.js +31 -0
  165. package/src/index.js +13 -23
  166. package/src/integrators/BidirectionalIntegrator.js +838 -0
  167. package/src/managers/AnimationManager.js +88 -6
  168. package/src/managers/CameraManager.js +103 -46
  169. package/src/managers/DenoisingManager.js +14 -10
  170. package/src/managers/EnvironmentManager.js +125 -103
  171. package/src/managers/GoboManager.js +9 -0
  172. package/src/managers/MaterialDataManager.js +48 -37
  173. package/src/managers/UniformManager.js +32 -10
  174. package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
  175. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  176. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -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
 
@@ -18,11 +18,14 @@ import {
18
18
  normalize,
19
19
  cross,
20
20
  length,
21
+ abs,
21
22
  } from 'three/tsl';
22
23
  import { MIN_PDF } from './Common.js';
24
+ import { sideAccepts } from './BVHTraversal.js';
23
25
  import { getRandomSample1D, getRandomSample2D } from './Random.js';
24
26
  import {
25
27
  EmissiveSample,
28
+ EmissiveSampleIndexed,
26
29
  sampleTriangle,
27
30
  interpolateNormal,
28
31
  fetchTriangleData,
@@ -103,7 +106,7 @@ export const lbvhNodeImportance = Fn( ( [ nMin, power, nMax, coneAxis, cosThetaO
103
106
  *
104
107
  * Returns an EmissiveSample struct.
105
108
  */
106
- export const sampleLightBVHTriangle = Fn( ( [
109
+ const makeSampleLightBVHTriangle = ( indexed ) => Fn( ( [
107
110
  hitPoint, surfaceNormal,
108
111
  rngState,
109
112
  pixelCoord, resolution, frame, dimBase,
@@ -111,9 +114,11 @@ export const sampleLightBVHTriangle = Fn( ( [
111
114
  emissiveTriangleBuffer,
112
115
  emissiveVec4Offset,
113
116
  triangleBuffer, bvhBuffer,
117
+ // The surface passes light through diffusely: a point behind it is as good as one in front.
118
+ throughSurface,
114
119
  ] ) => {
115
120
 
116
- const result = EmissiveSample( {
121
+ const fields = {
117
122
  position: vec3( 0.0 ),
118
123
  normal: vec3( 0.0 ),
119
124
  emission: vec3( 0.0 ),
@@ -123,7 +128,10 @@ export const sampleLightBVHTriangle = Fn( ( [
123
128
  area: float( 0.0 ),
124
129
  cosThetaLight: float( 0.0 ),
125
130
  valid: false,
126
- } ).toVar();
131
+ };
132
+ const result = ( indexed
133
+ ? EmissiveSampleIndexed( { ...fields, triangleIndex: int( - 1 ), faceNormal: vec3( 0.0 ) } )
134
+ : EmissiveSample( fields ) ).toVar();
127
135
 
128
136
  // Accumulated selection PDF (product of per-level choice probabilities)
129
137
  const selectionPdf = float( 1.0 ).toVar();
@@ -279,9 +287,9 @@ export const sampleLightBVHTriangle = Fn( ( [
279
287
  const samplePos = sphResult.position;
280
288
 
281
289
  const surfaceFacing = dot( dir, surfaceNormal );
282
- const emissiveFacing = dot( dir, geoNormal.negate() );
290
+ const emissiveFacing = abs( dot( dir, geoNormal ) );
283
291
 
284
- If( surfaceFacing.greaterThan( float( 0.0 ) ).and( emissiveFacing.greaterThan( float( 0.0 ) ) ), () => {
292
+ If( surfaceFacing.greaterThan( float( 0.0 ) ).or( throughSurface ).and( sideAccepts( triData.side, dot( dir, geoNormal ) ) ), () => {
285
293
 
286
294
  // Interpolate normal at sampled point via barycentric coords
287
295
  const barycentricCoords = barycentricFromPoint( samplePos, triData.v0, triData.v1, triData.v2 );
@@ -305,6 +313,12 @@ export const sampleLightBVHTriangle = Fn( ( [
305
313
  result.area.assign( area );
306
314
  result.cosThetaLight.assign( emissiveFacing );
307
315
  result.valid.assign( true );
316
+ if ( indexed ) {
317
+
318
+ result.triangleIndex.assign( triangleIndex );
319
+ result.faceNormal.assign( geoNormal );
320
+
321
+ }
308
322
 
309
323
  } );
310
324
 
@@ -322,9 +336,11 @@ export const sampleLightBVHTriangle = Fn( ( [
322
336
  const dir = toEmissive.div( dist );
323
337
 
324
338
  const surfaceFacing = dot( dir, surfaceNormal );
325
- const emissiveFacing = dot( dir, sampleNormal.negate() );
339
+ // The facet's cosine, not the shading normal's: it is what turns an area density into a solid-angle one.
340
+ const emissiveFacing = abs( dot( dir, geoNormal ) );
326
341
 
327
- If( surfaceFacing.greaterThan( float( 0.0 ) ).and( emissiveFacing.greaterThan( float( 0.0 ) ) ), () => {
342
+ // Edge-on (a two-sided emitter passes its side test there) the density is infinite and the light zero.
343
+ If( surfaceFacing.greaterThan( float( 0.0 ) ).or( throughSurface ).and( sideAccepts( triData.side, dot( dir, geoNormal ) ) ).and( emissiveFacing.greaterThan( float( 0.0 ) ) ), () => {
328
344
 
329
345
  // PDF: selectionPdf / area, converted to solid angle: pdfArea * distSq / cosLight
330
346
  const pdfArea = selectionPdf.div( max( area, float( 1e-10 ) ) );
@@ -339,6 +355,12 @@ export const sampleLightBVHTriangle = Fn( ( [
339
355
  result.area.assign( area );
340
356
  result.cosThetaLight.assign( emissiveFacing );
341
357
  result.valid.assign( true );
358
+ if ( indexed ) {
359
+
360
+ result.triangleIndex.assign( triangleIndex );
361
+ result.faceNormal.assign( geoNormal );
362
+
363
+ }
342
364
 
343
365
  } );
344
366
 
@@ -350,6 +372,9 @@ export const sampleLightBVHTriangle = Fn( ( [
350
372
 
351
373
  } );
352
374
 
375
+ export const sampleLightBVHTriangle = /*@__PURE__*/ makeSampleLightBVHTriangle( false );
376
+ export const sampleLightBVHTriangleIndexed = /*@__PURE__*/ makeSampleLightBVHTriangle( true );
377
+
353
378
  // ================================================================================
354
379
  // LIGHT-BVH MIS PDF (re-walk)
355
380
  // ================================================================================
@@ -482,6 +507,12 @@ export const calculateLightBVHPdf = Fn( ( [
482
507
 
483
508
  // Convert selection pdf → solid-angle measure using the SAME heuristic as the sampler.
484
509
  const triData = TriangleData.wrap( fetchTriangleData( triIdx, triangleBuffer, bvhBuffer, targetInstance ) );
510
+ // The sampler draws only the sides the triangle emits from (a unit normal: sideAccepts has a threshold).
511
+ If( sideAccepts( triData.side, dot( rayDir, normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) ) ) ).not(), () => {
512
+
513
+ selectionPdf.assign( float( 0.0 ) );
514
+
515
+ } );
485
516
  If( useSphericalSampling( triData.v0, triData.v1, triData.v2, shadingPoint ), () => {
486
517
 
487
518
  const solidAngle = sphericalTriangleSolidAngle( triData.v0, triData.v1, triData.v2, shadingPoint );
@@ -490,7 +521,7 @@ export const calculateLightBVHPdf = Fn( ( [
490
521
  } ).Else( () => {
491
522
 
492
523
  const geoNormal = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
493
- const cosLight = max( dot( rayDir.negate(), geoNormal ), float( 0.001 ) );
524
+ const cosLight = max( abs( dot( rayDir, geoNormal ) ), float( 0.001 ) );
494
525
  const area = triangleArea( triData.v0, triData.v1, triData.v2 );
495
526
  const distSq = hitDistance.mul( hitDistance );
496
527
  const pdfArea = selectionPdf.div( max( area, float( 1e-10 ) ) );
@@ -502,6 +533,6 @@ export const calculateLightBVHPdf = Fn( ( [
502
533
 
503
534
  } );
504
535
 
505
- return max( result, MIN_PDF );
536
+ return select( result.greaterThan( 0.0 ), max( result, MIN_PDF ), float( 0.0 ) );
506
537
 
507
538
  } );