rayzee 11.0.0 → 11.1.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/dist/{ArchiveImporter-BRQvLggb.js → ArchiveImporter-Hn35o6Z5.js} +1525 -680
- package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -0
- package/dist/{NeuralRetouch-BCXylThH.js → NeuralRetouch-BzNpiVHZ.js} +1 -1
- package/dist/{NeuralSuperRes-sZAeS142.js → NeuralSuperRes-Bk7bHg2B.js} +1 -1
- package/dist/{PathTracer-CQPW06Mm.js → PathTracer-72XufKAr.js} +79 -74
- package/dist/{PhysicalSky-BvqCw_dy.js → PhysicalSky-BgjdV88k.js} +1 -1
- package/dist/{RayzeeRenderer-COpwotpU.js → RayzeeRenderer-1QRO97eq.js} +3 -3
- 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/{archiveFormats-BZw7TiDp.js → archiveFormats-tnAoUL1j.js} +28 -9
- package/dist/rayzee-core.es.js +4 -4
- package/dist/rayzee.es.js +11 -11
- package/dist/rayzee.umd.js +43 -43
- package/dist/{sceneCachePolicy-BSSsKY98.js → sceneCachePolicy-C67jbci-.js} +1 -1
- package/package.json +1 -1
- package/src/Processor/ArchiveCache.js +19 -6
- package/src/Processor/ArchiveImporter.js +9 -3
- package/src/Processor/ArchiveReader.js +22 -4
- package/src/Processor/PBRT/PBRTMaterials.js +132 -8
- package/src/Processor/PBRT/PBRTParser.js +20 -3
- package/src/Processor/PBRT/PBRTScattering.js +93 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +513 -49
- package/src/Processor/PBRT/PBRTTextureBake.js +32 -7
- package/src/Processor/SceneMetadata.js +30 -5
- package/src/RayzeeRenderer.js +3 -2
- package/src/Stages/PathTracer.js +3 -5
- package/src/TSL/Bidirectional.js +6 -2
- package/src/TSL/ConnectKernel.js +31 -11
- package/src/TSL/LightSplatKernel.js +22 -7
- package/src/TSL/MergeKernel.js +13 -7
- package/src/TSL/ShadeKernel.js +51 -30
- package/src/integrators/BidirectionalIntegrator.js +6 -1
- package/dist/BidirectionalIntegrator-9lZQxFVH.js +0 -934
|
@@ -17,14 +17,14 @@
|
|
|
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, 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,
|
|
24
24
|
RepeatWrapping
|
|
25
25
|
} from 'three';
|
|
26
26
|
import { buildMaterial, pBool, pFloat, pString, resolveSpectrum } from './PBRTMaterials.js';
|
|
27
|
-
import { makeLayer, layerMean, bake, mixOf, srgbToLinear, linearToSRGB } from './PBRTTextureBake.js';
|
|
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
30
|
import { tessellateCurve } from './PBRTCurves.js';
|
|
@@ -107,7 +107,7 @@ const DEFAULT_CURVE_SIDES = { flat: 1, ribbon: 1, cylinder: 2 };
|
|
|
107
107
|
const DEFAULT_CURVE_TOLERANCE = 0.05;
|
|
108
108
|
|
|
109
109
|
/** Bumped whenever the same scene files build a different graph, so a stored graph is not reused. */
|
|
110
|
-
export const PBRT_BUILD_REVISION =
|
|
110
|
+
export const PBRT_BUILD_REVISION = 9;
|
|
111
111
|
|
|
112
112
|
function samePlacements( a, b ) {
|
|
113
113
|
|
|
@@ -244,11 +244,11 @@ function eightBit( texture, { srgb } ) {
|
|
|
244
244
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
// A baked image as a texture:
|
|
248
|
-
function bakedTexture( { data, width, height, mapping } ) {
|
|
247
|
+
// A baked image as a texture: bytes bottom row first, sRGB unless a data map, in the uv mapping it was baked in.
|
|
248
|
+
function bakedTexture( { data, width, height, mapping }, { srgb = true } = {} ) {
|
|
249
249
|
|
|
250
250
|
const texture = new DataTexture( data, width, height, RGBAFormat );
|
|
251
|
-
texture.colorSpace = SRGBColorSpace;
|
|
251
|
+
texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;
|
|
252
252
|
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
253
253
|
setMapping( texture, mapping );
|
|
254
254
|
texture.needsUpdate = true;
|
|
@@ -256,6 +256,248 @@ function bakedTexture( { data, width, height, mapping } ) {
|
|
|
256
256
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
/**
|
|
260
|
+
* pbrt's thin lens as the camera's own effects (CameraManager applies them when the camera is chosen): the same
|
|
261
|
+
* aperture radius in either DOF mode — `dofBlur` for 'look', a full-frame lens and its f-number for 'physical' at one
|
|
262
|
+
* scene unit a metre — focused by hand at pbrt's distance.
|
|
263
|
+
*/
|
|
264
|
+
function lensEffects( camera, radius, focusDistance ) {
|
|
265
|
+
|
|
266
|
+
const ortho = camera.isOrthographicCamera === true;
|
|
267
|
+
const tanHalf = ortho ? 1 : Math.tan( camera.fov * Math.PI / 360 );
|
|
268
|
+
const focalLength = ortho ? 50 : 12 / tanHalf;
|
|
269
|
+
return {
|
|
270
|
+
enableDOF: true,
|
|
271
|
+
focusDistance,
|
|
272
|
+
aperture: focalLength * 0.001 / ( 2 * radius ),
|
|
273
|
+
focalLength,
|
|
274
|
+
apertureScale: 1,
|
|
275
|
+
anamorphicRatio: 1,
|
|
276
|
+
dofBlur: radius / ( focusDistance * tanHalf ),
|
|
277
|
+
autoFocusMode: 'manual',
|
|
278
|
+
afScreenPoint: { x: 0.5, y: 0.5 },
|
|
279
|
+
orthoHeight: ortho ? ( camera.top - camera.bottom ) / camera.zoom : null,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// What the scene asks the renderer for, where it says: the Integrator's maxdepth, the Sampler's pixelsamples, the Film's
|
|
285
|
+
// resolution — scene metadata's `render`.
|
|
286
|
+
function renderRequest( ir ) {
|
|
287
|
+
|
|
288
|
+
const render = {};
|
|
289
|
+
if ( ir.integrator?.maxdepth > 0 ) render.maxBounces = Math.round( ir.integrator.maxdepth );
|
|
290
|
+
if ( ir.sampler?.pixelsamples > 0 ) render.samples = Math.round( ir.sampler.pixelsamples );
|
|
291
|
+
if ( ir.film?.resolutionGiven ) {
|
|
292
|
+
|
|
293
|
+
render.width = ir.film.xresolution;
|
|
294
|
+
render.height = ir.film.yresolution;
|
|
295
|
+
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Object.keys( render ).length ? render : null;
|
|
299
|
+
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const QUADRIC_SEGMENTS = 48; // around a whole turn
|
|
303
|
+
|
|
304
|
+
// pbrt's quadric parameters, clamped as pbrt clamps them (shapes.h).
|
|
305
|
+
function sphereParams( params ) {
|
|
306
|
+
|
|
307
|
+
const radius = pFloat( params, 'radius', 1 );
|
|
308
|
+
const z0 = pFloat( params, 'zmin', - radius ), z1 = pFloat( params, 'zmax', radius );
|
|
309
|
+
const clamp = ( z ) => Math.min( radius, Math.max( - radius, z ) );
|
|
310
|
+
return { radius, zMin: clamp( Math.min( z0, z1 ) ), zMax: clamp( Math.max( z0, z1 ) ), phiMax: phiMaxOf( params ) };
|
|
311
|
+
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function cylinderParams( params ) {
|
|
315
|
+
|
|
316
|
+
const z0 = pFloat( params, 'zmin', - 1 ), z1 = pFloat( params, 'zmax', 1 );
|
|
317
|
+
return { radius: pFloat( params, 'radius', 1 ), zMin: Math.min( z0, z1 ), zMax: Math.max( z0, z1 ), phiMax: phiMaxOf( params ) };
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function diskParams( params ) {
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
radius: pFloat( params, 'radius', 1 ), inner: pFloat( params, 'innerradius', 0 ),
|
|
325
|
+
height: pFloat( params, 'height', 0 ), phiMax: phiMaxOf( params )
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const phiMaxOf = ( params ) => Math.min( 360, Math.max( 0, pFloat( params, 'phimax', 360 ) ) ) * Math.PI / 180;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* A quadric as pbrt parameterises it: `at( u, v, p, n )` over [0, 1]², u around the z axis to `phiMax`; the uv is
|
|
334
|
+
* pbrt's own. Triangles face ∂p/∂u × ∂p/∂v, pbrt's normal; a cell edge of no length (a pole, a centre) drops its
|
|
335
|
+
* triangle.
|
|
336
|
+
*/
|
|
337
|
+
function quadric( phiMax, vSegments, at ) {
|
|
338
|
+
|
|
339
|
+
const uSegments = Math.max( 3, Math.ceil( QUADRIC_SEGMENTS * phiMax / ( 2 * Math.PI ) ) );
|
|
340
|
+
const row = uSegments + 1;
|
|
341
|
+
const position = new Float32Array( row * ( vSegments + 1 ) * 3 );
|
|
342
|
+
const normal = new Float32Array( position.length );
|
|
343
|
+
const uv = new Float32Array( row * ( vSegments + 1 ) * 2 );
|
|
344
|
+
const p = new Vector3(), n = new Vector3();
|
|
345
|
+
|
|
346
|
+
for ( let j = 0; j <= vSegments; j ++ ) for ( let i = 0; i <= uSegments; i ++ ) {
|
|
347
|
+
|
|
348
|
+
const k = j * row + i;
|
|
349
|
+
at( i / uSegments, j / vSegments, p, n );
|
|
350
|
+
p.toArray( position, k * 3 );
|
|
351
|
+
n.toArray( normal, k * 3 );
|
|
352
|
+
uv[ k * 2 ] = i / uSegments;
|
|
353
|
+
uv[ k * 2 + 1 ] = j / vSegments;
|
|
354
|
+
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let extent = 0;
|
|
358
|
+
for ( let k = 0; k < position.length; k ++ ) extent = Math.max( extent, Math.abs( position[ k ] ) );
|
|
359
|
+
const apart = ( a, b ) => Math.hypot( position[ a * 3 ] - position[ b * 3 ], position[ a * 3 + 1 ] - position[ b * 3 + 1 ], position[ a * 3 + 2 ] - position[ b * 3 + 2 ] ) > extent * 1e-6;
|
|
360
|
+
const index = [];
|
|
361
|
+
for ( let j = 0; j < vSegments; j ++ ) for ( let i = 0; i < uSegments; i ++ ) {
|
|
362
|
+
|
|
363
|
+
const a = j * row + i, b = a + 1, c = a + row, d = c + 1;
|
|
364
|
+
if ( apart( a, b ) ) index.push( a, b, d );
|
|
365
|
+
if ( apart( c, d ) ) index.push( a, d, c );
|
|
366
|
+
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const geometry = new BufferGeometry();
|
|
370
|
+
geometry.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
|
|
371
|
+
geometry.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) );
|
|
372
|
+
geometry.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) );
|
|
373
|
+
geometry.setIndex( new Uint32BufferAttribute( new Uint32Array( index ), 1 ) );
|
|
374
|
+
return geometry;
|
|
375
|
+
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// What pbrt calls a shape's area when it scales an area light's `power`: a quadric's in its own space, a mesh's in the
|
|
379
|
+
// scene's (pbrt keeps triangles transformed).
|
|
380
|
+
function shapeArea( shape, geometry ) {
|
|
381
|
+
|
|
382
|
+
const params = shape.params;
|
|
383
|
+
switch ( shape.type ) {
|
|
384
|
+
|
|
385
|
+
case 'sphere': {
|
|
386
|
+
|
|
387
|
+
const { radius, zMin, zMax, phiMax } = sphereParams( params );
|
|
388
|
+
return phiMax * radius * ( zMax - zMin );
|
|
389
|
+
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
case 'cylinder': {
|
|
393
|
+
|
|
394
|
+
const { radius, zMin, zMax, phiMax } = cylinderParams( params );
|
|
395
|
+
return phiMax * radius * ( zMax - zMin );
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case 'disk': {
|
|
400
|
+
|
|
401
|
+
const { radius, inner, phiMax } = diskParams( params );
|
|
402
|
+
return phiMax * 0.5 * ( radius * radius - inner * inner );
|
|
403
|
+
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const world = new Matrix4().fromArray( shape.ctm );
|
|
409
|
+
const position = geometry.getAttribute( 'position' );
|
|
410
|
+
const index = geometry.index;
|
|
411
|
+
const count = index ? index.count : position.count;
|
|
412
|
+
const a = new Vector3(), b = new Vector3(), c = new Vector3();
|
|
413
|
+
let area = 0;
|
|
414
|
+
for ( let t = 0; t + 2 < count; t += 3 ) {
|
|
415
|
+
|
|
416
|
+
a.fromBufferAttribute( position, index ? index.getX( t ) : t ).applyMatrix4( world );
|
|
417
|
+
b.fromBufferAttribute( position, index ? index.getX( t + 1 ) : t + 1 ).applyMatrix4( world );
|
|
418
|
+
c.fromBufferAttribute( position, index ? index.getX( t + 2 ) : t + 2 ).applyMatrix4( world );
|
|
419
|
+
area += b.sub( a ).cross( c.sub( a ) ).length() / 2;
|
|
420
|
+
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return area;
|
|
424
|
+
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const CHECK_TEXELS = 64; // a baked checkerboard's texels along one check
|
|
428
|
+
const BILERP_TEXELS = 64;
|
|
429
|
+
|
|
430
|
+
// `colorAt( s, t )` (linear RGB) over a w × h grid as sRGB bytes, bottom row first, clamped as pbrt clamps an albedo.
|
|
431
|
+
function rasterize( width, height, colorAt ) {
|
|
432
|
+
|
|
433
|
+
const data = new Uint8Array( width * height * 4 );
|
|
434
|
+
for ( let j = 0; j < height; j ++ ) for ( let i = 0; i < width; i ++ ) {
|
|
435
|
+
|
|
436
|
+
const rgb = colorAt( ( i + 0.5 ) / width, ( j + 0.5 ) / height );
|
|
437
|
+
const o = ( j * width + i ) * 4;
|
|
438
|
+
for ( let c = 0; c < 3; c ++ ) data[ o + c ] = Math.round( 255 * linearToSRGB( Math.min( 1, Math.max( 0, rgb[ c ] ) ) ) );
|
|
439
|
+
data[ o + 3 ] = 255;
|
|
440
|
+
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return data;
|
|
444
|
+
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// A clone that keeps the engine's own material properties, which three.js's copy() does not know.
|
|
448
|
+
function cloneMaterial( material ) {
|
|
449
|
+
|
|
450
|
+
const out = material.clone();
|
|
451
|
+
for ( const key of Object.keys( material ) ) if ( ! ( key in out ) ) out[ key ] = material[ key ];
|
|
452
|
+
return out;
|
|
453
|
+
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// pbrt's shape alpha as the engine's blend mode: a ray passes with chance 1 − α, shadow rays too.
|
|
457
|
+
function translucent( material, alpha ) {
|
|
458
|
+
|
|
459
|
+
const out = cloneMaterial( material );
|
|
460
|
+
out.transparent = true;
|
|
461
|
+
out.opacity = material.opacity * alpha;
|
|
462
|
+
return out;
|
|
463
|
+
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const sameMapping = ( texture, { su, sv, du, dv } ) =>
|
|
467
|
+
texture.repeat.x === su && texture.repeat.y === sv && texture.offset.x === du && texture.offset.y === dv;
|
|
468
|
+
|
|
469
|
+
// An 8-bit image's colour with its alpha decoded as pbrt reads it; null when its alpha is opaque throughout.
|
|
470
|
+
function withDecodedAlpha( pixels, decode ) {
|
|
471
|
+
|
|
472
|
+
if ( pixels.float || ! hasAlpha( pixels ) ) return null;
|
|
473
|
+
const { data, width, height } = pixels;
|
|
474
|
+
const lut = new Uint8Array( 256 );
|
|
475
|
+
for ( let v = 0; v < 256; v ++ ) lut[ v ] = Math.round( 255 * Math.min( 1, Math.max( 0, decode( v / 255 ) ) ) );
|
|
476
|
+
|
|
477
|
+
const out = new Uint8Array( width * height * 4 );
|
|
478
|
+
const rowBytes = width * 4;
|
|
479
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
480
|
+
|
|
481
|
+
const src = ( pixels.topDown ? height - 1 - y : y ) * rowBytes, dst = y * rowBytes;
|
|
482
|
+
for ( let x = 0; x < rowBytes; x += 4 ) {
|
|
483
|
+
|
|
484
|
+
out[ dst + x ] = data[ src + x ];
|
|
485
|
+
out[ dst + x + 1 ] = data[ src + x + 1 ];
|
|
486
|
+
out[ dst + x + 2 ] = data[ src + x + 2 ];
|
|
487
|
+
out[ dst + x + 3 ] = lut[ data[ src + x + 3 ] ];
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const texture = new DataTexture( out, width, height, RGBAFormat );
|
|
494
|
+
texture.colorSpace = SRGBColorSpace;
|
|
495
|
+
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
496
|
+
texture.needsUpdate = true;
|
|
497
|
+
return texture;
|
|
498
|
+
|
|
499
|
+
}
|
|
500
|
+
|
|
259
501
|
/**
|
|
260
502
|
* Names an unreadable texture may have been derived from. Moana binds colour as
|
|
261
503
|
* `"texture reflectance" "el:part_Color"` and leaves `MakeNamedMaterial "el:part"` in the
|
|
@@ -316,6 +558,7 @@ export class PBRTSceneBuilder {
|
|
|
316
558
|
this._imageCache = new Map(); // filename -> Promise<Texture|null>, decoded once for every named texture
|
|
317
559
|
this._termCache = new Map(); // texture name + channels -> Promise<{rgb}|{layer, tint?}|null>
|
|
318
560
|
this._mediumMaterials = new Map(); // material -> Map(medium name -> material with its attenuation)
|
|
561
|
+
this._alphaMaterials = new Map(); // material -> Map(alpha texture name or #value -> Promise<material>)
|
|
319
562
|
|
|
320
563
|
}
|
|
321
564
|
|
|
@@ -373,11 +616,12 @@ export class PBRTSceneBuilder {
|
|
|
373
616
|
}
|
|
374
617
|
|
|
375
618
|
const shape = ir.shapes[ i ];
|
|
376
|
-
const [ geometry,
|
|
619
|
+
const [ geometry, surface ] = await Promise.all( [
|
|
377
620
|
this._batches ? this._createGeometry( shape ) : this._buildGeometry( shape ),
|
|
378
621
|
this._getMaterial( shape )
|
|
379
622
|
] );
|
|
380
623
|
if ( ! geometry ) continue;
|
|
624
|
+
const sharedMaterial = shape.areaLight ? this._poweredMaterial( shape, geometry, surface ) : surface;
|
|
381
625
|
|
|
382
626
|
// A moving shape needs a node of its own to move.
|
|
383
627
|
if ( this._batches && ! shape.motion && geometry.getAttribute( 'position' ).count <= MERGE_VERTEX_LIMIT ) {
|
|
@@ -442,6 +686,7 @@ export class PBRTSceneBuilder {
|
|
|
442
686
|
|
|
443
687
|
return {
|
|
444
688
|
group, camera, environment, animations,
|
|
689
|
+
render: renderRequest( ir ),
|
|
445
690
|
report: this.report,
|
|
446
691
|
meshCount: this.reportedMeshes,
|
|
447
692
|
triangleCount: this.triangleCount,
|
|
@@ -865,7 +1110,7 @@ export class PBRTSceneBuilder {
|
|
|
865
1110
|
if ( ! material ) {
|
|
866
1111
|
|
|
867
1112
|
this.warn( `${name} (${shape.type}, "${shape.material?.type || 'diffuse'}") has a texture map but no UVs — dropping map, using base color` );
|
|
868
|
-
material = sharedMaterial
|
|
1113
|
+
material = cloneMaterial( sharedMaterial );
|
|
869
1114
|
material.map = null;
|
|
870
1115
|
this._noUVMaterials.set( sharedMaterial, material );
|
|
871
1116
|
|
|
@@ -1119,6 +1364,7 @@ export class PBRTSceneBuilder {
|
|
|
1119
1364
|
case 'plymesh': return this._plyMesh( shape.params );
|
|
1120
1365
|
case 'sphere': return this._sphere( shape.params );
|
|
1121
1366
|
case 'disk': return this._disk( shape.params );
|
|
1367
|
+
case 'cylinder': return this._cylinder( shape.params );
|
|
1122
1368
|
default:
|
|
1123
1369
|
this.warn( `shape "${shape.type}" not supported — skipped` );
|
|
1124
1370
|
return null;
|
|
@@ -1229,28 +1475,75 @@ export class PBRTSceneBuilder {
|
|
|
1229
1475
|
}
|
|
1230
1476
|
|
|
1231
1477
|
// Bilinear patch mesh → triangulate each quad (P + indices in quads of 4).
|
|
1478
|
+
/**
|
|
1479
|
+
* pbrt's bilinear patches as two triangles each: corners p00, p10, p01, p11 (not a ring), facing ∂p/∂u × ∂p/∂v. One
|
|
1480
|
+
* patch of four points needs no indices. Without uvs a patch takes its own (u, v), so its corners are not shared.
|
|
1481
|
+
*/
|
|
1232
1482
|
_bilinearMesh( params ) {
|
|
1233
1483
|
|
|
1234
1484
|
const P = params.P?.value;
|
|
1235
|
-
const quad = params.indices?.value;
|
|
1485
|
+
const quad = params.indices?.value ?? ( P?.length === 12 ? [ 0, 1, 2, 3 ] : null );
|
|
1236
1486
|
if ( ! P || ! quad ) {
|
|
1237
1487
|
|
|
1238
1488
|
this.warn( 'bilinearmesh missing P/indices' ); return null;
|
|
1239
1489
|
|
|
1240
1490
|
}
|
|
1241
1491
|
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1492
|
+
const count = P.length / 3;
|
|
1493
|
+
const uv = params.uv?.value?.length === count * 2 ? params.uv.value : null;
|
|
1494
|
+
const N = params.N?.value?.length === count * 3 ? params.N.value : null;
|
|
1495
|
+
const geo = new BufferGeometry();
|
|
1496
|
+
|
|
1497
|
+
if ( uv ) {
|
|
1244
1498
|
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1499
|
+
const index = [];
|
|
1500
|
+
for ( let i = 0; i + 3 < quad.length; i += 4 ) {
|
|
1501
|
+
|
|
1502
|
+
const [ a, b, c, d ] = [ quad[ i ], quad[ i + 1 ], quad[ i + 2 ], quad[ i + 3 ] ];
|
|
1503
|
+
index.push( a, b, d, a, d, c );
|
|
1504
|
+
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
geo.setAttribute( 'position', new Float32BufferAttribute( Float32Array.from( P ), 3 ) );
|
|
1508
|
+
geo.setAttribute( 'uv', new Float32BufferAttribute( Float32Array.from( uv ), 2 ) );
|
|
1509
|
+
if ( N ) geo.setAttribute( 'normal', new Float32BufferAttribute( Float32Array.from( N ), 3 ) );
|
|
1510
|
+
geo.setIndex( new Uint32BufferAttribute( new Uint32Array( index ), 1 ) );
|
|
1511
|
+
|
|
1512
|
+
} else {
|
|
1513
|
+
|
|
1514
|
+
const patches = Math.floor( quad.length / 4 );
|
|
1515
|
+
const position = new Float32Array( patches * 12 ), normal = N ? new Float32Array( patches * 12 ) : null;
|
|
1516
|
+
const corners = new Float32Array( patches * 8 ), index = new Uint32Array( patches * 6 );
|
|
1517
|
+
for ( let i = 0; i < patches; i ++ ) {
|
|
1518
|
+
|
|
1519
|
+
for ( let k = 0; k < 4; k ++ ) {
|
|
1520
|
+
|
|
1521
|
+
const v = quad[ i * 4 + k ];
|
|
1522
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
1523
|
+
|
|
1524
|
+
position[ i * 12 + k * 3 + c ] = P[ v * 3 + c ];
|
|
1525
|
+
if ( normal ) normal[ i * 12 + k * 3 + c ] = N[ v * 3 + c ];
|
|
1526
|
+
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
corners[ i * 8 + k * 2 ] = k & 1;
|
|
1530
|
+
corners[ i * 8 + k * 2 + 1 ] = k >> 1;
|
|
1531
|
+
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
index.set( [ 0, 1, 3, 0, 3, 2 ].map( ( k ) => i * 4 + k ), i * 6 );
|
|
1535
|
+
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
geo.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
|
|
1539
|
+
geo.setAttribute( 'uv', new Float32BufferAttribute( corners, 2 ) );
|
|
1540
|
+
if ( normal ) geo.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) );
|
|
1541
|
+
geo.setIndex( new Uint32BufferAttribute( index, 1 ) );
|
|
1247
1542
|
|
|
1248
1543
|
}
|
|
1249
1544
|
|
|
1250
|
-
|
|
1251
|
-
geo.
|
|
1252
|
-
geo.setIndex( tris );
|
|
1253
|
-
geo.computeVertexNormals();
|
|
1545
|
+
if ( N ) geo.userData.pbrtNormals = true;
|
|
1546
|
+
else geo.computeVertexNormals();
|
|
1254
1547
|
return geo;
|
|
1255
1548
|
|
|
1256
1549
|
}
|
|
@@ -1297,38 +1590,41 @@ export class PBRTSceneBuilder {
|
|
|
1297
1590
|
|
|
1298
1591
|
_sphere( params ) {
|
|
1299
1592
|
|
|
1300
|
-
const radius =
|
|
1301
|
-
|
|
1593
|
+
const { radius, zMin, zMax, phiMax } = sphereParams( params );
|
|
1594
|
+
const thetaMin = Math.acos( zMin / radius ), thetaMax = Math.acos( zMax / radius );
|
|
1595
|
+
return quadric( phiMax, Math.max( 1, Math.ceil( 32 * ( thetaMin - thetaMax ) / Math.PI ) ), ( u, v, p, n ) => {
|
|
1596
|
+
|
|
1597
|
+
const phi = u * phiMax, theta = thetaMin + v * ( thetaMax - thetaMin );
|
|
1598
|
+
n.set( Math.sin( theta ) * Math.cos( phi ), Math.sin( theta ) * Math.sin( phi ), Math.cos( theta ) );
|
|
1599
|
+
p.copy( n ).multiplyScalar( radius );
|
|
1600
|
+
|
|
1601
|
+
} );
|
|
1602
|
+
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
_cylinder( params ) {
|
|
1606
|
+
|
|
1607
|
+
const { radius, zMin, zMax, phiMax } = cylinderParams( params );
|
|
1608
|
+
return quadric( phiMax, 1, ( u, v, p, n ) => {
|
|
1609
|
+
|
|
1610
|
+
const phi = u * phiMax;
|
|
1611
|
+
n.set( Math.cos( phi ), Math.sin( phi ), 0 );
|
|
1612
|
+
p.set( radius * n.x, radius * n.y, zMin + v * ( zMax - zMin ) );
|
|
1613
|
+
|
|
1614
|
+
} );
|
|
1302
1615
|
|
|
1303
1616
|
}
|
|
1304
1617
|
|
|
1305
1618
|
_disk( params ) {
|
|
1306
1619
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const inner = pFloat( params, 'innerradius', 0 );
|
|
1310
|
-
const h = pFloat( params, 'height', 0 );
|
|
1311
|
-
const seg = 48;
|
|
1312
|
-
const pos = [];
|
|
1313
|
-
const idx = [];
|
|
1314
|
-
for ( let i = 0; i < seg; i ++ ) {
|
|
1315
|
-
|
|
1316
|
-
const a0 = ( i / seg ) * Math.PI * 2;
|
|
1317
|
-
const a1 = ( ( i + 1 ) / seg ) * Math.PI * 2;
|
|
1318
|
-
const base = pos.length / 3;
|
|
1319
|
-
pos.push( Math.cos( a0 ) * inner, Math.sin( a0 ) * inner, h );
|
|
1320
|
-
pos.push( Math.cos( a0 ) * radius, Math.sin( a0 ) * radius, h );
|
|
1321
|
-
pos.push( Math.cos( a1 ) * radius, Math.sin( a1 ) * radius, h );
|
|
1322
|
-
pos.push( Math.cos( a1 ) * inner, Math.sin( a1 ) * inner, h );
|
|
1323
|
-
idx.push( base, base + 1, base + 2, base, base + 2, base + 3 );
|
|
1620
|
+
const { radius, inner, height, phiMax } = diskParams( params );
|
|
1621
|
+
return quadric( phiMax, 1, ( u, v, p, n ) => {
|
|
1324
1622
|
|
|
1325
|
-
|
|
1623
|
+
const phi = u * phiMax, r = radius - v * ( radius - inner );
|
|
1624
|
+
p.set( r * Math.cos( phi ), r * Math.sin( phi ), height );
|
|
1625
|
+
n.set( 0, 0, 1 );
|
|
1326
1626
|
|
|
1327
|
-
|
|
1328
|
-
geo.setAttribute( 'position', new Float32BufferAttribute( Float32Array.from( pos ), 3 ) );
|
|
1329
|
-
geo.setIndex( idx );
|
|
1330
|
-
geo.computeVertexNormals();
|
|
1331
|
-
return geo;
|
|
1627
|
+
} );
|
|
1332
1628
|
|
|
1333
1629
|
}
|
|
1334
1630
|
|
|
@@ -1336,8 +1632,75 @@ export class PBRTSceneBuilder {
|
|
|
1336
1632
|
|
|
1337
1633
|
async _getMaterial( shape ) {
|
|
1338
1634
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1635
|
+
let material = await this._surfaceMaterial( shape );
|
|
1636
|
+
if ( shape.interior ) material = await this._inMedium( material, shape.interior );
|
|
1637
|
+
return shape.params.alpha ? this._withAlpha( material, shape.params.alpha ) : material;
|
|
1638
|
+
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/** The material under a shape's `alpha`, one per material and alpha. @private */
|
|
1642
|
+
_withAlpha( material, alpha ) {
|
|
1643
|
+
|
|
1644
|
+
const key = alpha.type === 'texture' ? alpha.value[ 0 ] : `#${alpha.value[ 0 ]}`;
|
|
1645
|
+
let byAlpha = this._alphaMaterials.get( material );
|
|
1646
|
+
if ( ! byAlpha ) this._alphaMaterials.set( material, byAlpha = new Map() );
|
|
1647
|
+
if ( ! byAlpha.has( key ) ) byAlpha.set( key, this._createAlphaMaterial( material, alpha ) );
|
|
1648
|
+
return byAlpha.get( key );
|
|
1649
|
+
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
async _createAlphaMaterial( material, alpha ) {
|
|
1653
|
+
|
|
1654
|
+
if ( alpha.type !== 'texture' ) return alpha.value[ 0 ] < 1 ? translucent( material, alpha.value[ 0 ] ) : material;
|
|
1655
|
+
|
|
1656
|
+
const name = alpha.value[ 0 ];
|
|
1657
|
+
const map = await this._alphaMap( material.map, name );
|
|
1658
|
+
if ( ! map ) {
|
|
1659
|
+
|
|
1660
|
+
this.warn( `shape alpha "${name}" could not be read here — drawn opaque` );
|
|
1661
|
+
return material;
|
|
1662
|
+
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if ( map.constant !== undefined ) return map.constant < 1 ? translucent( material, map.constant ) : material;
|
|
1666
|
+
const out = cloneMaterial( material );
|
|
1667
|
+
out.map = map;
|
|
1668
|
+
out.transparent = true;
|
|
1669
|
+
return out;
|
|
1670
|
+
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* The colour map with the float texture `name` in its alpha channel, `{ constant }` when that texture is one value,
|
|
1675
|
+
* or null when it cannot be read here.
|
|
1676
|
+
* @private
|
|
1677
|
+
*/
|
|
1678
|
+
async _alphaMap( colorMap, name ) {
|
|
1679
|
+
|
|
1680
|
+
const def = this.ir.namedTextures.get( name );
|
|
1681
|
+
const filename = def?.class === 'imagemap' ? pString( def.params, 'filename', null ) : null;
|
|
1682
|
+
// Most often the alpha is the colour map's own image: its colour stays as it is.
|
|
1683
|
+
if ( filename && colorMap?.userData.pbrtImage === filename && pString( def.params, 'mapping', 'uv' ) === 'uv'
|
|
1684
|
+
&& pFloat( def.params, 'scale', 1 ) === 1 && ! pBool( def.params, 'invert', false ) && sameMapping( colorMap, uvMapping( def.params ) ) ) {
|
|
1685
|
+
|
|
1686
|
+
const pixels = texturePixels( colorMap );
|
|
1687
|
+
const own = pixels && withDecodedAlpha( pixels, decoderFor( def.params ) );
|
|
1688
|
+
if ( own ) {
|
|
1689
|
+
|
|
1690
|
+
setMapping( own, uvMapping( def.params ) );
|
|
1691
|
+
return own;
|
|
1692
|
+
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
const alpha = await this._bakeTerm( name, 1 );
|
|
1698
|
+
if ( ! alpha ) return null;
|
|
1699
|
+
if ( ! alpha.layer ) return { constant: alpha.rgb[ 0 ] };
|
|
1700
|
+
const color = colorMap ? this._textureLayer( colorMap, 3 ) : null;
|
|
1701
|
+
if ( colorMap && ! color ) return null;
|
|
1702
|
+
const baked = bake( [ color ? { layer: color } : { rgb: [ 1, 1, 1 ] }, alpha ], ( [ c ] ) => c, { alphaOf: ( [ , a ] ) => a[ 0 ] } );
|
|
1703
|
+
return baked && bakedTexture( baked );
|
|
1341
1704
|
|
|
1342
1705
|
}
|
|
1343
1706
|
|
|
@@ -1380,6 +1743,17 @@ export class PBRTSceneBuilder {
|
|
|
1380
1743
|
|
|
1381
1744
|
},
|
|
1382
1745
|
bakeMaterialMix: ( a, b, amountName ) => this._bakeMaterialMix( a, b, amountName ),
|
|
1746
|
+
bakeFloatMap: ( params, keys, combine ) => this._floatMap( params, keys, combine ),
|
|
1747
|
+
textureMeanRGB: async ( n ) => {
|
|
1748
|
+
|
|
1749
|
+
const term = await this._bakeTerm( n, 3 );
|
|
1750
|
+
if ( ! term?.layer ) return term?.rgb ?? null;
|
|
1751
|
+
const { data } = term.layer;
|
|
1752
|
+
const sum = [ 0, 0, 0 ];
|
|
1753
|
+
for ( let i = 0; i < data.length; i ++ ) sum[ i % 3 ] += data[ i ];
|
|
1754
|
+
return sum.map( ( v, c ) => v / ( data.length / 3 ) * ( term.tint?.[ c ] ?? 1 ) );
|
|
1755
|
+
|
|
1756
|
+
},
|
|
1383
1757
|
resolveNormalMap: async ( filename ) => {
|
|
1384
1758
|
|
|
1385
1759
|
const image = await this._image( filename );
|
|
@@ -1429,7 +1803,7 @@ export class PBRTSceneBuilder {
|
|
|
1429
1803
|
const max = Math.max( ...sigma );
|
|
1430
1804
|
if ( ! ( max > 0 ) ) return material;
|
|
1431
1805
|
|
|
1432
|
-
const variant = material
|
|
1806
|
+
const variant = cloneMaterial( material );
|
|
1433
1807
|
variant.attenuationDistance = 1 / max;
|
|
1434
1808
|
variant.attenuationColor.setRGB( ...sigma.map( ( v ) => Math.exp( - v / max ) ) );
|
|
1435
1809
|
return this._dedupeMaterial( variant );
|
|
@@ -1445,11 +1819,12 @@ export class PBRTSceneBuilder {
|
|
|
1445
1819
|
|
|
1446
1820
|
const c = material.color, e = material.emissive;
|
|
1447
1821
|
const key = `${c.r},${c.g},${c.b}|${e.r},${e.g},${e.b}|${material.emissiveIntensity}|` +
|
|
1448
|
-
`${material.map ? material.map.uuid : '-'}|${material.roughness}|${material.metalness}|` +
|
|
1822
|
+
`${material.map ? material.map.uuid : '-'}|${material.roughness}|${material.roughnessMap?.uuid ?? '-'}|${material.metalness}|` +
|
|
1449
1823
|
`${material.transmission}|${material.ior}|${material.thickness}|` +
|
|
1450
|
-
`${material.clearcoat}|${material.clearcoatRoughness}|${material.opacity}|${material.side}|` +
|
|
1824
|
+
`${material.clearcoat}|${material.clearcoatRoughness}|${material.clearcoatRoughnessMap?.uuid ?? '-'}|${material.opacity}|${material.side}|` +
|
|
1451
1825
|
`${material.normalMap ? material.normalMap.uuid : '-'}|${material.attenuationDistance}|${material.attenuationColor.toArray()}|` +
|
|
1452
|
-
`${material.specularIntensity}|${material.diffuseTransmission ?? 0}|${material.diffuseTransmissionColor?.toArray() ?? '-'}
|
|
1826
|
+
`${material.specularIntensity}|${material.diffuseTransmission ?? 0}|${material.diffuseTransmissionColor?.toArray() ?? '-'}|` +
|
|
1827
|
+
`${material.subsurface ?? 0}|${material.subsurfaceColor?.toArray() ?? '-'}|${material.subsurfaceRadius ?? '-'}|${material.subsurfaceAnisotropy ?? 0}`;
|
|
1453
1828
|
|
|
1454
1829
|
const existing = this._materialBySignature.get( key );
|
|
1455
1830
|
if ( existing ) return existing;
|
|
@@ -1458,6 +1833,23 @@ export class PBRTSceneBuilder {
|
|
|
1458
1833
|
|
|
1459
1834
|
}
|
|
1460
1835
|
|
|
1836
|
+
/**
|
|
1837
|
+
* An area light given as `power`: pbrt scales its radiance to that power over this shape's area (both sides of a
|
|
1838
|
+
* twosided one); an image's mean is not in it, as an emitting image is not supported.
|
|
1839
|
+
* @private
|
|
1840
|
+
*/
|
|
1841
|
+
_poweredMaterial( shape, geometry, material ) {
|
|
1842
|
+
|
|
1843
|
+
const power = pFloat( shape.areaLight.params, 'power', - 1 );
|
|
1844
|
+
if ( ! ( power > 0 ) ) return material;
|
|
1845
|
+
const area = shapeArea( shape, geometry ) * ( pBool( shape.areaLight.params, 'twosided', false ) ? 2 : 1 );
|
|
1846
|
+
if ( ! ( area > 0 ) ) return material;
|
|
1847
|
+
const out = cloneMaterial( material );
|
|
1848
|
+
out.emissiveIntensity = material.emissiveIntensity * power / ( Math.PI * area );
|
|
1849
|
+
return out;
|
|
1850
|
+
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1461
1853
|
async _applyAreaLight( material, areaLight, ctx ) {
|
|
1462
1854
|
|
|
1463
1855
|
const rgb = await lightRGB( areaLight.params, 'L', ctx );
|
|
@@ -1494,6 +1886,10 @@ export class PBRTSceneBuilder {
|
|
|
1494
1886
|
if ( v && v.type === 'rgb' ) result = { constant: [ v.value[ 0 ], v.value[ 1 ], v.value[ 2 ] ] };
|
|
1495
1887
|
else if ( v ) result = { constant: [ v.value[ 0 ], v.value[ 0 ], v.value[ 0 ] ] };
|
|
1496
1888
|
|
|
1889
|
+
} else if ( def.class === 'checkerboard' || def.class === 'bilerp' ) {
|
|
1890
|
+
|
|
1891
|
+
result = await this._proceduralTexture( name, def );
|
|
1892
|
+
|
|
1497
1893
|
} else if ( def.class === 'scale' ) {
|
|
1498
1894
|
|
|
1499
1895
|
// Scale = inner_texture * scale_factor. Resolve the inner (recursively if
|
|
@@ -1523,6 +1919,52 @@ export class PBRTSceneBuilder {
|
|
|
1523
1919
|
|
|
1524
1920
|
}
|
|
1525
1921
|
|
|
1922
|
+
/**
|
|
1923
|
+
* pbrt's 2D `checkerboard` and `bilerp`, baked: one period of the checks (two each way), or the bilerp over the unit
|
|
1924
|
+
* square, in the texture's uv mapping. A textured input counts as its mean.
|
|
1925
|
+
* @private
|
|
1926
|
+
*/
|
|
1927
|
+
async _proceduralTexture( name, def ) {
|
|
1928
|
+
|
|
1929
|
+
const params = def.params;
|
|
1930
|
+
const ctx = this._materialContext();
|
|
1931
|
+
const value = async ( key, dflt ) => {
|
|
1932
|
+
|
|
1933
|
+
const p = params[ key ];
|
|
1934
|
+
if ( p?.type === 'texture' ) return ( await ctx.textureMeanRGB( p.value[ 0 ] ) ) ?? dflt;
|
|
1935
|
+
return ( await resolveSpectrum( params, key, ctx, dflt ) ).rgb ?? dflt;
|
|
1936
|
+
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
const mapping = pString( params, 'mapping', 'uv' );
|
|
1940
|
+
if ( mapping !== 'uv' ) this.warn( `texture "${name}": "${mapping}" mapping is not supported — using the mesh's uv` );
|
|
1941
|
+
const { su, sv, du, dv } = uvMapping( params );
|
|
1942
|
+
|
|
1943
|
+
if ( def.class === 'checkerboard' ) {
|
|
1944
|
+
|
|
1945
|
+
if ( pFloat( params, 'dimension', 2 ) !== 2 ) {
|
|
1946
|
+
|
|
1947
|
+
this.warn( `texture "${name}": a 3D checkerboard is not supported` );
|
|
1948
|
+
return null;
|
|
1949
|
+
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
const [ a, b ] = await Promise.all( [ value( 'tex1', [ 1, 1, 1 ] ), value( 'tex2', [ 0, 0, 0 ] ) ] );
|
|
1953
|
+
const n = 2 * CHECK_TEXELS;
|
|
1954
|
+
const data = rasterize( n, n, ( s, t ) => ( ( Math.floor( 2 * s ) + Math.floor( 2 * t ) ) % 2 ? b : a ) );
|
|
1955
|
+
return { texture: bakedTexture( { data, width: n, height: n, mapping: { su: su / 2, sv: sv / 2, du: du / 2, dv: dv / 2 } } ) };
|
|
1956
|
+
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
const [ v00, v01, v10, v11 ] = await Promise.all( [
|
|
1960
|
+
value( 'v00', [ 0, 0, 0 ] ), value( 'v01', [ 1, 1, 1 ] ), value( 'v10', [ 0, 0, 0 ] ), value( 'v11', [ 1, 1, 1 ] )
|
|
1961
|
+
] );
|
|
1962
|
+
const data = rasterize( BILERP_TEXELS, BILERP_TEXELS, ( s, t ) => [ 0, 1, 2 ].map( ( c ) =>
|
|
1963
|
+
( 1 - s ) * ( 1 - t ) * v00[ c ] + s * ( 1 - t ) * v10[ c ] + ( 1 - s ) * t * v01[ c ] + s * t * v11[ c ] ) );
|
|
1964
|
+
return { texture: bakedTexture( { data, width: BILERP_TEXELS, height: BILERP_TEXELS, mapping: { su, sv, du, dv } } ) };
|
|
1965
|
+
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1526
1968
|
/** A file's image, decoded once however many named textures read it. @private */
|
|
1527
1969
|
_image( filename ) {
|
|
1528
1970
|
|
|
@@ -1575,12 +2017,32 @@ export class PBRTSceneBuilder {
|
|
|
1575
2017
|
|
|
1576
2018
|
const color = eightBit( image, { srgb: true } );
|
|
1577
2019
|
const texture = color === image ? image.clone() : color;
|
|
2020
|
+
texture.userData.pbrtImage = filename;
|
|
1578
2021
|
setMapping( texture, uvMapping( def.params ) );
|
|
1579
2022
|
texture.needsUpdate = true;
|
|
1580
2023
|
return scale < 1 ? { texture, constant: [ scale, scale, scale ] } : { texture };
|
|
1581
2024
|
|
|
1582
2025
|
}
|
|
1583
2026
|
|
|
2027
|
+
/**
|
|
2028
|
+
* A data map (linear, in every channel) of `combine` over float parameters `keys`, constants or textures; null when
|
|
2029
|
+
* one cannot be read here or all are constants.
|
|
2030
|
+
* @private
|
|
2031
|
+
*/
|
|
2032
|
+
async _floatMap( params, keys, combine ) {
|
|
2033
|
+
|
|
2034
|
+
const terms = await Promise.all( keys.map( ( key ) => this._paramTerm( params, key, 1, [ 0, 0, 0 ] ) ) );
|
|
2035
|
+
if ( terms.some( ( term ) => ! term ) ) return null;
|
|
2036
|
+
const baked = bake( terms, ( values ) => {
|
|
2037
|
+
|
|
2038
|
+
const v = combine( values.map( ( value ) => value[ 0 ] ) );
|
|
2039
|
+
return [ v, v, v ];
|
|
2040
|
+
|
|
2041
|
+
}, { linear: true } );
|
|
2042
|
+
return baked && bakedTexture( baked, { srgb: false } );
|
|
2043
|
+
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1584
2046
|
/** pbrt's `mix` texture, ( 1 − amount ) · tex1 + amount · tex2, baked when any of the three is an image. @private */
|
|
1585
2047
|
async _mixTexture( name, def ) {
|
|
1586
2048
|
|
|
@@ -1617,7 +2079,7 @@ export class PBRTSceneBuilder {
|
|
|
1617
2079
|
* A named texture as pbrt evaluates it, for baking: `{ rgb }` for a constant, `{ layer, tint? }` for an image;
|
|
1618
2080
|
* null when it cannot be read in this runtime.
|
|
1619
2081
|
* @param {string} name
|
|
1620
|
-
* @param {number} channels - 1 where it is read as a float texture
|
|
2082
|
+
* @param {number} channels - 1 where it is read as a float texture, 3 as a spectrum
|
|
1621
2083
|
* @private
|
|
1622
2084
|
*/
|
|
1623
2085
|
_bakeTerm( name, channels ) {
|
|
@@ -1798,6 +2260,8 @@ export class PBRTSceneBuilder {
|
|
|
1798
2260
|
camera.name = 'PBRT Camera';
|
|
1799
2261
|
this._poseCamera( camera, cam.cameraToWorld );
|
|
1800
2262
|
camera.updateMatrixWorld( true );
|
|
2263
|
+
const lensRadius = pFloat( cam.params, 'lensradius', 0 );
|
|
2264
|
+
if ( lensRadius > 0 ) camera.userData.__rayzeeEffects = lensEffects( camera, lensRadius, pFloat( cam.params, 'focaldistance', 1e6 ) );
|
|
1801
2265
|
return camera;
|
|
1802
2266
|
|
|
1803
2267
|
}
|