rayzee 8.8.2 → 9.0.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 +71 -12
- package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CQLCLpcC.js} +2 -0
- package/dist/rayzee.es.js +3571 -3110
- package/dist/rayzee.umd.js +110 -99
- package/package.json +1 -1
- package/src/EngineDefaults.js +21 -19
- package/src/EngineEvents.js +4 -0
- package/src/Passes/OIDNTemporalHistory.js +25 -7
- package/src/PathTracerApp.js +23 -6
- package/src/Processor/AssetLoader.js +9 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +18 -6
- package/src/RenderSettings.js +11 -8
- package/src/Stages/MotionVector.js +8 -42
- package/src/Stages/NRD.js +30 -21
- package/src/Stages/NormalDepth.js +5 -17
- package/src/Stages/PathTracer.js +6 -2
- package/src/TSL/CameraRay.js +99 -14
- package/src/TSL/DebugKernel.js +2 -2
- package/src/TSL/GenerateKernel.js +2 -2
- package/src/index.js +4 -0
- package/src/managers/CameraManager.js +244 -56
- package/src/managers/DenoisingManager.js +4 -3
- package/src/managers/InteractionManager.js +30 -2
- package/src/managers/UniformManager.js +6 -4
- package/src/managers/VideoRenderManager.js +35 -16
- 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 +213 -0
- package/src/managers/timeline/TimelineManager.js +132 -0
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -14,6 +14,7 @@ export const RENDER_PROFILES = Object.freeze( {
|
|
|
14
14
|
environmentRotation: 0.0, // degrees — the HDRI as authored, as Blender shows it
|
|
15
15
|
toneMapping: 6, // AgXToneMapping
|
|
16
16
|
saturation: 1.0, // no grade
|
|
17
|
+
dofMode: 'look', // depth of field set by how blurry it looks, not by a lens — right at any scene scale
|
|
17
18
|
} ),
|
|
18
19
|
physical: Object.freeze( {
|
|
19
20
|
areaLightIntensityScale: 1.0,
|
|
@@ -22,6 +23,7 @@ export const RENDER_PROFILES = Object.freeze( {
|
|
|
22
23
|
// engine's own corpus (shade 1.48 vs AgX 3.01 from identical radiance).
|
|
23
24
|
toneMapping: 6,
|
|
24
25
|
saturation: 1.0, // no grade
|
|
26
|
+
dofMode: 'physical',
|
|
25
27
|
} ),
|
|
26
28
|
} );
|
|
27
29
|
|
|
@@ -101,7 +103,7 @@ export const ENGINE_DEFAULTS = {
|
|
|
101
103
|
skyMieAnisotropy: 0.76,
|
|
102
104
|
skyPreset: 'clearMorning',
|
|
103
105
|
|
|
104
|
-
// Camera projection — 'perspective' | 'equirectangular'. Panorama ranges are UI-facing degrees.
|
|
106
|
+
// Camera projection — 'perspective' | 'orthographic' | 'equirectangular'. Panorama ranges are UI-facing degrees.
|
|
105
107
|
cameraProjection: 'perspective',
|
|
106
108
|
panoramaLonRange: [ - 180, 180 ],
|
|
107
109
|
panoramaLatRange: [ - 90, 90 ],
|
|
@@ -114,6 +116,9 @@ export const ENGINE_DEFAULTS = {
|
|
|
114
116
|
focalLength: 50,
|
|
115
117
|
apertureScale: 1.0,
|
|
116
118
|
anamorphicRatio: 1.0,
|
|
119
|
+
unitsPerMetre: 1, // scene units per real metre; the lens is specified in mm
|
|
120
|
+
dofMode: RENDER_PROFILES.viewer.dofMode, // 'look' | 'physical'
|
|
121
|
+
dofBlur: 0.05, // look mode: a far background's blur, as a fraction of the image height
|
|
117
122
|
|
|
118
123
|
// Auto-focus
|
|
119
124
|
autoFocusMode: 'auto', // 'manual' | 'auto'
|
|
@@ -252,6 +257,9 @@ export const ENGINE_DEFAULTS = {
|
|
|
252
257
|
autoExposureAdaptSpeedDark: 0.5,
|
|
253
258
|
};
|
|
254
259
|
|
|
260
|
+
/** The `cameraProjection` uniform's value for each setting value. */
|
|
261
|
+
export const CAMERA_PROJECTION_IDS = Object.freeze( { perspective: 0, equirectangular: 1, orthographic: 2 } );
|
|
262
|
+
|
|
255
263
|
// Ray distance NormalDepth writes on a miss. Max finite half-float, so miss−miss diffs stay 0
|
|
256
264
|
// rather than Inf−Inf=NaN.
|
|
257
265
|
export const GBUFFER_MISS_DEPTH = 65504.0;
|
|
@@ -474,44 +482,39 @@ export const CAMERA_PRESETS = {
|
|
|
474
482
|
portrait: {
|
|
475
483
|
name: "Portrait",
|
|
476
484
|
description: "Shallow depth of field, background blur",
|
|
477
|
-
|
|
478
|
-
focusDistance: 1.5,
|
|
485
|
+
dofBlur: 0.10,
|
|
479
486
|
aperture: 1.4,
|
|
480
487
|
focalLength: 135,
|
|
481
|
-
apertureScale: 1.
|
|
488
|
+
apertureScale: 1.0
|
|
482
489
|
},
|
|
483
490
|
landscape: {
|
|
484
491
|
name: "Landscape",
|
|
485
492
|
description: "Maximum depth of field, everything in focus",
|
|
486
|
-
|
|
487
|
-
focusDistance: 10.0,
|
|
493
|
+
dofBlur: 0.001,
|
|
488
494
|
aperture: 16.0,
|
|
489
495
|
focalLength: 24,
|
|
490
496
|
apertureScale: 0.5
|
|
491
497
|
},
|
|
492
498
|
macro: {
|
|
493
499
|
name: "Macro",
|
|
494
|
-
description: "
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
aperture: 2.0,
|
|
500
|
+
description: "Very thin focus plane for close-ups",
|
|
501
|
+
dofBlur: 0.14,
|
|
502
|
+
aperture: 1.4,
|
|
498
503
|
focalLength: 100,
|
|
499
504
|
apertureScale: 2.0
|
|
500
505
|
},
|
|
501
506
|
product: {
|
|
502
507
|
name: "Product",
|
|
503
508
|
description: "Sharp detail with subtle background separation",
|
|
504
|
-
|
|
505
|
-
focusDistance: 0.8,
|
|
509
|
+
dofBlur: 0.013,
|
|
506
510
|
aperture: 2.8,
|
|
507
511
|
focalLength: 85,
|
|
508
|
-
apertureScale:
|
|
512
|
+
apertureScale: 0.4
|
|
509
513
|
},
|
|
510
514
|
architectural: {
|
|
511
515
|
name: "Architectural",
|
|
512
|
-
description: "
|
|
513
|
-
|
|
514
|
-
focusDistance: 5.0,
|
|
516
|
+
description: "Deep focus for rooms and buildings",
|
|
517
|
+
dofBlur: 0.001,
|
|
515
518
|
aperture: 11.0,
|
|
516
519
|
focalLength: 16,
|
|
517
520
|
apertureScale: 0.5
|
|
@@ -519,11 +522,10 @@ export const CAMERA_PRESETS = {
|
|
|
519
522
|
cinematic: {
|
|
520
523
|
name: "Cinematic",
|
|
521
524
|
description: "Dramatic depth separation with anamorphic bokeh",
|
|
522
|
-
|
|
523
|
-
focusDistance: 3.0,
|
|
525
|
+
dofBlur: 0.13,
|
|
524
526
|
aperture: 1.4,
|
|
525
527
|
focalLength: 200,
|
|
526
|
-
apertureScale: 1.
|
|
528
|
+
apertureScale: 1.0,
|
|
527
529
|
anamorphicRatio: 1.5
|
|
528
530
|
}
|
|
529
531
|
};
|
package/src/EngineEvents.js
CHANGED
|
@@ -36,6 +36,7 @@ export const EngineEvents = {
|
|
|
36
36
|
|
|
37
37
|
// Camera
|
|
38
38
|
AUTO_FOCUS_UPDATED: 'engine:autoFocusUpdated',
|
|
39
|
+
ORTHO_HEIGHT_UPDATED: 'engine:orthoHeightUpdated',
|
|
39
40
|
AUTO_EXPOSURE_UPDATED: 'engine:autoExposureUpdated',
|
|
40
41
|
AF_POINT_PLACED: 'engine:afPointPlaced',
|
|
41
42
|
|
|
@@ -48,6 +49,9 @@ export const EngineEvents = {
|
|
|
48
49
|
ANIMATION_STOPPED: 'engine:animationStopped',
|
|
49
50
|
ANIMATION_FINISHED: 'engine:animationFinished',
|
|
50
51
|
|
|
52
|
+
// Timeline (authored keyframes)
|
|
53
|
+
TIMELINE_CHANGED: 'engine:timelineChanged',
|
|
54
|
+
|
|
51
55
|
// Video rendering
|
|
52
56
|
VIDEO_RENDER_PROGRESS: 'engine:videoRenderProgress',
|
|
53
57
|
VIDEO_RENDER_COMPLETE: 'engine:videoRenderComplete',
|
|
@@ -22,10 +22,11 @@ export const HISTORY_DEFAULTS = Object.freeze( {
|
|
|
22
22
|
} );
|
|
23
23
|
|
|
24
24
|
const WG = 8;
|
|
25
|
-
const PARAMS_BYTES =
|
|
25
|
+
const PARAMS_BYTES = 304;
|
|
26
26
|
const MIN_MOVED_CAPACITY = 16;
|
|
27
27
|
const FLAG_CLEAN_AUX = 1;
|
|
28
28
|
const FLAG_SPLIT_COPIES = 2;
|
|
29
|
+
const FLAG_ORTHOGRAPHIC = 4;
|
|
29
30
|
const PICK_MOVED = 0x40000000;
|
|
30
31
|
|
|
31
32
|
const COMMON_WGSL = /* wgsl */`
|
|
@@ -47,6 +48,7 @@ struct Params {
|
|
|
47
48
|
prevCamPos: vec4<f32>,
|
|
48
49
|
moved: vec4<u32>,
|
|
49
50
|
movedLength: f32,
|
|
51
|
+
prevCamDir: vec4<f32>,
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
@group(0) @binding(0) var<uniform> P: Params;
|
|
@@ -104,9 +106,19 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
|
104
106
|
|
|
105
107
|
let size = vec2<f32>( P.size );
|
|
106
108
|
let uv = ( vec2<f32>( gid.xy ) + 0.5 ) / size;
|
|
107
|
-
|
|
108
|
-
let
|
|
109
|
-
|
|
109
|
+
// Depth is along the ray: from the camera, or from its image plane when orthographic.
|
|
110
|
+
let ortho = ( P.flags & ${ FLAG_ORTHOGRAPHIC }u ) != 0u;
|
|
111
|
+
let ndc = vec2<f32>( uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0 );
|
|
112
|
+
var origin = P.camWorld[ 3 ].xyz;
|
|
113
|
+
var dir: vec3<f32>;
|
|
114
|
+
if ( ortho ) {
|
|
115
|
+
origin = ( P.camWorld * vec4<f32>( ( P.projInv * vec4<f32>( ndc, 0.0, 1.0 ) ).xy, 0.0, 1.0 ) ).xyz;
|
|
116
|
+
dir = - normalize( P.camWorld[ 2 ].xyz );
|
|
117
|
+
} else {
|
|
118
|
+
let rc = P.projInv * vec4<f32>( ndc, 1.0, 1.0 );
|
|
119
|
+
dir = normalize( ( P.camWorld * vec4<f32>( rc.xyz / rc.w, 0.0 ) ).xyz );
|
|
120
|
+
}
|
|
121
|
+
var wp = origin + dir * g.w;
|
|
110
122
|
var n = g.xyz * 2.0 - 1.0;
|
|
111
123
|
|
|
112
124
|
// A moved object's surface is looked for where the object was.
|
|
@@ -132,7 +144,7 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
|
132
144
|
let pos = vec2<f32>( clip.x / clip.w * 0.5 + 0.5, 0.5 - clip.y / clip.w * 0.5 ) * size - 0.5;
|
|
133
145
|
let base = vec2<i32>( floor( pos ) );
|
|
134
146
|
let f = fract( pos );
|
|
135
|
-
let expected = length( wp - P.prevCamPos.xyz );
|
|
147
|
+
let expected = select( length( wp - P.prevCamPos.xyz ), dot( wp - P.prevCamPos.xyz, P.prevCamDir.xyz ), ortho );
|
|
136
148
|
|
|
137
149
|
for ( var j = 0; j < 2; j++ ) {
|
|
138
150
|
for ( var i = 0; i < 2; i++ ) {
|
|
@@ -306,6 +318,7 @@ export class OIDNTemporalHistory {
|
|
|
306
318
|
this._paramF32 = new Float32Array( this._paramData );
|
|
307
319
|
this._prevViewProj = new Float32Array( 16 );
|
|
308
320
|
this._prevCamPos = new Float32Array( 4 );
|
|
321
|
+
this._prevCamDir = new Float32Array( 4 );
|
|
309
322
|
|
|
310
323
|
}
|
|
311
324
|
|
|
@@ -325,7 +338,7 @@ export class OIDNTemporalHistory {
|
|
|
325
338
|
/**
|
|
326
339
|
* Blends the frame just traced (a restart: one sample) into the reprojected history.
|
|
327
340
|
* @param {Object} src - GPUTextures { color, albedo, normal, geo, geoPrev, shading, leaf } and { width, height }
|
|
328
|
-
* @param {{ world: Float32Array, projInv: Float32Array, viewProj: Float32Array }} camera - of that frame
|
|
341
|
+
* @param {{ world: Float32Array, projInv: Float32Array, viewProj: Float32Array, orthographic: boolean }} camera - of that frame
|
|
329
342
|
* @param {?{ count: number, leaves: Uint32Array, toPrev: Float32Array }} [moved] - TLAS leaves
|
|
330
343
|
* (ascending) that moved since the last frame, with each one's current→previous world matrix
|
|
331
344
|
*/
|
|
@@ -524,7 +537,7 @@ export class OIDNTemporalHistory {
|
|
|
524
537
|
u[ 0 ] = this.width;
|
|
525
538
|
u[ 1 ] = this.height;
|
|
526
539
|
u[ 2 ] = reset ? 1 : 0;
|
|
527
|
-
u[ 3 ] = ( s.cleanAux ? FLAG_CLEAN_AUX : 0 ) | ( s.splitCopies ? FLAG_SPLIT_COPIES : 0 );
|
|
540
|
+
u[ 3 ] = ( s.cleanAux ? FLAG_CLEAN_AUX : 0 ) | ( s.splitCopies ? FLAG_SPLIT_COPIES : 0 ) | ( camera.orthographic ? FLAG_ORTHOGRAPHIC : 0 );
|
|
528
541
|
f[ 4 ] = s.maxLength;
|
|
529
542
|
f[ 5 ] = s.smoothLength;
|
|
530
543
|
f[ 6 ] = s.depthTolerance;
|
|
@@ -539,6 +552,7 @@ export class OIDNTemporalHistory {
|
|
|
539
552
|
f.set( this._prevCamPos, 60 );
|
|
540
553
|
u[ 64 ] = this._movedCount;
|
|
541
554
|
f[ 68 ] = s.movedLength;
|
|
555
|
+
f.set( this._prevCamDir, 72 );
|
|
542
556
|
this.device.queue.writeBuffer( this._paramsBuffer, 0, this._paramData );
|
|
543
557
|
|
|
544
558
|
}
|
|
@@ -549,6 +563,10 @@ export class OIDNTemporalHistory {
|
|
|
549
563
|
this._prevCamPos[ 0 ] = camera.world[ 12 ];
|
|
550
564
|
this._prevCamPos[ 1 ] = camera.world[ 13 ];
|
|
551
565
|
this._prevCamPos[ 2 ] = camera.world[ 14 ];
|
|
566
|
+
const back = Math.hypot( camera.world[ 8 ], camera.world[ 9 ], camera.world[ 10 ] );
|
|
567
|
+
this._prevCamDir[ 0 ] = - camera.world[ 8 ] / back;
|
|
568
|
+
this._prevCamDir[ 1 ] = - camera.world[ 9 ] / back;
|
|
569
|
+
this._prevCamDir[ 2 ] = - camera.world[ 10 ] / back;
|
|
552
570
|
|
|
553
571
|
}
|
|
554
572
|
|
package/src/PathTracerApp.js
CHANGED
|
@@ -35,6 +35,7 @@ import { SceneProcessor } from './Processor/SceneProcessor.js';
|
|
|
35
35
|
// Managers
|
|
36
36
|
import { RenderSettings } from './RenderSettings.js';
|
|
37
37
|
import { CameraManager } from './managers/CameraManager.js';
|
|
38
|
+
import { TimelineManager } from './managers/timeline/TimelineManager.js';
|
|
38
39
|
import { LightManager } from './managers/LightManager.js';
|
|
39
40
|
import { GoboManager } from './managers/GoboManager.js';
|
|
40
41
|
import { IESManager } from './managers/IESManager.js';
|
|
@@ -181,6 +182,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
181
182
|
...DEFAULT_STATE,
|
|
182
183
|
environmentRotation: this._profile.environmentRotation,
|
|
183
184
|
saturation: this._profile.saturation,
|
|
185
|
+
dofMode: this._profile.dofMode,
|
|
184
186
|
},
|
|
185
187
|
{ issues: this._issues }
|
|
186
188
|
);
|
|
@@ -214,6 +216,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
214
216
|
// ── Managers (direct public access) ──
|
|
215
217
|
/** @type {CameraManager} */
|
|
216
218
|
this.cameraManager = null;
|
|
219
|
+
/** @type {TimelineManager} Authored animation: keyframed tracks. */
|
|
220
|
+
this.timeline = null;
|
|
217
221
|
/** @type {LightManager} */
|
|
218
222
|
this.lightManager = null;
|
|
219
223
|
/** @type {GoboManager} */
|
|
@@ -359,7 +363,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
359
363
|
|
|
360
364
|
}
|
|
361
365
|
|
|
362
|
-
|
|
366
|
+
this.timeline.update();
|
|
367
|
+
this.cameraManager.updateControls();
|
|
363
368
|
|
|
364
369
|
this._applyPendingRenderScale();
|
|
365
370
|
|
|
@@ -638,6 +643,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
638
643
|
this.iesManager?.dispose();
|
|
639
644
|
this.denoisingManager?.dispose();
|
|
640
645
|
this.interactionManager?.dispose();
|
|
646
|
+
this.timeline?.dispose();
|
|
641
647
|
this.cameraManager?.dispose();
|
|
642
648
|
|
|
643
649
|
this.pipeline?.dispose();
|
|
@@ -2088,6 +2094,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2088
2094
|
// refresh landing in the middle of that raced the renderer's own output pass.
|
|
2089
2095
|
this.denoisingManager?.setCadenceSuspended( isProduction );
|
|
2090
2096
|
|
|
2097
|
+
this.timeline.stop();
|
|
2091
2098
|
this.cameraManager.controls.enabled = ! isProduction;
|
|
2092
2099
|
|
|
2093
2100
|
// Anything with a SETTING_ROUTES entry must go through settings, not setUniform: set() early-returns on
|
|
@@ -3390,6 +3397,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3390
3397
|
_initCameraManager() {
|
|
3391
3398
|
|
|
3392
3399
|
this.cameraManager = new CameraManager( this.canvas );
|
|
3400
|
+
this.timeline = new TimelineManager( { cameraManager: this.cameraManager, onReset: () => this.reset() } );
|
|
3393
3401
|
|
|
3394
3402
|
}
|
|
3395
3403
|
|
|
@@ -3415,14 +3423,19 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3415
3423
|
this.assetLoader.setRenderer( this.renderer );
|
|
3416
3424
|
this.assetLoader.createFloorPlane();
|
|
3417
3425
|
|
|
3418
|
-
|
|
3426
|
+
const onCameraMoved = () => {
|
|
3419
3427
|
|
|
3420
3428
|
this.needsReset = true;
|
|
3421
3429
|
// Here rather than in render(), so the first frame of the move is already at the lower resolution.
|
|
3422
3430
|
this.stages.pathTracer?.enterInteractionMode();
|
|
3423
3431
|
this.wake();
|
|
3424
3432
|
|
|
3425
|
-
}
|
|
3433
|
+
};
|
|
3434
|
+
|
|
3435
|
+
this._addTrackedListener( this.cameraManager.controls, 'change', onCameraMoved );
|
|
3436
|
+
this._addTrackedListener( this.cameraManager.walkControls, 'change', onCameraMoved );
|
|
3437
|
+
// A held key moves the camera from the frame loop, which may be asleep.
|
|
3438
|
+
this._addTrackedListener( this.cameraManager.walkControls, 'start', () => this.wake() );
|
|
3426
3439
|
|
|
3427
3440
|
}
|
|
3428
3441
|
|
|
@@ -3526,6 +3539,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3526
3539
|
// Forward manager events → app events
|
|
3527
3540
|
this._addTrackedListener( this.cameraManager, 'CameraSwitched', ( e ) => this.dispatchEvent( e ) );
|
|
3528
3541
|
this._addTrackedListener( this.cameraManager, EngineEvents.AUTO_FOCUS_UPDATED, ( e ) => this.dispatchEvent( e ) );
|
|
3542
|
+
this._addTrackedListener( this.cameraManager, EngineEvents.ORTHO_HEIGHT_UPDATED, ( e ) => this.dispatchEvent( e ) );
|
|
3543
|
+
this._addTrackedListener( this.timeline, EngineEvents.TIMELINE_CHANGED, ( e ) => this.dispatchEvent( e ) );
|
|
3529
3544
|
|
|
3530
3545
|
this._forwardEvents( this.denoisingManager, [
|
|
3531
3546
|
EngineEvents.DENOISING_START, EngineEvents.DENOISING_END,
|
|
@@ -3566,9 +3581,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3566
3581
|
|
|
3567
3582
|
// Auto-focus context — CameraManager stores it, reads it each frame
|
|
3568
3583
|
this.cameraManager.initAutoFocus( {
|
|
3569
|
-
meshScene: this.meshScene,
|
|
3570
3584
|
assetLoader: this.assetLoader,
|
|
3571
|
-
floorPlane: this.assetLoader.floorPlane,
|
|
3572
3585
|
pathTracer: this.stages.pathTracer,
|
|
3573
3586
|
settings: this.settings,
|
|
3574
3587
|
softReset: () => this.reset( true ),
|
|
@@ -3582,12 +3595,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3582
3595
|
resetCallback: () => this.reset(),
|
|
3583
3596
|
reconcileCompletion: () => this._reconcileCompletion(),
|
|
3584
3597
|
denoisingManager: this.denoisingManager,
|
|
3585
|
-
cameraManager: this.cameraManager,
|
|
3586
3598
|
onInteractionRenderScale: () => {
|
|
3587
3599
|
|
|
3588
3600
|
if ( this.stages.pathTracer?.interactionMode ) this._requestRenderScale( this._interactionRenderScale() );
|
|
3589
3601
|
|
|
3590
3602
|
},
|
|
3603
|
+
onCameraProjection: ( value ) => this.cameraManager.applyProjection( value ),
|
|
3591
3604
|
} );
|
|
3592
3605
|
|
|
3593
3606
|
this.renderer.toneMappingExposure = this.settings.get( 'exposure' ) ?? 1.0;
|
|
@@ -3643,6 +3656,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3643
3656
|
|
|
3644
3657
|
const cameras = [ this.cameraManager.camera, ...( event.cameras || [] ) ];
|
|
3645
3658
|
this.cameraManager.setCameras( cameras );
|
|
3659
|
+
// Keys are poses in the scene being replaced, like the cameras saved in it.
|
|
3660
|
+
this.timeline.clear();
|
|
3646
3661
|
|
|
3647
3662
|
if ( this.interactionManager ) {
|
|
3648
3663
|
|
|
@@ -3821,8 +3836,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3821
3836
|
|
|
3822
3837
|
_syncControlsAfterLoad() {
|
|
3823
3838
|
|
|
3839
|
+
this.cameraManager.fitOrthographic();
|
|
3824
3840
|
this.cameraManager.controls.saveState();
|
|
3825
3841
|
this.cameraManager.controls.update();
|
|
3842
|
+
this.cameraManager.walkControls.fitSpeed( this.assetLoader.getSceneScale() );
|
|
3826
3843
|
|
|
3827
3844
|
}
|
|
3828
3845
|
|
|
@@ -97,6 +97,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
97
97
|
this.targetModel = null;
|
|
98
98
|
this.floorPlane = null;
|
|
99
99
|
this.sceneScale = 1.0;
|
|
100
|
+
this.sceneSize = new Vector3( 1, 1, 1 );
|
|
100
101
|
this.loaderCache = {};
|
|
101
102
|
this.uploadedFileInfo = null;
|
|
102
103
|
this.animations = [];
|
|
@@ -2000,6 +2001,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
2000
2001
|
|
|
2001
2002
|
// Calculate scene scale factor based on model size
|
|
2002
2003
|
const sceneScale = maxDim;
|
|
2004
|
+
this.sceneSize.copy( size );
|
|
2003
2005
|
|
|
2004
2006
|
// Rebuild path tracing
|
|
2005
2007
|
buildTimer.start( 'setupPathTracing' );
|
|
@@ -2216,6 +2218,13 @@ export class AssetLoader extends EventDispatcher {
|
|
|
2216
2218
|
|
|
2217
2219
|
}
|
|
2218
2220
|
|
|
2221
|
+
/** The loaded model's bounding-box size along x, y and z, in scene units. */
|
|
2222
|
+
getSceneSize() {
|
|
2223
|
+
|
|
2224
|
+
return this.sceneSize.clone();
|
|
2225
|
+
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2219
2228
|
getTargetModel() {
|
|
2220
2229
|
|
|
2221
2230
|
return this.targetModel;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Output: { group, camera, environment, animations, warnings }
|
|
5
5
|
* - group: THREE.Group of meshes (fed to PathTracerApp.loadObject3D)
|
|
6
|
-
* - camera:
|
|
6
|
+
* - camera: Perspective- or OrthographicCamera matching the pbrt Camera/LookAt, parented
|
|
7
7
|
* into the group so AssetLoader.extractCamerasFromModel finds it
|
|
8
8
|
* - environment: { texture } | null — set by the caller as scene.environment
|
|
9
9
|
* - animations: [ AnimationClip ] when the IR carries motion (see PBRTAnimation.js), else []
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
Group, Mesh, InstancedMesh, PerspectiveCamera, Matrix4, Vector3, Quaternion,
|
|
18
|
+
Group, Mesh, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Vector3, Quaternion,
|
|
19
19
|
BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute, SphereGeometry,
|
|
20
20
|
DataTexture, FloatType, RGBAFormat, LinearFilter, EquirectangularReflectionMapping,
|
|
21
21
|
SRGBColorSpace, AnimationClip, VectorKeyframeTrack, QuaternionKeyframeTrack,
|
|
@@ -184,7 +184,7 @@ export class PBRTSceneBuilder {
|
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* @param {object} ir - output of PBRTParser
|
|
187
|
-
* @returns {Promise<{group:Group, camera:PerspectiveCamera|null, environment:object|null, warnings:string[]}>}
|
|
187
|
+
* @returns {Promise<{group:Group, camera:PerspectiveCamera|OrthographicCamera|null, environment:object|null, warnings:string[]}>}
|
|
188
188
|
*/
|
|
189
189
|
async build( ir ) {
|
|
190
190
|
|
|
@@ -494,7 +494,7 @@ export class PBRTSceneBuilder {
|
|
|
494
494
|
|
|
495
495
|
} );
|
|
496
496
|
|
|
497
|
-
if ( camMotion.fov ) {
|
|
497
|
+
if ( camMotion.fov && camera.isPerspectiveCamera ) {
|
|
498
498
|
|
|
499
499
|
const fovs = Array.from( camMotion.fov, f => this._verticalFov( { fov: { type: 'float', value: [ f ] } }, camera.aspect ) );
|
|
500
500
|
tracks.push( new NumberKeyframeTrack( `${camera.name}.fov`, camMotion.times.slice(), fovs ) );
|
|
@@ -1175,9 +1175,21 @@ export class PBRTSceneBuilder {
|
|
|
1175
1175
|
_buildCamera( cam, film ) {
|
|
1176
1176
|
|
|
1177
1177
|
const aspect = film && film.yresolution ? film.xresolution / film.yresolution : 16 / 9;
|
|
1178
|
-
const fov = this._verticalFov( cam.params, aspect );
|
|
1179
1178
|
|
|
1180
|
-
|
|
1179
|
+
let camera;
|
|
1180
|
+
if ( cam.type === 'orthographic' ) {
|
|
1181
|
+
|
|
1182
|
+
// pbrt's default screen window spans [-1, 1] along the shorter image axis.
|
|
1183
|
+
const window = cam.params?.screenwindow?.value;
|
|
1184
|
+
const half = window?.length === 4 ? ( window[ 3 ] - window[ 2 ] ) / 2 : Math.max( 1, 1 / aspect );
|
|
1185
|
+
camera = new OrthographicCamera( - half * aspect, half * aspect, half, - half, 0.01, 10000 );
|
|
1186
|
+
|
|
1187
|
+
} else {
|
|
1188
|
+
|
|
1189
|
+
camera = new PerspectiveCamera( this._verticalFov( cam.params, aspect ), aspect, 0.01, 10000 );
|
|
1190
|
+
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1181
1193
|
camera.name = 'PBRT Camera';
|
|
1182
1194
|
this._poseCamera( camera, cam.cameraToWorld );
|
|
1183
1195
|
camera.updateMatrixWorld( true );
|
package/src/RenderSettings.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventDispatcher, Color, Vector2, MathUtils } from 'three';
|
|
2
|
-
import { ENGINE_DEFAULTS } from './EngineDefaults.js';
|
|
2
|
+
import { ENGINE_DEFAULTS, CAMERA_PROJECTION_IDS } from './EngineDefaults.js';
|
|
3
3
|
import { EngineEvents } from './EngineEvents.js';
|
|
4
4
|
import { ISSUE_CODES } from './EngineIssues.js';
|
|
5
5
|
|
|
@@ -40,6 +40,9 @@ const SETTING_ROUTES = {
|
|
|
40
40
|
aperture: { uniform: 'aperture', reset: true },
|
|
41
41
|
apertureScale: { uniform: 'apertureScale', reset: true },
|
|
42
42
|
anamorphicRatio: { uniform: 'anamorphicRatio', reset: true },
|
|
43
|
+
unitsPerMetre: { uniform: 'unitsPerMetre', reset: true },
|
|
44
|
+
dofMode: { handler: 'handleDofMode', reset: true },
|
|
45
|
+
dofBlur: { uniform: 'dofBlur', reset: true },
|
|
43
46
|
samplingTechnique: { uniform: 'samplingTechnique', reset: true },
|
|
44
47
|
fireflyThreshold: { uniform: 'fireflyThreshold', reset: true },
|
|
45
48
|
shadowTerminatorOffset: { uniform: 'shadowTerminatorOffset', reset: true },
|
|
@@ -149,8 +152,8 @@ export class RenderSettings extends EventDispatcher {
|
|
|
149
152
|
* @param {Function} params.resetCallback - Called to reset accumulation
|
|
150
153
|
* @param {Function} [params.reconcileCompletion] - Called when completion limits change
|
|
151
154
|
* @param {Object} [params.denoisingManager] - Needed to force ASVGF off under panorama
|
|
152
|
-
* @param {Object} [params.cameraManager] - Needed to force auto-focus manual under panorama
|
|
153
155
|
* @param {Function} [params.onInteractionRenderScale] - Applies a new moving-camera render scale
|
|
156
|
+
* @param {Function} [params.onCameraProjection] - The camera's side of a `cameraProjection` change
|
|
154
157
|
*/
|
|
155
158
|
bind( params ) {
|
|
156
159
|
|
|
@@ -165,7 +168,7 @@ export class RenderSettings extends EventDispatcher {
|
|
|
165
168
|
* Builds handler functions for multi-stage settings that can't
|
|
166
169
|
* be routed with a simple uniform forward.
|
|
167
170
|
*/
|
|
168
|
-
_buildHandlers( { stages, renderer, resetCallback, reconcileCompletion, denoisingManager,
|
|
171
|
+
_buildHandlers( { stages, renderer, resetCallback, reconcileCompletion, denoisingManager, onInteractionRenderScale, onCameraProjection } ) {
|
|
169
172
|
|
|
170
173
|
// UniformManager copies into the existing node, so one scratch vector serves every write.
|
|
171
174
|
const panoScratch = new Vector2();
|
|
@@ -180,20 +183,20 @@ export class RenderSettings extends EventDispatcher {
|
|
|
180
183
|
|
|
181
184
|
handleCameraProjection: ( value ) => {
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
stages.pathTracer?.setUniform( 'cameraProjection', CAMERA_PROJECTION_IDS[ value ] ?? CAMERA_PROJECTION_IDS.perspective );
|
|
187
|
+
onCameraProjection?.( value );
|
|
185
188
|
|
|
186
|
-
if (
|
|
189
|
+
if ( value !== 'equirectangular' ) return;
|
|
187
190
|
|
|
188
191
|
// MotionVector unprojects through projectionMatrixInverse, which is meaningless once
|
|
189
192
|
// every pixel is its own direction. Fall back to the spatial-only denoiser rather
|
|
190
193
|
// than leaving no strategy.
|
|
191
194
|
if ( denoisingManager?.requiresMotionVectors ) denoisingManager.setDenoiserStrategy( 'edgeaware' );
|
|
192
|
-
// Auto-focus raycasts via Raycaster.setFromCamera, which only knows the frustum.
|
|
193
|
-
cameraManager?.setAutoFocusMode( 'manual' );
|
|
194
195
|
|
|
195
196
|
},
|
|
196
197
|
|
|
198
|
+
handleDofMode: ( value ) => stages.pathTracer?.setUniform( 'dofMode', value === 'look' ? 1 : 0 ),
|
|
199
|
+
|
|
197
200
|
handlePanoramaLonRange: ( value ) => setPanoRange( 'panoLonRange', value ),
|
|
198
201
|
|
|
199
202
|
handlePanoramaLatRange: ( value ) => setPanoRange( 'panoLatRange', value ),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Fn, vec2, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If,
|
|
1
|
+
import { Fn, vec2, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If,
|
|
2
2
|
textureLoad, textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
4
|
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
5
5
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
6
6
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
7
|
+
import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* WebGPU Motion Vector Stage (Compute Shader)
|
|
@@ -20,8 +21,8 @@ import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
|
20
21
|
*
|
|
21
22
|
* Algorithm:
|
|
22
23
|
* 1. Read normalDepth from NormalDepth (linear depth in alpha)
|
|
23
|
-
* 2. Reconstruct camera ray
|
|
24
|
-
* 3. World position =
|
|
24
|
+
* 2. Reconstruct the camera ray NormalDepth traced for the pixel
|
|
25
|
+
* 3. World position = rayOrigin + rayDir * linearDepth
|
|
25
26
|
* 4. Project to previous frame: prevVP * worldPos
|
|
26
27
|
* 5. Motion = currentUV - prevUV
|
|
27
28
|
*
|
|
@@ -78,6 +79,7 @@ export class MotionVector extends RenderStage {
|
|
|
78
79
|
// Synced from PathTracer each frame (same source as NormalDepth)
|
|
79
80
|
this.cameraWorldMatrix = uniform( new Matrix4(), 'mat4' );
|
|
80
81
|
this.cameraProjectionMatrixInverse = uniform( new Matrix4(), 'mat4' );
|
|
82
|
+
this.cameraRay = cameraRayUniforms();
|
|
81
83
|
this.prevVP = uniform( new Matrix4(), 'mat4' );
|
|
82
84
|
this.isFirstFrameU = uniform( 1.0 ); // 1.0 = true, 0.0 = false
|
|
83
85
|
this.deltaTime = uniform( 1.0 / 60.0 );
|
|
@@ -175,29 +177,7 @@ export class MotionVector extends RenderStage {
|
|
|
175
177
|
// Sky / background (depth >= 65504 sentinel) — no motion
|
|
176
178
|
If( linearDepth.lessThan( float( 6e4 ) ), () => {
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
// Negate Y to match PathTracer convention
|
|
180
|
-
const ndcX = float( gx ).add( 0.5 ).div( resW ).mul( 2.0 ).sub( 1.0 );
|
|
181
|
-
const ndcY = float( gy ).add( 0.5 ).div( resH ).mul( 2.0 ).sub( 1.0 ).negate();
|
|
182
|
-
const ndcPos = vec3( ndcX, ndcY, 1.0 );
|
|
183
|
-
|
|
184
|
-
// Camera-space ray direction via inverse projection
|
|
185
|
-
const rayDirCS = cpi.mul( vec4( ndcPos, 1.0 ) );
|
|
186
|
-
|
|
187
|
-
// Transform to world space (rotation only, via mat3 of world matrix)
|
|
188
|
-
const rayDirWorld = normalize(
|
|
189
|
-
mat3(
|
|
190
|
-
cwm[ 0 ].xyz,
|
|
191
|
-
cwm[ 1 ].xyz,
|
|
192
|
-
cwm[ 2 ].xyz
|
|
193
|
-
).mul( rayDirCS.xyz.div( rayDirCS.w ) )
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Camera position (translation column of world matrix)
|
|
197
|
-
const camPos = vec3( cwm[ 3 ] );
|
|
198
|
-
|
|
199
|
-
// World position = camera origin + ray direction * linear depth
|
|
200
|
-
const worldPos = camPos.add( rayDirWorld.mul( linearDepth ) );
|
|
180
|
+
const worldPos = cameraRayPoint( currentUV, linearDepth, cwm, cpi, this.cameraRay );
|
|
201
181
|
|
|
202
182
|
// Project to previous frame
|
|
203
183
|
const prevClip = prevVP.mul( vec4( worldPos, 1.0 ) );
|
|
@@ -278,27 +258,12 @@ export class MotionVector extends RenderStage {
|
|
|
278
258
|
// Skip first frame and sky (depth >= 65504 sentinel)
|
|
279
259
|
If( isFirstFrameU.lessThan( 0.5 ).and( linearDepth.lessThan( float( 6e4 ) ) ), () => {
|
|
280
260
|
|
|
281
|
-
// Pixel coordinate → NDC
|
|
282
|
-
// Negate Y to match PathTracer convention
|
|
283
|
-
const ndcX = float( gx ).add( 0.5 ).div( resW ).mul( 2.0 ).sub( 1.0 );
|
|
284
|
-
const ndcY = float( gy ).add( 0.5 ).div( resH ).mul( 2.0 ).sub( 1.0 ).negate();
|
|
285
|
-
const ndcPos = vec3( ndcX, ndcY, 1.0 );
|
|
286
|
-
const rayDirCS = cpi.mul( vec4( ndcPos, 1.0 ) );
|
|
287
|
-
const rayDirWorld = normalize(
|
|
288
|
-
mat3(
|
|
289
|
-
cwm[ 0 ].xyz,
|
|
290
|
-
cwm[ 1 ].xyz,
|
|
291
|
-
cwm[ 2 ].xyz
|
|
292
|
-
).mul( rayDirCS.xyz.div( rayDirCS.w ) )
|
|
293
|
-
);
|
|
294
|
-
const camPos = vec3( cwm[ 3 ] );
|
|
295
|
-
const worldPos = camPos.add( rayDirWorld.mul( linearDepth ) );
|
|
296
|
-
|
|
297
261
|
// Current pixel UV
|
|
298
262
|
const currentUV = vec2(
|
|
299
263
|
float( gx ).add( 0.5 ).div( resW ),
|
|
300
264
|
float( gy ).add( 0.5 ).div( resH )
|
|
301
265
|
);
|
|
266
|
+
const worldPos = cameraRayPoint( currentUV, linearDepth, cwm, cpi, this.cameraRay );
|
|
302
267
|
|
|
303
268
|
// Project to previous frame
|
|
304
269
|
const prevClip = prevVP.mul( vec4( worldPos, 1.0 ) );
|
|
@@ -370,6 +335,7 @@ export class MotionVector extends RenderStage {
|
|
|
370
335
|
if ( pt && pt.uniforms ) {
|
|
371
336
|
|
|
372
337
|
// Sync from PathTracer (same source as NormalDepth)
|
|
338
|
+
this.cameraRay.sync( pt.uniforms );
|
|
373
339
|
worldMatrix = pt.uniforms.get( 'cameraWorldMatrix' ).value;
|
|
374
340
|
viewMatrix = pt.uniforms.get( 'cameraViewMatrix' ).value;
|
|
375
341
|
projMatrix = pt.uniforms.get( 'cameraProjectionMatrix' ).value;
|