rayzee 7.23.3 → 7.25.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 +40 -18
- package/dist/rayzee.es.js +1864 -1768
- package/dist/rayzee.umd.js +52 -52
- package/package.json +3 -3
- package/src/Passes/AIUpscaler.js +19 -11
- package/src/Passes/OIDNDenoiser.js +115 -56
- package/src/PathTracerApp.js +46 -4
- package/src/Pipeline/CompletionTracker.js +45 -6
- package/src/Processor/AssetLoader.js +77 -11
- package/src/Processor/ToneMapCPU.js +37 -7
- package/src/RenderSettings.js +1 -0
- package/src/Stages/PathTracerStage.js +6 -16
- package/src/managers/DenoisingManager.js +18 -16
|
@@ -9,7 +9,7 @@ import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
|
|
|
9
9
|
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
|
|
10
10
|
import { createMeshesFromMultiMaterialMesh } from 'three/addons/utils/SceneUtils.js';
|
|
11
11
|
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
|
|
12
|
-
import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
|
|
12
|
+
import { unzipSync, zipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
|
|
13
13
|
import { disposeObjectFromMemory, updateLoading } from './utils';
|
|
14
14
|
import { BuildTimer } from './BuildTimer.js';
|
|
15
15
|
import { getAssetConfig } from '../AssetConfig.js';
|
|
@@ -21,13 +21,20 @@ const SUPPORTED_FORMATS = {
|
|
|
21
21
|
'fbx': { type: 'model', name: 'FBX' }, 'obj': { type: 'model', name: 'OBJ' },
|
|
22
22
|
'stl': { type: 'model', name: 'STL' }, 'ply': { type: 'model', name: 'PLY (Polygon File Format)' },
|
|
23
23
|
'dae': { type: 'model', name: 'Collada' }, '3mf': { type: 'model', name: '3D Manufacturing Format' },
|
|
24
|
-
'
|
|
24
|
+
'usd': { type: 'model', name: 'USD (Universal Scene Description)' },
|
|
25
|
+
'usda': { type: 'model', name: 'USDA (USD ASCII)' },
|
|
26
|
+
'usdc': { type: 'model', name: 'USDC (USD Crate)' },
|
|
27
|
+
'usdz': { type: 'model', name: 'USDZ (USD Archive)' },
|
|
25
28
|
'hdr': { type: 'environment', name: 'HDR (High Dynamic Range)' }, 'exr': { type: 'environment', name: 'EXR (OpenEXR)' },
|
|
26
29
|
'png': { type: 'image', name: 'PNG' }, 'jpg': { type: 'image', name: 'JPEG' },
|
|
27
30
|
'jpeg': { type: 'image', name: 'JPEG' }, 'webp': { type: 'image', name: 'WebP' },
|
|
28
31
|
'zip': { type: 'archive', name: 'ZIP Archive' }
|
|
29
32
|
};
|
|
30
33
|
|
|
34
|
+
// Loose USD layers inside a ZIP compose into one scene; these pick out the
|
|
35
|
+
// layers and the image assets they reference.
|
|
36
|
+
const USD_LAYER_RE = /\.(usd|usda|usdc)$/i;
|
|
37
|
+
const USD_IMAGE_RE = /\.(png|jpg|jpeg|avif)$/i;
|
|
31
38
|
/**
|
|
32
39
|
* AssetLoader class - handles loading of 3D models, environment maps, and archives
|
|
33
40
|
*/
|
|
@@ -234,7 +241,10 @@ export class AssetLoader extends EventDispatcher {
|
|
|
234
241
|
case 'ply': return await this.loadPLYFromArrayBuffer( arrayBuffer, filename );
|
|
235
242
|
case 'dae': return await this.loadColladaFromFile( file, filename );
|
|
236
243
|
case '3mf': return await this.load3MFFromArrayBuffer( arrayBuffer, filename );
|
|
237
|
-
case '
|
|
244
|
+
case 'usd':
|
|
245
|
+
case 'usda':
|
|
246
|
+
case 'usdc':
|
|
247
|
+
case 'usdz': return await this.loadUSDFromArrayBuffer( arrayBuffer, filename );
|
|
238
248
|
default: throw new Error( `Support for ${extension} files is not yet implemented` );
|
|
239
249
|
|
|
240
250
|
}
|
|
@@ -647,6 +657,10 @@ export class AssetLoader extends EventDispatcher {
|
|
|
647
657
|
const extension = path.split( '.' ).pop().toLowerCase();
|
|
648
658
|
if ( SUPPORTED_FORMATS[ extension ] && SUPPORTED_FORMATS[ extension ].type === 'model' ) {
|
|
649
659
|
|
|
660
|
+
// A loose layer is only one slice of a USD scene — hand the whole
|
|
661
|
+
// archive over so its references and payloads can resolve.
|
|
662
|
+
if ( USD_LAYER_RE.test( path ) ) return await this.loadUSDHierarchyFromZip( zip );
|
|
663
|
+
|
|
650
664
|
console.log( `Loading model file from ZIP: ${path}` );
|
|
651
665
|
return await this.loadModelFromZipEntry( zip[ path ], path, extension, zip );
|
|
652
666
|
|
|
@@ -658,6 +672,48 @@ export class AssetLoader extends EventDispatcher {
|
|
|
658
672
|
|
|
659
673
|
}
|
|
660
674
|
|
|
675
|
+
// USDLoader only builds a cross-layer asset map on its USDZ branch, so repack
|
|
676
|
+
// the archive as USDZ — root layer first, per AOUSD core spec 16.4.1.2 — and
|
|
677
|
+
// let the loader resolve the references/payloads itself.
|
|
678
|
+
async loadUSDHierarchyFromZip( zip ) {
|
|
679
|
+
|
|
680
|
+
const layers = Object.keys( zip ).filter( name => USD_LAYER_RE.test( name ) );
|
|
681
|
+
if ( layers.length === 0 ) throw new Error( 'No USD layers found in the ZIP archive' );
|
|
682
|
+
|
|
683
|
+
const root = AssetLoader._pickUSDRootLayer( layers );
|
|
684
|
+
console.log( `Loading USD scene from ZIP: ${root} (${layers.length} layers)` );
|
|
685
|
+
|
|
686
|
+
const packed = { [ root ]: [ zip[ root ], { level: 0 } ] };
|
|
687
|
+
for ( const name of Object.keys( zip ) ) {
|
|
688
|
+
|
|
689
|
+
if ( name === root ) continue;
|
|
690
|
+
if ( USD_LAYER_RE.test( name ) || USD_IMAGE_RE.test( name ) ) packed[ name ] = [ zip[ name ], { level: 0 } ];
|
|
691
|
+
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return await this.loadUSDFromArrayBuffer( zipSync( packed ), root );
|
|
695
|
+
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Shallowest layer wins; among ties prefer the <dir>/<dir>.usd convention so a
|
|
699
|
+
// set's entry point beats its sibling variants.
|
|
700
|
+
static _pickUSDRootLayer( layers ) {
|
|
701
|
+
|
|
702
|
+
const depth = name => name.split( '/' ).length;
|
|
703
|
+
const minDepth = Math.min( ...layers.map( depth ) );
|
|
704
|
+
const candidates = layers.filter( name => depth( name ) === minDepth );
|
|
705
|
+
|
|
706
|
+
const conventional = candidates.find( name => {
|
|
707
|
+
|
|
708
|
+
const parts = name.split( '/' );
|
|
709
|
+
return parts.length > 1 && parts[ parts.length - 1 ].replace( USD_LAYER_RE, '' ) === parts[ parts.length - 2 ];
|
|
710
|
+
|
|
711
|
+
} );
|
|
712
|
+
|
|
713
|
+
return conventional || candidates[ 0 ];
|
|
714
|
+
|
|
715
|
+
}
|
|
716
|
+
|
|
661
717
|
async loadModelFromZipEntry( fileContent, filePath, extension, zipContents ) {
|
|
662
718
|
|
|
663
719
|
try {
|
|
@@ -698,8 +754,11 @@ export class AssetLoader extends EventDispatcher {
|
|
|
698
754
|
case '3mf':
|
|
699
755
|
result = await this.load3MFFromArrayBuffer( fileContent.buffer, filePath );
|
|
700
756
|
break;
|
|
757
|
+
case 'usd':
|
|
758
|
+
case 'usda':
|
|
759
|
+
case 'usdc':
|
|
701
760
|
case 'usdz':
|
|
702
|
-
result = await this.
|
|
761
|
+
result = await this.loadUSDFromArrayBuffer( fileContent, filePath );
|
|
703
762
|
break;
|
|
704
763
|
default:
|
|
705
764
|
throw new Error( `Support for ${extension} files is not yet implemented` );
|
|
@@ -1396,21 +1455,28 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1396
1455
|
|
|
1397
1456
|
}
|
|
1398
1457
|
|
|
1399
|
-
async
|
|
1458
|
+
async loadUSDFromArrayBuffer( data, filename = 'model.usd' ) {
|
|
1400
1459
|
|
|
1401
1460
|
try {
|
|
1402
1461
|
|
|
1403
|
-
updateLoading( { isLoading: true, status: "Processing
|
|
1462
|
+
updateLoading( { isLoading: true, status: "Processing USD Data...", progress: 5 } );
|
|
1404
1463
|
await new Promise( r => setTimeout( r, 0 ) );
|
|
1405
1464
|
|
|
1406
|
-
if ( ! this.loaderCache.
|
|
1465
|
+
if ( ! this.loaderCache.usd ) {
|
|
1407
1466
|
|
|
1408
|
-
const {
|
|
1409
|
-
this.loaderCache.
|
|
1467
|
+
const { USDLoader } = await import( 'three/examples/jsm/loaders/USDLoader.js' );
|
|
1468
|
+
this.loaderCache.usd = new USDLoader();
|
|
1410
1469
|
|
|
1411
1470
|
}
|
|
1412
1471
|
|
|
1413
|
-
|
|
1472
|
+
// parse() returns the group synchronously but resolves textures async;
|
|
1473
|
+
// setupPathTracing snapshots materials, so maps must land before it runs.
|
|
1474
|
+
const object = await new Promise( ( resolve, reject ) => {
|
|
1475
|
+
|
|
1476
|
+
this.loaderCache.usd.parse( data, '', resolve, reject );
|
|
1477
|
+
|
|
1478
|
+
} );
|
|
1479
|
+
|
|
1414
1480
|
object.name = filename;
|
|
1415
1481
|
|
|
1416
1482
|
this.releaseTargetModel();
|
|
@@ -1424,7 +1490,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1424
1490
|
|
|
1425
1491
|
} catch ( error ) {
|
|
1426
1492
|
|
|
1427
|
-
console.error( 'Error loading
|
|
1493
|
+
console.error( 'Error loading USD:', error );
|
|
1428
1494
|
this.dispatchEvent( { type: 'error', message: error.message, filename } );
|
|
1429
1495
|
throw error;
|
|
1430
1496
|
|
|
@@ -140,15 +140,26 @@ function neutralToneMap( r, g, b, exposure, out ) {
|
|
|
140
140
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Three.js clamps the fragment output with `.max( 0 )` *before* tone mapping
|
|
145
|
+
* (NodeMaterial.js, "force unsigned floats"), so the GPU curves never see a negative
|
|
146
|
+
* channel. Compositor's saturation grade (default 1.2) drives complementary channels
|
|
147
|
+
* below zero on a third of a typical frame, and AgX/Neutral mix those negatives across
|
|
148
|
+
* channels instead of clipping them — measured 7-10 levels of shadow error against the
|
|
149
|
+
* viewport until the readback clamps the same way.
|
|
150
|
+
*/
|
|
151
|
+
const clampNegative = fn => ( r, g, b, exposure, out ) =>
|
|
152
|
+
fn( r > 0 ? r : 0, g > 0 ? g : 0, b > 0 ? b : 0, exposure, out );
|
|
153
|
+
|
|
143
154
|
/** Look-up table mapping Three.js ToneMapping constants to CPU functions. */
|
|
144
155
|
export const TONE_MAP_FNS = new Map( [
|
|
145
|
-
[ NoToneMapping, noToneMap ],
|
|
146
|
-
[ LinearToneMapping, linearToneMap ],
|
|
147
|
-
[ ReinhardToneMapping, reinhardToneMap ],
|
|
148
|
-
[ CineonToneMapping, cineonToneMap ],
|
|
149
|
-
[ ACESFilmicToneMapping, acesFilmicToneMap ],
|
|
150
|
-
[ AgXToneMapping, agxToneMap ],
|
|
151
|
-
[ NeutralToneMapping, neutralToneMap ]
|
|
156
|
+
[ NoToneMapping, clampNegative( noToneMap ) ],
|
|
157
|
+
[ LinearToneMapping, clampNegative( linearToneMap ) ],
|
|
158
|
+
[ ReinhardToneMapping, clampNegative( reinhardToneMap ) ],
|
|
159
|
+
[ CineonToneMapping, clampNegative( cineonToneMap ) ],
|
|
160
|
+
[ ACESFilmicToneMapping, clampNegative( acesFilmicToneMap ) ],
|
|
161
|
+
[ AgXToneMapping, clampNegative( agxToneMap ) ],
|
|
162
|
+
[ NeutralToneMapping, clampNegative( neutralToneMap ) ]
|
|
152
163
|
] );
|
|
153
164
|
|
|
154
165
|
/** sRGB gamma (1/2.2) — fast pow approximation. Prefer `linearToSRGB` when matching Three.js's output. */
|
|
@@ -165,6 +176,25 @@ export function linearToSRGB( c ) {
|
|
|
165
176
|
|
|
166
177
|
}
|
|
167
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Exposure as the WebGPU output pass applies it.
|
|
181
|
+
*
|
|
182
|
+
* Three.js applies `toneMappingExposure` *inside* ToneMappingNode's tone-mapping branch, and
|
|
183
|
+
* that branch returns the colour untouched for NoToneMapping (ToneMappingNode.js: `if
|
|
184
|
+
* ( toneMapping === NoToneMapping ) return colorNode`). So exposure is a no-op on screen there,
|
|
185
|
+
* and a CPU readback that applies it anyway paints brighter than the viewport it replaces
|
|
186
|
+
* (measured +33.6 % at exposure 2).
|
|
187
|
+
*
|
|
188
|
+
* @param {number} exposure - renderer.toneMappingExposure
|
|
189
|
+
* @param {number} toneMapping - Three.js ToneMapping constant
|
|
190
|
+
* @returns {number}
|
|
191
|
+
*/
|
|
192
|
+
export function effectiveExposure( exposure, toneMapping ) {
|
|
193
|
+
|
|
194
|
+
return toneMapping === NoToneMapping ? 1.0 : exposure;
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
168
198
|
/** Rec.709 luminance coefficients (same as Display / Common.js). */
|
|
169
199
|
const LUM_R = 0.2126, LUM_G = 0.7152, LUM_B = 0.0722;
|
|
170
200
|
|
package/src/RenderSettings.js
CHANGED
|
@@ -1185,25 +1185,15 @@ export class PathTracerStage extends RenderStage {
|
|
|
1185
1185
|
}
|
|
1186
1186
|
|
|
1187
1187
|
/**
|
|
1188
|
-
* Update completion threshold based on render mode
|
|
1188
|
+
* Update completion threshold based on render mode. The ceiling holds in every limit mode —
|
|
1189
|
+
* uncapping it under a time budget lets a generous deadline silently slow every render.
|
|
1189
1190
|
*/
|
|
1190
1191
|
updateCompletionThreshold() {
|
|
1191
1192
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
this.completionThreshold = Infinity;
|
|
1198
|
-
|
|
1199
|
-
} else {
|
|
1200
|
-
|
|
1201
|
-
this.completionThreshold = updateCompletionThreshold(
|
|
1202
|
-
renderMode,
|
|
1203
|
-
maxFrames
|
|
1204
|
-
);
|
|
1205
|
-
|
|
1206
|
-
}
|
|
1193
|
+
this.completionThreshold = updateCompletionThreshold(
|
|
1194
|
+
this.renderMode.value,
|
|
1195
|
+
this.maxSamples.value
|
|
1196
|
+
);
|
|
1207
1197
|
|
|
1208
1198
|
}
|
|
1209
1199
|
|
|
@@ -138,6 +138,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
138
138
|
adapterInfo: null
|
|
139
139
|
} ),
|
|
140
140
|
|
|
141
|
+
refreshInput: () => {
|
|
142
|
+
|
|
143
|
+
const ctx = this.pipeline?.context;
|
|
144
|
+
if ( this._stages.compositor && ctx ) this._stages.compositor.render( ctx );
|
|
145
|
+
|
|
146
|
+
},
|
|
147
|
+
|
|
141
148
|
getGPUTextures: () => {
|
|
142
149
|
|
|
143
150
|
if ( ! pt?.storageTextures?.readTarget ) return null;
|
|
@@ -190,6 +197,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
190
197
|
|
|
191
198
|
},
|
|
192
199
|
|
|
200
|
+
refreshInput: () => {
|
|
201
|
+
|
|
202
|
+
const ctx = this.pipeline?.context;
|
|
203
|
+
if ( this._stages.compositor && ctx ) this._stages.compositor.render( ctx );
|
|
204
|
+
|
|
205
|
+
},
|
|
206
|
+
|
|
193
207
|
getGPUTextures: () => {
|
|
194
208
|
|
|
195
209
|
if ( ! pt?.storageTextures?.readTarget ) return null;
|
|
@@ -428,13 +442,6 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
428
442
|
// Remove any stale completion-chain listener from a previous render cycle
|
|
429
443
|
this._cleanupCompletionListener();
|
|
430
444
|
|
|
431
|
-
// Show post-process canvas if any post-process is enabled
|
|
432
|
-
if ( ( this.denoiser?.enabled || this.upscaler?.enabled ) && this.denoiserCanvas ) {
|
|
433
|
-
|
|
434
|
-
this.denoiserCanvas.style.display = 'block';
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
445
|
// Chain: denoise first (if enabled), then upscale (if enabled)
|
|
439
446
|
const startUpscaler = () => {
|
|
440
447
|
|
|
@@ -458,13 +465,6 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
458
465
|
|
|
459
466
|
} else {
|
|
460
467
|
|
|
461
|
-
// Re-render compositor stage so WebGPU canvas has valid content
|
|
462
|
-
if ( this.upscaler?.enabled && this._stages.compositor && context ) {
|
|
463
|
-
|
|
464
|
-
this._stages.compositor.render( context );
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
468
|
startUpscaler();
|
|
469
469
|
|
|
470
470
|
}
|
|
@@ -638,8 +638,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
638
638
|
setOIDNQuality( quality ) {
|
|
639
639
|
|
|
640
640
|
this.denoiser?.updateQuality( quality );
|
|
641
|
-
// Clean-aux normal follows the model
|
|
642
|
-
this._stages.pathTracer?.setCleanAuxNormal?.(
|
|
641
|
+
// Clean-aux normal follows the model, not the tier name — see OIDNDenoiser.QUALITY_MODELS.
|
|
642
|
+
this._stages.pathTracer?.setCleanAuxNormal?.(
|
|
643
|
+
!! this.denoiser?.enabled && !! this.denoiser?.expectsCleanAux( quality )
|
|
644
|
+
);
|
|
643
645
|
this._onPostProcessRefresh?.();
|
|
644
646
|
|
|
645
647
|
}
|