rayzee 9.4.1 → 9.5.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 +226 -25
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
- package/dist/ColorManagement-CheBCPaW.js +1488 -0
- package/dist/EngineDefaults-DH3bI5t-.js +844 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-BnHghprG.js +97 -0
- package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
- package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
- package/dist/PathTracer-Ccl9KOuF.js +29729 -0
- package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
- package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
- package/dist/SolarLimb-ken-JbDK.js +721 -0
- package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
- package/dist/ViewTransforms-D0rTLBsX.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2266 -44125
- package/dist/rayzee.umd.js +485 -473
- package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/EngineDefaults.js +23 -14
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +617 -4016
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +322 -1746
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +4 -95
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +12 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +8 -7
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +12 -3
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +15 -55
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/TextureCreator.js +1 -1
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3902 -0
- package/src/RenderSettings.js +49 -41
- package/src/Stages/ASVGF.js +5 -7
- package/src/Stages/AutoExposure.js +3 -12
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/NRD.js +9 -2
- package/src/Stages/NormalDepth.js +9 -6
- package/src/Stages/PathTracer.js +254 -38
- package/src/Stages/PathTracerStage.js +11 -16
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +2 -4
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +62 -24
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +19 -14
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +64 -17
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +90 -38
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +4 -0
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +881 -103
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +65 -60
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +35 -0
- package/src/index.js +3 -1
- package/src/integrators/BidirectionalIntegrator.js +832 -0
- package/src/managers/EnvironmentManager.js +94 -91
- package/src/managers/MaterialDataManager.js +42 -0
- package/src/managers/UniformManager.js +0 -1
- package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -19,15 +19,31 @@ import {
|
|
|
19
19
|
Group, Mesh, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Vector3, Quaternion,
|
|
20
20
|
BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute, SphereGeometry,
|
|
21
21
|
DataTexture, FloatType, RGBAFormat, LinearFilter, EquirectangularReflectionMapping,
|
|
22
|
-
SRGBColorSpace, AnimationClip, VectorKeyframeTrack, QuaternionKeyframeTrack,
|
|
23
|
-
NumberKeyframeTrack, BooleanKeyframeTrack
|
|
22
|
+
SRGBColorSpace, NoColorSpace, AnimationClip, VectorKeyframeTrack, QuaternionKeyframeTrack,
|
|
23
|
+
NumberKeyframeTrack, BooleanKeyframeTrack, DirectionalLight, PointLight, SpotLight, FrontSide, DoubleSide,
|
|
24
|
+
RepeatWrapping
|
|
24
25
|
} from 'three';
|
|
25
|
-
import { buildMaterial, pFloat, pString, resolveSpectrum } from './PBRTMaterials.js';
|
|
26
|
+
import { buildMaterial, pBool, pFloat, pString, resolveSpectrum } from './PBRTMaterials.js';
|
|
27
|
+
import { makeLayer, layerMean, bake, mixOf, srgbToLinear, linearToSRGB } from './PBRTTextureBake.js';
|
|
26
28
|
import { loopSubdivide } from './LoopSubdivision.js';
|
|
27
29
|
import { octahedralToEquirect } from './EqualAreaOctahedral.js';
|
|
28
30
|
import { tessellateCurve } from './PBRTCurves.js';
|
|
29
31
|
import * as M from './PBRTMath.js';
|
|
30
32
|
|
|
33
|
+
const LAMP_TYPES = new Set( [ 'distant', 'point', 'spot' ] );
|
|
34
|
+
|
|
35
|
+
// pbrt scales a light's spectrum to luminance 1 unless it is RGB, so a blackbody of any temperature is equally bright.
|
|
36
|
+
async function lightRGB( params, name, ctx ) {
|
|
37
|
+
|
|
38
|
+
const p = params[ name ];
|
|
39
|
+
if ( p?.type === 'spectrum' && typeof p.value[ 0 ] === 'string' && p.value[ 0 ].startsWith( 'stdillum' ) ) return [ 1, 1, 1 ];
|
|
40
|
+
const rgb = ( await resolveSpectrum( params, name, ctx, [ 1, 1, 1 ] ) ).rgb || [ 1, 1, 1 ];
|
|
41
|
+
if ( ! p || p.type === 'rgb' || p.type === 'color' || p.type === 'float' ) return rgb;
|
|
42
|
+
const y = 0.2126 * rgb[ 0 ] + 0.7152 * rgb[ 1 ] + 0.0722 * rgb[ 2 ];
|
|
43
|
+
return y > 0 ? rgb.map( v => v / y ) : rgb;
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
const FLIP_Z = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 1 ];
|
|
32
48
|
const MAX_SKY_READBACK = 4096;
|
|
33
49
|
|
|
@@ -75,11 +91,7 @@ const DEFAULT_TRIANGLE_BUDGET = 45_000_000;
|
|
|
75
91
|
// Placements are their own budget: geometry is shared, but each costs a TLAS leaf and an
|
|
76
92
|
// instance record. isCoastline's 5.09M loaded and rendered at 23 fps.
|
|
77
93
|
const DEFAULT_PLACEMENT_BUDGET = 6_000_000;
|
|
78
|
-
|
|
79
|
-
// passes these instead. The whole 15-part Moana subset (55.7M / 7.0M) loads cold under them, at
|
|
80
|
-
// an 8.1 GB preflight estimate; the preflight, not the budget, refuses anything larger.
|
|
81
|
-
export const SPILL_TRIANGLE_BUDGET = 60_000_000;
|
|
82
|
-
export const SPILL_PLACEMENT_BUDGET = 8_000_000;
|
|
94
|
+
export { SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET } from '../../EngineDefaults.js';
|
|
83
95
|
// Non-instanced shapes merge into a few large meshes: isPalmRig declares 173,338 of them,
|
|
84
96
|
// and one BufferGeometry + Mesh each measured 3.3 GB — the loader OOM'd before tracing.
|
|
85
97
|
const DEFAULT_MERGE_MIN_SHAPES = 256;
|
|
@@ -95,7 +107,7 @@ const DEFAULT_CURVE_SIDES = { flat: 1, ribbon: 1, cylinder: 2 };
|
|
|
95
107
|
const DEFAULT_CURVE_TOLERANCE = 0.05;
|
|
96
108
|
|
|
97
109
|
/** Bumped whenever the same scene files build a different graph, so a stored graph is not reused. */
|
|
98
|
-
export const PBRT_BUILD_REVISION =
|
|
110
|
+
export const PBRT_BUILD_REVISION = 8;
|
|
99
111
|
|
|
100
112
|
function samePlacements( a, b ) {
|
|
101
113
|
|
|
@@ -135,6 +147,115 @@ function pixelsFromDrawable( image, srgb ) {
|
|
|
135
147
|
|
|
136
148
|
}
|
|
137
149
|
|
|
150
|
+
/**
|
|
151
|
+
* A texture's pixels, for baking: `{ data, width, height, channels: 4, topDown, float }`, or null where this runtime
|
|
152
|
+
* cannot read them. A DataTexture's rows run bottom up unless it says flipY; a drawable's top down.
|
|
153
|
+
*/
|
|
154
|
+
function texturePixels( texture ) {
|
|
155
|
+
|
|
156
|
+
const image = texture?.image;
|
|
157
|
+
if ( ! image ) return null;
|
|
158
|
+
|
|
159
|
+
if ( ArrayBuffer.isView( image.data ) ) {
|
|
160
|
+
|
|
161
|
+
return { data: image.data, width: image.width, height: image.height, channels: 4, topDown: texture.flipY === true, float: ! ( image.data instanceof Uint8Array || image.data instanceof Uint8ClampedArray ) };
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { width, height } = image;
|
|
166
|
+
if ( ! width || ! height ) return null;
|
|
167
|
+
let canvas = null;
|
|
168
|
+
if ( typeof OffscreenCanvas !== 'undefined' ) canvas = new OffscreenCanvas( width, height );
|
|
169
|
+
else if ( typeof document !== 'undefined' ) {
|
|
170
|
+
|
|
171
|
+
canvas = document.createElement( 'canvas' );
|
|
172
|
+
canvas.width = width;
|
|
173
|
+
canvas.height = height;
|
|
174
|
+
|
|
175
|
+
} else return null;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
|
|
179
|
+
const ctx = canvas.getContext( '2d', { willReadFrequently: true } );
|
|
180
|
+
if ( ! ctx ) return null;
|
|
181
|
+
ctx.drawImage( image, 0, 0 );
|
|
182
|
+
return { data: ctx.getImageData( 0, 0, width, height ).data, width, height, channels: 4, topDown: true, float: false };
|
|
183
|
+
|
|
184
|
+
} catch {
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// pbrt's `encoding`: an 8-bit image is sRGB unless it says "linear" (or "gamma g").
|
|
193
|
+
function decoderFor( params ) {
|
|
194
|
+
|
|
195
|
+
const encoding = pString( params, 'encoding', 'sRGB' );
|
|
196
|
+
if ( encoding === 'linear' ) return ( v ) => v;
|
|
197
|
+
const gamma = /^gamma\s+([\d.]+)$/.exec( encoding );
|
|
198
|
+
return gamma ? ( v ) => v ** Number( gamma[ 1 ] ) : srgbToLinear;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function uvMapping( params ) {
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
su: pFloat( params, 'uscale', 1 ), sv: pFloat( params, 'vscale', 1 ),
|
|
206
|
+
du: pFloat( params, 'udelta', 0 ), dv: pFloat( params, 'vdelta', 0 )
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function setMapping( texture, { su, sv, du, dv } ) {
|
|
212
|
+
|
|
213
|
+
texture.repeat.set( su, sv );
|
|
214
|
+
texture.offset.set( du, dv );
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A float image (EXR, HDR, PFM) as 8-bit, the only texel a material map takes here: sRGB-encoded for a colour,
|
|
220
|
+
* linear for data. Any other texture comes back as it is.
|
|
221
|
+
*/
|
|
222
|
+
function eightBit( texture, { srgb } ) {
|
|
223
|
+
|
|
224
|
+
const data = texture?.image?.data;
|
|
225
|
+
if ( ! ArrayBuffer.isView( data ) || data instanceof Uint8Array || data instanceof Uint8ClampedArray ) return texture;
|
|
226
|
+
|
|
227
|
+
const { width, height } = texture.image;
|
|
228
|
+
const bytes = new Uint8Array( width * height * 4 );
|
|
229
|
+
const channels = data.length / ( width * height );
|
|
230
|
+
for ( let i = 0; i < width * height; i ++ ) for ( let c = 0; c < 4; c ++ ) {
|
|
231
|
+
|
|
232
|
+
const v = c < channels ? Math.min( 1, Math.max( 0, data[ i * channels + c ] ) ) : 1;
|
|
233
|
+
bytes[ i * 4 + c ] = Math.round( 255 * ( srgb && c < 3 ? linearToSRGB( v ) : v ) );
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const out = new DataTexture( bytes, width, height, RGBAFormat );
|
|
238
|
+
out.flipY = texture.flipY;
|
|
239
|
+
out.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;
|
|
240
|
+
out.wrapS = out.wrapT = RepeatWrapping;
|
|
241
|
+
// No archive path: a stored scene keeps these bytes rather than decoding the float file again.
|
|
242
|
+
out.needsUpdate = true;
|
|
243
|
+
return out;
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// A baked image as a texture: sRGB bytes, bottom row first, in the uv mapping it was baked in.
|
|
248
|
+
function bakedTexture( { data, width, height, mapping } ) {
|
|
249
|
+
|
|
250
|
+
const texture = new DataTexture( data, width, height, RGBAFormat );
|
|
251
|
+
texture.colorSpace = SRGBColorSpace;
|
|
252
|
+
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
253
|
+
setMapping( texture, mapping );
|
|
254
|
+
texture.needsUpdate = true;
|
|
255
|
+
return texture;
|
|
256
|
+
|
|
257
|
+
}
|
|
258
|
+
|
|
138
259
|
/**
|
|
139
260
|
* Names an unreadable texture may have been derived from. Moana binds colour as
|
|
140
261
|
* `"texture reflectance" "el:part_Color"` and leaves `MakeNamedMaterial "el:part"` in the
|
|
@@ -192,6 +313,9 @@ export class PBRTSceneBuilder {
|
|
|
192
313
|
this._materialBySignature = new Map(); // visual signature -> shared MeshPhysicalMaterial
|
|
193
314
|
this._noUVMaterials = new Map(); // textured material -> its map-less clone
|
|
194
315
|
this._plyCache = new Map(); // filename -> Promise<BufferGeometry|null>
|
|
316
|
+
this._imageCache = new Map(); // filename -> Promise<Texture|null>, decoded once for every named texture
|
|
317
|
+
this._termCache = new Map(); // texture name + channels -> Promise<{rgb}|{layer, tint?}|null>
|
|
318
|
+
this._mediumMaterials = new Map(); // material -> Map(medium name -> material with its attenuation)
|
|
195
319
|
|
|
196
320
|
}
|
|
197
321
|
|
|
@@ -308,6 +432,7 @@ export class PBRTSceneBuilder {
|
|
|
308
432
|
|
|
309
433
|
// Infinite light → environment
|
|
310
434
|
const environment = await this._buildEnvironment( ir.lights );
|
|
435
|
+
await this._buildLamps( ir.lights, group );
|
|
311
436
|
|
|
312
437
|
this._reportUnsupportedLights( ir.lights );
|
|
313
438
|
|
|
@@ -933,6 +1058,58 @@ export class PBRTSceneBuilder {
|
|
|
933
1058
|
|
|
934
1059
|
async _createGeometry( shape ) {
|
|
935
1060
|
|
|
1061
|
+
const geometry = await this._shapeGeometry( shape );
|
|
1062
|
+
return geometry && shape.areaLight && ! pBool( shape.areaLight.params, 'twosided', false ) ? this._facingEmission( shape, geometry ) : geometry;
|
|
1063
|
+
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* A one-sided area light emits on the side pbrt's surface normal faces: towards the vertex normals where the shape
|
|
1068
|
+
* has them, else its winding's side, turned over by ReverseOrientation. The engine emits from a triangle's front,
|
|
1069
|
+
* so a triangle facing the other way is rewound, on an index of its own: the geometry may be shared.
|
|
1070
|
+
* @private
|
|
1071
|
+
*/
|
|
1072
|
+
_facingEmission( shape, geometry ) {
|
|
1073
|
+
|
|
1074
|
+
const normal = geometry.userData.pbrtNormals ? geometry.getAttribute( 'normal' ) : null;
|
|
1075
|
+
if ( ! normal && ! shape.reverseOrientation ) return geometry;
|
|
1076
|
+
|
|
1077
|
+
const p = geometry.getAttribute( 'position' ).array, n = normal?.array;
|
|
1078
|
+
const src = geometry.index?.array ?? null;
|
|
1079
|
+
const count = src ? src.length : geometry.getAttribute( 'position' ).count;
|
|
1080
|
+
const index = new Uint32Array( count );
|
|
1081
|
+
let flipped = 0;
|
|
1082
|
+
|
|
1083
|
+
for ( let t = 0; t + 2 < count; t += 3 ) {
|
|
1084
|
+
|
|
1085
|
+
const a = src ? src[ t ] : t, b = src ? src[ t + 1 ] : t + 1, c = src ? src[ t + 2 ] : t + 2;
|
|
1086
|
+
let flip = shape.reverseOrientation;
|
|
1087
|
+
if ( n ) {
|
|
1088
|
+
|
|
1089
|
+
const ux = p[ b * 3 ] - p[ a * 3 ], uy = p[ b * 3 + 1 ] - p[ a * 3 + 1 ], uz = p[ b * 3 + 2 ] - p[ a * 3 + 2 ];
|
|
1090
|
+
const vx = p[ c * 3 ] - p[ a * 3 ], vy = p[ c * 3 + 1 ] - p[ a * 3 + 1 ], vz = p[ c * 3 + 2 ] - p[ a * 3 + 2 ];
|
|
1091
|
+
const sx = n[ a * 3 ] + n[ b * 3 ] + n[ c * 3 ], sy = n[ a * 3 + 1 ] + n[ b * 3 + 1 ] + n[ c * 3 + 1 ], sz = n[ a * 3 + 2 ] + n[ b * 3 + 2 ] + n[ c * 3 + 2 ];
|
|
1092
|
+
flip = ( uy * vz - uz * vy ) * sx + ( uz * vx - ux * vz ) * sy + ( ux * vy - uy * vx ) * sz < 0;
|
|
1093
|
+
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
index[ t ] = a;
|
|
1097
|
+
index[ t + 1 ] = flip ? c : b;
|
|
1098
|
+
index[ t + 2 ] = flip ? b : c;
|
|
1099
|
+
if ( flip ) flipped ++;
|
|
1100
|
+
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if ( flipped === 0 ) return geometry;
|
|
1104
|
+
const oriented = new BufferGeometry();
|
|
1105
|
+
for ( const name in geometry.attributes ) oriented.setAttribute( name, geometry.getAttribute( name ) );
|
|
1106
|
+
oriented.setIndex( new Uint32BufferAttribute( index, 1 ) );
|
|
1107
|
+
return oriented;
|
|
1108
|
+
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
_shapeGeometry( shape ) {
|
|
1112
|
+
|
|
936
1113
|
switch ( shape.type ) {
|
|
937
1114
|
|
|
938
1115
|
case 'trianglemesh': return this._triangleMesh( shape.params );
|
|
@@ -963,7 +1140,12 @@ export class PBRTSceneBuilder {
|
|
|
963
1140
|
geo.setAttribute( 'position', new Float32BufferAttribute( float32( P ), 3 ) );
|
|
964
1141
|
|
|
965
1142
|
const N = params.N?.value;
|
|
966
|
-
if ( N && N.length === P.length )
|
|
1143
|
+
if ( N && N.length === P.length ) {
|
|
1144
|
+
|
|
1145
|
+
geo.setAttribute( 'normal', new Float32BufferAttribute( float32( N ), 3 ) );
|
|
1146
|
+
geo.userData.pbrtNormals = true;
|
|
1147
|
+
|
|
1148
|
+
}
|
|
967
1149
|
|
|
968
1150
|
const uv = ( params.uv || params.st )?.value;
|
|
969
1151
|
if ( uv && uv.length === ( P.length / 3 ) * 2 ) geo.setAttribute( 'uv', new Float32BufferAttribute( float32( uv ), 2 ) );
|
|
@@ -1100,7 +1282,8 @@ export class PBRTSceneBuilder {
|
|
|
1100
1282
|
|
|
1101
1283
|
}
|
|
1102
1284
|
|
|
1103
|
-
if (
|
|
1285
|
+
if ( geo.getAttribute( 'normal' ) ) geo.userData.pbrtNormals = true;
|
|
1286
|
+
else geo.computeVertexNormals();
|
|
1104
1287
|
return geo;
|
|
1105
1288
|
|
|
1106
1289
|
} catch ( e ) {
|
|
@@ -1153,6 +1336,13 @@ export class PBRTSceneBuilder {
|
|
|
1153
1336
|
|
|
1154
1337
|
async _getMaterial( shape ) {
|
|
1155
1338
|
|
|
1339
|
+
const material = await this._surfaceMaterial( shape );
|
|
1340
|
+
return shape.interior ? this._inMedium( material, shape.interior ) : material;
|
|
1341
|
+
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
async _surfaceMaterial( shape ) {
|
|
1345
|
+
|
|
1156
1346
|
// Cache by (material, areaLight) object identity — many shapes share a
|
|
1157
1347
|
// NamedMaterial, so this dedupes the build + texture-decode work. Nested
|
|
1158
1348
|
// Map keys on the object refs directly (null is a valid key).
|
|
@@ -1166,11 +1356,7 @@ export class PBRTSceneBuilder {
|
|
|
1166
1356
|
|
|
1167
1357
|
if ( byLight.has( shape.areaLight ) ) return byLight.get( shape.areaLight );
|
|
1168
1358
|
|
|
1169
|
-
const ctx =
|
|
1170
|
-
resolveNamedTexture: ( n ) => this._resolveNamedTexture( n ),
|
|
1171
|
-
namedMaterials: this.ir.namedMaterials,
|
|
1172
|
-
warn: ( m ) => this.warn( m )
|
|
1173
|
-
};
|
|
1359
|
+
const ctx = this._materialContext();
|
|
1174
1360
|
const material = await buildMaterial( shape.material, ctx );
|
|
1175
1361
|
|
|
1176
1362
|
if ( shape.areaLight ) await this._applyAreaLight( material, shape.areaLight, ctx );
|
|
@@ -1181,6 +1367,75 @@ export class PBRTSceneBuilder {
|
|
|
1181
1367
|
|
|
1182
1368
|
}
|
|
1183
1369
|
|
|
1370
|
+
_materialContext() {
|
|
1371
|
+
|
|
1372
|
+
return this._ctx ??= {
|
|
1373
|
+
resolveNamedTexture: ( n ) => this._resolveNamedTexture( n ),
|
|
1374
|
+
namedMaterials: this.ir.namedMaterials,
|
|
1375
|
+
warn: ( m ) => this.warn( m ),
|
|
1376
|
+
floatTextureMean: async ( n ) => {
|
|
1377
|
+
|
|
1378
|
+
const term = await this._bakeTerm( n, 1 );
|
|
1379
|
+
return term ? ( term.layer ? layerMean( term.layer ) : term.rgb[ 0 ] ) : null;
|
|
1380
|
+
|
|
1381
|
+
},
|
|
1382
|
+
bakeMaterialMix: ( a, b, amountName ) => this._bakeMaterialMix( a, b, amountName ),
|
|
1383
|
+
resolveNormalMap: async ( filename ) => {
|
|
1384
|
+
|
|
1385
|
+
const image = await this._image( filename );
|
|
1386
|
+
if ( ! image ) return null;
|
|
1387
|
+
const texture = eightBit( image, { srgb: false } );
|
|
1388
|
+
const normalMap = texture === image ? image.clone() : texture;
|
|
1389
|
+
normalMap.colorSpace = NoColorSpace;
|
|
1390
|
+
return normalMap;
|
|
1391
|
+
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Glass filled with a homogeneous medium: what passes straight through it (σt = σa + σs) becomes Beer–Lambert
|
|
1399
|
+
* attenuation, since the engine traces no scattering inside a solid. A medium in an opaque shape changes nothing.
|
|
1400
|
+
* @private
|
|
1401
|
+
*/
|
|
1402
|
+
async _inMedium( material, name ) {
|
|
1403
|
+
|
|
1404
|
+
if ( ! ( material.transmission > 0 ) ) return material;
|
|
1405
|
+
let byName = this._mediumMaterials.get( material );
|
|
1406
|
+
if ( ! byName ) this._mediumMaterials.set( material, byName = new Map() );
|
|
1407
|
+
if ( ! byName.has( name ) ) byName.set( name, this._attenuated( material, name ) );
|
|
1408
|
+
return byName.get( name );
|
|
1409
|
+
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
async _attenuated( material, name ) {
|
|
1413
|
+
|
|
1414
|
+
const params = this.ir.media?.get( name );
|
|
1415
|
+
const type = params && pString( params, 'type', '' );
|
|
1416
|
+
if ( type !== 'homogeneous' ) {
|
|
1417
|
+
|
|
1418
|
+
this.warn( params ? `medium "${name}" (${type}) is not supported — "${name}" glass left clear` : `medium "${name}" not defined` );
|
|
1419
|
+
return material;
|
|
1420
|
+
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if ( params.preset ) this.warn( `medium preset "${pString( params, 'preset', '' )}" is not supported — using sigma_a / sigma_s` );
|
|
1424
|
+
const ctx = this._materialContext();
|
|
1425
|
+
const a = ( await resolveSpectrum( params, 'sigma_a', ctx, [ 1, 1, 1 ] ) ).rgb;
|
|
1426
|
+
const s = ( await resolveSpectrum( params, 'sigma_s', ctx, [ 1, 1, 1 ] ) ).rgb;
|
|
1427
|
+
const scale = pFloat( params, 'scale', 1 );
|
|
1428
|
+
const sigma = [ 0, 1, 2 ].map( ( c ) => ( a[ c ] + s[ c ] ) * scale );
|
|
1429
|
+
const max = Math.max( ...sigma );
|
|
1430
|
+
if ( ! ( max > 0 ) ) return material;
|
|
1431
|
+
|
|
1432
|
+
const variant = material.clone();
|
|
1433
|
+
variant.attenuationDistance = 1 / max;
|
|
1434
|
+
variant.attenuationColor.setRGB( ...sigma.map( ( v ) => Math.exp( - v / max ) ) );
|
|
1435
|
+
return this._dedupeMaterial( variant );
|
|
1436
|
+
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1184
1439
|
/**
|
|
1185
1440
|
* Collapse materials that render identically onto one instance — an inline `Material` per
|
|
1186
1441
|
* shape is a distinct object every time, so every batch would hold exactly one shape.
|
|
@@ -1192,7 +1447,9 @@ export class PBRTSceneBuilder {
|
|
|
1192
1447
|
const key = `${c.r},${c.g},${c.b}|${e.r},${e.g},${e.b}|${material.emissiveIntensity}|` +
|
|
1193
1448
|
`${material.map ? material.map.uuid : '-'}|${material.roughness}|${material.metalness}|` +
|
|
1194
1449
|
`${material.transmission}|${material.ior}|${material.thickness}|` +
|
|
1195
|
-
`${material.clearcoat}|${material.clearcoatRoughness}|${material.opacity}|${material.side}
|
|
1450
|
+
`${material.clearcoat}|${material.clearcoatRoughness}|${material.opacity}|${material.side}|` +
|
|
1451
|
+
`${material.normalMap ? material.normalMap.uuid : '-'}|${material.attenuationDistance}|${material.attenuationColor.toArray()}|` +
|
|
1452
|
+
`${material.specularIntensity}|${material.diffuseTransmission ?? 0}|${material.diffuseTransmissionColor?.toArray() ?? '-'}`;
|
|
1196
1453
|
|
|
1197
1454
|
const existing = this._materialBySignature.get( key );
|
|
1198
1455
|
if ( existing ) return existing;
|
|
@@ -1203,11 +1460,12 @@ export class PBRTSceneBuilder {
|
|
|
1203
1460
|
|
|
1204
1461
|
async _applyAreaLight( material, areaLight, ctx ) {
|
|
1205
1462
|
|
|
1206
|
-
const
|
|
1463
|
+
const rgb = await lightRGB( areaLight.params, 'L', ctx );
|
|
1207
1464
|
const scale = pFloat( areaLight.params, 'scale', 1 );
|
|
1208
|
-
const rgb = L.rgb || [ 1, 1, 1 ];
|
|
1209
1465
|
material.emissive.setRGB( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
|
|
1210
1466
|
material.emissiveIntensity = scale;
|
|
1467
|
+
// pbrt's diffuse area light is one-sided unless "twosided" (_facingEmission turns the triangles).
|
|
1468
|
+
material.side = pBool( areaLight.params, 'twosided', false ) ? DoubleSide : FrontSide;
|
|
1211
1469
|
|
|
1212
1470
|
}
|
|
1213
1471
|
|
|
@@ -1224,22 +1482,11 @@ export class PBRTSceneBuilder {
|
|
|
1224
1482
|
|
|
1225
1483
|
} else if ( def.class === 'imagemap' ) {
|
|
1226
1484
|
|
|
1227
|
-
|
|
1228
|
-
if ( filename ) {
|
|
1229
|
-
|
|
1230
|
-
try {
|
|
1485
|
+
result = await this._imageMapTexture( name, def );
|
|
1231
1486
|
|
|
1232
|
-
|
|
1233
|
-
if ( tex ) result = { texture: tex };
|
|
1234
|
-
else this.warn( `image not found for texture "${name}": ${filename}` );
|
|
1487
|
+
} else if ( def.class === 'mix' ) {
|
|
1235
1488
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
this.warn( `failed to load texture "${name}" (${filename}): ${e.message}` );
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1489
|
+
result = await this._mixTexture( name, def );
|
|
1243
1490
|
|
|
1244
1491
|
} else if ( def.class === 'constant' ) {
|
|
1245
1492
|
|
|
@@ -1276,6 +1523,176 @@ export class PBRTSceneBuilder {
|
|
|
1276
1523
|
|
|
1277
1524
|
}
|
|
1278
1525
|
|
|
1526
|
+
/** A file's image, decoded once however many named textures read it. @private */
|
|
1527
|
+
_image( filename ) {
|
|
1528
|
+
|
|
1529
|
+
let pending = this._imageCache.get( filename );
|
|
1530
|
+
if ( ! pending ) {
|
|
1531
|
+
|
|
1532
|
+
pending = Promise.resolve().then( () => this.resolveImage( filename ) ).catch( ( e ) => {
|
|
1533
|
+
|
|
1534
|
+
this.warn( `failed to load image ${filename}: ${e.message}` );
|
|
1535
|
+
return null;
|
|
1536
|
+
|
|
1537
|
+
} );
|
|
1538
|
+
this._imageCache.set( filename, pending );
|
|
1539
|
+
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
return pending;
|
|
1543
|
+
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* An image texture with pbrt's uv mapping; its `scale` becomes the material's tint, except that a scale above 1 or
|
|
1548
|
+
* `invert` is baked (pbrt clamps an albedo per texel, which a tint cannot).
|
|
1549
|
+
* @private
|
|
1550
|
+
*/
|
|
1551
|
+
async _imageMapTexture( name, def ) {
|
|
1552
|
+
|
|
1553
|
+
const filename = pString( def.params, 'filename', null );
|
|
1554
|
+
if ( ! filename ) return null;
|
|
1555
|
+
const image = await this._image( filename );
|
|
1556
|
+
if ( ! image ) {
|
|
1557
|
+
|
|
1558
|
+
this.warn( `image not found for texture "${name}": ${filename}` );
|
|
1559
|
+
return null;
|
|
1560
|
+
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
const mapping = pString( def.params, 'mapping', 'uv' );
|
|
1564
|
+
if ( mapping !== 'uv' ) this.warn( `texture "${name}": "${mapping}" mapping is not supported — using the mesh's uv` );
|
|
1565
|
+
|
|
1566
|
+
const scale = pFloat( def.params, 'scale', 1 );
|
|
1567
|
+
if ( scale > 1 || pBool( def.params, 'invert', false ) ) {
|
|
1568
|
+
|
|
1569
|
+
const term = await this._bakeTerm( name, 3 );
|
|
1570
|
+
const baked = term?.layer && bake( [ { ...term, clamp: true } ], ( [ v ] ) => v );
|
|
1571
|
+
if ( baked ) return { texture: bakedTexture( baked ) };
|
|
1572
|
+
this.warn( `texture "${name}": its scale or invert could not be baked here — ignored` );
|
|
1573
|
+
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
const color = eightBit( image, { srgb: true } );
|
|
1577
|
+
const texture = color === image ? image.clone() : color;
|
|
1578
|
+
setMapping( texture, uvMapping( def.params ) );
|
|
1579
|
+
texture.needsUpdate = true;
|
|
1580
|
+
return scale < 1 ? { texture, constant: [ scale, scale, scale ] } : { texture };
|
|
1581
|
+
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/** pbrt's `mix` texture, ( 1 − amount ) · tex1 + amount · tex2, baked when any of the three is an image. @private */
|
|
1585
|
+
async _mixTexture( name, def ) {
|
|
1586
|
+
|
|
1587
|
+
const [ a, b, amount ] = await Promise.all( [
|
|
1588
|
+
this._paramTerm( def.params, 'tex1', 3, [ 0, 0, 0 ] ),
|
|
1589
|
+
this._paramTerm( def.params, 'tex2', 3, [ 1, 1, 1 ] ),
|
|
1590
|
+
this._paramTerm( def.params, 'amount', 1, [ 0.5, 0.5, 0.5 ] )
|
|
1591
|
+
] );
|
|
1592
|
+
if ( ! a || ! b || ! amount ) {
|
|
1593
|
+
|
|
1594
|
+
this.warn( `texture "${name}": a "mix" input could not be read here` );
|
|
1595
|
+
return null;
|
|
1596
|
+
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const unit = ( rgb ) => rgb.map( ( v ) => Math.min( 1, Math.max( 0, v ) ) );
|
|
1600
|
+
const baked = bake( [ { ...a, clamp: true }, { ...b, clamp: true }, amount ], mixOf );
|
|
1601
|
+
return baked ? { texture: bakedTexture( baked ) } : { constant: mixOf( [ unit( a.rgb ), unit( b.rgb ), amount.rgb ] ) };
|
|
1602
|
+
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/** A texture parameter as a bake term: a constant, or the texture it names. @private */
|
|
1606
|
+
async _paramTerm( params, key, channels, dflt ) {
|
|
1607
|
+
|
|
1608
|
+
const p = params[ key ];
|
|
1609
|
+
if ( ! p ) return { rgb: dflt };
|
|
1610
|
+
if ( p.type === 'texture' ) return this._bakeTerm( p.value[ 0 ], channels );
|
|
1611
|
+
const rgb = ( await resolveSpectrum( params, key, this._materialContext(), dflt ) ).rgb;
|
|
1612
|
+
return rgb ? { rgb } : null;
|
|
1613
|
+
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* A named texture as pbrt evaluates it, for baking: `{ rgb }` for a constant, `{ layer, tint? }` for an image;
|
|
1618
|
+
* null when it cannot be read in this runtime.
|
|
1619
|
+
* @param {string} name
|
|
1620
|
+
* @param {number} channels - 1 where it is read as a float texture (its red channel), 3 as a spectrum
|
|
1621
|
+
* @private
|
|
1622
|
+
*/
|
|
1623
|
+
_bakeTerm( name, channels ) {
|
|
1624
|
+
|
|
1625
|
+
const key = `${name}\0${channels}`;
|
|
1626
|
+
let pending = this._termCache.get( key );
|
|
1627
|
+
if ( ! pending ) this._termCache.set( key, pending = this._createBakeTerm( name, channels ) );
|
|
1628
|
+
return pending;
|
|
1629
|
+
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
async _createBakeTerm( name, channels ) {
|
|
1633
|
+
|
|
1634
|
+
const def = this.ir.namedTextures.get( name );
|
|
1635
|
+
if ( def?.class === 'imagemap' ) {
|
|
1636
|
+
|
|
1637
|
+
if ( pString( def.params, 'mapping', 'uv' ) !== 'uv' ) return null;
|
|
1638
|
+
const filename = pString( def.params, 'filename', null );
|
|
1639
|
+
const pixels = texturePixels( filename && await this._image( filename ) );
|
|
1640
|
+
if ( ! pixels ) return null;
|
|
1641
|
+
return { layer: makeLayer( pixels, {
|
|
1642
|
+
channels, decode: pixels.float ? null : decoderFor( def.params ),
|
|
1643
|
+
scale: pFloat( def.params, 'scale', 1 ), invert: pBool( def.params, 'invert', false ), mapping: uvMapping( def.params )
|
|
1644
|
+
} ) };
|
|
1645
|
+
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
const resolved = await this._resolveNamedTexture( name );
|
|
1649
|
+
if ( ! resolved ) return null;
|
|
1650
|
+
if ( ! resolved.texture ) return resolved.constant ? { rgb: resolved.constant } : null;
|
|
1651
|
+
const layer = this._textureLayer( resolved.texture, channels );
|
|
1652
|
+
return layer ? { layer, tint: resolved.constant ?? null } : null;
|
|
1653
|
+
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// A texture already built for a material (an image or a bake) as a layer, in the mapping it carries.
|
|
1657
|
+
_textureLayer( texture, channels ) {
|
|
1658
|
+
|
|
1659
|
+
const pixels = texturePixels( texture );
|
|
1660
|
+
if ( ! pixels ) return null;
|
|
1661
|
+
const srgb = ! pixels.float && texture.colorSpace === SRGBColorSpace;
|
|
1662
|
+
return makeLayer( pixels, {
|
|
1663
|
+
channels, decode: pixels.float ? null : ( srgb ? srgbToLinear : ( v ) => v ),
|
|
1664
|
+
mapping: { su: texture.repeat.x, sv: texture.repeat.y, du: texture.offset.x, dv: texture.offset.y }
|
|
1665
|
+
} );
|
|
1666
|
+
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* A mix material whose amount is a texture: its two materials' colours (map × colour) baked by that texture, and
|
|
1671
|
+
* the texture's mean to weigh everything else. Null when nothing here can read it.
|
|
1672
|
+
* @private
|
|
1673
|
+
*/
|
|
1674
|
+
async _bakeMaterialMix( matA, matB, amountName ) {
|
|
1675
|
+
|
|
1676
|
+
const amount = await this._bakeTerm( amountName, 1 );
|
|
1677
|
+
if ( ! amount ) return null;
|
|
1678
|
+
if ( ! amount.layer ) return { mean: amount.rgb[ 0 ], texture: null };
|
|
1679
|
+
|
|
1680
|
+
const mean = layerMean( amount.layer );
|
|
1681
|
+
const term = ( m ) => {
|
|
1682
|
+
|
|
1683
|
+
if ( ! m.map ) return { rgb: m.color.toArray(), clamp: true };
|
|
1684
|
+
const layer = this._textureLayer( m.map, 3 );
|
|
1685
|
+
return layer && { layer, tint: m.color.toArray(), clamp: true };
|
|
1686
|
+
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
const a = term( matA ), b = term( matB );
|
|
1690
|
+
if ( ! a || ! b ) return { mean, texture: null };
|
|
1691
|
+
const baked = bake( [ a, b, amount ], mixOf );
|
|
1692
|
+
return { mean, texture: baked ? bakedTexture( baked ) : null };
|
|
1693
|
+
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1279
1696
|
_colorFromLikeNamedMaterial( texName ) {
|
|
1280
1697
|
|
|
1281
1698
|
for ( const candidate of materialNameCandidates( texName ) ) {
|
|
@@ -1297,6 +1714,25 @@ export class PBRTSceneBuilder {
|
|
|
1297
1714
|
async _resolveScaleTexture( name, def ) {
|
|
1298
1715
|
|
|
1299
1716
|
// pbrt-v4 uses "tex" (the inner texture or constant) and "scale" (the multiplier).
|
|
1717
|
+
if ( def.params.scale?.type === 'texture' ) {
|
|
1718
|
+
|
|
1719
|
+
// A texture multiplier (kroken's bricks: colour × dirt) is baked.
|
|
1720
|
+
const [ inner, factor ] = await Promise.all( [
|
|
1721
|
+
this._paramTerm( def.params, 'tex', 3, [ 1, 1, 1 ] ),
|
|
1722
|
+
this._paramTerm( def.params, 'scale', 1, [ 1, 1, 1 ] )
|
|
1723
|
+
] );
|
|
1724
|
+
const product = ( [ a, f ] ) => [ a[ 0 ] * f[ 0 ], a[ 1 ] * f[ 0 ], a[ 2 ] * f[ 0 ] ];
|
|
1725
|
+
if ( inner && factor ) {
|
|
1726
|
+
|
|
1727
|
+
const baked = bake( [ { ...inner, clamp: true }, factor ], product );
|
|
1728
|
+
return baked ? { texture: bakedTexture( baked ) } : { constant: product( [ inner.rgb, factor.rgb ] ) };
|
|
1729
|
+
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
this.warn( `texture "${name}": its "scale" texture could not be read here — ignored` );
|
|
1733
|
+
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1300
1736
|
const innerP = def.params.tex;
|
|
1301
1737
|
let inner = null;
|
|
1302
1738
|
|
|
@@ -1500,8 +1936,7 @@ export class PBRTSceneBuilder {
|
|
|
1500
1936
|
|
|
1501
1937
|
// Constant-radiance infinite light → tiny float texture (CDF-buildable).
|
|
1502
1938
|
const ctx = { resolveNamedTexture: async () => null, warn: ( m ) => this.warn( m ) };
|
|
1503
|
-
const
|
|
1504
|
-
const rgb = ( L.rgb || [ 1, 1, 1 ] ).map( v => v * scale );
|
|
1939
|
+
const rgb = ( await lightRGB( inf.params, 'L', ctx ) ).map( v => v * scale );
|
|
1505
1940
|
|
|
1506
1941
|
const w = 2, h = 1;
|
|
1507
1942
|
const data = new Float32Array( w * h * 4 );
|
|
@@ -1523,13 +1958,89 @@ export class PBRTSceneBuilder {
|
|
|
1523
1958
|
|
|
1524
1959
|
}
|
|
1525
1960
|
|
|
1961
|
+
/**
|
|
1962
|
+
* `distant`, `point` and `spot` lights as three.js lamps, in the engine's units: a directional lamp's
|
|
1963
|
+
* intensity is the irradiance it gives (pbrt's L), a point or spot lamp's is 4π × its radiant intensity.
|
|
1964
|
+
*/
|
|
1965
|
+
async _buildLamps( lights, group ) {
|
|
1966
|
+
|
|
1967
|
+
const ctx = { resolveNamedTexture: async () => null, warn: ( m ) => this.warn( m ) };
|
|
1968
|
+
for ( const l of lights ) {
|
|
1969
|
+
|
|
1970
|
+
if ( ! LAMP_TYPES.has( l.type ) ) continue;
|
|
1971
|
+
|
|
1972
|
+
const world = new Matrix4().fromArray( this.convertHandedness ? M.multiply( FLIP_Z, l.ctm ) : l.ctm );
|
|
1973
|
+
const point = ( name, dflt ) => {
|
|
1974
|
+
|
|
1975
|
+
const v = l.params[ name ]?.value;
|
|
1976
|
+
return new Vector3().fromArray( v?.length >= 3 ? v : dflt ).applyMatrix4( world );
|
|
1977
|
+
|
|
1978
|
+
};
|
|
1979
|
+
|
|
1980
|
+
const rgb = await lightRGB( l.params, l.type === 'distant' ? 'L' : 'I', ctx );
|
|
1981
|
+
let scale = pFloat( l.params, 'scale', 1 );
|
|
1982
|
+
const from = point( 'from', [ 0, 0, 0 ] );
|
|
1983
|
+
const to = point( 'to', [ 0, 0, 1 ] );
|
|
1984
|
+
let light;
|
|
1985
|
+
if ( l.type === 'distant' ) {
|
|
1986
|
+
|
|
1987
|
+
const illuminance = pFloat( l.params, 'illuminance', - 1 );
|
|
1988
|
+
if ( illuminance > 0 ) scale *= illuminance;
|
|
1989
|
+
light = new DirectionalLight();
|
|
1990
|
+
light.userData.__luxConverted = true;
|
|
1991
|
+
|
|
1992
|
+
} else if ( l.type === 'point' ) {
|
|
1993
|
+
|
|
1994
|
+
// `power` sets the radiant intensity to power / 4π.
|
|
1995
|
+
const power = pFloat( l.params, 'power', - 1 );
|
|
1996
|
+
scale *= power > 0 ? power : 4 * Math.PI;
|
|
1997
|
+
light = new PointLight();
|
|
1998
|
+
light.userData.__candelaConverted = true;
|
|
1999
|
+
|
|
2000
|
+
} else {
|
|
2001
|
+
|
|
2002
|
+
// pbrt's falloff is smoothstep( cos cone, cos( cone − delta ), cos θ ); the engine's blend is that width over 1 − cos cone.
|
|
2003
|
+
const cone = Math.min( Math.max( pFloat( l.params, 'coneangle', 30 ), 0 ), 90 ) * Math.PI / 180;
|
|
2004
|
+
const delta = Math.min( Math.max( pFloat( l.params, 'conedeltaangle', 5 ) * Math.PI / 180, 0 ), cone );
|
|
2005
|
+
const cosEnd = Math.cos( cone ), cosStart = Math.cos( cone - delta );
|
|
2006
|
+
const power = pFloat( l.params, 'power', - 1 );
|
|
2007
|
+
if ( power > 0 ) scale *= power / ( 2 * Math.PI * ( ( 1 - cosStart ) + ( cosStart - cosEnd ) / 2 ) );
|
|
2008
|
+
scale *= 4 * Math.PI;
|
|
2009
|
+
light = new SpotLight();
|
|
2010
|
+
light.angle = cone;
|
|
2011
|
+
light.penumbra = cosEnd < 1 ? Math.min( ( cosStart - cosEnd ) / ( 1 - cosEnd ), 1 ) : 0;
|
|
2012
|
+
light.userData.__candelaConverted = true;
|
|
2013
|
+
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
const peak = Math.max( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
|
|
2017
|
+
if ( ! ( peak > 0 ) || ! ( scale > 0 ) ) continue;
|
|
2018
|
+
light.color.setRGB( rgb[ 0 ] / peak, rgb[ 1 ] / peak, rgb[ 2 ] / peak );
|
|
2019
|
+
light.intensity = peak * scale;
|
|
2020
|
+
light.name = `pbrt ${l.type}`;
|
|
2021
|
+
light.position.copy( from );
|
|
2022
|
+
light.decay = 2;
|
|
2023
|
+
light.distance = 0;
|
|
2024
|
+
if ( light.target ) {
|
|
2025
|
+
|
|
2026
|
+
light.target.position.subVectors( to, from );
|
|
2027
|
+
light.add( light.target );
|
|
2028
|
+
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
group.add( light );
|
|
2032
|
+
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1526
2037
|
_reportUnsupportedLights( lights ) {
|
|
1527
2038
|
|
|
1528
2039
|
for ( const l of lights ) {
|
|
1529
2040
|
|
|
1530
|
-
if ( l.type !== 'infinite' ) {
|
|
2041
|
+
if ( l.type !== 'infinite' && ! LAMP_TYPES.has( l.type ) ) {
|
|
1531
2042
|
|
|
1532
|
-
this.warn( `light "${l.type}" not supported (only infinite lights and emissive area lights are mapped)` );
|
|
2043
|
+
this.warn( `light "${l.type}" not supported (only infinite, distant, point and spot lights and emissive area lights are mapped)` );
|
|
1533
2044
|
|
|
1534
2045
|
}
|
|
1535
2046
|
|