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.
- package/README.md +139 -8
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10353 -5337
- package/dist/rayzee.umd.js +265 -265
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +38 -56
- package/src/EngineEvents.js +3 -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/PathTracerApp.js +290 -19
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +514 -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/KernelManager.js +4 -1
- package/src/Processor/PBRT/PBRTCurves.js +104 -27
- package/src/Processor/PBRT/PBRTParser.js +132 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
- 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 +38 -0
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/NormalDepth.js +7 -6
- package/src/Stages/PathTracer.js +108 -4
- 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/Common.js +11 -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 +3 -3
- 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 +18 -0
- package/src/managers/CameraManager.js +169 -1
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- 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 +5 -3
- package/src/managers/VideoRenderManager.js +3 -1
- package/src/managers/timeline/CameraTrack.js +22 -0
- package/src/managers/timeline/TimelineManager.js +14 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
|
@@ -17,6 +17,7 @@ import { packMaterial, UNIT_RANGE_PROPERTIES, clampUnit } from '../Processor/Mat
|
|
|
17
17
|
import { resolveMaterialTextures, MATERIAL_VALUE_SOURCE } from '../Processor/GeometryExtractor.js';
|
|
18
18
|
import { convertLinearTriple, convertLinearTriples, getWorkingMatrixSpace } from '../Color/WorkingMatrix.js';
|
|
19
19
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
20
|
+
import { toPortable } from '../SceneState/portable.js';
|
|
20
21
|
|
|
21
22
|
/** Material buffers already moved into the working space, so a re-init cannot convert twice. */
|
|
22
23
|
const convertedBuffers = new WeakSet();
|
|
@@ -30,6 +31,9 @@ const TRI_FLAGS_OFFSET = T.MATERIAL_FLAGS_OFFSET;
|
|
|
30
31
|
// Material properties that affect the shadow-ray opaque-blocker flag.
|
|
31
32
|
const BLOCKER_PROPS = new Set( [ 'transmission', 'transparent', 'opacity', 'alphaMode' ] );
|
|
32
33
|
|
|
34
|
+
/** Material properties whose edit rewrites triangle records (side, shadow blocker, emitters). */
|
|
35
|
+
export const TRIANGLE_PATCH_PROPERTIES = new Set( [ 'side', ...BLOCKER_PROPS, 'emissive', 'emissiveIntensity' ] );
|
|
36
|
+
|
|
33
37
|
// Map slot → sRGB pool (true) or linear pool.
|
|
34
38
|
const TEXTURE_POOLS = [
|
|
35
39
|
[ 'map', true ], [ 'emissiveMap', true ], [ 'sheenColorMap', true ], [ 'specularColorMap', true ],
|
|
@@ -98,7 +102,7 @@ export class MaterialDataManager {
|
|
|
98
102
|
*/
|
|
99
103
|
this.callbacks = {};
|
|
100
104
|
|
|
101
|
-
// Per material index: createMaterialObject's sources, and properties a host set since.
|
|
105
|
+
// Per material index: createMaterialObject's sources, and properties a host set since (name → portable value).
|
|
102
106
|
this._sources = [];
|
|
103
107
|
this._hostSet = [];
|
|
104
108
|
|
|
@@ -310,6 +314,22 @@ export class MaterialDataManager {
|
|
|
310
314
|
|
|
311
315
|
}
|
|
312
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Every property a host set, per material, as portable values (see SceneState/portable.js).
|
|
319
|
+
* @returns {Array<{index: number, props: Object<string, *>}>}
|
|
320
|
+
*/
|
|
321
|
+
serializeHostEdits() {
|
|
322
|
+
|
|
323
|
+
const out = [];
|
|
324
|
+
this._hostSet.forEach( ( props, index ) => {
|
|
325
|
+
|
|
326
|
+
if ( props?.size ) out.push( { index, props: Object.fromEntries( props ) } );
|
|
327
|
+
|
|
328
|
+
} );
|
|
329
|
+
return out;
|
|
330
|
+
|
|
331
|
+
}
|
|
332
|
+
|
|
313
333
|
/**
|
|
314
334
|
* Update a single material property in the storage buffer.
|
|
315
335
|
* @param {number} materialIndex
|
|
@@ -481,7 +501,7 @@ export class MaterialDataManager {
|
|
|
481
501
|
}
|
|
482
502
|
|
|
483
503
|
this.materialStorageAttr.needsUpdate = true;
|
|
484
|
-
( this._hostSet[ materialIndex ] ??= new
|
|
504
|
+
( this._hostSet[ materialIndex ] ??= new Map() ).set( property, toPortable( value ) );
|
|
485
505
|
|
|
486
506
|
// Recompute triangle-data opaque-blocker flag when any input to it changes.
|
|
487
507
|
if ( BLOCKER_PROPS.has( property ) ) {
|
|
@@ -14,6 +14,13 @@ import { EngineEvents } from '../EngineEvents.js';
|
|
|
14
14
|
// does not mutate its arguments, so this can be a shared constant.
|
|
15
15
|
const FORWARD = new Vector3( 0, 0, - 1 );
|
|
16
16
|
|
|
17
|
+
const rootScene = object => {
|
|
18
|
+
|
|
19
|
+
while ( object.parent ) object = object.parent;
|
|
20
|
+
return object.isScene ? object : null;
|
|
21
|
+
|
|
22
|
+
};
|
|
23
|
+
|
|
17
24
|
export class TransformManager {
|
|
18
25
|
|
|
19
26
|
constructor( { camera, canvas, orbitControls, app } ) {
|
|
@@ -33,6 +40,8 @@ export class TransformManager {
|
|
|
33
40
|
this._meshes = null;
|
|
34
41
|
this._refitInFlight = false;
|
|
35
42
|
this._baselineComputed = false;
|
|
43
|
+
// Objects the gizmo moved, so a saved session can put them back.
|
|
44
|
+
this._moved = new Set();
|
|
36
45
|
|
|
37
46
|
// Light transform state — spot/directional lights aim via a separate
|
|
38
47
|
// `.target` Object3D rather than their own rotation (see attach()).
|
|
@@ -56,6 +65,21 @@ export class TransformManager {
|
|
|
56
65
|
setMeshData( meshes ) {
|
|
57
66
|
|
|
58
67
|
this._meshes = meshes;
|
|
68
|
+
for ( const object of this._moved ) if ( ! rootScene( object ) ) this._moved.delete( object );
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Objects the gizmo has moved that are still in the scene (lights are saved with the lights). */
|
|
73
|
+
get movedObjects() {
|
|
74
|
+
|
|
75
|
+
return [ ...this._moved ].filter( object => rootScene( object ) );
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Counts an object as moved, as a gizmo drag would — for a restored session. */
|
|
80
|
+
noteMoved( object ) {
|
|
81
|
+
|
|
82
|
+
if ( object && ! object.isLight ) this._moved.add( object );
|
|
59
83
|
|
|
60
84
|
}
|
|
61
85
|
|
|
@@ -335,6 +359,7 @@ export class TransformManager {
|
|
|
335
359
|
// instead costs a pass over every vertex, and corrupts any other object sharing
|
|
336
360
|
// this geometry.
|
|
337
361
|
this._app.updateMeshTransforms( affectedIndices );
|
|
362
|
+
this._moved.add( this._attached );
|
|
338
363
|
|
|
339
364
|
} catch ( err ) {
|
|
340
365
|
|
|
@@ -393,6 +418,7 @@ export class TransformManager {
|
|
|
393
418
|
this._controls.dispose();
|
|
394
419
|
|
|
395
420
|
this._meshes = null;
|
|
421
|
+
this._moved.clear();
|
|
396
422
|
this._baselineComputed = false;
|
|
397
423
|
this._tempForward = null;
|
|
398
424
|
this._lightTargetDistance = null;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { uniform, uniformArray } from 'three/tsl';
|
|
9
|
-
import { Vector2, Matrix4, Vector3, Color, MathUtils } from 'three';
|
|
9
|
+
import { Vector2, Matrix4, Vector3, Vector4, Color, MathUtils } from 'three';
|
|
10
10
|
import { samplingTechniqueUniform } from '../TSL/Random.js';
|
|
11
11
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
12
12
|
|
|
@@ -249,9 +249,11 @@ export class UniformManager {
|
|
|
249
249
|
ub( 'enableGroundCatcher', DEFAULT_STATE.enableGroundCatcher );
|
|
250
250
|
u( 'groundCatcherHeight', DEFAULT_STATE.groundCatcherHeight, 'float' );
|
|
251
251
|
|
|
252
|
-
// Sun
|
|
252
|
+
// The physical sky's sun (TSL/Sun.js): world direction, disc-average radiance, and
|
|
253
|
+
// ( cos half-angle, solid angle, 1 / sin² half-angle, horizon dip sin )
|
|
253
254
|
u( 'sunDirection', new Vector3( 0, 1, 0 ), 'vec3' );
|
|
254
|
-
u( '
|
|
255
|
+
u( 'sunRadiance', new Vector3(), 'vec3' );
|
|
256
|
+
u( 'sunParams', new Vector4( 1, 0, 0, 0 ), 'vec4' );
|
|
255
257
|
ub( 'hasSun', false );
|
|
256
258
|
|
|
257
259
|
// Lighting
|
|
@@ -30,6 +30,7 @@ export class VideoRenderManager {
|
|
|
30
30
|
* @param {boolean} [options.enableOIDN=true] - Run OIDN denoiser per frame
|
|
31
31
|
* @param {number} [options.speed=1] - Playback speed multiplier (maps video time to animation time)
|
|
32
32
|
* @param {number} [options.totalFrames] - Override total frame count (for looped animations)
|
|
33
|
+
* @param {number} [options.startFrame=0] - First frame to render: a resumed export skips those it has
|
|
33
34
|
* @param {import('./timeline/TimelineManager.js').TimelineManager} [options.timeline] - Keyframes to
|
|
34
35
|
* play in video time; without a clip the video lasts as long as they do
|
|
35
36
|
* @param {Function} [options.onFrame] - async (ImageBitmap, frameIndex, totalFrames) => void
|
|
@@ -45,6 +46,7 @@ export class VideoRenderManager {
|
|
|
45
46
|
samplesPerFrame = PRODUCTION_RENDER_CONFIG.maxSamples,
|
|
46
47
|
enableOIDN = true,
|
|
47
48
|
timeline = null,
|
|
49
|
+
startFrame = 0,
|
|
48
50
|
onFrame,
|
|
49
51
|
onProgress,
|
|
50
52
|
onComplete,
|
|
@@ -94,7 +96,7 @@ export class VideoRenderManager {
|
|
|
94
96
|
|
|
95
97
|
try {
|
|
96
98
|
|
|
97
|
-
for ( let i = 0; i < totalFrames; i ++ ) {
|
|
99
|
+
for ( let i = Math.max( 0, startFrame ); i < totalFrames; i ++ ) {
|
|
98
100
|
|
|
99
101
|
if ( this._cancelled ) break;
|
|
100
102
|
|
|
@@ -133,6 +133,28 @@ export class CameraTrack {
|
|
|
133
133
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/** The keys as plain data, for a saved session. */
|
|
137
|
+
serialize() {
|
|
138
|
+
|
|
139
|
+
return this.keys.map( ( { time, position, target, fov, orthoHeight } ) => ( { time, position: position.toArray(), target: target.toArray(), fov, orthoHeight } ) );
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Replaces every key with the ones {@link serialize} described. */
|
|
144
|
+
restore( keys ) {
|
|
145
|
+
|
|
146
|
+
this.keys = ( keys ?? [] ).map( key => ( {
|
|
147
|
+
id: this._nextId ++,
|
|
148
|
+
time: key.time,
|
|
149
|
+
position: new Vector3().fromArray( key.position ),
|
|
150
|
+
target: new Vector3().fromArray( key.target ),
|
|
151
|
+
fov: key.fov,
|
|
152
|
+
orthoHeight: key.orthoHeight,
|
|
153
|
+
} ) );
|
|
154
|
+
this._changed();
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
136
158
|
dispose() {
|
|
137
159
|
|
|
138
160
|
this._cameraManager = this._onChange = null;
|
|
@@ -105,6 +105,20 @@ export class TimelineManager extends EventDispatcher {
|
|
|
105
105
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/** Every track's keys as plain data, for a saved session. */
|
|
109
|
+
serialize() {
|
|
110
|
+
|
|
111
|
+
return { camera: this.camera.serialize() };
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
restore( state ) {
|
|
116
|
+
|
|
117
|
+
this.stop();
|
|
118
|
+
this.camera.restore( state?.camera );
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
108
122
|
/** Drops every key: they belonged to the scene being replaced. */
|
|
109
123
|
clear() {
|
|
110
124
|
|
package/src/version.js
ADDED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RGBAFormat, FloatType, LinearFilter, RepeatWrapping, ClampToEdgeWrapping,
|
|
3
|
-
EquirectangularReflectionMapping, LinearSRGBColorSpace, DataTexture
|
|
4
|
-
} from 'three';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* ProceduralSky
|
|
8
|
-
*
|
|
9
|
-
* CPU-based Preetham atmospheric scattering sky generator.
|
|
10
|
-
* Produces a DataTexture directly — no render targets, no GPU readback,
|
|
11
|
-
* no resource lifecycle issues with the WebGPU backend.
|
|
12
|
-
*
|
|
13
|
-
* For a 512×256 texture this takes ~5-10ms on CPU, which is negligible
|
|
14
|
-
* since it only runs on parameter change (not per-frame).
|
|
15
|
-
*
|
|
16
|
-
* Public API matches ProceduralSkyRenderer: render(params) → texture.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// ── Constants (from preetham_sky.glsl) ──
|
|
20
|
-
|
|
21
|
-
const PI = Math.PI;
|
|
22
|
-
const TWO_PI = Math.PI * 2;
|
|
23
|
-
const E = Math.E;
|
|
24
|
-
|
|
25
|
-
const TOTAL_RAYLEIGH = [ 5.804542996261093e-6, 1.3562911419845635e-5, 3.0265902468824876e-5 ];
|
|
26
|
-
const MIE_CONST = [ 1.8399918514433978e14, 2.7798023919660528e14, 4.0790479543861094e14 ];
|
|
27
|
-
|
|
28
|
-
const CUTOFF_ANGLE = 1.6110731556870734;
|
|
29
|
-
const STEEPNESS = 1.5;
|
|
30
|
-
const EE_CONST = 1000.0;
|
|
31
|
-
|
|
32
|
-
const RAYLEIGH_ZENITH_LENGTH = 8400.0;
|
|
33
|
-
const MIE_ZENITH_LENGTH = 1250.0;
|
|
34
|
-
const SUN_ANGULAR_DIAMETER_COS = 0.9999566769464484;
|
|
35
|
-
|
|
36
|
-
const THREE_OVER_SIXTEEN_PI = 0.05968310365946075;
|
|
37
|
-
const ONE_OVER_FOUR_PI = 0.07957747154594767;
|
|
38
|
-
|
|
39
|
-
// ── CPU Math ──
|
|
40
|
-
|
|
41
|
-
function computeSunIntensity( zenithAngleCos ) {
|
|
42
|
-
|
|
43
|
-
const clamped = Math.max( - 1.0, Math.min( 1.0, zenithAngleCos ) );
|
|
44
|
-
return EE_CONST * Math.max( 0.0, 1.0 - Math.pow( E, - ( CUTOFF_ANGLE - Math.acos( clamped ) ) / STEEPNESS ) );
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function totalMie( T ) {
|
|
49
|
-
|
|
50
|
-
const c = ( 0.2 * T ) * 10e-18;
|
|
51
|
-
return [ 0.434 * c * MIE_CONST[ 0 ], 0.434 * c * MIE_CONST[ 1 ], 0.434 * c * MIE_CONST[ 2 ] ];
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function rayleighPhase( cosTheta ) {
|
|
56
|
-
|
|
57
|
-
return THREE_OVER_SIXTEEN_PI * ( 1.0 + Math.pow( cosTheta, 2.0 ) );
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function hgPhase( cosTheta, g ) {
|
|
62
|
-
|
|
63
|
-
const g2 = g * g;
|
|
64
|
-
return ONE_OVER_FOUR_PI * ( ( 1.0 - g2 ) / Math.pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ) );
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function dot3( a, b ) {
|
|
69
|
-
|
|
70
|
-
return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ];
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function normalize3( v ) {
|
|
75
|
-
|
|
76
|
-
const len = Math.sqrt( v[ 0 ] * v[ 0 ] + v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] );
|
|
77
|
-
if ( len === 0 ) return [ 0, 0, 0 ];
|
|
78
|
-
return [ v[ 0 ] / len, v[ 1 ] / len, v[ 2 ] / len ];
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function computePreethamPixel( u, v, sunDir, sunIntensity, rayleighDensity, mieDensity, mieAnisotropy, turbidity ) {
|
|
83
|
-
|
|
84
|
-
// Equirectangular → direction
|
|
85
|
-
const theta = ( 1.0 - v ) * PI;
|
|
86
|
-
const phi = ( u - 0.5 ) * TWO_PI;
|
|
87
|
-
const sinTheta = Math.sin( theta );
|
|
88
|
-
const direction = normalize3( [
|
|
89
|
-
sinTheta * Math.sin( phi ),
|
|
90
|
-
Math.cos( theta ),
|
|
91
|
-
sinTheta * Math.cos( phi )
|
|
92
|
-
] );
|
|
93
|
-
|
|
94
|
-
const vSunDirection = normalize3( sunDir );
|
|
95
|
-
|
|
96
|
-
// Sun intensity from zenith angle
|
|
97
|
-
const vSunE = computeSunIntensity( vSunDirection[ 1 ] ) * sunIntensity;
|
|
98
|
-
|
|
99
|
-
// Sun fade near horizon
|
|
100
|
-
const vSunfade = 1.0 - Math.max( 0.0, Math.min( 1.0, 1.0 - Math.exp( vSunDirection[ 1 ] / 450000.0 ) ) );
|
|
101
|
-
|
|
102
|
-
// Rayleigh coefficient
|
|
103
|
-
const rayleighCoeff = rayleighDensity - ( 1.0 * ( 1.0 - vSunfade ) );
|
|
104
|
-
const vBetaR = [ TOTAL_RAYLEIGH[ 0 ] * rayleighCoeff, TOTAL_RAYLEIGH[ 1 ] * rayleighCoeff, TOTAL_RAYLEIGH[ 2 ] * rayleighCoeff ];
|
|
105
|
-
|
|
106
|
-
// Mie coefficient
|
|
107
|
-
const mieCoeff = totalMie( turbidity );
|
|
108
|
-
const vBetaM = [ mieCoeff[ 0 ] * mieDensity, mieCoeff[ 1 ] * mieDensity, mieCoeff[ 2 ] * mieDensity ];
|
|
109
|
-
|
|
110
|
-
// Zenith angle and optical path length
|
|
111
|
-
const fragZenithAngle = Math.acos( Math.max( 0.0, direction[ 1 ] ) );
|
|
112
|
-
const cosZenith = Math.cos( fragZenithAngle );
|
|
113
|
-
const zenithDeg = fragZenithAngle * ( 180.0 / PI );
|
|
114
|
-
const inverseFactor = cosZenith + 0.15 * Math.pow( 93.885 - zenithDeg, - 1.253 );
|
|
115
|
-
const sR = RAYLEIGH_ZENITH_LENGTH / inverseFactor;
|
|
116
|
-
const sM = MIE_ZENITH_LENGTH / inverseFactor;
|
|
117
|
-
|
|
118
|
-
// Extinction (Beer's law)
|
|
119
|
-
const Fex = [
|
|
120
|
-
Math.exp( - ( vBetaR[ 0 ] * sR + vBetaM[ 0 ] * sM ) ),
|
|
121
|
-
Math.exp( - ( vBetaR[ 1 ] * sR + vBetaM[ 1 ] * sM ) ),
|
|
122
|
-
Math.exp( - ( vBetaR[ 2 ] * sR + vBetaM[ 2 ] * sM ) )
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
// Scattering angle
|
|
126
|
-
const cosViewSun = dot3( direction, vSunDirection );
|
|
127
|
-
|
|
128
|
-
// Phase functions
|
|
129
|
-
const rPhase = rayleighPhase( cosViewSun * 0.5 + 0.5 );
|
|
130
|
-
const betaRTheta = [ vBetaR[ 0 ] * rPhase, vBetaR[ 1 ] * rPhase, vBetaR[ 2 ] * rPhase ];
|
|
131
|
-
|
|
132
|
-
const mPhase = hgPhase( cosViewSun, mieAnisotropy );
|
|
133
|
-
const betaMTheta = [ vBetaM[ 0 ] * mPhase, vBetaM[ 1 ] * mPhase, vBetaM[ 2 ] * mPhase ];
|
|
134
|
-
|
|
135
|
-
// Inscattered light
|
|
136
|
-
const betaSum = [ betaRTheta[ 0 ] + betaMTheta[ 0 ], betaRTheta[ 1 ] + betaMTheta[ 1 ], betaRTheta[ 2 ] + betaMTheta[ 2 ] ];
|
|
137
|
-
const betaRplusM = [ vBetaR[ 0 ] + vBetaM[ 0 ], vBetaR[ 1 ] + vBetaM[ 1 ], vBetaR[ 2 ] + vBetaM[ 2 ] ];
|
|
138
|
-
|
|
139
|
-
const Lin = [
|
|
140
|
-
Math.pow( vSunE * ( betaSum[ 0 ] / betaRplusM[ 0 ] ) * ( 1.0 - Fex[ 0 ] ), 1.5 ),
|
|
141
|
-
Math.pow( vSunE * ( betaSum[ 1 ] / betaRplusM[ 1 ] ) * ( 1.0 - Fex[ 1 ] ), 1.5 ),
|
|
142
|
-
Math.pow( vSunE * ( betaSum[ 2 ] / betaRplusM[ 2 ] ) * ( 1.0 - Fex[ 2 ] ), 1.5 )
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
// Sunset mix
|
|
146
|
-
const sunsetFactor = Math.max( 0.0, Math.min( 1.0, 1.0 - Math.pow( 1.0 - vSunDirection[ 1 ], 5.0 ) ) );
|
|
147
|
-
const sunsetColor = [
|
|
148
|
-
Math.sqrt( vSunE * ( betaSum[ 0 ] / betaRplusM[ 0 ] ) * Fex[ 0 ] ),
|
|
149
|
-
Math.sqrt( vSunE * ( betaSum[ 1 ] / betaRplusM[ 1 ] ) * Fex[ 1 ] ),
|
|
150
|
-
Math.sqrt( vSunE * ( betaSum[ 2 ] / betaRplusM[ 2 ] ) * Fex[ 2 ] )
|
|
151
|
-
];
|
|
152
|
-
|
|
153
|
-
const mixW = 1.0 - sunsetFactor;
|
|
154
|
-
for ( let i = 0; i < 3; i ++ ) {
|
|
155
|
-
|
|
156
|
-
const mixVal = 1.0 * ( 1.0 - mixW ) + sunsetColor[ i ] * mixW;
|
|
157
|
-
Lin[ i ] *= mixVal;
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Base luminance + sun disk
|
|
162
|
-
const L0 = [ 0.1 * Fex[ 0 ], 0.1 * Fex[ 1 ], 0.1 * Fex[ 2 ] ];
|
|
163
|
-
const sundisk = smoothstep( SUN_ANGULAR_DIAMETER_COS, SUN_ANGULAR_DIAMETER_COS + 0.00002, cosViewSun );
|
|
164
|
-
for ( let i = 0; i < 3; i ++ ) {
|
|
165
|
-
|
|
166
|
-
L0[ i ] += vSunE * 19000.0 * Fex[ i ] * sundisk;
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Final color — clamp to half-float max (65504) to avoid overflow
|
|
171
|
-
// in the CDF builder's half-float conversion
|
|
172
|
-
const MAX_HDR = 65504.0;
|
|
173
|
-
return [
|
|
174
|
-
Math.min( ( Lin[ 0 ] + L0[ 0 ] ) * 0.04, MAX_HDR ),
|
|
175
|
-
Math.min( ( Lin[ 1 ] + L0[ 1 ] ) * 0.04 + 0.0003, MAX_HDR ),
|
|
176
|
-
Math.min( ( Lin[ 2 ] + L0[ 2 ] ) * 0.04 + 0.00075, MAX_HDR ),
|
|
177
|
-
1.0
|
|
178
|
-
];
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function smoothstep( edge0, edge1, x ) {
|
|
183
|
-
|
|
184
|
-
const t = Math.max( 0.0, Math.min( 1.0, ( x - edge0 ) / ( edge1 - edge0 ) ) );
|
|
185
|
-
return t * t * ( 3.0 - 2.0 * t );
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ── Renderer Class ──
|
|
190
|
-
|
|
191
|
-
export class ProceduralSky {
|
|
192
|
-
|
|
193
|
-
constructor( width = 512, height = 256 ) {
|
|
194
|
-
|
|
195
|
-
this.width = width;
|
|
196
|
-
this.height = height;
|
|
197
|
-
this.lastRenderTime = 0;
|
|
198
|
-
|
|
199
|
-
// Pre-allocate pixel buffer and DataTexture (reused across renders)
|
|
200
|
-
this._pixels = new Float32Array( width * height * 4 );
|
|
201
|
-
this._texture = new DataTexture( this._pixels, width, height, RGBAFormat, FloatType );
|
|
202
|
-
this._texture.mapping = EquirectangularReflectionMapping;
|
|
203
|
-
this._texture.colorSpace = LinearSRGBColorSpace;
|
|
204
|
-
this._texture.minFilter = LinearFilter;
|
|
205
|
-
this._texture.magFilter = LinearFilter;
|
|
206
|
-
this._texture.wrapS = RepeatWrapping;
|
|
207
|
-
this._texture.wrapT = ClampToEdgeWrapping;
|
|
208
|
-
this._texture.generateMipmaps = false;
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Generate Preetham sky into a reusable DataTexture.
|
|
214
|
-
* @param {Object} params - Sky parameters
|
|
215
|
-
* @returns {DataTexture} Equirectangular sky texture with CPU data
|
|
216
|
-
*/
|
|
217
|
-
render( params ) {
|
|
218
|
-
|
|
219
|
-
const startTime = performance.now();
|
|
220
|
-
|
|
221
|
-
const sunDir = [ params.sunDirection.x, params.sunDirection.y, params.sunDirection.z ];
|
|
222
|
-
const sunIntensity = params.sunIntensity || 1.0;
|
|
223
|
-
const rayleighDensity = params.rayleighDensity || 2.0;
|
|
224
|
-
const mieDensity = params.mieDensity || 0.005;
|
|
225
|
-
const mieAnisotropy = params.mieAnisotropy || 0.8;
|
|
226
|
-
const turbidity = params.turbidity || 2.0;
|
|
227
|
-
|
|
228
|
-
const { width, height } = this;
|
|
229
|
-
const pixels = this._pixels;
|
|
230
|
-
|
|
231
|
-
for ( let y = 0; y < height; y ++ ) {
|
|
232
|
-
|
|
233
|
-
const v = ( y + 0.5 ) / height;
|
|
234
|
-
|
|
235
|
-
for ( let x = 0; x < width; x ++ ) {
|
|
236
|
-
|
|
237
|
-
const u = ( x + 0.5 ) / width;
|
|
238
|
-
const color = computePreethamPixel( u, v, sunDir, sunIntensity, rayleighDensity, mieDensity, mieAnisotropy, turbidity );
|
|
239
|
-
|
|
240
|
-
const idx = ( y * width + x ) * 4;
|
|
241
|
-
pixels[ idx ] = color[ 0 ];
|
|
242
|
-
pixels[ idx + 1 ] = color[ 1 ];
|
|
243
|
-
pixels[ idx + 2 ] = color[ 2 ];
|
|
244
|
-
pixels[ idx + 3 ] = color[ 3 ];
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
this._texture.needsUpdate = true;
|
|
251
|
-
// Fresh linear Rec.709 pixels in a reused texture. Colour management records which space a
|
|
252
|
-
// texture's pixels hold so it never converts them twice; left in place, that record would
|
|
253
|
-
// claim these were already converted and they never would be.
|
|
254
|
-
delete this._texture.userData?.__rayzeeColorSpace;
|
|
255
|
-
this.lastRenderTime = performance.now() - startTime;
|
|
256
|
-
|
|
257
|
-
return this._texture;
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
setResolution( width, height ) {
|
|
262
|
-
|
|
263
|
-
if ( this.width === width && this.height === height ) return;
|
|
264
|
-
this.width = width;
|
|
265
|
-
this.height = height;
|
|
266
|
-
this._pixels = new Float32Array( width * height * 4 );
|
|
267
|
-
this._texture.dispose();
|
|
268
|
-
this._texture = new DataTexture( this._pixels, width, height, RGBAFormat, FloatType );
|
|
269
|
-
this._texture.mapping = EquirectangularReflectionMapping;
|
|
270
|
-
this._texture.colorSpace = LinearSRGBColorSpace;
|
|
271
|
-
this._texture.minFilter = LinearFilter;
|
|
272
|
-
this._texture.magFilter = LinearFilter;
|
|
273
|
-
this._texture.wrapS = RepeatWrapping;
|
|
274
|
-
this._texture.wrapT = ClampToEdgeWrapping;
|
|
275
|
-
this._texture.generateMipmaps = false;
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
getLastRenderTime() {
|
|
280
|
-
|
|
281
|
-
return this.lastRenderTime;
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
dispose() {
|
|
286
|
-
|
|
287
|
-
this._texture.dispose();
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
}
|