rayzee 11.2.1 → 11.4.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 +91 -34
- package/dist/ArchiveImporter-Cgyx5AF7.js +7860 -0
- package/dist/BidirectionalIntegrator-BKF-cwH2.js +954 -0
- package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
- package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-C3Q2XqbF.js} +4 -3
- package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-DV5TtURP.js} +11 -5
- package/dist/{PathTracer-D6nbknj5.js → PathTracer-D3MvEaZK.js} +4384 -4618
- package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-CY4jiz6a.js} +2 -2
- package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-D23m6NqG.js} +486 -391
- package/dist/SolarLimb-bZE6y63S.js +762 -0
- package/dist/{ToneMapGPU-BOjpul7A.js → ToneMapGPU-CTDBvmmS.js} +31 -22
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +7 -7
- package/dist/rayzee.es.js +1327 -838
- package/dist/rayzee.umd.js +146 -134
- package/dist/sceneCachePolicy-AKOEu2lf.js +1602 -0
- package/package.json +1 -1
- package/src/EngineDefaults.js +2 -2
- package/src/Passes/AIUpscaler.js +2 -0
- package/src/PathTracerApp.js +126 -19
- package/src/Pipeline/CompletionTracker.js +7 -3
- package/src/Processor/ArchiveImporter.js +173 -94
- package/src/Processor/ArchiveReader.js +20 -23
- package/src/Processor/AssetLoader.js +48 -1
- package/src/Processor/BVHRefitter.js +109 -13
- package/src/Processor/BufferLayout.js +115 -0
- package/src/Processor/ChunkedRecords.js +3 -2
- package/src/Processor/EmissiveTriangleBuilder.js +6 -6
- package/src/Processor/GeometryExtractor.js +189 -79
- package/src/Processor/HostMemory.js +39 -9
- package/src/Processor/InstanceTable.js +714 -42
- package/src/Processor/PBRT/PBRTCurves.js +224 -6
- package/src/Processor/PBRT/PBRTMaterials.js +3 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +82 -25
- package/src/Processor/PackedRayBuffer.js +5 -2
- package/src/Processor/SceneProcessor.js +747 -141
- package/src/Processor/TLASBuilder.js +32 -17
- package/src/Processor/ToneMapCPU.js +16 -4
- package/src/Processor/ToneMapGPU.js +30 -14
- package/src/Processor/USD/USDLayer.js +1149 -0
- package/src/Processor/USD/USDScene.js +1655 -0
- package/src/Processor/USD/USDStage.js +682 -0
- package/src/Processor/USD/USDText.js +674 -0
- package/src/Processor/USD/index.js +138 -0
- package/src/Processor/Workers/BVHRefitWorker.js +3 -3
- package/src/RayzeeRenderer.js +221 -33
- package/src/RenderSettings.js +4 -2
- package/src/Stages/AutoExposure.js +484 -494
- package/src/Stages/Compositor.js +27 -6
- package/src/Stages/LocalExposure.js +546 -0
- package/src/Stages/NormalDepth.js +7 -12
- package/src/Stages/PathTracer.js +18 -21
- package/src/Stages/PathTracerStage.js +282 -103
- package/src/Storage/GeometrySpill.js +46 -14
- package/src/Storage/SceneGraphCodec.js +17 -3
- package/src/Storage/SpillStore.js +9 -0
- package/src/TSL/BVHTraversal.js +183 -7
- package/src/TSL/Common.js +57 -13
- package/src/TSL/ShadeKernel.js +3 -2
- package/src/TSL/TextureSampling.js +11 -8
- package/src/TSL/patches.js +84 -0
- package/src/index.js +1 -0
- package/src/integrators/BidirectionalIntegrator.js +20 -2
- package/src/managers/DenoisingManager.js +15 -19
- package/src/managers/InteractionManager.js +19 -6
- package/src/managers/MaterialDataManager.js +22 -5
- package/src/managers/UniformManager.js +3 -0
- package/src/managers/VideoRenderManager.js +30 -4
- package/src/neural/NeuralRetouch.js +1 -0
- package/src/neural/NeuralSuperRes.js +6 -1
- package/dist/ArchiveImporter-D_x863yu.js +0 -5198
- package/dist/BidirectionalIntegrator-DmkxxEOR.js +0 -946
- package/dist/SolarLimb-CPaeZVwz.js +0 -721
- package/dist/sceneCachePolicy-C4-4qwt6.js +0 -485
package/src/TSL/patches.js
CHANGED
|
@@ -147,6 +147,90 @@ export function uploadStorageChunks( renderer, attr, chunks ) {
|
|
|
147
147
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* GPU-only storage attributes standing for one array too large for a single binding: lane l lives in part
|
|
152
|
+
* ⌊l / lanesPerPart⌋, and a part holds whole records, so no record straddles two. Read through `splitStorage`
|
|
153
|
+
* (TSL/Common.js).
|
|
154
|
+
*/
|
|
155
|
+
export class StorageParts {
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {number} totalLanes
|
|
159
|
+
* @param {number} lanesPerRecord
|
|
160
|
+
* @param {number} maxPartBytes - at most maxStorageBufferBindingSize
|
|
161
|
+
* @param {Function} [LaneType]
|
|
162
|
+
* @param {number} [itemSize] - lanes an element: 4 for vec4 storage, 2 for vec2
|
|
163
|
+
*/
|
|
164
|
+
constructor( totalLanes, lanesPerRecord, maxPartBytes, LaneType = Float32Array, itemSize = 4 ) {
|
|
165
|
+
|
|
166
|
+
this.LaneType = LaneType;
|
|
167
|
+
this.itemSize = itemSize;
|
|
168
|
+
this.lanesPerPart = Math.max( 1, Math.floor( maxPartBytes / ( lanesPerRecord * LaneType.BYTES_PER_ELEMENT ) ) ) * lanesPerRecord;
|
|
169
|
+
this.totalLanes = totalLanes;
|
|
170
|
+
this.attrs = [];
|
|
171
|
+
for ( let at = 0; at < totalLanes || this.attrs.length === 0; at += this.lanesPerPart ) {
|
|
172
|
+
|
|
173
|
+
const lanes = Math.max( lanesPerRecord, Math.min( this.lanesPerPart, totalLanes - at ) );
|
|
174
|
+
this.attrs.push( gpuOnlyStorageAttribute( lanes / itemSize, itemSize, LaneType ) );
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Elements a full part holds. */
|
|
181
|
+
get elementsPerPart() {
|
|
182
|
+
|
|
183
|
+
return this.lanesPerPart / this.itemSize;
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Creates every part's GPU buffer now. */
|
|
188
|
+
create( backend ) {
|
|
189
|
+
|
|
190
|
+
for ( const attr of this.attrs ) backend.createStorageAttribute( attr );
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Writes `count` lanes of `src` from `srcOffset` to lane `lane`, across a part boundary if need be. */
|
|
195
|
+
write( backend, lane, src, srcOffset, count ) {
|
|
196
|
+
|
|
197
|
+
const bytes = this.LaneType.BYTES_PER_ELEMENT;
|
|
198
|
+
while ( count > 0 ) {
|
|
199
|
+
|
|
200
|
+
const p = Math.floor( lane / this.lanesPerPart );
|
|
201
|
+
const local = lane - p * this.lanesPerPart;
|
|
202
|
+
const n = Math.min( count, this.lanesPerPart - local );
|
|
203
|
+
const attr = this.attrs[ p ];
|
|
204
|
+
backend.createStorageAttribute( attr );
|
|
205
|
+
backend.device.queue.writeBuffer( backend.get( attr ).buffer, local * bytes, src, srcOffset, n );
|
|
206
|
+
lane += n;
|
|
207
|
+
srcOffset += n;
|
|
208
|
+
count -= n;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Writes lanes [startLane, startLane + laneCount) of chunked `records`. */
|
|
215
|
+
writeRecords( backend, records, startLane, laneCount ) {
|
|
216
|
+
|
|
217
|
+
const lanesPerChunk = records.recordsPerChunk * records.lanesPerRecord;
|
|
218
|
+
for ( let lane = startLane, end = startLane + laneCount; lane < end; ) {
|
|
219
|
+
|
|
220
|
+
const ci = Math.floor( lane / lanesPerChunk );
|
|
221
|
+
const chunk = records.chunks[ ci ];
|
|
222
|
+
if ( ! chunk ) throw new Error( `chunk ${ci} is on disk and was never uploaded` );
|
|
223
|
+
const local = lane - ci * lanesPerChunk;
|
|
224
|
+
const n = Math.min( chunk.length - local, end - lane );
|
|
225
|
+
this.write( backend, lane, chunk, local, n );
|
|
226
|
+
lane += n;
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
150
234
|
/**
|
|
151
235
|
* Re-upload one lane range of a chunked storage attribute, spanning chunks as needed.
|
|
152
236
|
*
|
package/src/index.js
CHANGED
|
@@ -68,6 +68,7 @@ export {
|
|
|
68
68
|
} from './Stages/DenoiserSettings.js';
|
|
69
69
|
export { SKY_DEFAULTS, DEFAULT_SUN_PATH } from './managers/EnvironmentManager.js';
|
|
70
70
|
export { AUTO_EXPOSURE_DEFAULTS } from './Stages/AutoExposure.js';
|
|
71
|
+
export { LOCAL_EXPOSURE_DEFAULTS } from './Stages/LocalExposure.js';
|
|
71
72
|
export { AUTO_FOCUS_DEFAULTS } from './managers/CameraManager.js';
|
|
72
73
|
export { TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS } from './Processor/BufferLayout.js';
|
|
73
74
|
export { sunPosition, timeForSunElevation, dayOfYearForMonth } from './Processor/SunPosition.js';
|
|
@@ -13,7 +13,7 @@ import { LIGHT_FLOATS } from '../managers/UniformManager.js';
|
|
|
13
13
|
import { HIT_STRIDE_BIDIRECTIONAL, LIGHT_VERTEX_STRIDE, freeStorageAttribute } from '../Processor/PackedRayBuffer.js';
|
|
14
14
|
import { COUNTER, ENERGY_SCALE } from '../Processor/QueueManager.js';
|
|
15
15
|
import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
16
|
-
import { BVH_MAX_INDEX } from '../Processor/BufferLayout.js';
|
|
16
|
+
import { BVH_MAX_INDEX, BVH_LEAF_MARKERS, CLUSTER_COUNT_SHIFT, CLUSTER_COPY_HIDDEN } from '../Processor/BufferLayout.js';
|
|
17
17
|
import { BOUNCE_KERNELS, LIST_WG_SIZE } from '../Stages/PathTracer.js';
|
|
18
18
|
import { buildLightGenerateKernel, LIGHT_GENERATE_WG_SIZE } from '../TSL/LightGenerateKernel.js';
|
|
19
19
|
import { buildGuideKernel, buildGuideClearKernel, guidedDiscPdf, recordEscape, GUIDE_BINS, GUIDE_ROW_STRIDE, GUIDE_TEXTURE_WIDTH } from '../TSL/LightGuide.js';
|
|
@@ -652,6 +652,14 @@ export class BidirectionalIntegrator {
|
|
|
652
652
|
const records = this.pt._bvhRecords;
|
|
653
653
|
const flat = records ? null : this.pt.bvhStorageAttr?.array;
|
|
654
654
|
if ( ! records && ! ( flat?.length >= 16 ) ) return null;
|
|
655
|
+
// A spilled TLAS comes back for the next frame's disc.
|
|
656
|
+
const blasBase = this.pt._instanceTable?.blasBase ?? 1;
|
|
657
|
+
if ( records && ! records.isResident( 0, blasBase ) ) {
|
|
658
|
+
|
|
659
|
+
this.pt._whenTLASResident( () => {} );
|
|
660
|
+
return null;
|
|
661
|
+
|
|
662
|
+
}
|
|
655
663
|
|
|
656
664
|
const view = ( f ) => {
|
|
657
665
|
|
|
@@ -668,6 +676,16 @@ export class BidirectionalIntegrator {
|
|
|
668
676
|
|
|
669
677
|
};
|
|
670
678
|
|
|
679
|
+
// A copy cluster is visible while any of its copies is (BufferLayout CLUSTER_LEAF).
|
|
680
|
+
const leafVisible = ( n ) => {
|
|
681
|
+
|
|
682
|
+
if ( n.u[ n.o + 3 ] !== BVH_LEAF_MARKERS.CLUSTER_LEAF ) return n.f[ n.o + 2 ] !== 0;
|
|
683
|
+
const count = ( ( n.u[ n.o ] >>> CLUSTER_COUNT_SHIFT ) & 3 ) + 1;
|
|
684
|
+
for ( let k = 0; k < count; k ++ ) if ( ! ( n.u[ n.o + 12 + k ] & CLUSTER_COPY_HIDDEN ) ) return true;
|
|
685
|
+
return false;
|
|
686
|
+
|
|
687
|
+
};
|
|
688
|
+
|
|
671
689
|
const root = node( 0 );
|
|
672
690
|
if ( root.u[ root.o + 3 ] >= BVH_MAX_INDEX ) return null;
|
|
673
691
|
// A tree that reaches a node twice is malformed; no search visits more nodes than the tree holds.
|
|
@@ -699,7 +717,7 @@ export class BidirectionalIntegrator {
|
|
|
699
717
|
const n = node( index );
|
|
700
718
|
if ( n.u[ n.o + 3 ] >= BVH_MAX_INDEX ) {
|
|
701
719
|
|
|
702
|
-
if (
|
|
720
|
+
if ( leafVisible( n ) ) best = s;
|
|
703
721
|
|
|
704
722
|
} else push( n );
|
|
705
723
|
|
|
@@ -104,9 +104,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
104
104
|
* @param {import('../Pipeline/RenderPipeline.js').RenderPipeline} params.pipeline
|
|
105
105
|
* @param {Function} params.getExposure - () => current exposure value
|
|
106
106
|
* @param {Function} params.getSaturation - () => current saturation value
|
|
107
|
+
* @param {Function} [params.getDisplayGain] - () => the shown picture's per-pixel gain (PackedToneMapper `gain`)
|
|
107
108
|
* @param {import('../EngineIssues.js').IssueLog} [params.issues]
|
|
108
109
|
*/
|
|
109
|
-
constructor( { renderer, mainCanvas, stages, pipeline, getExposure, getSaturation, issues = null } ) {
|
|
110
|
+
constructor( { renderer, mainCanvas, stages, pipeline, getExposure, getSaturation, getDisplayGain = null, issues = null } ) {
|
|
110
111
|
|
|
111
112
|
super();
|
|
112
113
|
|
|
@@ -121,6 +122,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
121
122
|
|
|
122
123
|
this._getExposure = getExposure;
|
|
123
124
|
this._getSaturation = getSaturation;
|
|
125
|
+
this._getDisplayGain = getDisplayGain;
|
|
124
126
|
|
|
125
127
|
this.denoiser = null;
|
|
126
128
|
this.upscaler = null;
|
|
@@ -409,6 +411,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
409
411
|
getExposure: () => this._getEffectiveExposure(),
|
|
410
412
|
getToneMapping: () => this._getToneMapping(),
|
|
411
413
|
getSaturation: () => this._getSaturation(),
|
|
414
|
+
getDisplayGain: () => this._getDisplayGain?.() ?? null,
|
|
412
415
|
} );
|
|
413
416
|
|
|
414
417
|
this.upscaler.enabled = DENOISER_DEFAULTS.enableUpscaler || false;
|
|
@@ -546,21 +549,11 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
546
549
|
|
|
547
550
|
/**
|
|
548
551
|
* @param {boolean} enabled
|
|
549
|
-
* @param {number} manualExposure -
|
|
552
|
+
* @param {number} manualExposure - the compensation while on, the exposure again once off
|
|
550
553
|
*/
|
|
551
554
|
setAutoExposureEnabled( enabled, manualExposure ) {
|
|
552
555
|
|
|
553
|
-
|
|
554
|
-
if ( ! s.autoExposure ) return;
|
|
555
|
-
|
|
556
|
-
s.autoExposure.enabled = enabled;
|
|
557
|
-
|
|
558
|
-
// AutoExposure overwrites renderer.toneMappingExposure each frame; restore manual on disable.
|
|
559
|
-
if ( ! enabled && this.renderer ) {
|
|
560
|
-
|
|
561
|
-
this.renderer.toneMappingExposure = manualExposure;
|
|
562
|
-
|
|
563
|
-
}
|
|
556
|
+
this._stages.autoExposure?.setEnabled( enabled, manualExposure );
|
|
564
557
|
|
|
565
558
|
}
|
|
566
559
|
|
|
@@ -988,10 +981,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
988
981
|
* @param {Float32Array} world - the placement matrix pool
|
|
989
982
|
* @param {number} offset - where this placement's matrix starts in it
|
|
990
983
|
*/
|
|
991
|
-
notePlacementMoving( leaf,
|
|
984
|
+
notePlacementMoving( leaf, matrixOf ) {
|
|
992
985
|
|
|
993
986
|
if ( leaf < 0 || ! this.historyActive || this._movedPlacements.has( leaf ) ) return;
|
|
994
|
-
this._movedPlacements.set( leaf, {
|
|
987
|
+
this._movedPlacements.set( leaf, { matrixOf, prev: matrixOf().slice() } );
|
|
995
988
|
|
|
996
989
|
}
|
|
997
990
|
|
|
@@ -1013,8 +1006,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1013
1006
|
const leaves = [ ...moved.keys() ].sort( ( a, b ) => a - b );
|
|
1014
1007
|
for ( let i = 0; i < leaves.length; i ++ ) {
|
|
1015
1008
|
|
|
1016
|
-
const {
|
|
1017
|
-
this._matB.fromArray(
|
|
1009
|
+
const { matrixOf, prev } = moved.get( leaves[ i ] );
|
|
1010
|
+
this._matB.fromArray( matrixOf() ).invert();
|
|
1018
1011
|
this._matA.fromArray( prev ).multiply( this._matB ).toArray( upload.toPrev, i * 16 );
|
|
1019
1012
|
upload.leaves[ i ] = leaves[ i ];
|
|
1020
1013
|
|
|
@@ -1246,6 +1239,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1246
1239
|
exposure: this._getEffectiveExposure(),
|
|
1247
1240
|
toneMapping: this._getToneMapping(),
|
|
1248
1241
|
saturation: this._getSaturation?.() ?? 1,
|
|
1242
|
+
gain: this._getDisplayGain?.() ?? null,
|
|
1249
1243
|
};
|
|
1250
1244
|
|
|
1251
1245
|
// Scene-referred throughout. Exposure reaches the detail pass through its own `paper_white`
|
|
@@ -1561,6 +1555,9 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1561
1555
|
this._onReset = null;
|
|
1562
1556
|
this._onPostProcessRefresh = null;
|
|
1563
1557
|
this._onDisplayRefresh = null;
|
|
1558
|
+
this._getExposure = null;
|
|
1559
|
+
this._getSaturation = null;
|
|
1560
|
+
this._getDisplayGain = null;
|
|
1564
1561
|
this._issues = null;
|
|
1565
1562
|
|
|
1566
1563
|
if ( this.upscalerCanvas?.parentNode ) {
|
|
@@ -1764,13 +1761,12 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1764
1761
|
// ── Convenience (match DenoisingAPI names with reset) ────────
|
|
1765
1762
|
|
|
1766
1763
|
/**
|
|
1767
|
-
* Enables or disables auto-exposure
|
|
1764
|
+
* Enables or disables auto-exposure. A finished render is metered as it stands, not restarted.
|
|
1768
1765
|
* @param {boolean} enabled
|
|
1769
1766
|
*/
|
|
1770
1767
|
setAutoExposure( enabled ) {
|
|
1771
1768
|
|
|
1772
1769
|
this.setAutoExposureEnabled( enabled, this._getExposure() );
|
|
1773
|
-
this._onReset?.();
|
|
1774
1770
|
|
|
1775
1771
|
}
|
|
1776
1772
|
|
|
@@ -38,12 +38,13 @@ export function focusDistanceOfHit( hit, camera, panorama = false ) {
|
|
|
38
38
|
*/
|
|
39
39
|
export class InteractionManager extends EventDispatcher {
|
|
40
40
|
|
|
41
|
-
constructor( { scene, camera, canvas, assetLoader, pathTracer, floorPlane } ) {
|
|
41
|
+
constructor( { scene, camera, canvas, assetLoader, pathTracer, floorPlane, isGeometryOnDisk = null } ) {
|
|
42
42
|
|
|
43
43
|
super();
|
|
44
44
|
|
|
45
45
|
// Core dependencies
|
|
46
46
|
this.scene = scene;
|
|
47
|
+
this.isGeometryOnDisk = isGeometryOnDisk;
|
|
47
48
|
this.camera = camera;
|
|
48
49
|
this.canvas = canvas;
|
|
49
50
|
this.assetLoader = assetLoader;
|
|
@@ -125,7 +126,8 @@ export class InteractionManager extends EventDispatcher {
|
|
|
125
126
|
select( object ) {
|
|
126
127
|
|
|
127
128
|
const outline = this._overlayManager?.getHelper( 'outline' );
|
|
128
|
-
|
|
129
|
+
// A spilled model's geometry is on disk: there is nothing to draw the outline from.
|
|
130
|
+
if ( outline ) outline.setSelectedObjects( object && ! this.isGeometryOnDisk?.() ? [ object ] : [] );
|
|
129
131
|
|
|
130
132
|
this.selectedObject = object || null;
|
|
131
133
|
|
|
@@ -221,7 +223,7 @@ export class InteractionManager extends EventDispatcher {
|
|
|
221
223
|
const mouseCoords = this.getMouseCoordinates( event );
|
|
222
224
|
this.raycaster.setFromCamera( mouseCoords, this.camera );
|
|
223
225
|
|
|
224
|
-
const intersects = this.
|
|
226
|
+
const intersects = this._intersectScene();
|
|
225
227
|
|
|
226
228
|
if ( intersects.length > 0 ) {
|
|
227
229
|
|
|
@@ -490,7 +492,7 @@ export class InteractionManager extends EventDispatcher {
|
|
|
490
492
|
const mouseCoords = this.getMouseCoordinates( event );
|
|
491
493
|
this.raycaster.setFromCamera( mouseCoords, this.camera );
|
|
492
494
|
|
|
493
|
-
const intersects = this.
|
|
495
|
+
const intersects = this._intersectScene();
|
|
494
496
|
const validIntersects = this.filterValidIntersects( intersects );
|
|
495
497
|
const lightHit = this._pickLightHelper();
|
|
496
498
|
|
|
@@ -576,7 +578,7 @@ export class InteractionManager extends EventDispatcher {
|
|
|
576
578
|
const mouseCoords = this.getMouseCoordinates( event );
|
|
577
579
|
this.raycaster.setFromCamera( mouseCoords, this.camera );
|
|
578
580
|
|
|
579
|
-
const intersects = this.
|
|
581
|
+
const intersects = this._intersectScene();
|
|
580
582
|
const validIntersects = this.filterValidIntersects( intersects );
|
|
581
583
|
|
|
582
584
|
if ( validIntersects.length > 0 ) {
|
|
@@ -695,11 +697,22 @@ export class InteractionManager extends EventDispatcher {
|
|
|
695
697
|
|
|
696
698
|
}
|
|
697
699
|
|
|
700
|
+
/**
|
|
701
|
+
* The scene under the raycaster. Nothing while a spilled model's geometry is on disk: its arrays are empty, and
|
|
702
|
+
* testing tens of millions of empty copies held the page for seconds a pick.
|
|
703
|
+
* @private
|
|
704
|
+
*/
|
|
705
|
+
_intersectScene() {
|
|
706
|
+
|
|
707
|
+
return this.isGeometryOnDisk?.() ? [] : this.raycaster.intersectObjects( this.scene.children, true );
|
|
708
|
+
|
|
709
|
+
}
|
|
710
|
+
|
|
698
711
|
/** The nearest scene surface under an NDC point, skipping the floor and helpers. */
|
|
699
712
|
pickSurface( ndcX, ndcY ) {
|
|
700
713
|
|
|
701
714
|
this.raycaster.setFromCamera( { x: ndcX, y: ndcY }, this.camera );
|
|
702
|
-
return this.filterValidIntersects( this.
|
|
715
|
+
return this.filterValidIntersects( this._intersectScene() )[ 0 ] ?? null;
|
|
703
716
|
|
|
704
717
|
}
|
|
705
718
|
|
|
@@ -173,8 +173,17 @@ export class MaterialDataManager {
|
|
|
173
173
|
this._convertColorsToWorkingSpace( matImageData );
|
|
174
174
|
|
|
175
175
|
const vec4Count = matImageData.length / 4;
|
|
176
|
+
this.materialCount = Math.floor( vec4Count / PIXELS_PER_MATERIAL );
|
|
177
|
+
|
|
178
|
+
// The owning stage keeps materials at the front of its scene data buffer, the light data behind them: one
|
|
179
|
+
// binding for both, which the shading kernel's ten could not spare.
|
|
180
|
+
const shared = this.callbacks.adoptMaterials?.( matImageData );
|
|
181
|
+
if ( shared ) {
|
|
176
182
|
|
|
177
|
-
|
|
183
|
+
this.materialStorageAttr = shared.attr;
|
|
184
|
+
this.materialStorageNode = shared.node;
|
|
185
|
+
|
|
186
|
+
} else if ( this.materialStorageNode ) {
|
|
178
187
|
|
|
179
188
|
this.materialStorageAttr = new StorageInstancedBufferAttribute( matImageData, 4 );
|
|
180
189
|
this.materialStorageNode.value = this.materialStorageAttr;
|
|
@@ -187,7 +196,6 @@ export class MaterialDataManager {
|
|
|
187
196
|
|
|
188
197
|
}
|
|
189
198
|
|
|
190
|
-
this.materialCount = Math.floor( vec4Count / PIXELS_PER_MATERIAL );
|
|
191
199
|
this._sources = [ ...sources ];
|
|
192
200
|
this._hostSet = [];
|
|
193
201
|
this.callbacks.onMaterialFeaturesChanged?.();
|
|
@@ -195,6 +203,15 @@ export class MaterialDataManager {
|
|
|
195
203
|
|
|
196
204
|
}
|
|
197
205
|
|
|
206
|
+
/** Sends the materials to the GPU: only their region when the buffer carries the light data too. @private */
|
|
207
|
+
_uploadMaterials() {
|
|
208
|
+
|
|
209
|
+
const attr = this.materialStorageAttr;
|
|
210
|
+
if ( this.callbacks.adoptMaterials ) attr.addUpdateRange( 0, this.materialCount * PIXELS_PER_MATERIAL * 4 );
|
|
211
|
+
attr.needsUpdate = true;
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
198
215
|
/**
|
|
199
216
|
* Get the material storage attribute (for dependent stages).
|
|
200
217
|
* @returns {StorageInstancedBufferAttribute|null}
|
|
@@ -538,7 +555,7 @@ export class MaterialDataManager {
|
|
|
538
555
|
|
|
539
556
|
}
|
|
540
557
|
|
|
541
|
-
this.
|
|
558
|
+
this._uploadMaterials();
|
|
542
559
|
( this._hostSet[ materialIndex ] ??= new Map() ).set( property, toPortable( value ) );
|
|
543
560
|
|
|
544
561
|
// Recompute triangle-data opaque-blocker flag when any input to it changes.
|
|
@@ -588,7 +605,7 @@ export class MaterialDataManager {
|
|
|
588
605
|
this._recomputeOpaqueBlockerForMaterial( materialIndex );
|
|
589
606
|
this.callbacks.onMaterialFeaturesChanged?.();
|
|
590
607
|
|
|
591
|
-
this.
|
|
608
|
+
this._uploadMaterials();
|
|
592
609
|
this._notifyReset();
|
|
593
610
|
|
|
594
611
|
}
|
|
@@ -668,7 +685,7 @@ export class MaterialDataManager {
|
|
|
668
685
|
|
|
669
686
|
}
|
|
670
687
|
|
|
671
|
-
this.
|
|
688
|
+
this._uploadMaterials();
|
|
672
689
|
this._notifyReset();
|
|
673
690
|
|
|
674
691
|
}
|
|
@@ -333,6 +333,9 @@ export class UniformManager {
|
|
|
333
333
|
u( 'emissiveTriangleCount', 0, 'int' );
|
|
334
334
|
u( 'emissiveTotalPower', 0.0, 'float' );
|
|
335
335
|
u( 'lightBVHNodeCount', 0, 'int' );
|
|
336
|
+
// Where the light data starts in the scene data buffer, after the materials (vec4 elements). Every light read
|
|
337
|
+
// adds it; the offsets below are within the light data.
|
|
338
|
+
u( 'lightVec4Offset', 0, 'int' );
|
|
336
339
|
// Offset (in vec4 elements) within the packed light buffer where emissive
|
|
337
340
|
// triangle data starts. Equals lightBVHNodeCount * LBVH_STRIDE; computed on upload.
|
|
338
341
|
u( 'emissiveVec4Offset', 0, 'int' );
|
|
@@ -87,8 +87,13 @@ export class VideoRenderManager {
|
|
|
87
87
|
// Configure for high-quality offline rendering
|
|
88
88
|
app.configureForMode( 'production' );
|
|
89
89
|
|
|
90
|
-
//
|
|
91
|
-
app.
|
|
90
|
+
// Auto exposure adapts in video time, once a frame, not to each pass's wall clock.
|
|
91
|
+
const autoExposure = app.stages.autoExposure?.enabled ? app.stages.autoExposure : null;
|
|
92
|
+
if ( autoExposure ) autoExposure.instant = false;
|
|
93
|
+
|
|
94
|
+
// Override samples per frame. 'frames': this loop never checks the deadline, so a 'timeOnly'
|
|
95
|
+
// ceiling of Infinity would never finish a frame.
|
|
96
|
+
app.settings.setMany( { maxSamples: samplesPerFrame, renderLimitMode: 'frames' }, { silent: true, reset: false } );
|
|
92
97
|
app.stages.pathTracer?.updateCompletionThreshold?.();
|
|
93
98
|
|
|
94
99
|
// Disable camera controls during render
|
|
@@ -131,11 +136,23 @@ export class VideoRenderManager {
|
|
|
131
136
|
// kill it immediately so it doesn't race with our manual render loop
|
|
132
137
|
app.stopAnimation();
|
|
133
138
|
|
|
139
|
+
// Before accumulating: the stage meters as it goes, and a reading of this frame's view filed under the last
|
|
140
|
+
// one read as a scene change, moving the room the whole way each frame.
|
|
141
|
+
if ( autoExposure ) app._noteExposureView();
|
|
142
|
+
|
|
134
143
|
// 3. Accumulate samples until convergence
|
|
135
144
|
await this._accumulateFrame( app );
|
|
136
145
|
|
|
137
146
|
if ( this._cancelled ) break;
|
|
138
147
|
|
|
148
|
+
if ( autoExposure ) {
|
|
149
|
+
|
|
150
|
+
await autoExposure.meter( app.pipeline.context );
|
|
151
|
+
autoExposure.advance( i === Math.max( 0, startFrame ) ? Infinity : frameDuration );
|
|
152
|
+
app._presentDisplay();
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
139
156
|
// 4. Denoise if enabled
|
|
140
157
|
if ( enableOIDN && app.denoisingManager?.finalDenoise ) {
|
|
141
158
|
|
|
@@ -298,17 +315,19 @@ export class VideoRenderManager {
|
|
|
298
315
|
|
|
299
316
|
const app = this._app;
|
|
300
317
|
const effective = app.settings.getEffective();
|
|
301
|
-
const keys = Object.keys( modePresetSettings( PRODUCTION_RENDER_CONFIG ) );
|
|
318
|
+
const keys = [ ...Object.keys( modePresetSettings( PRODUCTION_RENDER_CONFIG ) ), 'renderLimitMode' ];
|
|
302
319
|
if ( view ) keys.push( 'focusDistance' );
|
|
303
320
|
|
|
304
321
|
return {
|
|
305
322
|
view: view ? app.cameraManager.captureView() : null,
|
|
306
|
-
// Everything configureForMode( 'production' )
|
|
323
|
+
// Everything configureForMode( 'production' ) and the export overwrite, with who set it.
|
|
307
324
|
settings: keys.map( key => [ key, effective[ key ] ] ),
|
|
308
325
|
renderMode: app.stages.pathTracer?.renderMode?.value,
|
|
309
326
|
controlsEnabled: app.cameraManager.controls?.enabled,
|
|
310
327
|
oidnEnabled: app.denoisingManager?.finalDenoise,
|
|
311
328
|
oidnQuality: app.denoisingManager?.oidnQuality,
|
|
329
|
+
instantExposure: app.stages.autoExposure?.instant,
|
|
330
|
+
holdExposureSamples: app.stages.autoExposure?.holdSamples,
|
|
312
331
|
wasPlaying: app.animationManager?.isPlaying,
|
|
313
332
|
pauseRendering: app.pauseRendering,
|
|
314
333
|
};
|
|
@@ -347,6 +366,13 @@ export class VideoRenderManager {
|
|
|
347
366
|
}
|
|
348
367
|
|
|
349
368
|
app.pauseRendering = state.pauseRendering ?? false;
|
|
369
|
+
if ( app.stages.autoExposure ) {
|
|
370
|
+
|
|
371
|
+
app.stages.autoExposure.instant = state.instantExposure ?? false;
|
|
372
|
+
// configureForMode( 'production' ) set it, and nothing switches the mode back: left on, it froze the exposure while orbiting.
|
|
373
|
+
app.stages.autoExposure.holdSamples = state.holdExposureSamples ?? 0;
|
|
374
|
+
|
|
375
|
+
}
|
|
350
376
|
|
|
351
377
|
if ( state.view ) app.cameraManager.applyPose( state.view );
|
|
352
378
|
|
|
@@ -373,6 +373,7 @@ export class NeuralSuperRes {
|
|
|
373
373
|
exposure: tone.exposure ?? 1,
|
|
374
374
|
toneMapping: tone.toneMapping ?? 0,
|
|
375
375
|
saturation: tone.saturation ?? 1,
|
|
376
|
+
gain: tone.gain,
|
|
376
377
|
// The network emits its picture vertically flipped.
|
|
377
378
|
flipY: true,
|
|
378
379
|
} );
|
|
@@ -482,6 +483,7 @@ export async function renderUpscaled( app, {
|
|
|
482
483
|
width: pathTracer?.width,
|
|
483
484
|
height: pathTracer?.height,
|
|
484
485
|
maxSamples: app.settings?.get?.( 'maxSamples' ),
|
|
486
|
+
renderLimitMode: app.settings?.get?.( 'renderLimitMode' ),
|
|
485
487
|
finalDenoise: app.denoisingManager?.finalDenoise,
|
|
486
488
|
paused: app.pauseRendering,
|
|
487
489
|
};
|
|
@@ -512,7 +514,8 @@ export async function renderUpscaled( app, {
|
|
|
512
514
|
await new Promise( r => setTimeout( r, 300 ) );
|
|
513
515
|
|
|
514
516
|
app.denoisingManager.applyOIDNEnabled( true );
|
|
515
|
-
|
|
517
|
+
// 'frames': under 'timeOnly' the deadline, not `samples`, would end the render.
|
|
518
|
+
app.settings.setMany( { maxSamples: samples, renderLimitMode: 'frames' }, { reset: false } );
|
|
516
519
|
const done = waitForDenoise( app, samples, timeoutMs );
|
|
517
520
|
app.reset();
|
|
518
521
|
await done;
|
|
@@ -529,6 +532,7 @@ export async function renderUpscaled( app, {
|
|
|
529
532
|
exposure: app.renderer.toneMappingExposure,
|
|
530
533
|
toneMapping: app.renderer.toneMapping,
|
|
531
534
|
saturation: app.settings.get( 'saturation' ) ?? 1,
|
|
535
|
+
gain: app._displayGain?.() ?? null,
|
|
532
536
|
};
|
|
533
537
|
|
|
534
538
|
const { rgba8, width, height, ms, toneMs } = await sr.upscaleToRGBA8( source, tone );
|
|
@@ -550,6 +554,7 @@ export async function renderUpscaled( app, {
|
|
|
550
554
|
|
|
551
555
|
if ( restore.width && restore.height ) app.setCanvasSize( restore.width, restore.height );
|
|
552
556
|
if ( restore.maxSamples !== undefined ) app.settings?.set?.( 'maxSamples', restore.maxSamples );
|
|
557
|
+
if ( restore.renderLimitMode !== undefined ) app.settings?.set?.( 'renderLimitMode', restore.renderLimitMode, { reset: false } );
|
|
553
558
|
if ( restore.finalDenoise !== undefined ) app.denoisingManager?.applyOIDNEnabled( restore.finalDenoise );
|
|
554
559
|
app.pauseRendering = restore.paused;
|
|
555
560
|
|