rayzee 8.8.2 → 9.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.
- package/README.md +210 -20
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CbNvQjCw.js} +5 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10795 -5324
- package/dist/rayzee.umd.js +279 -268
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +60 -76
- package/src/EngineEvents.js +7 -0
- package/src/EngineIssues.js +6 -0
- package/src/Headless.js +3 -1
- package/src/Passes/AIUpscaler.js +2 -1
- package/src/Passes/OIDNDenoiser.js +3 -6
- package/src/Passes/OIDNTemporalHistory.js +25 -7
- package/src/PathTracerApp.js +313 -25
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +523 -76
- package/src/Processor/AtmosphereModel.js +465 -0
- package/src/Processor/BVHBuilder.js +4 -1
- package/src/Processor/BVHLeafFold.js +100 -0
- package/src/Processor/BVHRefitter.js +98 -214
- package/src/Processor/ChunkedRecords.js +189 -4
- package/src/Processor/EmissiveTriangleBuilder.js +8 -0
- package/src/Processor/GeometryExtractor.js +238 -40
- package/src/Processor/InstanceTable.js +55 -37
- package/src/Processor/PBRT/PBRTCurves.js +104 -27
- package/src/Processor/PBRT/PBRTParser.js +132 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +210 -55
- package/src/Processor/PBRT/buffers.js +27 -0
- package/src/Processor/PBRT/index.js +55 -15
- package/src/Processor/PackedRayBuffer.js +10 -13
- package/src/Processor/ParallelBVHBuilder.js +36 -15
- package/src/Processor/PhysicalSky.js +308 -0
- package/src/Processor/QueueManager.js +1 -0
- package/src/Processor/SceneProcessor.js +1132 -181
- package/src/Processor/SplitBLAS.js +249 -0
- package/src/Processor/SunPosition.js +41 -0
- package/src/Processor/TextureCreator.js +21 -0
- package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
- package/src/Processor/Workers/BVHWorker.js +17 -13
- package/src/Processor/ZipReader.js +207 -0
- package/src/RenderSettings.js +49 -8
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/MotionVector.js +8 -42
- package/src/Stages/NRD.js +30 -21
- package/src/Stages/NormalDepth.js +12 -23
- package/src/Stages/PathTracer.js +114 -6
- package/src/Stages/PathTracerStage.js +251 -104
- package/src/Storage/BLASCache.js +190 -0
- package/src/Storage/CDFCache.js +77 -0
- package/src/Storage/DownloadCache.js +266 -0
- package/src/Storage/GeometrySpill.js +217 -0
- package/src/Storage/SceneGraphCodec.js +503 -0
- package/src/Storage/SpillStore.js +112 -0
- package/src/Storage/StorageManager.js +934 -0
- package/src/Storage/StorageOps.js +137 -0
- package/src/Storage/StorageWorker.js +37 -0
- package/src/Storage/events.js +29 -0
- package/src/Storage/identity.js +77 -0
- package/src/Storage/index.js +9 -0
- package/src/Storage/inlineTransport.js +24 -0
- package/src/Storage/locks.js +99 -0
- package/src/Storage/openStorage.js +87 -0
- package/src/Storage/sceneCachePolicy.js +12 -0
- package/src/Storage/shared.js +9 -0
- package/src/Storage/transport.js +62 -0
- package/src/TSL/Atmosphere.js +684 -0
- package/src/TSL/BVHTraversal.js +288 -239
- package/src/TSL/CameraRay.js +99 -14
- package/src/TSL/Common.js +11 -2
- package/src/TSL/DebugKernel.js +2 -2
- package/src/TSL/Displacement.js +6 -6
- package/src/TSL/EmissiveSampling.js +5 -5
- package/src/TSL/EnvironmentCDF.js +176 -0
- package/src/TSL/GenerateKernel.js +5 -5
- package/src/TSL/HitFacet.js +4 -4
- package/src/TSL/LightsDirect.js +8 -8
- package/src/TSL/LightsSampling.js +55 -37
- package/src/TSL/Random.js +1 -0
- package/src/TSL/ShadeKernel.js +75 -19
- package/src/TSL/Sun.js +40 -0
- package/src/TSL/TextureSampling.js +6 -7
- package/src/index.js +22 -0
- package/src/managers/CameraManager.js +412 -56
- package/src/managers/DenoisingManager.js +4 -3
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- package/src/managers/InteractionManager.js +30 -2
- package/src/managers/LightManager.js +98 -5
- package/src/managers/MaterialDataManager.js +22 -2
- package/src/managers/TransformManager.js +26 -0
- package/src/managers/UniformManager.js +11 -7
- package/src/managers/VideoRenderManager.js +38 -17
- package/src/managers/ViewCamera.js +77 -0
- package/src/managers/WalkControls.js +262 -0
- package/src/managers/helpers/OutlineHelper.js +22 -2
- package/src/managers/timeline/CameraTrack.js +235 -0
- package/src/managers/timeline/TimelineManager.js +146 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
|
@@ -11,31 +11,51 @@ import {
|
|
|
11
11
|
RGBAFormat, RedFormat, FloatType, LinearFilter, Vector2, Vector3, Color, Matrix4, DataTexture,
|
|
12
12
|
} from 'three';
|
|
13
13
|
import { EquirectHDRInfo } from '../Processor/EquirectHDRInfo.js';
|
|
14
|
-
import {
|
|
14
|
+
import { PhysicalSky } from '../Processor/PhysicalSky.js';
|
|
15
15
|
import { SimpleSky } from '../Processor/SimpleSky.js';
|
|
16
|
+
import { convertLinearTriple } from '../Color/WorkingMatrix.js';
|
|
16
17
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
17
18
|
|
|
18
19
|
const log = createLogger( 'env' );
|
|
19
20
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
20
21
|
import { getActiveColorManagement } from '../Color/ColorManagement.js';
|
|
22
|
+
import { loadCDF, saveCDF } from '../Storage/CDFCache.js';
|
|
23
|
+
|
|
24
|
+
const SKY_WIDTH = 1024;
|
|
25
|
+
const SKY_HEIGHT = 512;
|
|
26
|
+
const DEG2RAD = Math.PI / 180;
|
|
27
|
+
const _rotation = new Matrix4();
|
|
28
|
+
|
|
29
|
+
// Bakes queued on the GPU at once. More only queue behind frames already waiting there.
|
|
30
|
+
const SKY_BAKES_IN_FLIGHT = 4;
|
|
21
31
|
|
|
22
32
|
export class EnvironmentManager {
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
35
|
* @param {Object} scene - Three.js scene
|
|
26
36
|
* @param {import('./UniformManager').UniformManager} uniforms
|
|
37
|
+
* @param {import('three/webgpu').WebGPURenderer} [renderer] - bakes the physical sky
|
|
27
38
|
*/
|
|
28
|
-
constructor( scene, uniforms ) {
|
|
39
|
+
constructor( scene, uniforms, renderer = null ) {
|
|
29
40
|
|
|
30
41
|
this.scene = scene;
|
|
31
42
|
this.uniforms = uniforms;
|
|
43
|
+
this.renderer = renderer;
|
|
32
44
|
|
|
33
45
|
// CDF computation engine
|
|
34
46
|
this.equirectHdrInfo = new EquirectHDRInfo();
|
|
35
47
|
|
|
36
48
|
// Sky renderers (lazy init)
|
|
37
|
-
this.
|
|
49
|
+
this.physicalSky = null;
|
|
38
50
|
this.simpleSkyRenderer = null;
|
|
51
|
+
this._sun = null;
|
|
52
|
+
this._skyRequested = false;
|
|
53
|
+
this._skyScheduled = false;
|
|
54
|
+
this._skyInFlight = 0;
|
|
55
|
+
this._skyCaughtUp = null;
|
|
56
|
+
this._resolveSky = null;
|
|
57
|
+
this._skyBakes = 0;
|
|
58
|
+
this._skyStatsBake = 0;
|
|
39
59
|
|
|
40
60
|
// Environment texture — 1×1 black placeholder for shader compilation. Filters must be
|
|
41
61
|
// Linear: DataTexture defaults to Nearest, which is unfilterable, and WGSLNodeBuilder then
|
|
@@ -71,12 +91,15 @@ export class EnvironmentManager {
|
|
|
71
91
|
// Solid Color Sky
|
|
72
92
|
solidSkyColor: new Color( DEFAULT_STATE.solidSkyColor ),
|
|
73
93
|
|
|
74
|
-
//
|
|
94
|
+
// Physical sky. The sun direction is in the sky's own frame; environment rotation turns both.
|
|
75
95
|
skySunDirection: this._calculateInitialSunDirection(),
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
skySunStrength: DEFAULT_STATE.skySunStrength,
|
|
97
|
+
skySunSize: DEFAULT_STATE.skySunSize,
|
|
78
98
|
skyTurbidity: DEFAULT_STATE.skyTurbidity,
|
|
79
|
-
|
|
99
|
+
skyOzone: DEFAULT_STATE.skyOzone,
|
|
100
|
+
skyAirDensity: DEFAULT_STATE.skyAirDensity,
|
|
101
|
+
skyGroundAlbedo: new Color( DEFAULT_STATE.skyGroundAlbedo ),
|
|
102
|
+
skyAltitude: DEFAULT_STATE.skyAltitude,
|
|
80
103
|
};
|
|
81
104
|
|
|
82
105
|
/**
|
|
@@ -93,7 +116,7 @@ export class EnvironmentManager {
|
|
|
93
116
|
// ===== MODE STATE MACHINE =====
|
|
94
117
|
|
|
95
118
|
/**
|
|
96
|
-
* Switches the environment mode (hdri, gradient, color, procedural).
|
|
119
|
+
* Switches the environment mode (hdri, gradient, color, procedural — the physical sky).
|
|
97
120
|
* Preserves the HDRI texture when switching away, restores when switching back.
|
|
98
121
|
* @param {'hdri'|'gradient'|'color'|'procedural'} mode
|
|
99
122
|
*/
|
|
@@ -128,6 +151,8 @@ export class EnvironmentManager {
|
|
|
128
151
|
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
if ( mode !== 'procedural' && prev === 'procedural' ) this._releaseSky();
|
|
155
|
+
|
|
131
156
|
this.markDirty();
|
|
132
157
|
this.callbacks.onAutoExposureReset?.();
|
|
133
158
|
this._notifyReset();
|
|
@@ -143,6 +168,71 @@ export class EnvironmentManager {
|
|
|
143
168
|
|
|
144
169
|
}
|
|
145
170
|
|
|
171
|
+
// ===== SAVED SESSIONS =====
|
|
172
|
+
|
|
173
|
+
/** Where the HDRI came from (see Storage/CDFCache.js), shown or held while a sky is. */
|
|
174
|
+
get hdriSource() {
|
|
175
|
+
|
|
176
|
+
const hdri = this.envParams.mode === 'hdri' ? this.environmentTexture : this._previousHDRI;
|
|
177
|
+
return hdri?.userData?.__rayzeeSource ?? null;
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The mode, the sky parameters and the HDRI's source, as plain data. */
|
|
182
|
+
serialize() {
|
|
183
|
+
|
|
184
|
+
const p = this.envParams;
|
|
185
|
+
return {
|
|
186
|
+
mode: p.mode,
|
|
187
|
+
hdri: this.hdriSource,
|
|
188
|
+
gradientZenithColor: p.gradientZenithColor.toArray(),
|
|
189
|
+
gradientHorizonColor: p.gradientHorizonColor.toArray(),
|
|
190
|
+
gradientGroundColor: p.gradientGroundColor.toArray(),
|
|
191
|
+
solidSkyColor: p.solidSkyColor.toArray(),
|
|
192
|
+
skyModel: 'atmosphere',
|
|
193
|
+
skySunDirection: p.skySunDirection.toArray(),
|
|
194
|
+
skySunStrength: p.skySunStrength,
|
|
195
|
+
skySunSize: p.skySunSize,
|
|
196
|
+
skyTurbidity: p.skyTurbidity,
|
|
197
|
+
skyOzone: p.skyOzone,
|
|
198
|
+
skyAirDensity: p.skyAirDensity,
|
|
199
|
+
skyGroundAlbedo: p.skyGroundAlbedo.toArray(),
|
|
200
|
+
skyAltitude: p.skyAltitude,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Puts back {@link serialize}'s parameters and mode. The HDRI itself is the caller's to load
|
|
207
|
+
* first: only it can resolve the source.
|
|
208
|
+
*/
|
|
209
|
+
async restore( state ) {
|
|
210
|
+
|
|
211
|
+
if ( ! state ) return;
|
|
212
|
+
const p = this.envParams;
|
|
213
|
+
// Sessions saved before the physical sky gave the same names other meanings.
|
|
214
|
+
const atmosphere = state.skyModel === 'atmosphere';
|
|
215
|
+
const colors = [ 'gradientZenithColor', 'gradientHorizonColor', 'gradientGroundColor', 'solidSkyColor', 'skySunDirection' ];
|
|
216
|
+
for ( const key of atmosphere ? [ ...colors, 'skyGroundAlbedo' ] : colors ) {
|
|
217
|
+
|
|
218
|
+
if ( Array.isArray( state[ key ] ) ) p[ key ].fromArray( state[ key ] );
|
|
219
|
+
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if ( atmosphere ) {
|
|
223
|
+
|
|
224
|
+
for ( const key of [ 'skySunStrength', 'skySunSize', 'skyTurbidity', 'skyOzone', 'skyAirDensity', 'skyAltitude' ] ) {
|
|
225
|
+
|
|
226
|
+
if ( typeof state[ key ] === 'number' ) p[ key ] = state[ key ];
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if ( state.mode && ( state.mode !== 'hdri' || p.mode !== 'hdri' ) ) await this.setMode( state.mode );
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
|
|
146
236
|
// ===== Aliases (match Sub-API surface for zero-churn migration) =====
|
|
147
237
|
|
|
148
238
|
/** @see envParams */
|
|
@@ -219,7 +309,7 @@ export class EnvironmentManager {
|
|
|
219
309
|
|
|
220
310
|
}
|
|
221
311
|
|
|
222
|
-
this.envCDFTexture?.dispose?.();
|
|
312
|
+
if ( ! this.envCDFTexture?._isPhysicalSky ) this.envCDFTexture?.dispose?.();
|
|
223
313
|
this.envCDFTexture = new DataTexture( data, texW, H, RedFormat, FloatType );
|
|
224
314
|
this.envCDFTexture.needsUpdate = true;
|
|
225
315
|
|
|
@@ -280,6 +370,7 @@ export class EnvironmentManager {
|
|
|
280
370
|
const rotationRadians = rotationDegrees * ( Math.PI / 180 );
|
|
281
371
|
this.environmentRotationMatrix.makeRotationY( rotationRadians );
|
|
282
372
|
this.uniforms.get( 'environmentMatrix' ).value.copy( this.environmentRotationMatrix );
|
|
373
|
+
this.refreshSun();
|
|
283
374
|
|
|
284
375
|
}
|
|
285
376
|
|
|
@@ -294,6 +385,7 @@ export class EnvironmentManager {
|
|
|
294
385
|
|
|
295
386
|
if ( ! this.scene.environment ) {
|
|
296
387
|
|
|
388
|
+
this._cdfSignature = null;
|
|
297
389
|
this._updateCDFTexture();
|
|
298
390
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
299
391
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
@@ -301,6 +393,9 @@ export class EnvironmentManager {
|
|
|
301
393
|
|
|
302
394
|
}
|
|
303
395
|
|
|
396
|
+
// Its table is built with it, on the GPU.
|
|
397
|
+
if ( this.scene.environment._isPhysicalSky ) return;
|
|
398
|
+
|
|
304
399
|
try {
|
|
305
400
|
|
|
306
401
|
const startTime = performance.now();
|
|
@@ -308,6 +403,7 @@ export class EnvironmentManager {
|
|
|
308
403
|
|
|
309
404
|
if ( ! textureForCDF.image ) {
|
|
310
405
|
|
|
406
|
+
this._cdfSignature = null;
|
|
311
407
|
this._updateCDFTexture();
|
|
312
408
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
313
409
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
@@ -315,7 +411,16 @@ export class EnvironmentManager {
|
|
|
315
411
|
|
|
316
412
|
}
|
|
317
413
|
|
|
318
|
-
|
|
414
|
+
// The same pixels as last time: the tables and uniforms already describe them.
|
|
415
|
+
const signature = `${textureForCDF.uuid}:${textureForCDF.version}`;
|
|
416
|
+
if ( signature === this._cdfSignature ) return;
|
|
417
|
+
|
|
418
|
+
const cached = await loadCDF( textureForCDF ).catch( () => null );
|
|
419
|
+
if ( cached ) {
|
|
420
|
+
|
|
421
|
+
Object.assign( this.equirectHdrInfo, cached );
|
|
422
|
+
|
|
423
|
+
} else if ( useWorker ) {
|
|
319
424
|
|
|
320
425
|
await this.equirectHdrInfo.updateFromAsync( textureForCDF );
|
|
321
426
|
|
|
@@ -325,6 +430,8 @@ export class EnvironmentManager {
|
|
|
325
430
|
|
|
326
431
|
}
|
|
327
432
|
|
|
433
|
+
if ( ! cached ) saveCDF( textureForCDF, this.equirectHdrInfo ).catch( () => {} );
|
|
434
|
+
this._cdfSignature = signature;
|
|
328
435
|
this.cdfBuildTime = performance.now() - startTime;
|
|
329
436
|
|
|
330
437
|
this._updateCDFTexture();
|
|
@@ -340,11 +447,12 @@ export class EnvironmentManager {
|
|
|
340
447
|
|
|
341
448
|
log.info( fmt.list( [
|
|
342
449
|
fmt.px( this.envTexSize.x, this.envTexSize.y ),
|
|
343
|
-
`CDF ${fmt.ms( this.cdfBuildTime )}${useWorker ? '' : ' (main thread)'}`,
|
|
450
|
+
`CDF ${fmt.ms( this.cdfBuildTime )}${cached ? ' (stored)' : useWorker ? '' : ' (main thread)'}`,
|
|
344
451
|
] ) );
|
|
345
452
|
|
|
346
453
|
} catch ( error ) {
|
|
347
454
|
|
|
455
|
+
this._cdfSignature = null;
|
|
348
456
|
log.error( 'CDF build failed:', error );
|
|
349
457
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
350
458
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
@@ -391,7 +499,7 @@ export class EnvironmentManager {
|
|
|
391
499
|
// stashed by setMode() for a later sky→hdri restore. Only when actually
|
|
392
500
|
// installing a new non-null texture (the null-env branch keeps the old ref).
|
|
393
501
|
const oldTex = this.environmentTexture;
|
|
394
|
-
if ( envMap && oldTex && oldTex !== envMap && oldTex !== this._envPlaceholder && oldTex !== this._previousHDRI ) {
|
|
502
|
+
if ( envMap && oldTex && oldTex !== envMap && oldTex !== this._envPlaceholder && oldTex !== this._previousHDRI && ! oldTex._isPhysicalSky ) {
|
|
395
503
|
|
|
396
504
|
oldTex.dispose?.();
|
|
397
505
|
|
|
@@ -406,6 +514,7 @@ export class EnvironmentManager {
|
|
|
406
514
|
|
|
407
515
|
} else {
|
|
408
516
|
|
|
517
|
+
this._cdfSignature = null;
|
|
409
518
|
this._updateCDFTexture();
|
|
410
519
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
411
520
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
@@ -430,6 +539,8 @@ export class EnvironmentManager {
|
|
|
430
539
|
|
|
431
540
|
}
|
|
432
541
|
|
|
542
|
+
if ( envMap && oldTex?._isPhysicalSky && this.envParams.mode !== 'procedural' ) this._releaseSky();
|
|
543
|
+
|
|
433
544
|
this._notifyReset();
|
|
434
545
|
|
|
435
546
|
}
|
|
@@ -528,43 +639,158 @@ export class EnvironmentManager {
|
|
|
528
639
|
}
|
|
529
640
|
|
|
530
641
|
/**
|
|
531
|
-
*
|
|
642
|
+
* Bake the physical sky and set it as the environment. Requests made in one task become one
|
|
643
|
+
* bake, queued straight away while the GPU keeps up and otherwise as soon as it finishes an
|
|
644
|
+
* earlier one. Resolves once the environment has caught up with the latest request.
|
|
532
645
|
*/
|
|
533
|
-
|
|
646
|
+
generateProceduralSkyTexture() {
|
|
647
|
+
|
|
648
|
+
this._skyRequested = true;
|
|
649
|
+
this._skyCaughtUp ??= new Promise( resolve => void ( this._resolveSky = resolve ) );
|
|
650
|
+
this._pumpSky();
|
|
651
|
+
return this._skyCaughtUp;
|
|
652
|
+
|
|
653
|
+
}
|
|
534
654
|
|
|
535
|
-
|
|
655
|
+
/** @private */
|
|
656
|
+
_pumpSky() {
|
|
657
|
+
|
|
658
|
+
if ( this._skyScheduled || ! this._skyRequested || this._skyInFlight >= SKY_BAKES_IN_FLIGHT ) return;
|
|
659
|
+
this._skyScheduled = true;
|
|
660
|
+
queueMicrotask( () => {
|
|
661
|
+
|
|
662
|
+
this._skyScheduled = false;
|
|
663
|
+
if ( this._skyRequested && this._skyInFlight < SKY_BAKES_IN_FLIGHT ) this._bakePhysicalSky();
|
|
536
664
|
|
|
537
|
-
|
|
665
|
+
} );
|
|
666
|
+
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** @private */
|
|
670
|
+
_settleSky() {
|
|
671
|
+
|
|
672
|
+
const resolve = this._resolveSky;
|
|
673
|
+
this._skyCaughtUp = this._resolveSky = null;
|
|
674
|
+
resolve?.();
|
|
675
|
+
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** @private */
|
|
679
|
+
async _bakePhysicalSky() {
|
|
680
|
+
|
|
681
|
+
this._skyRequested = false;
|
|
682
|
+
const p = this.envParams;
|
|
683
|
+
if ( ! this.renderer || p.mode !== 'procedural' ) {
|
|
684
|
+
|
|
685
|
+
if ( ! this.renderer ) log.warn( 'physical sky needs a renderer' );
|
|
686
|
+
if ( this._skyInFlight === 0 ) this._settleSky();
|
|
687
|
+
return;
|
|
538
688
|
|
|
539
689
|
}
|
|
540
690
|
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
mieAnisotropy: this.envParams.skyMieAnisotropy,
|
|
547
|
-
turbidity: this.envParams.skyTurbidity * 2.0,
|
|
548
|
-
};
|
|
691
|
+
const sky = this.physicalSky ??= new PhysicalSky( SKY_WIDTH, SKY_HEIGHT );
|
|
692
|
+
const albedo = p.skyGroundAlbedo;
|
|
693
|
+
const bake = ++ this._skyBakes;
|
|
694
|
+
const latest = () => bake === this._skyBakes && ! this._skyRequested;
|
|
695
|
+
this._skyInFlight ++;
|
|
549
696
|
|
|
550
697
|
try {
|
|
551
698
|
|
|
552
|
-
const texture =
|
|
553
|
-
|
|
554
|
-
|
|
699
|
+
const { texture, cdfTexture, sun, stats } = sky.bake( this.renderer, {
|
|
700
|
+
sunDirection: p.skySunDirection.toArray(),
|
|
701
|
+
turbidity: p.skyTurbidity,
|
|
702
|
+
ozone: p.skyOzone,
|
|
703
|
+
airDensity: p.skyAirDensity,
|
|
704
|
+
groundAlbedo: [ albedo.r, albedo.g, albedo.b ],
|
|
705
|
+
altitude: p.skyAltitude,
|
|
706
|
+
sunAngularDiameter: p.skySunSize * DEG2RAD,
|
|
707
|
+
sunStrength: p.skySunStrength,
|
|
708
|
+
} );
|
|
709
|
+
this._sun = sun;
|
|
710
|
+
this._installSky( texture, cdfTexture );
|
|
711
|
+
|
|
712
|
+
// Frames until they land normalise by an older bake's.
|
|
713
|
+
const { totalSum, compensationDelta } = await stats;
|
|
714
|
+
if ( p.mode === 'procedural' && sky === this.physicalSky && bake > this._skyStatsBake ) {
|
|
715
|
+
|
|
716
|
+
this._skyStatsBake = bake;
|
|
717
|
+
this.uniforms.set( 'envTotalSum', totalSum );
|
|
718
|
+
this.uniforms.set( 'envCompensationDelta', compensationDelta );
|
|
719
|
+
if ( latest() ) this._notifyReset();
|
|
555
720
|
|
|
556
|
-
|
|
557
|
-
this.uniforms.set( 'sunAngularSize', 0.0087 );
|
|
558
|
-
this.uniforms.set( 'hasSun', 1 );
|
|
721
|
+
}
|
|
559
722
|
|
|
560
|
-
log.debug( `
|
|
723
|
+
if ( latest() ) log.debug( `physical sky ${fmt.ms( sky.getLastRenderTime() )} · sun ${sun.radiance.map( v => v.toPrecision( 3 ) ).join( ',' )}` );
|
|
561
724
|
|
|
562
725
|
} catch ( error ) {
|
|
563
726
|
|
|
564
|
-
log.error( '
|
|
727
|
+
log.error( 'physical sky generation failed:', error );
|
|
728
|
+
|
|
729
|
+
} finally {
|
|
730
|
+
|
|
731
|
+
this._skyInFlight --;
|
|
732
|
+
if ( latest() ) this._settleSky();
|
|
733
|
+
else this._pumpSky();
|
|
734
|
+
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** @private */
|
|
740
|
+
_installSky( texture, cdfTexture ) {
|
|
741
|
+
|
|
742
|
+
const oldTex = this.environmentTexture;
|
|
743
|
+
if ( oldTex && oldTex !== texture && oldTex !== this._envPlaceholder && oldTex !== this._previousHDRI ) oldTex.dispose?.();
|
|
744
|
+
if ( this.envCDFTexture !== cdfTexture ) {
|
|
745
|
+
|
|
746
|
+
this.envCDFTexture?.dispose?.();
|
|
747
|
+
this.envCDFTexture = cdfTexture;
|
|
565
748
|
|
|
566
749
|
}
|
|
567
750
|
|
|
751
|
+
// So an HDRI coming back rebuilds its own table.
|
|
752
|
+
this._cdfSignature = null;
|
|
753
|
+
texture._isGeneratedProcedural = true;
|
|
754
|
+
this.scene.environment = texture;
|
|
755
|
+
this.environmentTexture = texture;
|
|
756
|
+
this.envTexSize.set( texture.image.width, texture.image.height );
|
|
757
|
+
this.uniforms.get( 'envResolution' ).value.set( texture.image.width, texture.image.height );
|
|
758
|
+
const nodes = this.callbacks.getSceneTextureNodes?.();
|
|
759
|
+
if ( nodes?.envTex ) nodes.envTex.value = texture;
|
|
760
|
+
this.refreshSun();
|
|
761
|
+
this._notifyReset();
|
|
762
|
+
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/** @private */
|
|
766
|
+
_releaseSky() {
|
|
767
|
+
|
|
768
|
+
if ( ! this.physicalSky ) return;
|
|
769
|
+
this.physicalSky.dispose( this.renderer );
|
|
770
|
+
this.physicalSky = null;
|
|
771
|
+
this._sun = null;
|
|
772
|
+
this.refreshSun();
|
|
773
|
+
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Push the sun to the shader: into world space through the environment rotation, and into
|
|
778
|
+
* the working colour space. Call after either changes.
|
|
779
|
+
*/
|
|
780
|
+
refreshSun() {
|
|
781
|
+
|
|
782
|
+
const sun = this.envParams.mode === 'procedural' ? this._sun : null;
|
|
783
|
+
const on = !! sun && sun.radiance.some( v => v > 0 );
|
|
784
|
+
this.uniforms.set( 'hasSun', on ? 1 : 0 );
|
|
785
|
+
if ( ! on ) return;
|
|
786
|
+
|
|
787
|
+
const direction = this.uniforms.get( 'sunDirection' ).value;
|
|
788
|
+
direction.fromArray( sun.direction ).transformDirection( _rotation.copy( this.environmentRotationMatrix ).transpose() );
|
|
789
|
+
const rgb = [ ...sun.radiance ];
|
|
790
|
+
convertLinearTriple( rgb );
|
|
791
|
+
this.uniforms.get( 'sunRadiance' ).value.fromArray( rgb );
|
|
792
|
+
this.uniforms.get( 'sunParams' ).value.set( sun.cosHalfAngle, sun.solidAngle, 1 / ( sun.sinHalfAngle * sun.sinHalfAngle ), sun.horizonSin );
|
|
793
|
+
|
|
568
794
|
}
|
|
569
795
|
|
|
570
796
|
// ===== HELPERS =====
|
|
@@ -597,8 +823,12 @@ export class EnvironmentManager {
|
|
|
597
823
|
|
|
598
824
|
dispose() {
|
|
599
825
|
|
|
600
|
-
this.
|
|
826
|
+
this.callbacks = {};
|
|
827
|
+
this._settleSky();
|
|
828
|
+
this.physicalSky?.dispose( this.renderer );
|
|
829
|
+
this.physicalSky = null;
|
|
601
830
|
this.simpleSkyRenderer = null;
|
|
831
|
+
this._sun = null;
|
|
602
832
|
|
|
603
833
|
this.envCDFTexture?.dispose?.();
|
|
604
834
|
this.envCDFTexture = null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DataArrayTexture, LinearFilter, RGBAFormat, UnsignedByteType } from 'three';
|
|
2
|
+
import { cachedObjectURL } from '../Storage/DownloadCache.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Manages projection masks ("gobos" / "cookies") for spot lights.
|
|
@@ -256,17 +257,27 @@ export class GoboManager {
|
|
|
256
257
|
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
function loadImage( url ) {
|
|
260
|
+
async function loadImage( url ) {
|
|
260
261
|
|
|
261
|
-
|
|
262
|
+
const source = await cachedObjectURL( url );
|
|
262
263
|
|
|
263
|
-
|
|
264
|
-
img.crossOrigin = 'anonymous';
|
|
265
|
-
img.onload = () => resolve( img );
|
|
266
|
-
img.onerror = () => reject( new Error( `Failed to load gobo image: ${url}` ) );
|
|
267
|
-
img.src = url;
|
|
264
|
+
try {
|
|
268
265
|
|
|
269
|
-
|
|
266
|
+
return await new Promise( ( resolve, reject ) => {
|
|
267
|
+
|
|
268
|
+
const img = new Image();
|
|
269
|
+
img.crossOrigin = 'anonymous';
|
|
270
|
+
img.onload = () => resolve( img );
|
|
271
|
+
img.onerror = () => reject( new Error( `Failed to load gobo image: ${url}` ) );
|
|
272
|
+
img.src = source.url;
|
|
273
|
+
|
|
274
|
+
} );
|
|
275
|
+
|
|
276
|
+
} finally {
|
|
277
|
+
|
|
278
|
+
source.release();
|
|
279
|
+
|
|
280
|
+
}
|
|
270
281
|
|
|
271
282
|
}
|
|
272
283
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DataArrayTexture, LinearFilter, RGBAFormat, UnsignedByteType } from 'three';
|
|
2
2
|
import { parseIES, resampleIESToGrid, deriveIESBeamAngle, deriveIESPenumbra } from '../Processor/IESParser.js';
|
|
3
|
+
import { sharedDownloads } from '../Storage/DownloadCache.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Manages IES photometric profiles for spot lights.
|
|
@@ -63,9 +64,8 @@ export class IESManager {
|
|
|
63
64
|
|
|
64
65
|
try {
|
|
65
66
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const text = await res.text();
|
|
67
|
+
const { file, release } = await sharedDownloads().fetch( it.url );
|
|
68
|
+
const text = await file.text().finally( release );
|
|
69
69
|
const profile = parseIES( text, it.name );
|
|
70
70
|
const grid = resampleIESToGrid( profile, gridWidth, gridHeight );
|
|
71
71
|
return { it, profile, grid };
|
|
@@ -3,9 +3,28 @@ import {
|
|
|
3
3
|
Raycaster,
|
|
4
4
|
SphereGeometry,
|
|
5
5
|
Mesh,
|
|
6
|
-
MeshBasicMaterial
|
|
6
|
+
MeshBasicMaterial,
|
|
7
|
+
Vector3
|
|
7
8
|
} from 'three';
|
|
8
9
|
import { EngineEvents } from '../EngineEvents.js';
|
|
10
|
+
import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
11
|
+
|
|
12
|
+
const _viewAxis = new Vector3();
|
|
13
|
+
const _toPoint = new Vector3();
|
|
14
|
+
|
|
15
|
+
/** Depth of a point along the camera's view axis — the distance the flat focal plane is measured in. */
|
|
16
|
+
export function viewDepth( point, camera ) {
|
|
17
|
+
|
|
18
|
+
return _toPoint.subVectors( point, camera.position ).dot( camera.getWorldDirection( _viewAxis ) );
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The focus distance that puts a raycast hit in focus: a panorama focuses along each ray, not on a plane. */
|
|
23
|
+
export function focusDistanceOfHit( hit, camera, panorama = false ) {
|
|
24
|
+
|
|
25
|
+
return panorama ? hit.distance : viewDepth( hit.point, camera );
|
|
26
|
+
|
|
27
|
+
}
|
|
9
28
|
|
|
10
29
|
/**
|
|
11
30
|
* InteractionManager
|
|
@@ -207,7 +226,8 @@ export class InteractionManager extends EventDispatcher {
|
|
|
207
226
|
if ( intersects.length > 0 ) {
|
|
208
227
|
|
|
209
228
|
const intersection = intersects[ 0 ];
|
|
210
|
-
const
|
|
229
|
+
const panorama = this.pathTracer?.cameraProjection?.value === CAMERA_PROJECTION_IDS.equirectangular;
|
|
230
|
+
const distance = focusDistanceOfHit( intersection, this.camera, panorama );
|
|
211
231
|
|
|
212
232
|
// Show visual indicator at focus point
|
|
213
233
|
this.showFocusPoint( intersection.point );
|
|
@@ -675,6 +695,14 @@ export class InteractionManager extends EventDispatcher {
|
|
|
675
695
|
|
|
676
696
|
}
|
|
677
697
|
|
|
698
|
+
/** The nearest scene surface under an NDC point, skipping the floor and helpers. */
|
|
699
|
+
pickSurface( ndcX, ndcY ) {
|
|
700
|
+
|
|
701
|
+
this.raycaster.setFromCamera( { x: ndcX, y: ndcY }, this.camera );
|
|
702
|
+
return this.filterValidIntersects( this.raycaster.intersectObjects( this.scene.children, true ) )[ 0 ] ?? null;
|
|
703
|
+
|
|
704
|
+
}
|
|
705
|
+
|
|
678
706
|
/**
|
|
679
707
|
* Filter intersections to exclude helper objects and floor plane
|
|
680
708
|
* @private
|