rayzee 9.0.0 → 9.1.1

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 (95) hide show
  1. package/README.md +139 -8
  2. package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
  3. package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
  4. package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
  5. package/dist/rayzee.es.js +10353 -5337
  6. package/dist/rayzee.umd.js +265 -265
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +4 -0
  9. package/src/EngineDefaults.js +38 -56
  10. package/src/EngineEvents.js +3 -0
  11. package/src/EngineIssues.js +6 -0
  12. package/src/Headless.js +3 -1
  13. package/src/Passes/AIUpscaler.js +2 -1
  14. package/src/Passes/OIDNDenoiser.js +3 -6
  15. package/src/PathTracerApp.js +290 -19
  16. package/src/Processor/ArchiveCache.js +120 -0
  17. package/src/Processor/ArchiveReader.js +179 -32
  18. package/src/Processor/AssetLoader.js +514 -76
  19. package/src/Processor/AtmosphereModel.js +465 -0
  20. package/src/Processor/BVHBuilder.js +4 -1
  21. package/src/Processor/BVHLeafFold.js +100 -0
  22. package/src/Processor/BVHRefitter.js +98 -214
  23. package/src/Processor/ChunkedRecords.js +189 -4
  24. package/src/Processor/EmissiveTriangleBuilder.js +8 -0
  25. package/src/Processor/GeometryExtractor.js +238 -40
  26. package/src/Processor/InstanceTable.js +55 -37
  27. package/src/Processor/KernelManager.js +4 -1
  28. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  29. package/src/Processor/PBRT/PBRTParser.js +132 -22
  30. package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
  31. package/src/Processor/PBRT/buffers.js +27 -0
  32. package/src/Processor/PBRT/index.js +55 -15
  33. package/src/Processor/PackedRayBuffer.js +10 -13
  34. package/src/Processor/ParallelBVHBuilder.js +36 -15
  35. package/src/Processor/PhysicalSky.js +308 -0
  36. package/src/Processor/QueueManager.js +1 -0
  37. package/src/Processor/SceneProcessor.js +1132 -181
  38. package/src/Processor/SplitBLAS.js +249 -0
  39. package/src/Processor/SunPosition.js +41 -0
  40. package/src/Processor/TextureCreator.js +21 -0
  41. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  42. package/src/Processor/Workers/BVHWorker.js +17 -13
  43. package/src/Processor/ZipReader.js +207 -0
  44. package/src/RenderSettings.js +38 -0
  45. package/src/SceneState/SceneState.js +367 -0
  46. package/src/SceneState/portable.js +52 -0
  47. package/src/Stages/NormalDepth.js +7 -6
  48. package/src/Stages/PathTracer.js +108 -4
  49. package/src/Stages/PathTracerStage.js +251 -104
  50. package/src/Storage/BLASCache.js +190 -0
  51. package/src/Storage/CDFCache.js +77 -0
  52. package/src/Storage/DownloadCache.js +266 -0
  53. package/src/Storage/GeometrySpill.js +217 -0
  54. package/src/Storage/SceneGraphCodec.js +503 -0
  55. package/src/Storage/SpillStore.js +112 -0
  56. package/src/Storage/StorageManager.js +934 -0
  57. package/src/Storage/StorageOps.js +137 -0
  58. package/src/Storage/StorageWorker.js +37 -0
  59. package/src/Storage/events.js +29 -0
  60. package/src/Storage/identity.js +77 -0
  61. package/src/Storage/index.js +9 -0
  62. package/src/Storage/inlineTransport.js +24 -0
  63. package/src/Storage/locks.js +99 -0
  64. package/src/Storage/openStorage.js +87 -0
  65. package/src/Storage/sceneCachePolicy.js +12 -0
  66. package/src/Storage/shared.js +9 -0
  67. package/src/Storage/transport.js +62 -0
  68. package/src/TSL/Atmosphere.js +684 -0
  69. package/src/TSL/BVHTraversal.js +288 -239
  70. package/src/TSL/Common.js +11 -2
  71. package/src/TSL/Displacement.js +6 -6
  72. package/src/TSL/EmissiveSampling.js +5 -5
  73. package/src/TSL/EnvironmentCDF.js +176 -0
  74. package/src/TSL/GenerateKernel.js +3 -3
  75. package/src/TSL/HitFacet.js +4 -4
  76. package/src/TSL/LightsDirect.js +8 -8
  77. package/src/TSL/LightsSampling.js +55 -37
  78. package/src/TSL/Random.js +1 -0
  79. package/src/TSL/ShadeKernel.js +75 -19
  80. package/src/TSL/Sun.js +40 -0
  81. package/src/TSL/TextureSampling.js +6 -7
  82. package/src/index.js +18 -0
  83. package/src/managers/CameraManager.js +169 -1
  84. package/src/managers/EnvironmentManager.js +263 -33
  85. package/src/managers/GoboManager.js +19 -8
  86. package/src/managers/IESManager.js +3 -3
  87. package/src/managers/LightManager.js +98 -5
  88. package/src/managers/MaterialDataManager.js +22 -2
  89. package/src/managers/TransformManager.js +26 -0
  90. package/src/managers/UniformManager.js +5 -3
  91. package/src/managers/VideoRenderManager.js +3 -1
  92. package/src/managers/timeline/CameraTrack.js +22 -0
  93. package/src/managers/timeline/TimelineManager.js +14 -0
  94. package/src/version.js +3 -0
  95. package/src/Processor/ProceduralSky.js +0 -291
@@ -31,7 +31,9 @@ const MAX_PARALLEL_WORKERS = 8;
31
31
  * @param {number} depth - Maximum BVH depth
32
32
  * @param {Function|null} progressCallback - Optional progress callback (0-100)
33
33
  * @param {Object} config - Builder config (maxLeafSize, numBins, treelet settings, etc.)
34
- * @returns {Promise<{bvhData: Float32Array, bvhRoot: true, reorderedTriangles: Uint32Array, splitStats: Object}>}
34
+ * @returns {Promise<{bvhData: Float32Array, bvhRoot: true, order?: Uint32Array, triangles?: Uint32Array, reorderedTriangles?: Uint32Array, splitStats: Object}>}
35
+ * the parallel build returns the input `triangles` and the BVH `order` of them (record i of the
36
+ * result is record order[i] of the input); its single-worker fallback returns `reorderedTriangles`.
35
37
  */
36
38
  export function buildBVHParallel( triangles, depth, progressCallback, config ) {
37
39
 
@@ -45,9 +47,10 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
45
47
 
46
48
  ( async () => {
47
49
 
48
- // Allocate SharedArrayBuffers
49
- const sharedTriangleData = new SharedArrayBuffer( triangles.byteLength );
50
- new Uint32Array( sharedTriangleData ).set( triangles );
50
+ // A caller that already holds the records in a SharedArrayBuffer hands it over as is.
51
+ const owned = triangles.buffer instanceof SharedArrayBuffer && triangles.byteOffset === 0 && triangles.byteLength === triangles.buffer.byteLength;
52
+ const sharedTriangleData = owned ? triangles.buffer : new SharedArrayBuffer( triangles.byteLength );
53
+ if ( ! owned ) new Uint32Array( sharedTriangleData ).set( triangles );
51
54
  triangles = null; // shared copy is the only one needed; the fallback rebuilds from it
52
55
 
53
56
  const sharedCentroids = new SharedArrayBuffer( triangleCount * 3 * 4 );
@@ -55,7 +58,9 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
55
58
  const sharedBMax = new SharedArrayBuffer( triangleCount * 3 * 4 );
56
59
  const sharedIndices = new SharedArrayBuffer( triangleCount * 4 );
57
60
  const sharedMortonCodes = new SharedArrayBuffer( triangleCount * 4 );
58
- const sharedReorderBuffer = new SharedArrayBuffer( triangleCount * FPT * 4 );
61
+ // No reordered copy: the caller writes records in `order` from `triangles` itself. At
62
+ // 14.9M triangles that copy was 1.2 GB, and the allocation that failed the build.
63
+ const sharedReorderBuffer = null;
59
64
 
60
65
  // Phase 1: Coordinator worker
61
66
  const coordinatorWorker = new BVHWorker();
@@ -96,11 +101,21 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
96
101
  settled = true;
97
102
  log.warn( `parallel build failed (${reason}), falling back to single worker` );
98
103
  cleanup();
99
- // Copy from SharedArrayBuffer to regular ArrayBuffer for transfer
100
- const restoredBuffer = new ArrayBuffer( sharedTriangleData.byteLength );
101
- new Uint32Array( restoredBuffer ).set( new Uint32Array( sharedTriangleData ) );
102
- const restoredTriangles = new Uint32Array( restoredBuffer );
103
- resolve( buildSingleWorker( restoredTriangles, depth, progressCallback, config ) );
104
+
105
+ try {
106
+
107
+ // Copy from SharedArrayBuffer to regular ArrayBuffer for transfer
108
+ const restoredBuffer = new ArrayBuffer( sharedTriangleData.byteLength );
109
+ new Uint32Array( restoredBuffer ).set( new Uint32Array( sharedTriangleData ) );
110
+ const restoredTriangles = new Uint32Array( restoredBuffer );
111
+ resolve( buildSingleWorker( restoredTriangles, depth, progressCallback, config ) );
112
+
113
+ } catch ( error ) {
114
+
115
+ // Out of memory for the copy as well: fail the build rather than leave it waiting.
116
+ reject( error );
117
+
118
+ }
104
119
 
105
120
  };
106
121
 
@@ -150,8 +165,11 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
150
165
 
151
166
  settled = true;
152
167
  cleanup();
153
- const reorderedTriangles = new Uint32Array( sharedReorderBuffer );
154
- resolve( { bvhData: msg.bvhData, bvhRoot: true, reorderedTriangles, originalToBvh: msg.originalToBvh || null, splitStats: phase1Stats || {} } );
168
+ resolve( {
169
+ bvhData: msg.bvhData, bvhRoot: true,
170
+ order: new Uint32Array( sharedIndices ), triangles: new Uint32Array( sharedTriangleData ),
171
+ originalToBvh: msg.originalToBvh || null, splitStats: phase1Stats || {}
172
+ } );
155
173
  return;
156
174
 
157
175
  }
@@ -215,7 +233,8 @@ async function handlePhase2(
215
233
  sharedTriangleData,
216
234
  sharedIndices,
217
235
  sharedReorderBuffer,
218
- triangleCount
236
+ triangleCount,
237
+ foldLeaves: config.foldLeaves
219
238
  }, [ topFlatData.buffer ] );
220
239
  return;
221
240
 
@@ -293,7 +312,8 @@ async function handlePhase2(
293
312
  sharedTriangleData,
294
313
  sharedIndices,
295
314
  sharedReorderBuffer,
296
- triangleCount
315
+ triangleCount,
316
+ foldLeaves: config.foldLeaves
297
317
  }, transferables );
298
318
 
299
319
  };
@@ -464,7 +484,8 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
464
484
  reportProgress: !! progressCallback,
465
485
  sharedReorderBuffer,
466
486
  treeletOptimization: config.treeletOptimization,
467
- reinsertionOptimization: config.reinsertionOptimization
487
+ reinsertionOptimization: config.reinsertionOptimization,
488
+ foldLeaves: config.foldLeaves
468
489
  }, [ transferBuffer ] );
469
490
 
470
491
  } )().catch( ( error ) => {
@@ -0,0 +1,308 @@
1
+ import {
2
+ RGBAFormat, RedFormat, FloatType, LinearFilter, RepeatWrapping, ClampToEdgeWrapping,
3
+ EquirectangularReflectionMapping, LinearSRGBColorSpace, DataTexture, Vector3, Vector4,
4
+ } from 'three';
5
+ import { StorageInstancedBufferAttribute } from 'three/webgpu';
6
+ import { storage, uniform } from 'three/tsl';
7
+
8
+ import { gpuOnlyStorageAttribute } from '../TSL/patches.js';
9
+ import { freeStorageAttribute } from './PackedRayBuffer.js';
10
+ import {
11
+ TRANSMITTANCE_W, TRANSMITTANCE_H, MS_MU_S, MS_H, MS_BANDS, MS_SECTORS, MS_CELLS, MS_VIEW_MU, MS_VIEW_PHI, MS_VIEWS, IRRADIANCE_SIZE, SKY_COLUMNS,
12
+ buildTransmittanceKernel, buildIncomingKernel, buildScatterKernel, buildIrradianceKernel, buildSkyViewKernels,
13
+ } from '../TSL/Atmosphere.js';
14
+ import { buildEnvironmentCDFKernels, cdfRowStride } from '../TSL/EnvironmentCDF.js';
15
+ import { getWorkingMatrix } from '../Color/WorkingMatrix.js';
16
+ import {
17
+ LAMBDA_COUNT, MIE_PHASE_G, SKY_RADIANCE_SCALE, SOLAR_SPECTRUM,
18
+ atmosphereCoefficients, spectrumToRec709, sunLight, horizonDipSin, phaseCellWeights,
19
+ } from './AtmosphereModel.js';
20
+
21
+ // Filled on the GPU only: created without pixels, so three.js never uploads any.
22
+ function gpuTexture( width, height, format ) {
23
+
24
+ const texture = new DataTexture( null, width, height, format, FloatType );
25
+ texture.source.dataReady = false;
26
+ texture.needsUpdate = true;
27
+ texture._isPhysicalSky = true;
28
+ return texture;
29
+
30
+ }
31
+
32
+ /**
33
+ * Physically based clear sky, baked on the GPU into an equirect and its importance-sampling
34
+ * table. The sun disc is not in the texture: `bake()` returns it as a light.
35
+ */
36
+ export class PhysicalSky {
37
+
38
+ constructor( width = 1024, height = 512, quality = {} ) {
39
+
40
+ this.width = width;
41
+ this.height = height;
42
+ this._quality = quality;
43
+ this.lastRenderTime = 0;
44
+
45
+ this._texture = gpuTexture( width, height, RGBAFormat );
46
+ this._texture.mapping = EquirectangularReflectionMapping;
47
+ this._texture.colorSpace = LinearSRGBColorSpace;
48
+ this._texture.minFilter = LinearFilter;
49
+ this._texture.magFilter = LinearFilter;
50
+ this._texture.wrapS = RepeatWrapping;
51
+ this._texture.wrapT = ClampToEdgeWrapping;
52
+ this._texture.generateMipmaps = false;
53
+ this._cdfTexture = gpuTexture( width + 1, height, RedFormat );
54
+ this._rec709Weights = spectrumToRec709( SKY_RADIANCE_SCALE );
55
+
56
+ this._attrs = null;
57
+ this._kernels = null;
58
+ this._airKey = null;
59
+ this._transmittanceBuilt = false;
60
+
61
+ }
62
+
63
+ _init() {
64
+
65
+ const vec4s = n => Array.from( { length: n }, () => uniform( new Vector4() ) );
66
+ const coefficient = vec4s( 6 * 4 );
67
+ this._uniforms = {
68
+ coefficient,
69
+ rgb: vec4s( 12 ),
70
+ sunDirection: uniform( new Vector3( 0, 1, 0 ) ),
71
+ altitude: uniform( 0.05 ),
72
+ sunSinRadius: uniform( 0.0046 ),
73
+ };
74
+
75
+ const attr = count => gpuOnlyStorageAttribute( count, 4 );
76
+ this._attrs = {
77
+ transmittance: attr( TRANSMITTANCE_W * TRANSMITTANCE_H ),
78
+ incomingA: attr( MS_MU_S * MS_H * MS_CELLS * 4 ),
79
+ incomingB: attr( MS_MU_S * MS_H * MS_CELLS * 4 ),
80
+ transfer: attr( MS_MU_S * MS_H * MS_CELLS * 4 ),
81
+ multipleScattering: attr( MS_MU_S * MS_H * MS_VIEWS * 8 ),
82
+ psiSum: attr( MS_MU_S * MS_H * MS_VIEWS * 8 ),
83
+ groundE: attr( MS_MU_S * 4 ),
84
+ irradiance: attr( IRRADIANCE_SIZE * 4 ),
85
+ sky: attr( this.width * this.height ),
86
+ skyRows: attr( SKY_COLUMNS * this.height ),
87
+ cdfRows: attr( this.height ),
88
+ cdfPrefix: gpuOnlyStorageAttribute( this.width * this.height, 2 ),
89
+ cdfStats: attr( 2 ),
90
+ cdf: gpuOnlyStorageAttribute( cdfRowStride( this.width ) * this.height, 1 ),
91
+ phaseCells: new StorageInstancedBufferAttribute( phaseCellWeights( MS_VIEW_MU, MS_VIEW_PHI, MS_BANDS, MS_SECTORS ), 4 ),
92
+ };
93
+ const node = ( key, type = 'vec4' ) => storage( this._attrs[ key ], type, this._attrs[ key ].count );
94
+
95
+ const slice = i => coefficient.slice( i * 4, i * 4 + 4 );
96
+ const io = {
97
+ transmittance: node( 'transmittance' ),
98
+ transfer: node( 'transfer' ),
99
+ psiSum: node( 'psiSum' ),
100
+ groundE: node( 'groundE' ),
101
+ phaseCells: node( 'phaseCells' ),
102
+ multipleScattering: node( 'multipleScattering' ),
103
+ irradiance: node( 'irradiance' ),
104
+ sky: node( 'sky' ),
105
+ skyRows: node( 'skyRows' ),
106
+ coefficients: {
107
+ rayleigh: slice( 0 ), mieScattering: slice( 1 ), mieExtinction: slice( 2 ),
108
+ ozone: slice( 3 ), solar: slice( 4 ), albedo: slice( 5 ),
109
+ },
110
+ rgb: this._uniforms.rgb,
111
+ sunDirection: this._uniforms.sunDirection,
112
+ altitude: this._uniforms.altitude,
113
+ sunSinRadius: this._uniforms.sunSinRadius,
114
+ mieG: MIE_PHASE_G,
115
+ };
116
+
117
+ const q = this._quality;
118
+ const incomingA = node( 'incomingA' ), incomingB = node( 'incomingB' );
119
+ this._kernels = {
120
+ transmittance: buildTransmittanceKernel( io ),
121
+ air: [
122
+ buildIncomingKernel( io, { first: true, out: incomingA, ...q.incoming } ),
123
+ buildScatterKernel( io, { incoming: incomingA, stage: 'first' } ),
124
+ buildIncomingKernel( io, { first: false, out: incomingB, ...q.incoming } ),
125
+ buildScatterKernel( io, { incoming: incomingB, stage: 'middle' } ),
126
+ buildIncomingKernel( io, { first: false, out: incomingA, ...q.incoming } ),
127
+ buildScatterKernel( io, { incoming: incomingA, stage: 'last' } ),
128
+ buildIrradianceKernel( io, q.irradiance ),
129
+ ],
130
+ sky: buildSkyViewKernels( io, { width: this.width, height: this.height, ...q.sky } ),
131
+ cdf: buildEnvironmentCDFKernels( {
132
+ pixels: io.sky, rows: node( 'cdfRows' ), prefix: node( 'cdfPrefix', 'vec2' ), stats: node( 'cdfStats' ),
133
+ cdf: node( 'cdf', 'float' ), width: this.width, height: this.height,
134
+ } ),
135
+ };
136
+
137
+ this._setSpectrum( 4, SOLAR_SPECTRUM );
138
+
139
+ }
140
+
141
+ // Straight into the working space: pixels on the GPU cannot be converted afterwards.
142
+ _setColorWeights() {
143
+
144
+ const w = this._rec709Weights;
145
+ const m = getWorkingMatrix();
146
+ const out = [ 0, 0, 0, 0 ];
147
+ for ( let row = 0; row < 3; row ++ ) {
148
+
149
+ for ( let g = 0; g < 4; g ++ ) {
150
+
151
+ for ( let j = 0; j < 4; j ++ ) {
152
+
153
+ const k = g * 4 + j;
154
+ out[ j ] = m
155
+ ? m[ row * 3 ] * w[ k ] + m[ row * 3 + 1 ] * w[ LAMBDA_COUNT + k ] + m[ row * 3 + 2 ] * w[ 2 * LAMBDA_COUNT + k ]
156
+ : w[ row * LAMBDA_COUNT + k ];
157
+
158
+ }
159
+
160
+ this._uniforms.rgb[ row * 4 + g ].value.fromArray( out );
161
+
162
+ }
163
+
164
+ }
165
+
166
+ }
167
+
168
+ _copyToTextures( renderer ) {
169
+
170
+ const backend = renderer.backend;
171
+ renderer.initTexture( this._texture );
172
+ renderer.initTexture( this._cdfTexture );
173
+ const encoder = backend.device.createCommandEncoder( { label: 'PhysicalSky' } );
174
+ encoder.copyBufferToTexture(
175
+ { buffer: backend.get( this._attrs.sky ).buffer, bytesPerRow: this.width * 16, rowsPerImage: this.height },
176
+ { texture: backend.get( this._texture ).texture },
177
+ [ this.width, this.height ],
178
+ );
179
+ encoder.copyBufferToTexture(
180
+ { buffer: backend.get( this._attrs.cdf ).buffer, bytesPerRow: cdfRowStride( this.width ) * 4, rowsPerImage: this.height },
181
+ { texture: backend.get( this._cdfTexture ).texture },
182
+ [ this.width + 1, this.height ],
183
+ );
184
+ backend.device.queue.submit( [ encoder.finish() ] );
185
+
186
+ }
187
+
188
+ _setSpectrum( slot, values ) {
189
+
190
+ for ( let g = 0; g < 4; g ++ ) {
191
+
192
+ this._uniforms.coefficient[ slot * 4 + g ].value.set( values[ g * 4 ], values[ g * 4 + 1 ], values[ g * 4 + 2 ], values[ g * 4 + 3 ] );
193
+
194
+ }
195
+
196
+ }
197
+
198
+ /**
199
+ * Queue the bake on the GPU; the textures fill ahead of any frame submitted later. Only the
200
+ * table's two normalisers come back, in `stats`.
201
+ *
202
+ * @param {import('three/webgpu').WebGPURenderer} renderer - initialised
203
+ * @param {Object} p
204
+ * @param {number[]} p.sunDirection - unit, y up, in the sky's own frame
205
+ * @param {number} p.turbidity
206
+ * @param {number} p.ozone - Dobson units
207
+ * @param {number} p.airDensity
208
+ * @param {number[]} p.groundAlbedo - linear Rec.709
209
+ * @param {number} p.altitude - metres
210
+ * @param {number} p.sunAngularDiameter - radians
211
+ * @param {number} [p.sunStrength=1]
212
+ * @returns {{ texture: DataTexture, cdfTexture: DataTexture, sun: Object, stats: Promise<{ totalSum: number, compensationDelta: number }> }}
213
+ */
214
+ bake( renderer, p ) {
215
+
216
+ const start = performance.now();
217
+ if ( ! this._kernels ) this._init();
218
+
219
+ const air = {
220
+ airDensity: p.airDensity, turbidity: p.turbidity, ozone: p.ozone,
221
+ groundAlbedo: [ ...p.groundAlbedo ],
222
+ };
223
+ const coefficients = atmosphereCoefficients( air );
224
+ const altitude = Math.max( p.altitude, 1 ) / 1000;
225
+ const halfAngle = p.sunAngularDiameter / 2;
226
+
227
+ const u = this._uniforms;
228
+ u.sunDirection.value.fromArray( p.sunDirection ).normalize();
229
+ u.altitude.value = altitude;
230
+ u.sunSinRadius.value = Math.sin( halfAngle );
231
+ this._setColorWeights();
232
+
233
+ const dispatches = [];
234
+ if ( ! this._transmittanceBuilt ) {
235
+
236
+ dispatches.push( this._kernels.transmittance );
237
+ this._transmittanceBuilt = true;
238
+
239
+ }
240
+
241
+ const airKey = JSON.stringify( [ air, u.sunSinRadius.value ] );
242
+ if ( airKey !== this._airKey ) {
243
+
244
+ this._setSpectrum( 0, coefficients.rayleigh );
245
+ this._setSpectrum( 1, coefficients.mieScattering );
246
+ this._setSpectrum( 2, coefficients.mieExtinction );
247
+ this._setSpectrum( 3, coefficients.ozone );
248
+ this._setSpectrum( 5, coefficients.albedo );
249
+ dispatches.push( ...this._kernels.air );
250
+ this._airKey = airKey;
251
+
252
+ }
253
+
254
+ dispatches.push( ...this._kernels.sky, ...this._kernels.cdf );
255
+ renderer.compute( dispatches );
256
+ this._copyToTextures( renderer );
257
+
258
+ const stats = renderer.getArrayBufferAsync( this._attrs.cdfStats ).then( buffer => {
259
+
260
+ const s = new Float32Array( buffer );
261
+ return { totalSum: s[ 4 ], compensationDelta: s[ 5 ] };
262
+
263
+ } );
264
+
265
+ const sun = sunLight( {
266
+ direction: [ u.sunDirection.value.x, u.sunDirection.value.y, u.sunDirection.value.z ],
267
+ altitude,
268
+ angularDiameter: p.sunAngularDiameter,
269
+ strength: p.sunStrength ?? 1,
270
+ coefficients,
271
+ } );
272
+ sun.cosHalfAngle = Math.cos( halfAngle );
273
+ sun.sinHalfAngle = Math.sin( halfAngle );
274
+ sun.horizonSin = horizonDipSin( altitude );
275
+
276
+ this.lastRenderTime = performance.now() - start;
277
+ return { texture: this._texture, cdfTexture: this._cdfTexture, sun, stats };
278
+
279
+ }
280
+
281
+ /** The last bake's pixels (RGBA, row 0 first), packed table and stats — for tests and debugging. */
282
+ async readBack( renderer ) {
283
+
284
+ const [ pixels, cdf, stats ] = await Promise.all(
285
+ [ 'sky', 'cdf', 'cdfStats' ].map( key => renderer.getArrayBufferAsync( this._attrs[ key ] ) )
286
+ );
287
+ return { pixels: new Float32Array( pixels ), cdf: new Float32Array( cdf ), cdfStride: cdfRowStride( this.width ), stats: new Float32Array( stats ) };
288
+
289
+ }
290
+
291
+ getLastRenderTime() {
292
+
293
+ return this.lastRenderTime;
294
+
295
+ }
296
+
297
+ dispose( renderer = null ) {
298
+
299
+ for ( const k of Object.values( this._kernels ?? {} ).flat() ) k.dispose();
300
+ for ( const a of Object.values( this._attrs ?? {} ) ) freeStorageAttribute( renderer, a );
301
+ this._kernels = null;
302
+ this._attrs = null;
303
+ this._texture.dispose();
304
+ this._cdfTexture.dispose();
305
+
306
+ }
307
+
308
+ }
@@ -50,6 +50,7 @@ export const RAY_FLAG = {
50
50
  HAS_HIT_OPAQUE: 1 << 16, // bit 16: ray chain has hit non-transmissive geometry (transparent-bg alpha; megakernel hasHitOpaqueSurface)
51
51
  AUX_LOCKED: 1 << 17, // bit 17: OIDN aux (normal/albedo) locked onto first non-specular hit (megakernel auxLocked)
52
52
  REDIRECTED: 1 << 18, // bit 18: ray has been redirected (refraction/reflection/SSS/opaque scatter) since the camera, so env it reaches is transported light (sharp), NOT the direct backdrop. NOT set by pure alpha/transparent passthrough (direction unchanged) → env through cutout holes is still the backdrop (blur/intensity/show/color/ground-projection). Set via bitOr only (positive mask) so it never disturbs ACTIVE/bounce bits.
53
+ SUN_NEE: 1 << 19, // bit 19: the last opaque scatter's sun NEE could have drawn this direction (MIS partner for a sun hit on miss)
53
54
  };
54
55
 
55
56
  export class QueueManager {