rayzee 11.7.0 → 11.9.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 +31 -6
- package/dist/{ArchiveImporter-DYcRXeKE.js → ArchiveImporter-BJxszU_k.js} +2353 -1356
- package/dist/{BidirectionalIntegrator-BvjRpbTP.js → BidirectionalIntegrator-DWg66vAd.js} +302 -301
- package/dist/{BufferLayout-xPRH9jYD.js → BufferLayout-Bzgccg1o.js} +1 -0
- package/dist/{ColorManagement-oeEk7oSu.js → ColorManagement-B_HjAiy2.js} +1 -1
- package/dist/{EngineIssues-CIZnXHsF.js → EngineIssues-CfJiBaNj.js} +19 -8
- package/dist/{NeuralRetouch-ClaMiKYg.js → NeuralRetouch-BD9E-Yht.js} +2 -2
- package/dist/{NeuralSuperRes-WNEXun9s.js → NeuralSuperRes-C8Mf3tJX.js} +2 -2
- package/dist/{PathTracer-B9wZPPVL.js → PathTracer-DeE9UJ1n.js} +3530 -3402
- package/dist/{PhysicalSky-DcjItqKi.js → PhysicalSky-cKy75-oR.js} +45 -45
- package/dist/{RayzeeRenderer-p-NeE6E2.js → RayzeeRenderer-CzLyBA3G.js} +378 -374
- package/dist/SceneMetadata-C17UfOEp.js +183 -0
- package/dist/{SolarLimb-bZE6y63S.js → SolarLimb-DRYy8tln.js} +184 -172
- package/dist/{ToneMapGPU-sAs8wdgI.js → ToneMapGPU-4GPwoG_c.js} +13 -6
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/color.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/addons/storage.es.js +1 -1
- package/dist/{openStorage-Fe9BoS1_.js → openStorage-CdyVjmps.js} +1 -1
- package/dist/rayzee-core.es.js +7 -7
- package/dist/rayzee.es.js +821 -794
- package/dist/rayzee.umd.js +112 -103
- package/dist/{sceneCachePolicy-B9w3BnaP.js → sceneCachePolicy-ywbGKHD3.js} +9 -6
- package/package.json +1 -1
- package/src/EngineDefaults.js +1 -0
- package/src/EngineIssues.js +21 -6
- package/src/Passes/AIUpscaler.js +4 -0
- package/src/Passes/OIDNDenoiser.js +8 -6
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +10 -1
- package/src/Processor/ArchiveImporter.js +106 -27
- package/src/Processor/AssetLoader.js +22 -0
- package/src/Processor/BufferLayout.js +3 -3
- package/src/Processor/GeometryExtractor.js +12 -4
- package/src/Processor/LightSerializer.js +6 -2
- package/src/Processor/MaterialPacking.js +2 -2
- package/src/Processor/PBRT/PBRTMaterials.js +49 -13
- package/src/Processor/PBRT/PBRTParser.js +144 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +287 -67
- package/src/Processor/PBRT/PBRTTextureBake.js +191 -9
- package/src/Processor/PBRT/index.js +19 -4
- package/src/Processor/PlatformImageLoader.js +9 -0
- package/src/Processor/SceneProcessor.js +42 -26
- package/src/Processor/TextureCreator.js +44 -4
- package/src/Processor/ToneMapCPU.js +34 -5
- package/src/Processor/ToneMapGPU.js +15 -9
- package/src/Processor/USD/USDScene.js +981 -17
- package/src/RayzeeRenderer.js +85 -28
- package/src/Stages/AutoExposure.js +21 -8
- package/src/Stages/Compositor.js +39 -1
- package/src/Stages/DenoiserSettings.js +0 -4
- package/src/Stages/LocalExposure.js +44 -18
- package/src/Stages/NRD.js +53 -24
- package/src/Stages/NormalDepth.js +12 -6
- package/src/Stages/PathTracer.js +27 -1
- package/src/Stages/PathTracerStage.js +14 -2
- package/src/Storage/SceneGraphCodec.js +6 -3
- package/src/TSL/BVHTraversal.js +33 -20
- package/src/TSL/CameraRay.js +12 -1
- package/src/TSL/Common.js +25 -8
- package/src/TSL/DebugKernel.js +3 -2
- package/src/TSL/Displacement.js +66 -113
- package/src/TSL/FinalWriteKernel.js +4 -7
- package/src/TSL/GenerateKernel.js +6 -2
- package/src/TSL/LightSplatKernel.js +7 -4
- package/src/TSL/LightsCore.js +6 -2
- package/src/TSL/LightsDirect.js +33 -11
- package/src/TSL/LightsSampling.js +45 -12
- package/src/TSL/SceneResources.js +19 -0
- package/src/TSL/ShadeKernel.js +3 -4
- package/src/TSL/Struct.js +1 -0
- package/src/integrators/BidirectionalIntegrator.js +1 -0
- package/src/managers/CameraManager.js +9 -0
- package/src/managers/DenoisingManager.js +1 -0
- package/src/managers/InteractionManager.js +6 -0
- package/src/managers/MaterialDataManager.js +11 -0
- package/src/managers/UniformManager.js +3 -1
- package/dist/SceneMetadata-D2Sh-0e-.js +0 -110
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
* A layer is an image as pbrt evaluates it: linear values, top row first, `channels` 1 (a float texture reads
|
|
6
6
|
* an image's alpha where it has one, else the mean of its colour) or 3, already scaled and inverted, and the uv
|
|
7
7
|
* mapping pbrt applies before the lookup (st = scale · uv + delta, image row from the top = 1 − t).
|
|
8
|
+
*
|
|
9
|
+
* pbrt's position mappings (`planar`, `spherical`, `cylindrical`) read no uv: their (s, t) becomes a uv set generated per
|
|
10
|
+
* shape (`generateUV`), and a layer in one carries it as `uv` (`{ type, key, … }`; null for the mesh's own uv).
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
const MAX_BAKE_SIZE = 4096;
|
|
11
14
|
|
|
15
|
+
export const uvKey = ( uv ) => uv?.key ?? '';
|
|
16
|
+
|
|
12
17
|
export const srgbToLinear = ( v ) => ( v <= 0.04045 ? v / 12.92 : ( ( v + 0.055 ) / 1.055 ) ** 2.4 );
|
|
13
18
|
export const linearToSRGB = ( v ) => ( v <= 0.0031308 ? v * 12.92 : 1.055 * v ** ( 1 / 2.4 ) - 0.055 );
|
|
14
19
|
|
|
@@ -20,7 +25,7 @@ export const linearToSRGB = ( v ) => ( v <= 0.0031308 ? v * 12.92 : 1.055 * v **
|
|
|
20
25
|
* @param {(v: number) => number} [options.decode] - 8-bit value / 255 → linear; omitted for float data
|
|
21
26
|
* @param {number} [options.scale=1]
|
|
22
27
|
* @param {boolean} [options.invert=false]
|
|
23
|
-
* @param {{ su: number, sv: number, du: number, dv: number }} [options.mapping]
|
|
28
|
+
* @param {{ su: number, sv: number, du: number, dv: number, uv?: object }} [options.mapping]
|
|
24
29
|
*/
|
|
25
30
|
export function makeLayer( pixels, { channels, decode = null, scale = 1, invert = false, mapping = null } ) {
|
|
26
31
|
|
|
@@ -29,7 +34,9 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
|
|
|
29
34
|
const data = new Float32Array( width * height * channels );
|
|
30
35
|
const topDown = pixels.topDown !== false;
|
|
31
36
|
// pbrt's encoding applies to every channel of an 8-bit image, alpha included.
|
|
32
|
-
const
|
|
37
|
+
const bytes = decode && ( pixels.data instanceof Uint8Array || pixels.data instanceof Uint8ClampedArray );
|
|
38
|
+
const lut = bytes ? Float64Array.from( { length: 256 }, ( _, v ) => decode( v / 255 ) ) : null;
|
|
39
|
+
const read = lut ? ( i ) => lut[ pixels.data[ i ] ] : ( i ) => ( decode ? decode( pixels.data[ i ] / 255 ) : pixels.data[ i ] );
|
|
33
40
|
const fromAlpha = channels === 1 && stride === 4 && hasAlpha( pixels );
|
|
34
41
|
const mean = channels === 1 && stride >= 3 && ! fromAlpha;
|
|
35
42
|
|
|
@@ -53,7 +60,7 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
|
|
|
53
60
|
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
return { width, height, channels, data, su: 1, sv: 1, du: 0, dv: 0, ...mapping };
|
|
63
|
+
return { width, height, channels, data, su: 1, sv: 1, du: 0, dv: 0, uv: null, ...mapping };
|
|
57
64
|
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -76,6 +83,15 @@ export function layerMean( layer ) {
|
|
|
76
83
|
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
function layerMeanRGB( { data, channels } ) {
|
|
87
|
+
|
|
88
|
+
const sum = [ 0, 0, 0 ];
|
|
89
|
+
for ( let i = 0; i < data.length; i ++ ) sum[ i % channels ] += data[ i ];
|
|
90
|
+
const texels = data.length / channels || 1;
|
|
91
|
+
return [ 0, 1, 2 ].map( ( c ) => sum[ Math.min( c, channels - 1 ) ] / texels );
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
// Bilinear, repeating, at pbrt's (s, t).
|
|
80
96
|
function sample( layer, s, t, out ) {
|
|
81
97
|
|
|
@@ -102,20 +118,36 @@ function sample( layer, s, t, out ) {
|
|
|
102
118
|
/**
|
|
103
119
|
* Each term is `{ rgb }` (a constant) or `{ layer, tint? }` (an image, times `tint`); `clamp` clamps it to [0, 1]
|
|
104
120
|
* first, as pbrt clamps a colour texture read as an albedo, before anything mixes it. Evaluates
|
|
105
|
-
* `combine( values )` over the texel grid of the largest image among the terms
|
|
106
|
-
* a term with another mapping is sampled through it
|
|
121
|
+
* `combine( values )` over the texel grid of the largest image among the terms in the first image's uv set, in that
|
|
122
|
+
* image's uv mapping; a term with another mapping is sampled through it, and one in another uv set counts as its mean
|
|
123
|
+
* (`averaged` says how many). The result is sRGB bytes laid out for a DataTexture
|
|
107
124
|
* (bottom row first), clamped to [0, 1] as pbrt clamps an albedo. Null when every term is a constant.
|
|
108
125
|
* @param {Array<{ rgb?: number[], layer?: object, tint?: number[] }>} terms
|
|
109
126
|
* @param {(values: number[][]) => number[]} combine - linear RGB from each term's linear RGB
|
|
110
127
|
* @param {object} [options]
|
|
111
128
|
* @param {(values: number[][]) => number} [options.alphaOf] - the alpha channel, linear; opaque without it
|
|
112
129
|
* @param {boolean} [options.linear=false] - linear bytes, for a data map
|
|
113
|
-
* @returns {{ data: Uint8Array, width: number, height: number, mapping: { su: number, sv: number, du: number, dv: number } } | null}
|
|
130
|
+
* @returns {{ data: Uint8Array, width: number, height: number, mapping: { su: number, sv: number, du: number, dv: number, uv: object|null }, averaged: number } | null}
|
|
114
131
|
*/
|
|
115
132
|
export function bake( terms, combine, { alphaOf = null, linear = false } = {} ) {
|
|
116
133
|
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
const set = uvKey( terms.find( ( term ) => term.layer )?.layer.uv );
|
|
135
|
+
let driver = null, averaged = 0;
|
|
136
|
+
terms = terms.map( ( term ) => {
|
|
137
|
+
|
|
138
|
+
if ( ! term.layer ) return term;
|
|
139
|
+
if ( uvKey( term.layer.uv ) !== set ) {
|
|
140
|
+
|
|
141
|
+
averaged ++;
|
|
142
|
+
const mean = layerMeanRGB( term.layer );
|
|
143
|
+
return { rgb: mean.map( ( v, c ) => v * ( term.tint?.[ c ] ?? 1 ) ), clamp: term.clamp };
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if ( ! driver || term.layer.width * term.layer.height > driver.width * driver.height ) driver = term.layer;
|
|
148
|
+
return term;
|
|
149
|
+
|
|
150
|
+
} );
|
|
119
151
|
if ( ! driver ) return null;
|
|
120
152
|
|
|
121
153
|
const width = Math.min( driver.width, MAX_BAKE_SIZE ), height = Math.min( driver.height, MAX_BAKE_SIZE );
|
|
@@ -166,7 +198,7 @@ export function bake( terms, combine, { alphaOf = null, linear = false } = {} )
|
|
|
166
198
|
|
|
167
199
|
}
|
|
168
200
|
|
|
169
|
-
return { data: out, width, height, mapping: { su: driver.su, sv: driver.sv, du: driver.du, dv: driver.dv } };
|
|
201
|
+
return { data: out, width, height, mapping: { su: driver.su, sv: driver.sv, du: driver.du, dv: driver.dv, uv: driver.uv ?? null }, averaged };
|
|
170
202
|
|
|
171
203
|
}
|
|
172
204
|
|
|
@@ -177,3 +209,153 @@ export const mixOf = ( [ a, b, amount ] ) => {
|
|
|
177
209
|
return [ a[ 0 ] + ( b[ 0 ] - a[ 0 ] ) * t, a[ 1 ] + ( b[ 1 ] - a[ 1 ] ) * t, a[ 2 ] + ( b[ 2 ] - a[ 2 ] ) * t ];
|
|
178
210
|
|
|
179
211
|
};
|
|
212
|
+
|
|
213
|
+
// The coordinate that goes once around the axis: spherical φ is t, cylindrical φ is s.
|
|
214
|
+
const WRAPPED = { spherical: 1, cylindrical: 0 };
|
|
215
|
+
|
|
216
|
+
/** pbrt-v4's (s, t) of a point in texture space (textures.h: PlanarMapping, SphericalMapping, CylindricalMapping). */
|
|
217
|
+
export function mapPoint( mapping, x, y, z, out ) {
|
|
218
|
+
|
|
219
|
+
if ( mapping.type === 'planar' ) {
|
|
220
|
+
|
|
221
|
+
const { vs, vt } = mapping;
|
|
222
|
+
out[ 0 ] = mapping.ds + x * vs[ 0 ] + y * vs[ 1 ] + z * vs[ 2 ];
|
|
223
|
+
out[ 1 ] = mapping.dt + x * vt[ 0 ] + y * vt[ 1 ] + z * vt[ 2 ];
|
|
224
|
+
|
|
225
|
+
} else if ( mapping.type === 'spherical' ) {
|
|
226
|
+
|
|
227
|
+
const length = Math.hypot( x, y, z ) || 1;
|
|
228
|
+
const phi = Math.atan2( y, x );
|
|
229
|
+
out[ 0 ] = Math.acos( Math.min( 1, Math.max( - 1, z / length ) ) ) / Math.PI;
|
|
230
|
+
out[ 1 ] = ( phi < 0 ? phi + 2 * Math.PI : phi ) / ( 2 * Math.PI );
|
|
231
|
+
|
|
232
|
+
} else {
|
|
233
|
+
|
|
234
|
+
out[ 0 ] = ( Math.PI + Math.atan2( y, x ) ) / ( 2 * Math.PI );
|
|
235
|
+
out[ 1 ] = z;
|
|
236
|
+
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return out;
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* pbrt's (s, t) at each vertex as a uv set, `toTexture` (column-major) taking `positions` to texture space. A triangle
|
|
245
|
+
* across the seam gets copies of its vertices a turn on (`extra` names their sources, `index` the rewritten triangles,
|
|
246
|
+
* null when none); a vertex on the axis takes its triangle's mean angle.
|
|
247
|
+
* @param {Float32Array} positions
|
|
248
|
+
* @param {ArrayLike<number>|null} index
|
|
249
|
+
* @param {number[]} toTexture
|
|
250
|
+
* @param {{ type: string }} mapping
|
|
251
|
+
* @returns {{ uv: Float32Array, index: Uint32Array|null, extra: Uint32Array }}
|
|
252
|
+
*/
|
|
253
|
+
export function generateUV( positions, index, toTexture, mapping ) {
|
|
254
|
+
|
|
255
|
+
const count = positions.length / 3;
|
|
256
|
+
const m = toTexture;
|
|
257
|
+
const k = WRAPPED[ mapping.type ];
|
|
258
|
+
const st = new Float64Array( count * 2 );
|
|
259
|
+
const radius = k === undefined ? null : new Float64Array( count );
|
|
260
|
+
const out = [ 0, 0 ];
|
|
261
|
+
let maxRadius = 0;
|
|
262
|
+
|
|
263
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
264
|
+
|
|
265
|
+
const px = positions[ i * 3 ], py = positions[ i * 3 + 1 ], pz = positions[ i * 3 + 2 ];
|
|
266
|
+
const x = m[ 0 ] * px + m[ 4 ] * py + m[ 8 ] * pz + m[ 12 ];
|
|
267
|
+
const y = m[ 1 ] * px + m[ 5 ] * py + m[ 9 ] * pz + m[ 13 ];
|
|
268
|
+
const z = m[ 2 ] * px + m[ 6 ] * py + m[ 10 ] * pz + m[ 14 ];
|
|
269
|
+
mapPoint( mapping, x, y, z, out );
|
|
270
|
+
st[ i * 2 ] = out[ 0 ];
|
|
271
|
+
st[ i * 2 + 1 ] = out[ 1 ];
|
|
272
|
+
if ( ! radius ) continue;
|
|
273
|
+
radius[ i ] = Math.hypot( x, y );
|
|
274
|
+
if ( radius[ i ] > maxRadius ) maxRadius = radius[ i ];
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const extra = [];
|
|
279
|
+
let triangles = index;
|
|
280
|
+
|
|
281
|
+
if ( radius ) {
|
|
282
|
+
|
|
283
|
+
const onAxis = maxRadius * 1e-6;
|
|
284
|
+
const copies = new Map();
|
|
285
|
+
const corner = [ 0, 0, 0 ], value = [ 0, 0, 0 ];
|
|
286
|
+
const triangleCount = ( index ? index.length : count ) / 3;
|
|
287
|
+
|
|
288
|
+
for ( let t = 0; t < triangleCount; t ++ ) {
|
|
289
|
+
|
|
290
|
+
let lo = Infinity, hi = - Infinity, sum = 0, around = 0;
|
|
291
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
292
|
+
|
|
293
|
+
const v = corner[ c ] = index ? index[ t * 3 + c ] : t * 3 + c;
|
|
294
|
+
if ( radius[ v ] <= onAxis ) continue;
|
|
295
|
+
lo = Math.min( lo, st[ v * 2 + k ] );
|
|
296
|
+
hi = Math.max( hi, st[ v * 2 + k ] );
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const across = hi - lo > 0.5;
|
|
301
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
302
|
+
|
|
303
|
+
const w = st[ corner[ c ] * 2 + k ];
|
|
304
|
+
if ( radius[ corner[ c ] ] <= onAxis ) value[ c ] = NaN;
|
|
305
|
+
else {
|
|
306
|
+
|
|
307
|
+
value[ c ] = across && w < 0.5 ? w + 1 : w;
|
|
308
|
+
sum += value[ c ];
|
|
309
|
+
around ++;
|
|
310
|
+
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
316
|
+
|
|
317
|
+
const v = corner[ c ];
|
|
318
|
+
const w = Number.isNaN( value[ c ] ) ? ( around > 0 ? sum / around : st[ v * 2 + k ] ) : value[ c ];
|
|
319
|
+
if ( w === st[ v * 2 + k ] ) continue;
|
|
320
|
+
|
|
321
|
+
if ( ! index ) {
|
|
322
|
+
|
|
323
|
+
st[ v * 2 + k ] = w;
|
|
324
|
+
continue;
|
|
325
|
+
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const key = `${v}|${w}`;
|
|
329
|
+
let copy = copies.get( key );
|
|
330
|
+
if ( copy === undefined ) {
|
|
331
|
+
|
|
332
|
+
copies.set( key, copy = count + extra.length / 2 );
|
|
333
|
+
extra.push( v, w );
|
|
334
|
+
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if ( triangles === index ) triangles = Uint32Array.from( index );
|
|
338
|
+
triangles[ t * 3 + c ] = copy;
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const added = extra.length / 2;
|
|
347
|
+
const uv = new Float32Array( ( count + added ) * 2 );
|
|
348
|
+
for ( let i = 0; i < count * 2; i ++ ) uv[ i ] = st[ i ];
|
|
349
|
+
const sources = new Uint32Array( added );
|
|
350
|
+
for ( let i = 0; i < added; i ++ ) {
|
|
351
|
+
|
|
352
|
+
const v = sources[ i ] = extra[ i * 2 ];
|
|
353
|
+
uv[ ( count + i ) * 2 ] = st[ v * 2 ];
|
|
354
|
+
uv[ ( count + i ) * 2 + 1 ] = st[ v * 2 + 1 ];
|
|
355
|
+
uv[ ( count + i ) * 2 + k ] = extra[ i * 2 + 1 ];
|
|
356
|
+
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { uv, index: triangles === index ? null : triangles, extra: sources };
|
|
360
|
+
|
|
361
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { freeNow } from './buffers.js';
|
|
18
|
-
import { PBRTParser } from './PBRTParser.js';
|
|
18
|
+
import { PBRTParser, ParamUsage } from './PBRTParser.js';
|
|
19
19
|
import { PBRTSceneBuilder } from './PBRTSceneBuilder.js';
|
|
20
20
|
import { findFrameSequence, FrameSequenceMerger, motionFromShutter, SEQUENCE_FPS } from './PBRTAnimation.js';
|
|
21
21
|
|
|
@@ -380,7 +380,8 @@ export async function listEntryPathsFrom( vfs ) {
|
|
|
380
380
|
* @param {boolean} [args.convertHandedness=true]
|
|
381
381
|
* @param {boolean} [args.animation=true] - load a frame sequence (`frame25.pbrt`, `frame35.pbrt`, …)
|
|
382
382
|
* as one animated scene instead of its first frame alone. Ignored when `entryPath` names a file.
|
|
383
|
-
* @returns {Promise<{group, camera, environment, animations, warnings, entryPath, frames}>}
|
|
383
|
+
* @returns {Promise<{group, camera, environment, animations, warnings, entryPath, frames, parameters}>} - `parameters`: what
|
|
384
|
+
* ParamUsage.finish() reports, the parameters this load ignored or approximated
|
|
384
385
|
*/
|
|
385
386
|
export async function loadPBRTScene( args ) {
|
|
386
387
|
|
|
@@ -401,6 +402,7 @@ export async function loadPBRTScene( args ) {
|
|
|
401
402
|
}
|
|
402
403
|
|
|
403
404
|
const entryPath = requested || candidates[ 0 ];
|
|
405
|
+
const usage = new ParamUsage();
|
|
404
406
|
|
|
405
407
|
// Parse (with Include resolution). Bytes go straight to the lexer — a scene file can
|
|
406
408
|
// be larger than the longest string JavaScript will build.
|
|
@@ -415,7 +417,8 @@ export async function loadPBRTScene( args ) {
|
|
|
415
417
|
// scene file at once — the difference between 5.7 GB resident and a few MB.
|
|
416
418
|
releaseInclude: ( include, currentDir, bytes ) => vfs.releasePath( include, currentDir, bytes ),
|
|
417
419
|
maxPlacements: args.maxPlacements,
|
|
418
|
-
instanceIncludes: args.instanceIncludes
|
|
420
|
+
instanceIncludes: args.instanceIncludes,
|
|
421
|
+
usage
|
|
419
422
|
} );
|
|
420
423
|
|
|
421
424
|
const ir = await parser.parse( bytes, path.includes( '/' ) ? path.slice( 0, path.lastIndexOf( '/' ) ) : '' );
|
|
@@ -490,6 +493,7 @@ export async function loadPBRTScene( args ) {
|
|
|
490
493
|
curveSteps: args.curveSteps,
|
|
491
494
|
curveSides: args.curveSides,
|
|
492
495
|
curveTolerance: args.curveTolerance,
|
|
496
|
+
usage,
|
|
493
497
|
resolvePLY: async ( filename ) => {
|
|
494
498
|
|
|
495
499
|
const rec = vfs.findRecord( filename );
|
|
@@ -515,9 +519,20 @@ export async function loadPBRTScene( args ) {
|
|
|
515
519
|
} );
|
|
516
520
|
|
|
517
521
|
const buildStart = performance.now();
|
|
518
|
-
|
|
522
|
+
let result, parameters;
|
|
523
|
+
try {
|
|
524
|
+
|
|
525
|
+
result = await builder.build( ir );
|
|
526
|
+
|
|
527
|
+
} finally {
|
|
528
|
+
|
|
529
|
+
parameters = usage.finish();
|
|
530
|
+
|
|
531
|
+
}
|
|
532
|
+
|
|
519
533
|
return {
|
|
520
534
|
...result,
|
|
535
|
+
parameters,
|
|
521
536
|
entryPath: sequence ? sequence[ 0 ].path : entryPath,
|
|
522
537
|
frames: sequence ? sequence.map( f => f.path ) : null,
|
|
523
538
|
candidates,
|
|
@@ -97,6 +97,15 @@ export async function loadPlatformImage( url, mimeType = mimeTypeOf( { uri: url
|
|
|
97
97
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/** An image file's bytes as a texture, decoded by the platform: {@link loadPlatformImage} without the fetch. */
|
|
101
|
+
export async function platformImageFromBytes( bytes, filename ) {
|
|
102
|
+
|
|
103
|
+
const texture = pixelTexture( await decodeBytes( bytes, mimeTypeOf( { uri: filename } ) ), { shared: true } );
|
|
104
|
+
texture.flipY = true;
|
|
105
|
+
return texture;
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
100
109
|
/**
|
|
101
110
|
* A GLTFLoader plugin for a runtime with no way to decode an image (Node without `decodeImage`): each image fails with
|
|
102
111
|
* what to configure, where three.js's own path threw `self is not defined` and took the whole load down.
|
|
@@ -63,6 +63,14 @@ const GEOMETRY_QUEUE_BYTES = 256 * 1024 * 1024;
|
|
|
63
63
|
|
|
64
64
|
const log = createLogger( 'scene' );
|
|
65
65
|
|
|
66
|
+
// The per-type texture lists an extraction hands over (GeometryExtractor.getExtractedData).
|
|
67
|
+
const TEXTURE_LISTS = Object.freeze( [
|
|
68
|
+
'maps', 'normalMaps', 'bumpMaps', 'roughnessMaps', 'metalnessMaps', 'emissiveMaps', 'displacementMaps',
|
|
69
|
+
'anisotropyMaps', 'transmissionMaps', 'clearcoatMaps', 'clearcoatRoughnessMaps', 'sheenColorMaps',
|
|
70
|
+
'sheenRoughnessMaps', 'iridescenceMaps', 'iridescenceThicknessMaps', 'specularIntensityMaps', 'specularColorMaps',
|
|
71
|
+
'diffuseTransmissionMaps', 'diffuseTransmissionColorMaps',
|
|
72
|
+
] );
|
|
73
|
+
|
|
66
74
|
// A BLAS's nodes: one array, or a split mesh's parts in order — never one array that large.
|
|
67
75
|
const nodeParts = nodes => ( Array.isArray( nodes ) ? nodes : [ nodes ] );
|
|
68
76
|
const nodeBytes = nodes => nodeParts( nodes ).reduce( ( sum, part ) => sum + part.byteLength, 0 );
|
|
@@ -913,25 +921,7 @@ export class SceneProcessor {
|
|
|
913
921
|
this.materialTriangleCounts = extractedData.materialTriangleCounts; // Per-material tri count for sort-bin remap
|
|
914
922
|
this.meshes = extractedData.meshes;
|
|
915
923
|
this.meshTriangleRanges = extractedData.meshTriangleRanges; // Per-mesh { start, count } for TLAS/BLAS
|
|
916
|
-
this.
|
|
917
|
-
this.normalMaps = extractedData.normalMaps;
|
|
918
|
-
this.bumpMaps = extractedData.bumpMaps;
|
|
919
|
-
this.roughnessMaps = extractedData.roughnessMaps;
|
|
920
|
-
this.metalnessMaps = extractedData.metalnessMaps;
|
|
921
|
-
this.emissiveMaps = extractedData.emissiveMaps;
|
|
922
|
-
this.displacementMaps = extractedData.displacementMaps;
|
|
923
|
-
this.anisotropyMaps = extractedData.anisotropyMaps;
|
|
924
|
-
this.transmissionMaps = extractedData.transmissionMaps;
|
|
925
|
-
this.clearcoatMaps = extractedData.clearcoatMaps;
|
|
926
|
-
this.clearcoatRoughnessMaps = extractedData.clearcoatRoughnessMaps;
|
|
927
|
-
this.sheenColorMaps = extractedData.sheenColorMaps;
|
|
928
|
-
this.sheenRoughnessMaps = extractedData.sheenRoughnessMaps;
|
|
929
|
-
this.iridescenceMaps = extractedData.iridescenceMaps;
|
|
930
|
-
this.iridescenceThicknessMaps = extractedData.iridescenceThicknessMaps;
|
|
931
|
-
this.specularIntensityMaps = extractedData.specularIntensityMaps;
|
|
932
|
-
this.specularColorMaps = extractedData.specularColorMaps;
|
|
933
|
-
this.diffuseTransmissionMaps = extractedData.diffuseTransmissionMaps ?? [];
|
|
934
|
-
this.diffuseTransmissionColorMaps = extractedData.diffuseTransmissionColorMaps ?? [];
|
|
924
|
+
this._adoptTextureLists( extractedData );
|
|
935
925
|
this.directionalLights = extractedData.directionalLights;
|
|
936
926
|
this.cameras = extractedData.cameras;
|
|
937
927
|
|
|
@@ -2453,13 +2443,7 @@ export class SceneProcessor {
|
|
|
2453
2443
|
// Update material arrays (but keep existing triangle data)
|
|
2454
2444
|
this.materials = extractedData.materials;
|
|
2455
2445
|
this.meshes = extractedData.meshes; // Update mesh data
|
|
2456
|
-
this.
|
|
2457
|
-
this.normalMaps = extractedData.normalMaps;
|
|
2458
|
-
this.bumpMaps = extractedData.bumpMaps;
|
|
2459
|
-
this.roughnessMaps = extractedData.roughnessMaps;
|
|
2460
|
-
this.metalnessMaps = extractedData.metalnessMaps;
|
|
2461
|
-
this.emissiveMaps = extractedData.emissiveMaps;
|
|
2462
|
-
this.displacementMaps = extractedData.displacementMaps;
|
|
2446
|
+
this._adoptTextureLists( extractedData );
|
|
2463
2447
|
|
|
2464
2448
|
// Bucket textures, remap material indices, regenerate raw material data, and
|
|
2465
2449
|
// build the consolidated bucket arrays — same path as the initial build.
|
|
@@ -2487,6 +2471,13 @@ export class SceneProcessor {
|
|
|
2487
2471
|
|
|
2488
2472
|
}
|
|
2489
2473
|
|
|
2474
|
+
/** @private The extraction's material indices point into these lists, so every one is taken from it. */
|
|
2475
|
+
_adoptTextureLists( extractedData ) {
|
|
2476
|
+
|
|
2477
|
+
for ( const name of TEXTURE_LISTS ) this[ name ] = extractedData[ name ] ?? [];
|
|
2478
|
+
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2490
2481
|
/**
|
|
2491
2482
|
* Dispose only material-related textures
|
|
2492
2483
|
* @private
|
|
@@ -2726,6 +2717,7 @@ export class SceneProcessor {
|
|
|
2726
2717
|
const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
2727
2718
|
const triRanges = [];
|
|
2728
2719
|
const blasRanges = [];
|
|
2720
|
+
const refit = [];
|
|
2729
2721
|
let owners = 0;
|
|
2730
2722
|
for ( let i = 0; i < table.count; i ++ ) {
|
|
2731
2723
|
|
|
@@ -2736,6 +2728,7 @@ export class SceneProcessor {
|
|
|
2736
2728
|
if ( ! p ) continue;
|
|
2737
2729
|
this._updateMeshTrianglePositions( i, p );
|
|
2738
2730
|
blasRanges.push( table.blasOffsetOf( i ), table.blasNodeCountOf( i ) );
|
|
2731
|
+
refit.push( i );
|
|
2739
2732
|
|
|
2740
2733
|
// Smooth normals overwrite the face normals just computed, so they follow per mesh.
|
|
2741
2734
|
if ( normalsFor ) {
|
|
@@ -2764,6 +2757,12 @@ export class SceneProcessor {
|
|
|
2764
2757
|
const msg = e.data;
|
|
2765
2758
|
if ( msg.type === 'refitComplete' ) {
|
|
2766
2759
|
|
|
2760
|
+
// Later moves and main-thread refits box placements by these.
|
|
2761
|
+
for ( const i of refit ) table.recomputeAABB( i, this.bvh, this.triangles );
|
|
2762
|
+
table.computeGroupAABBs();
|
|
2763
|
+
// The worker boxes a group copy by its shown members only.
|
|
2764
|
+
const leaves = this._hiddenGroupLeaves();
|
|
2765
|
+
if ( leaves.length ) this._refitTLAS( leaves );
|
|
2767
2766
|
resolve( { refitTimeMs: msg.refitTimeMs, triRanges, bvhRanges } );
|
|
2768
2767
|
|
|
2769
2768
|
} else if ( msg.type === 'error' ) {
|
|
@@ -2979,6 +2978,22 @@ export class SceneProcessor {
|
|
|
2979
2978
|
|
|
2980
2979
|
}
|
|
2981
2980
|
|
|
2981
|
+
/** The TLAS leaves of every copy of a group with a hidden member. @private */
|
|
2982
|
+
_hiddenGroupLeaves() {
|
|
2983
|
+
|
|
2984
|
+
const table = this.instanceTable;
|
|
2985
|
+
const leaves = [];
|
|
2986
|
+
for ( const g of table.groups ) {
|
|
2987
|
+
|
|
2988
|
+
if ( g.members.every( ( _, j ) => table.visible[ g.starts[ j ] ] ) ) continue;
|
|
2989
|
+
for ( let c = 0; c < g.count; c ++ ) leaves.push( table.leafOfPlacement( g.starts[ 0 ] + c ) );
|
|
2990
|
+
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
return leaves;
|
|
2994
|
+
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2982
2997
|
/**
|
|
2983
2998
|
* A grouped mesh's parts are traced as one object, so a part moved alone takes its siblings with it: each sibling
|
|
2984
2999
|
* is given the first moved member's world transform, and all of them are moved.
|
|
@@ -4276,6 +4291,7 @@ export class SceneProcessor {
|
|
|
4276
4291
|
// scene's 4), producing a tree of a different size that the swap below rejects —
|
|
4277
4292
|
// so every rebuild was thrown away.
|
|
4278
4293
|
maxLeafSize: this.bvhBuilder.maxLeafSize,
|
|
4294
|
+
foldLeaves: this._foldLeaves === true,
|
|
4279
4295
|
numBins: this.bvhBuilder.numBins,
|
|
4280
4296
|
maxBins: this.bvhBuilder.maxBins,
|
|
4281
4297
|
minBins: this.bvhBuilder.minBins,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping, FloatType, HalfFloatType } from "three";
|
|
1
|
+
import { DataArrayTexture, RGBAFormat, RGFormat, RedFormat, LinearFilter, UnsignedByteType, UnsignedShortType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping, FloatType, HalfFloatType, DataUtils } from "three";
|
|
2
2
|
import { MATERIAL_DATA_LAYOUT, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from './BufferLayout.js';
|
|
3
3
|
import { alignBucketWidth, TEXTURE_CONSTANTS } from './TextureBuckets.js';
|
|
4
4
|
import { packMaterial } from './MaterialPacking.js';
|
|
@@ -1260,7 +1260,7 @@ export class TextureCreator {
|
|
|
1260
1260
|
|
|
1261
1261
|
const image = texture?.image;
|
|
1262
1262
|
const pixels = texture?.isCompressedTexture ? texture.mipmaps?.[ 0 ] : image;
|
|
1263
|
-
const data = pixels?.data;
|
|
1263
|
+
const data = texture?.isCompressedTexture ? pixels?.data : rgba8Pixels( texture );
|
|
1264
1264
|
if ( data && pixels.width > 0 && pixels.height > 0 && data.byteLength >= pixels.width * pixels.height * 4 ) {
|
|
1265
1265
|
|
|
1266
1266
|
return { data: new Uint8Array( data.buffer, data.byteOffset, pixels.width * pixels.height * 4 ), width: pixels.width, height: pixels.height, flipY: texture.flipY !== false };
|
|
@@ -1817,7 +1817,8 @@ export class TextureCreator {
|
|
|
1817
1817
|
|
|
1818
1818
|
const idx = normalized.length;
|
|
1819
1819
|
normalized.push( null );
|
|
1820
|
-
|
|
1820
|
+
const pixels = rgba8Pixels( tex );
|
|
1821
|
+
bitmapJobs.push( { index: idx, flipY: tex.flipY, promise: pixels ? _rawPixelsToBitmap( pixels, tex.image.width, tex.image.height ) : Promise.reject( new Error( 'pixels in a layout the texture arrays cannot take' ) ) } );
|
|
1821
1822
|
continue;
|
|
1822
1823
|
|
|
1823
1824
|
}
|
|
@@ -1900,10 +1901,49 @@ export class TextureCreator {
|
|
|
1900
1901
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
1901
1902
|
|
|
1902
1903
|
/** Convert raw RGBA pixel data to an ImageBitmap (zero-copy Uint8ClampedArray view). */
|
|
1903
|
-
|
|
1904
|
+
// Async, so a bad layer rejects into its placeholder instead of throwing through the whole bucket.
|
|
1905
|
+
async function _rawPixelsToBitmap( data, width, height ) {
|
|
1904
1906
|
|
|
1905
1907
|
const clamped = new Uint8ClampedArray( data.buffer, data.byteOffset, data.byteLength );
|
|
1906
1908
|
return createImageBitmap( new ImageData( clamped, width, height ) );
|
|
1907
1909
|
|
|
1908
1910
|
}
|
|
1909
1911
|
|
|
1912
|
+
/**
|
|
1913
|
+
* A data texture's pixels as RGBA8 (the arrays are 8-bit), or null. Float and half-float maps — an archive's EXR, HDR or
|
|
1914
|
+
* PFM — clamp to [0, 1]; one channel fills grey. Read as bytes, a float buffer was noise in Node and failed the bucket in
|
|
1915
|
+
* the browser.
|
|
1916
|
+
*/
|
|
1917
|
+
export function rgba8Pixels( texture ) {
|
|
1918
|
+
|
|
1919
|
+
const image = texture?.image;
|
|
1920
|
+
const data = image?.data;
|
|
1921
|
+
if ( ! data || ! ( image.width > 0 ) || ! ( image.height > 0 ) ) return null;
|
|
1922
|
+
const count = image.width * image.height;
|
|
1923
|
+
if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) {
|
|
1924
|
+
|
|
1925
|
+
return data.byteLength >= count * 4 ? new Uint8Array( data.buffer, data.byteOffset, count * 4 ) : null;
|
|
1926
|
+
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
const channels = { [ RGBAFormat ]: 4, [ RGFormat ]: 2, [ RedFormat ]: 1 }[ texture.format ] ?? Math.round( data.length / count );
|
|
1930
|
+
if ( ! ( channels >= 1 && channels <= 4 ) || data.length < count * channels ) return null;
|
|
1931
|
+
const half = texture.type === HalfFloatType;
|
|
1932
|
+
const scale = texture.type === UnsignedShortType ? 1 / 65535 : 1;
|
|
1933
|
+
const out = new Uint8Array( count * 4 );
|
|
1934
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
1935
|
+
|
|
1936
|
+
for ( let c = 0; c < 4; c ++ ) {
|
|
1937
|
+
|
|
1938
|
+
const raw = c < channels ? data[ i * channels + c ] : null;
|
|
1939
|
+
const v = raw === null ? ( c === 3 ? 1 : out[ i * 4 ] / 255 ) : ( half ? DataUtils.fromHalfFloat( raw ) : raw * scale );
|
|
1940
|
+
out[ i * 4 + c ] = Math.max( 0, Math.min( 255, Math.round( v * 255 ) ) );
|
|
1941
|
+
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
return out;
|
|
1947
|
+
|
|
1948
|
+
}
|
|
1949
|
+
|
|
@@ -98,6 +98,26 @@ export function effectiveExposure( exposure, toneMapping ) {
|
|
|
98
98
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The gain that shows a pixel of a solid backdrop at `heldAt` while the picture is shown at `exposure`: auto
|
|
103
|
+
* exposure does not move the backdrop. 1 under a view that applies no exposure.
|
|
104
|
+
*/
|
|
105
|
+
export function backdropGain( heldAt, exposure, toneMapping ) {
|
|
106
|
+
|
|
107
|
+
return effectiveExposure( heldAt, toneMapping ) / effectiveExposure( exposure, toneMapping );
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A pixel's gain once the backdrop is held: `backdrop` where the camera saw only the backdrop (coverage 0), the
|
|
113
|
+
* scene's own gain where it saw the scene, blended by coverage between. The GPU pass computes the same.
|
|
114
|
+
*/
|
|
115
|
+
export function heldGain( gain, backdrop, coverage ) {
|
|
116
|
+
|
|
117
|
+
return gain + ( backdrop - gain ) * ( 1 - Math.min( Math.max( coverage, 0 ), 1 ) );
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
/** Rec.709 luminance coefficients (same as Display / Common.js). */
|
|
102
122
|
const LUM_R = 0.2126, LUM_G = 0.7152, LUM_B = 0.0722;
|
|
103
123
|
|
|
@@ -128,8 +148,9 @@ export function applySaturation( out, saturation ) {
|
|
|
128
148
|
* @param {number} options.toneMapping - a registered view transform id
|
|
129
149
|
* @param {number} [options.saturation=1]
|
|
130
150
|
* @param {boolean} [options.preserveAlpha=false]
|
|
131
|
-
* @param {{fn
|
|
132
|
-
* the twin of the GPU pass's `gain`: `fn( u, v, luminance, log2 exposure )` per pixel, before the curve
|
|
151
|
+
* @param {{fn?: function(number, number, number, number): number, width: number, height: number, backdrop?: number}} [options.pixelGain] -
|
|
152
|
+
* the twin of the GPU pass's `gain`: `fn( u, v, luminance, log2 exposure )` per pixel, before the curve, and
|
|
153
|
+
* `backdrop` the gain a solid backdrop is held at (`heldGain`)
|
|
133
154
|
* @returns {Uint8ClampedArray} RGBA bytes
|
|
134
155
|
*/
|
|
135
156
|
export function toneMapToRGBA8( linear, { exposure, toneMapping, saturation = 1, preserveAlpha = false, pixelGain = null } ) {
|
|
@@ -146,9 +167,17 @@ export function toneMapToRGBA8( linear, { exposure, toneMapping, saturation = 1,
|
|
|
146
167
|
let g = gain;
|
|
147
168
|
if ( pixelGain ) {
|
|
148
169
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
170
|
+
let pg = 1;
|
|
171
|
+
if ( pixelGain.fn ) {
|
|
172
|
+
|
|
173
|
+
const p = i >> 2, x = p % pixelGain.width, y = ( p - x ) / pixelGain.width;
|
|
174
|
+
const lum = linear[ i ] * 0.2126 + linear[ i + 1 ] * 0.7152 + linear[ i + 2 ] * 0.0722;
|
|
175
|
+
pg = pixelGain.fn( ( x + 0.5 ) / pixelGain.width, ( y + 0.5 ) / pixelGain.height, lum, logExposure );
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if ( pixelGain.backdrop > 0 ) pg = heldGain( pg, pixelGain.backdrop, linear[ i + 3 ] );
|
|
180
|
+
g *= pg;
|
|
152
181
|
|
|
153
182
|
}
|
|
154
183
|
|