rayzee 11.2.1 → 11.3.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 +58 -29
- package/dist/ArchiveImporter-D735xX-4.js +7860 -0
- package/dist/BidirectionalIntegrator-M89c0AlP.js +954 -0
- package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
- package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-DiBH63kz.js} +1 -1
- package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-CZNQP13W.js} +1 -1
- package/dist/{PathTracer-D6nbknj5.js → PathTracer-BXCCVrPK.js} +4371 -4612
- package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-DqmaS0lb.js} +2 -2
- package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-DUSMFhmc.js} +426 -363
- package/dist/SolarLimb-bZE6y63S.js +762 -0
- 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 +472 -468
- package/dist/rayzee.umd.js +106 -105
- package/dist/sceneCachePolicy-BAib5fCp.js +1602 -0
- package/package.json +1 -1
- package/src/PathTracerApp.js +4 -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/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 +167 -26
- package/src/Stages/NormalDepth.js +7 -12
- package/src/Stages/PathTracer.js +12 -17
- package/src/Stages/PathTracerStage.js +273 -96
- 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/integrators/BidirectionalIntegrator.js +20 -2
- package/src/managers/DenoisingManager.js +4 -4
- package/src/managers/InteractionManager.js +19 -6
- package/src/managers/MaterialDataManager.js +22 -5
- package/src/managers/UniformManager.js +3 -0
- 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
|
@@ -94,6 +94,24 @@ function pointAt( P, basis, spans, u, w, out, o ) {
|
|
|
94
94
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** Width at x in [0, 1] along a curve from a width per control point, weighted as the points are. */
|
|
98
|
+
function widthAlong( widths, basis, spans ) {
|
|
99
|
+
|
|
100
|
+
const w = [ 0, 0, 0, 0 ];
|
|
101
|
+
return x => {
|
|
102
|
+
|
|
103
|
+
const u = x * spans;
|
|
104
|
+
let span = Math.floor( u );
|
|
105
|
+
if ( span >= spans ) span = spans - 1;
|
|
106
|
+
if ( basis === 'bspline' ) bsplineWeights( u - span, w );
|
|
107
|
+
else bezierWeights( u - span, w );
|
|
108
|
+
const base = basis === 'bspline' ? span : span * 3;
|
|
109
|
+
return w[ 0 ] * widths[ base ] + w[ 1 ] * widths[ base + 1 ] + w[ 2 ] * widths[ base + 2 ] + w[ 3 ] * widths[ base + 3 ];
|
|
110
|
+
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
97
115
|
/**
|
|
98
116
|
* Sample a curve's centreline.
|
|
99
117
|
* @returns {{points: Float64Array, count: number}} count points, 3 floats each
|
|
@@ -130,7 +148,7 @@ function segmentDistance( pa, a, pb, b, c ) {
|
|
|
130
148
|
* half-width, or by the uniform strip's own worst error on this curve if that is larger.
|
|
131
149
|
* @returns {Int32Array|null} kept sample indices, or null when every sample is kept
|
|
132
150
|
*/
|
|
133
|
-
function adaptiveSamples( P, basis, line, tolerance,
|
|
151
|
+
function adaptiveSamples( P, basis, line, tolerance, widthOf ) {
|
|
134
152
|
|
|
135
153
|
const { points, count, spans } = line;
|
|
136
154
|
if ( count < 3 ) return null;
|
|
@@ -143,7 +161,7 @@ function adaptiveSamples( P, basis, line, tolerance, width0, width1 ) {
|
|
|
143
161
|
let worst = 0;
|
|
144
162
|
for ( let k = 0; k < total; k ++ ) worst = Math.max( worst, segmentDistance( mids, k, points, k, k + 1 ) );
|
|
145
163
|
|
|
146
|
-
const allowed = x => Math.max( worst, tolerance * 0.5 * (
|
|
164
|
+
const allowed = x => Math.max( worst, tolerance * 0.5 * widthOf( x / total ) );
|
|
147
165
|
const fits = ( i, j ) => {
|
|
148
166
|
|
|
149
167
|
for ( let k = i + 1; k < j; k ++ ) if ( segmentDistance( points, k, points, i, j ) > allowed( k ) ) return false;
|
|
@@ -174,6 +192,8 @@ function adaptiveSamples( P, basis, line, tolerance, width0, width1 ) {
|
|
|
174
192
|
* @param {string} [spec.basis='bezier'] - 'bezier' | 'bspline'
|
|
175
193
|
* @param {number} [spec.width0=1]
|
|
176
194
|
* @param {number} [spec.width1=1]
|
|
195
|
+
* @param {ArrayLike<number>} [spec.widths] - a width per control point, following the curve's basis like the points;
|
|
196
|
+
* overrides width0 / width1
|
|
177
197
|
* @param {ArrayLike<number>} [spec.N] - ribbon normals (start, end), 3 floats each
|
|
178
198
|
* @param {number} [spec.steps] - samples per spline span
|
|
179
199
|
* @param {number} [spec.sides=1] - 1 ribbon, 2 crossed ribbons, >=3 closed tube
|
|
@@ -193,8 +213,9 @@ export function tessellateCurve( spec ) {
|
|
|
193
213
|
const line = sampleCenterline( P, basis, steps );
|
|
194
214
|
if ( ! line ) return null;
|
|
195
215
|
|
|
196
|
-
const { points, count } = line;
|
|
197
|
-
const
|
|
216
|
+
const { points, count, spans } = line;
|
|
217
|
+
const widthOf = spec.widths ? widthAlong( spec.widths, basis, spans ) : x => width0 + ( width1 - width0 ) * x;
|
|
218
|
+
const kept = spec.tolerance > 0 ? adaptiveSamples( P, basis, line, spec.tolerance, widthOf ) : null;
|
|
198
219
|
const rows = kept ? kept.length : count;
|
|
199
220
|
const rings = sides >= 3 ? sides : sides * 2;
|
|
200
221
|
const positions = new Float32Array( rows * rings * 3 );
|
|
@@ -256,8 +277,7 @@ export function tessellateCurve( spec ) {
|
|
|
256
277
|
cross( tangent, side, up );
|
|
257
278
|
normalize( up );
|
|
258
279
|
|
|
259
|
-
const
|
|
260
|
-
const radius = ( width0 + ( width1 - width0 ) * u ) * 0.5;
|
|
280
|
+
const radius = widthOf( i / ( count - 1 || 1 ) ) * 0.5;
|
|
261
281
|
const cx = points[ i * 3 ], cy = points[ i * 3 + 1 ], cz = points[ i * 3 + 2 ];
|
|
262
282
|
|
|
263
283
|
for ( let r = 0; r < rings; r ++ ) {
|
|
@@ -324,3 +344,201 @@ export function curveTriangleCount( controlPoints, basis, steps, sides ) {
|
|
|
324
344
|
return segments * Math.max( 1, Math.round( sides ) ) * 2;
|
|
325
345
|
|
|
326
346
|
}
|
|
347
|
+
|
|
348
|
+
const lerp3 = ( out, o, P, a, b, t ) => {
|
|
349
|
+
|
|
350
|
+
for ( let k = 0; k < 3; k ++ ) out[ o + k ] = P[ a * 3 + k ] + ( P[ b * 3 + k ] - P[ a * 3 + k ] ) * t;
|
|
351
|
+
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/** Linear or Catmull-Rom spans as cubic Bézier ones (exact for both), with widths carried the same way. */
|
|
355
|
+
function bezierSpans( P, widths, segments, ends ) {
|
|
356
|
+
|
|
357
|
+
const out = new Float64Array( ( segments.length * 3 + 1 ) * 3 );
|
|
358
|
+
const w = widths ? new Float64Array( segments.length * 3 + 1 ) : null;
|
|
359
|
+
let o = 0;
|
|
360
|
+
for ( let s = 0; s < segments.length; s ++ ) {
|
|
361
|
+
|
|
362
|
+
const [ a, b, prev, next ] = segments[ s ];
|
|
363
|
+
if ( s === 0 ) {
|
|
364
|
+
|
|
365
|
+
for ( let k = 0; k < 3; k ++ ) out[ k ] = P[ a * 3 + k ];
|
|
366
|
+
if ( w ) w[ 0 ] = widths[ a ];
|
|
367
|
+
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if ( ends === 'linear' ) {
|
|
371
|
+
|
|
372
|
+
lerp3( out, ( o + 1 ) * 3, P, a, b, 1 / 3 );
|
|
373
|
+
lerp3( out, ( o + 2 ) * 3, P, a, b, 2 / 3 );
|
|
374
|
+
|
|
375
|
+
} else {
|
|
376
|
+
|
|
377
|
+
for ( let k = 0; k < 3; k ++ ) {
|
|
378
|
+
|
|
379
|
+
out[ ( o + 1 ) * 3 + k ] = P[ a * 3 + k ] + ( P[ b * 3 + k ] - P[ prev * 3 + k ] ) / 6;
|
|
380
|
+
out[ ( o + 2 ) * 3 + k ] = P[ b * 3 + k ] - ( P[ next * 3 + k ] - P[ a * 3 + k ] ) / 6;
|
|
381
|
+
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for ( let k = 0; k < 3; k ++ ) out[ ( o + 3 ) * 3 + k ] = P[ b * 3 + k ];
|
|
387
|
+
if ( w ) {
|
|
388
|
+
|
|
389
|
+
w[ o + 1 ] = widths[ a ] + ( widths[ b ] - widths[ a ] ) / 3;
|
|
390
|
+
w[ o + 2 ] = widths[ a ] + ( widths[ b ] - widths[ a ] ) * 2 / 3;
|
|
391
|
+
w[ o + 3 ] = widths[ b ];
|
|
392
|
+
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
o += 3;
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { P: out, widths: w, basis: 'bezier' };
|
|
400
|
+
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** One USD curve as the cubic B-spline or Bézier controls tessellateCurve takes, or null. */
|
|
404
|
+
function cubicControls( P, widths, type, basis, wrap ) {
|
|
405
|
+
|
|
406
|
+
const n = P.length / 3;
|
|
407
|
+
const periodic = wrap === 'periodic';
|
|
408
|
+
if ( type === 'linear' ) {
|
|
409
|
+
|
|
410
|
+
if ( n < 2 ) return null;
|
|
411
|
+
const segments = [];
|
|
412
|
+
for ( let i = 0; i + 1 < n; i ++ ) segments.push( [ i, i + 1 ] );
|
|
413
|
+
if ( periodic && n > 2 ) segments.push( [ n - 1, 0 ] );
|
|
414
|
+
return bezierSpans( P, widths, segments, 'linear' );
|
|
415
|
+
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if ( basis === 'bspline' ) {
|
|
419
|
+
|
|
420
|
+
if ( wrap === 'pinned' ) {
|
|
421
|
+
|
|
422
|
+
const index = [ 0, 0, ...Array.from( { length: n }, ( _, i ) => i ), n - 1, n - 1 ];
|
|
423
|
+
return withIndex( P, widths, index, 'bspline' );
|
|
424
|
+
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if ( periodic ) return withIndex( P, widths, [ ...Array.from( { length: n }, ( _, i ) => i ), 0, 1, 2 ], 'bspline' );
|
|
428
|
+
return n >= 4 ? { P, widths, basis: 'bspline' } : null;
|
|
429
|
+
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if ( basis === 'bezier' ) {
|
|
433
|
+
|
|
434
|
+
if ( periodic ) return withIndex( P, widths, [ ...Array.from( { length: n }, ( _, i ) => i ), 0 ], 'bezier' );
|
|
435
|
+
return n >= 4 ? { P, widths, basis: 'bezier' } : null;
|
|
436
|
+
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if ( basis === 'catmullRom' ) {
|
|
440
|
+
|
|
441
|
+
const segments = [];
|
|
442
|
+
const at = i => ( periodic ? ( i + n ) % n : Math.min( n - 1, Math.max( 0, i ) ) );
|
|
443
|
+
const first = periodic || wrap === 'pinned' ? 0 : 1;
|
|
444
|
+
const last = periodic ? n : wrap === 'pinned' ? n - 1 : n - 2;
|
|
445
|
+
for ( let i = first; i < last; i ++ ) segments.push( [ at( i ), at( i + 1 ), at( i - 1 ), at( i + 2 ) ] );
|
|
446
|
+
return segments.length ? bezierSpans( P, widths, segments, 'catmullRom' ) : null;
|
|
447
|
+
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return null;
|
|
451
|
+
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function withIndex( P, widths, index, basis ) {
|
|
455
|
+
|
|
456
|
+
const out = new Float64Array( index.length * 3 );
|
|
457
|
+
const w = widths ? new Float64Array( index.length ) : null;
|
|
458
|
+
index.forEach( ( v, i ) => {
|
|
459
|
+
|
|
460
|
+
for ( let k = 0; k < 3; k ++ ) out[ i * 3 + k ] = P[ v * 3 + k ];
|
|
461
|
+
if ( w ) w[ i ] = widths[ v ];
|
|
462
|
+
|
|
463
|
+
} );
|
|
464
|
+
return { P: out, widths: w, basis };
|
|
465
|
+
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* A USD BasisCurves prim's curves (one point array, a count per curve) as one strip mesh. Modes and types are USD's;
|
|
470
|
+
* the first normal of a curve orients its ribbon.
|
|
471
|
+
*/
|
|
472
|
+
export function tessellateCurves( spec ) {
|
|
473
|
+
|
|
474
|
+
const { P, counts, widths = null, widthMode = 'constant', N = null, normalMode = 'vertex', type = 'cubic', basis = 'bspline', wrap = 'nonperiodic' } = spec;
|
|
475
|
+
const pieces = [];
|
|
476
|
+
let vertices = 0, indexCount = 0, skipped = 0, start = 0, varying = 0;
|
|
477
|
+
|
|
478
|
+
for ( let c = 0; c < counts.length; c ++ ) {
|
|
479
|
+
|
|
480
|
+
const n = counts[ c ];
|
|
481
|
+
const points = P.subarray ? P.subarray( start * 3, ( start + n ) * 3 ) : P.slice( start * 3, ( start + n ) * 3 );
|
|
482
|
+
let curveWidths = null, width = 1;
|
|
483
|
+
if ( widths?.length ) {
|
|
484
|
+
|
|
485
|
+
if ( widthMode === 'vertex' ) curveWidths = Float64Array.from( { length: n }, ( _, i ) => widths[ start + i ] );
|
|
486
|
+
else if ( widthMode === 'uniform' ) width = widths[ c ] ?? widths[ 0 ];
|
|
487
|
+
else if ( widthMode === 'varying' ) {
|
|
488
|
+
|
|
489
|
+
const spans = type === 'linear' ? n - 1 : basis === 'bezier' ? ( n - 1 ) / 3 : n - 3;
|
|
490
|
+
const k = Math.max( 1, Math.round( spans ) + 1 );
|
|
491
|
+
const values = Array.from( { length: k }, ( _, i ) => widths[ varying + i ] ?? widths[ widths.length - 1 ] );
|
|
492
|
+
varying += k;
|
|
493
|
+
curveWidths = Float64Array.from( { length: n }, ( _, i ) => values[ Math.min( k - 1, Math.round( i / Math.max( 1, n - 1 ) * ( k - 1 ) ) ) ] );
|
|
494
|
+
|
|
495
|
+
} else width = widths[ 0 ];
|
|
496
|
+
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let seed = null;
|
|
500
|
+
if ( N?.length ) {
|
|
501
|
+
|
|
502
|
+
const at = normalMode === 'uniform' ? c : normalMode === 'constant' ? 0 : start;
|
|
503
|
+
seed = [ N[ at * 3 ], N[ at * 3 + 1 ], N[ at * 3 + 2 ] ];
|
|
504
|
+
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const cubic = cubicControls( points, curveWidths, type, basis, wrap );
|
|
508
|
+
const built = cubic && tessellateCurve( {
|
|
509
|
+
P: cubic.P, basis: cubic.basis, widths: cubic.widths, width0: width, width1: width, N: seed,
|
|
510
|
+
steps: spec.steps, sides: spec.sides, tolerance: spec.tolerance,
|
|
511
|
+
} );
|
|
512
|
+
|
|
513
|
+
if ( built ) {
|
|
514
|
+
|
|
515
|
+
pieces.push( built );
|
|
516
|
+
vertices += built.positions.length / 3;
|
|
517
|
+
indexCount += built.indices.length;
|
|
518
|
+
|
|
519
|
+
} else {
|
|
520
|
+
|
|
521
|
+
skipped ++;
|
|
522
|
+
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
start += n;
|
|
526
|
+
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if ( pieces.length === 0 ) return null;
|
|
530
|
+
const positions = new Float32Array( vertices * 3 );
|
|
531
|
+
const indices = new Uint32Array( indexCount );
|
|
532
|
+
let v = 0, i = 0;
|
|
533
|
+
for ( const piece of pieces ) {
|
|
534
|
+
|
|
535
|
+
positions.set( piece.positions, v * 3 );
|
|
536
|
+
for ( let k = 0; k < piece.indices.length; k ++ ) indices[ i + k ] = piece.indices[ k ] + v;
|
|
537
|
+
v += piece.positions.length / 3;
|
|
538
|
+
i += piece.indices.length;
|
|
539
|
+
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return { positions, indices, skipped };
|
|
543
|
+
|
|
544
|
+
}
|
|
@@ -348,6 +348,9 @@ async function applyConductorColor( mat, params, ctx, prefix, applyAlbedo, setCo
|
|
|
348
348
|
*/
|
|
349
349
|
export async function buildMaterial( def, ctx ) {
|
|
350
350
|
|
|
351
|
+
// Built already, by the USD importer.
|
|
352
|
+
if ( def?.material ) return def.material;
|
|
353
|
+
|
|
351
354
|
const type = def?.type || 'diffuse';
|
|
352
355
|
const params = def?.params || {};
|
|
353
356
|
const mat = new MeshPhysicalMaterial( { side: DoubleSide, roughness: 1, metalness: 0 } );
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { freeNow, resized } from './buffers.js';
|
|
18
18
|
import {
|
|
19
19
|
Group, Mesh, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Vector3, Quaternion,
|
|
20
|
-
BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute,
|
|
20
|
+
BufferGeometry, BufferAttribute, Float32BufferAttribute, Uint32BufferAttribute,
|
|
21
21
|
DataTexture, FloatType, RGBAFormat, LinearFilter, EquirectangularReflectionMapping,
|
|
22
22
|
SRGBColorSpace, NoColorSpace, AnimationClip, VectorKeyframeTrack, QuaternionKeyframeTrack,
|
|
23
23
|
NumberKeyframeTrack, BooleanKeyframeTrack, DirectionalLight, PointLight, SpotLight, FrontSide, DoubleSide,
|
|
@@ -27,8 +27,9 @@ import { buildMaterial, pBool, pFloat, pString, resolveSpectrum } from './PBRTMa
|
|
|
27
27
|
import { makeLayer, layerMean, bake, mixOf, srgbToLinear, linearToSRGB, hasAlpha } from './PBRTTextureBake.js';
|
|
28
28
|
import { loopSubdivide } from './LoopSubdivision.js';
|
|
29
29
|
import { octahedralToEquirect } from './EqualAreaOctahedral.js';
|
|
30
|
-
import { tessellateCurve } from './PBRTCurves.js';
|
|
30
|
+
import { tessellateCurve, tessellateCurves } from './PBRTCurves.js';
|
|
31
31
|
import * as M from './PBRTMath.js';
|
|
32
|
+
import { packUnitAttribute } from '../GeometryExtractor.js';
|
|
32
33
|
|
|
33
34
|
const LAMP_TYPES = new Set( [ 'distant', 'point', 'spot' ] );
|
|
34
35
|
|
|
@@ -107,7 +108,7 @@ const DEFAULT_CURVE_SIDES = { flat: 1, ribbon: 1, cylinder: 2 };
|
|
|
107
108
|
const DEFAULT_CURVE_TOLERANCE = 0.05;
|
|
108
109
|
|
|
109
110
|
/** Bumped whenever the same scene files build a different graph, so a stored graph is not reused. */
|
|
110
|
-
export const PBRT_BUILD_REVISION =
|
|
111
|
+
export const PBRT_BUILD_REVISION = 10;
|
|
111
112
|
|
|
112
113
|
function samePlacements( a, b ) {
|
|
113
114
|
|
|
@@ -445,7 +446,7 @@ function rasterize( width, height, colorAt ) {
|
|
|
445
446
|
}
|
|
446
447
|
|
|
447
448
|
// A clone that keeps the engine's own material properties, which three.js's copy() does not know.
|
|
448
|
-
function cloneMaterial( material ) {
|
|
449
|
+
export function cloneMaterial( material ) {
|
|
449
450
|
|
|
450
451
|
const out = material.clone();
|
|
451
452
|
for ( const key of Object.keys( material ) ) if ( ! ( key in out ) ) out[ key ] = material[ key ];
|
|
@@ -617,14 +618,14 @@ export class PBRTSceneBuilder {
|
|
|
617
618
|
|
|
618
619
|
const shape = ir.shapes[ i ];
|
|
619
620
|
const [ geometry, surface ] = await Promise.all( [
|
|
620
|
-
this._batches ? this._createGeometry( shape ) : this._buildGeometry( shape ),
|
|
621
|
+
this._batches && ! shape.shared ? this._createGeometry( shape ) : this._buildGeometry( shape ),
|
|
621
622
|
this._getMaterial( shape )
|
|
622
623
|
] );
|
|
623
624
|
if ( ! geometry ) continue;
|
|
624
625
|
const sharedMaterial = shape.areaLight ? this._poweredMaterial( shape, geometry, surface ) : surface;
|
|
625
626
|
|
|
626
627
|
// A moving shape needs a node of its own to move.
|
|
627
|
-
if ( this._batches && ! shape.motion && geometry.getAttribute( 'position' ).count <= MERGE_VERTEX_LIMIT ) {
|
|
628
|
+
if ( this._batches && ! shape.motion && ! shape.shared && geometry.getAttribute( 'position' ).count <= MERGE_VERTEX_LIMIT ) {
|
|
628
629
|
|
|
629
630
|
this._mergeShape( shape, geometry, sharedMaterial, group );
|
|
630
631
|
ir.shapes[ i ] = null; // its parsed arrays are copied out; let them go
|
|
@@ -738,30 +739,45 @@ export class PBRTSceneBuilder {
|
|
|
738
739
|
|
|
739
740
|
const scratch = new Float64Array( 16 );
|
|
740
741
|
const matrix = new Matrix4();
|
|
742
|
+
// A template's shapes at the same relative transform share one matrix attribute, which the
|
|
743
|
+
// engine places as one object: one TLAS entry a copy, not one a shape.
|
|
744
|
+
const sharedMatrices = new Map();
|
|
741
745
|
|
|
742
746
|
const place = ( list, geometry, material, rel, shapeType, materialType, tris ) => {
|
|
743
747
|
|
|
748
|
+
const relKey = rel ? Array.prototype.join.call( rel, ',' ) : '';
|
|
749
|
+
let byRel = sharedMatrices.get( list );
|
|
750
|
+
if ( ! byRel ) sharedMatrices.set( list, byRel = new Map() );
|
|
751
|
+
const shared = byRel.get( relKey );
|
|
752
|
+
|
|
744
753
|
const affordable = Math.max( 0, this.maxPlacements - this.placementCount );
|
|
745
|
-
const count = Math.min( list.count, affordable );
|
|
746
|
-
if ( count < list.count ) this.skippedForBudget += list.count - count;
|
|
754
|
+
const count = shared ? shared.count : Math.min( list.count, affordable );
|
|
755
|
+
if ( ! shared && count < list.count ) this.skippedForBudget += list.count - count;
|
|
747
756
|
if ( count === 0 ) return;
|
|
748
757
|
|
|
749
758
|
const mesh = new InstancedMesh( geometry, material, count );
|
|
750
759
|
mesh.name = `instance_${n ++}`;
|
|
751
760
|
mesh.frustumCulled = false;
|
|
752
761
|
|
|
753
|
-
|
|
762
|
+
if ( shared ) mesh.instanceMatrix = shared.attribute;
|
|
763
|
+
else {
|
|
764
|
+
|
|
765
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
766
|
+
|
|
767
|
+
if ( rel ) M.multiplyInto( scratch, list.matrices, i * 16, rel );
|
|
768
|
+
else for ( let e = 0; e < 16; e ++ ) scratch[ e ] = list.matrices[ i * 16 + e ];
|
|
769
|
+
mesh.setMatrixAt( i, matrix.fromArray( this.convertHandedness ? M.multiply( FLIP_Z, scratch ) : scratch ) );
|
|
770
|
+
|
|
771
|
+
}
|
|
754
772
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
773
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
774
|
+
byRel.set( relKey, { attribute: mesh.instanceMatrix, count } );
|
|
775
|
+
this.placementCount += count;
|
|
758
776
|
|
|
759
777
|
}
|
|
760
778
|
|
|
761
|
-
mesh.instanceMatrix.needsUpdate = true;
|
|
762
779
|
group.add( mesh );
|
|
763
780
|
|
|
764
|
-
this.placementCount += count;
|
|
765
781
|
this.reportedMeshes += count;
|
|
766
782
|
if ( this.report.length < MAX_REPORT_ROWS ) this.report.push( {
|
|
767
783
|
mesh: `${mesh.name} ×${count}`,
|
|
@@ -800,7 +816,7 @@ export class PBRTSceneBuilder {
|
|
|
800
816
|
// eight Pandanus trees in Moana hold 22,965 leaves each. A .ply may be shared with
|
|
801
817
|
// other templates and stays whole.
|
|
802
818
|
let inline = 0;
|
|
803
|
-
for ( const shape of template ) if ( shape.type !== 'plymesh' ) inline ++;
|
|
819
|
+
for ( const shape of template ) if ( shape.type !== 'plymesh' && ! shape.shared ) inline ++;
|
|
804
820
|
const batches = inline >= 2 ? new Map() : null;
|
|
805
821
|
|
|
806
822
|
for ( const shape of template ) {
|
|
@@ -818,7 +834,7 @@ export class PBRTSceneBuilder {
|
|
|
818
834
|
] );
|
|
819
835
|
if ( ! geometry ) continue;
|
|
820
836
|
|
|
821
|
-
if ( batches && shape.type !== 'plymesh' && geometry.getAttribute( 'position' ).count <= MERGE_VERTEX_LIMIT ) {
|
|
837
|
+
if ( batches && shape.type !== 'plymesh' && ! shape.shared && geometry.getAttribute( 'position' ).count <= MERGE_VERTEX_LIMIT ) {
|
|
822
838
|
|
|
823
839
|
this._geometryCache.delete( shape );
|
|
824
840
|
const full = this._appendToBatch( batches, shape, geometry, sharedMaterial, shape.relativeCTM || shape.ctm );
|
|
@@ -1253,11 +1269,12 @@ export class PBRTSceneBuilder {
|
|
|
1253
1269
|
/** A batch's triangles as one geometry; the batch's own arrays are released. @private */
|
|
1254
1270
|
_batchGeometry( batch ) {
|
|
1255
1271
|
|
|
1272
|
+
// The batch's arrays cut to size, not copied: Float32BufferAttribute copies whatever it is given.
|
|
1256
1273
|
const geometry = new BufferGeometry();
|
|
1257
|
-
geometry.setAttribute( 'position', new
|
|
1258
|
-
geometry.setAttribute( 'normal', new
|
|
1259
|
-
if ( batch.uvs ) geometry.setAttribute( 'uv', new
|
|
1260
|
-
geometry.setIndex( new
|
|
1274
|
+
geometry.setAttribute( 'position', new BufferAttribute( resized( batch.positions, batch.vertexCount * 3 ), 3 ) );
|
|
1275
|
+
geometry.setAttribute( 'normal', new BufferAttribute( resized( batch.normals, batch.vertexCount * 3 ), 3 ) );
|
|
1276
|
+
if ( batch.uvs ) geometry.setAttribute( 'uv', new BufferAttribute( resized( batch.uvs, batch.vertexCount * 2 ), 2 ) );
|
|
1277
|
+
geometry.setIndex( new BufferAttribute( resized( batch.indices, batch.indexCount ), 1 ) );
|
|
1261
1278
|
batch.positions = batch.normals = batch.uvs = batch.indices = null;
|
|
1262
1279
|
return geometry;
|
|
1263
1280
|
|
|
@@ -1294,9 +1311,11 @@ export class PBRTSceneBuilder {
|
|
|
1294
1311
|
|
|
1295
1312
|
// An ObjectInstance template is built once per placement, and the Moana palm debris
|
|
1296
1313
|
// alone places one 208-triangle leaf 2.25 million times. three.js is happy to share
|
|
1297
|
-
// one BufferGeometry across meshes; only the per-mesh matrix differs.
|
|
1298
|
-
|
|
1299
|
-
|
|
1314
|
+
// one BufferGeometry across meshes; only the per-mesh matrix differs. Shapes of one
|
|
1315
|
+
// `geometryKey` (a USD mesh read again elsewhere) share it the same way.
|
|
1316
|
+
const key = shape.geometryKey ?? shape;
|
|
1317
|
+
let pending = this._geometryCache.get( key );
|
|
1318
|
+
if ( ! pending ) this._geometryCache.set( key, pending = this._createGeometry( shape ) );
|
|
1300
1319
|
return pending;
|
|
1301
1320
|
|
|
1302
1321
|
}
|
|
@@ -1361,6 +1380,7 @@ export class PBRTSceneBuilder {
|
|
|
1361
1380
|
case 'bilinearmesh': return this._bilinearMesh( shape.params );
|
|
1362
1381
|
case 'loopsubdiv': return this._loopSubdiv( shape.params );
|
|
1363
1382
|
case 'curve': return this._curve( shape.params );
|
|
1383
|
+
case 'curves': return this._curves( shape.params );
|
|
1364
1384
|
case 'plymesh': return this._plyMesh( shape.params );
|
|
1365
1385
|
case 'sphere': return this._sphere( shape.params );
|
|
1366
1386
|
case 'disk': return this._disk( shape.params );
|
|
@@ -1439,10 +1459,47 @@ export class PBRTSceneBuilder {
|
|
|
1439
1459
|
|
|
1440
1460
|
}
|
|
1441
1461
|
|
|
1462
|
+
// The tessellation's own arrays, not copies; normals at 16 bits from the start, as extraction would store them.
|
|
1463
|
+
const geo = new BufferGeometry();
|
|
1464
|
+
geo.setAttribute( 'position', new BufferAttribute( built.positions, 3 ) );
|
|
1465
|
+
geo.setIndex( new BufferAttribute( built.indices, 1 ) );
|
|
1466
|
+
geo.computeVertexNormals();
|
|
1467
|
+
packUnitAttribute( geo, 'normal' );
|
|
1468
|
+
return geo;
|
|
1469
|
+
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/** Many curves on one point array, as USD writes them. */
|
|
1473
|
+
_curves( params ) {
|
|
1474
|
+
|
|
1475
|
+
const built = tessellateCurves( {
|
|
1476
|
+
P: params.P.value,
|
|
1477
|
+
counts: params.counts.value,
|
|
1478
|
+
widths: params.widths?.value ?? null,
|
|
1479
|
+
widthMode: pString( params, 'widthMode', 'constant' ),
|
|
1480
|
+
N: params.N?.value ?? null,
|
|
1481
|
+
normalMode: pString( params, 'normalMode', 'vertex' ),
|
|
1482
|
+
type: pString( params, 'type', 'cubic' ),
|
|
1483
|
+
basis: pString( params, 'basis', 'bspline' ),
|
|
1484
|
+
wrap: pString( params, 'wrap', 'nonperiodic' ),
|
|
1485
|
+
steps: this.curveSteps,
|
|
1486
|
+
sides: this.curveSides ?? 1,
|
|
1487
|
+
tolerance: this.curveTolerance,
|
|
1488
|
+
} );
|
|
1489
|
+
|
|
1490
|
+
if ( ! built ) {
|
|
1491
|
+
|
|
1492
|
+
this.warn( 'curves could not be tessellated' ); return null;
|
|
1493
|
+
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if ( built.skipped > 0 ) this.warn( `${built.skipped} curve(s) of an unsupported basis or too few points skipped` );
|
|
1497
|
+
// The tessellation's own arrays, not copies; normals at 16 bits from the start, as extraction would store them.
|
|
1442
1498
|
const geo = new BufferGeometry();
|
|
1443
|
-
geo.setAttribute( 'position', new
|
|
1444
|
-
geo.setIndex( new
|
|
1499
|
+
geo.setAttribute( 'position', new BufferAttribute( built.positions, 3 ) );
|
|
1500
|
+
geo.setIndex( new BufferAttribute( built.indices, 1 ) );
|
|
1445
1501
|
geo.computeVertexNormals();
|
|
1502
|
+
packUnitAttribute( geo, 'normal' );
|
|
1446
1503
|
return geo;
|
|
1447
1504
|
|
|
1448
1505
|
}
|
|
@@ -57,6 +57,8 @@ export const LIGHT_VERTEX_STRIDE = 4;
|
|
|
57
57
|
// SoA region stride, baked into the shader graph at build time; single instance, rebuilt on resize.
|
|
58
58
|
let _cap = 0;
|
|
59
59
|
let _lightVertexBase = 0;
|
|
60
|
+
// The G-buffer is the hit buffer's last region, one uvec4 a path: in a buffer of its own it took Shade's tenth binding.
|
|
61
|
+
let _gBufferBase = 0;
|
|
60
62
|
|
|
61
63
|
const soa = ( id, slot ) => ( slot === 0 ? id : id.add( slot * _cap ) );
|
|
62
64
|
|
|
@@ -104,6 +106,7 @@ export class PackedRayBuffer {
|
|
|
104
106
|
_cap = capacity;
|
|
105
107
|
const hitSlots = lightVertices > 0 ? HIT_STRIDE_BIDIRECTIONAL : HIT_STRIDE;
|
|
106
108
|
_lightVertexBase = capacity * hitSlots;
|
|
109
|
+
_gBufferBase = _lightVertexBase + lightVertices * LIGHT_VERTEX_STRIDE;
|
|
107
110
|
|
|
108
111
|
// count=0 so StorageBufferNode.getHash() shares the buffer → RW and RO nodes bind the same GPU data.
|
|
109
112
|
const rayCount = capacity * RAY_STRIDE;
|
|
@@ -114,7 +117,7 @@ export class PackedRayBuffer {
|
|
|
114
117
|
ro: storage( rayAttr, 'vec4' ).toReadOnly(),
|
|
115
118
|
};
|
|
116
119
|
|
|
117
|
-
const hitCount =
|
|
120
|
+
const hitCount = _gBufferBase + capacity * GBUFFER_STRIDE;
|
|
118
121
|
const hitAttr = gpuOnlyStorageAttribute( hitCount, 4, Uint32Array );
|
|
119
122
|
this._attrs.hit = hitAttr;
|
|
120
123
|
this.hitBuffer = {
|
|
@@ -178,7 +181,7 @@ export const readRayRadiance = ( buf, id ) =>
|
|
|
178
181
|
// normal: raw unit vec3; depth: linear [0,1]; albedo: vec3 [0,1]. Packed values live in u32 lanes
|
|
179
182
|
// verbatim (no f32 bitcast) so NaN-range bit patterns (snorm ±1 → 0x7FFF) survive store/load intact.
|
|
180
183
|
// One uvec4 per pixel (stride 1); AoS base = pixelIndex * GBUFFER_STRIDE.
|
|
181
|
-
const gbLane = ( pixelIndex ) => uint( pixelIndex ).mul( GBUFFER_STRIDE );
|
|
184
|
+
const gbLane = ( pixelIndex ) => uint( pixelIndex ).mul( GBUFFER_STRIDE ).add( _gBufferBase );
|
|
182
185
|
|
|
183
186
|
// hitDist = null keeps the lane's current normHitDist.
|
|
184
187
|
export const writeGBuffer = ( buf, pixelIndex, normal, depth, albedo, hitDist = null ) => {
|