rayzee 10.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/README.md +252 -44
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-Hn35o6Z5.js +5145 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -0
- package/dist/BufferLayout-D_IBo2ci.js +150 -0
- package/dist/ColorManagement-oeEk7oSu.js +1488 -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-dDDGj9Av.js +97 -0
- package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BzNpiVHZ.js} +8 -6
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-Bk7bHg2B.js} +55 -120
- package/dist/PathTracer-72XufKAr.js +29976 -0
- package/dist/PhysicalSky-BgjdV88k.js +1219 -0
- package/dist/RayzeeRenderer-1QRO97eq.js +3551 -0
- package/dist/SolarLimb-CPaeZVwz.js +721 -0
- package/dist/ToneMapGPU-CjU3osDd.js +177 -0
- package/dist/ViewTransforms-CYN7bq2w.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-tnAoUL1j.js +94 -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 +2551 -44024
- package/dist/rayzee.umd.js +528 -487
- package/dist/sceneCachePolicy-C67jbci-.js +443 -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/Color/ViewTransforms.js +5 -1
- package/src/EngineDefaults.js +62 -1029
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +2 -0
- package/src/Headless.js +6 -5
- package/src/Passes/AIUpscaler.js +58 -141
- package/src/Passes/OIDNDenoiser.js +92 -65
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +620 -4026
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +20 -7
- package/src/Processor/ArchiveImporter.js +1265 -0
- package/src/Processor/ArchiveReader.js +22 -4
- package/src/Processor/AssetLoader.js +327 -1763
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +14 -126
- package/src/Processor/BVHLeafFold.js +4 -6
- package/src/Processor/BVHRefitter.js +67 -46
- package/src/Processor/BufferLayout.js +291 -0
- package/src/Processor/EmissiveTriangleBuilder.js +1 -1
- 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 +15 -5
- package/src/Processor/HostMemory.js +7 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/InstanceTable.js +1 -1
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +10 -8
- package/src/Processor/PBRT/PBRTMaterials.js +254 -42
- package/src/Processor/PBRT/PBRTParser.js +49 -19
- package/src/Processor/PBRT/PBRTScattering.js +93 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +1050 -75
- package/src/Processor/PBRT/PBRTTextureBake.js +179 -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 +39 -5
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneMetadata.js +30 -5
- package/src/Processor/SceneProcessor.js +53 -62
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/SplitBLAS.js +1 -1
- package/src/Processor/StorageTexturePool.js +22 -1
- package/src/Processor/TLASBuilder.js +1 -3
- package/src/Processor/TextureBuckets.js +237 -0
- package/src/Processor/TextureCreator.js +181 -8
- package/src/Processor/ToneMapGPU.js +64 -15
- package/src/Processor/Workers/BVHRefitWorker.js +5 -2
- 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/Workers/TexturesWorker.js +103 -18
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3900 -0
- package/src/RenderSettings.js +68 -62
- package/src/Stages/ASVGF.js +13 -14
- package/src/Stages/AutoExposure.js +17 -17
- package/src/Stages/BilateralFilter.js +2 -1
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/DenoiserSettings.js +182 -0
- package/src/Stages/EdgeFilter.js +2 -1
- package/src/Stages/MotionVector.js +1 -1
- package/src/Stages/NRD.js +11 -5
- package/src/Stages/NormalDepth.js +12 -8
- package/src/Stages/PathTracer.js +336 -43
- package/src/Stages/PathTracerStage.js +69 -66
- package/src/Stages/Variance.js +1 -1
- 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 +45 -9
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +50 -25
- package/src/TSL/Bidirectional.js +208 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +20 -15
- package/src/TSL/ConnectKernel.js +167 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +65 -18
- 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 +180 -0
- package/src/TSL/LightVisibility.js +111 -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 +239 -51
- 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 +224 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +5 -1
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +985 -101
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +66 -61
- 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 +31 -0
- package/src/index.js +13 -23
- package/src/integrators/BidirectionalIntegrator.js +838 -0
- package/src/managers/AnimationManager.js +88 -6
- package/src/managers/CameraManager.js +103 -46
- package/src/managers/DenoisingManager.js +14 -10
- package/src/managers/EnvironmentManager.js +125 -103
- package/src/managers/GoboManager.js +9 -0
- package/src/managers/MaterialDataManager.js +48 -37
- package/src/managers/UniformManager.js +32 -10
- package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -125,19 +125,39 @@ export function parseIES( text, name = 'ies' ) {
|
|
|
125
125
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* The horizontal angle a type C profile stores for `hDeg` in [0, 360), by the symmetry its range implies
|
|
130
|
+
* (LM-63): one angle — the same all round; 0–90 — mirrored about the 0–180 and 90–270 planes; 0–180 — about the
|
|
131
|
+
* 0–180 plane; 90–270 — about the 90–270 plane; 0–360 — none.
|
|
132
|
+
*/
|
|
133
|
+
function storedHorizontalAngle( hDeg, hMin, hMax ) {
|
|
134
|
+
|
|
135
|
+
if ( hMin === 0 && hMax === 90 ) {
|
|
136
|
+
|
|
137
|
+
const half = hDeg % 180;
|
|
138
|
+
return half > 90 ? 180 - half : half;
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if ( hMin === 0 && hMax === 180 ) return hDeg > 180 ? 360 - hDeg : hDeg;
|
|
143
|
+
if ( hMin === 90 && hMax === 270 ) return hDeg >= 90 && hDeg <= 270 ? hDeg : ( ( 540 - hDeg ) % 360 );
|
|
144
|
+
return hDeg;
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
128
148
|
/**
|
|
129
149
|
* Resample an IESProfile onto a fixed-size 2D grid suitable for a
|
|
130
150
|
* DataArrayTexture layer. Values are normalised to [0,1] by `maxCandela`
|
|
131
151
|
* so the shader gets a pure shape multiplier; absolute intensity scaling
|
|
132
152
|
* stays in `light.intensity`.
|
|
133
153
|
*
|
|
134
|
-
* UV convention:
|
|
154
|
+
* UV convention (TSL/LightsCore.js sampleIESProfile reads it so):
|
|
135
155
|
* U = horizontal angle / 360 (0..1)
|
|
136
156
|
* V = vertical angle / 180 (0..1, 0 = bulb axis, 1 = opposite axis)
|
|
137
157
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
158
|
+
* The grid always spans the whole sphere: a downlight's 0–90° fills the lower half and leaves the upper dark, and
|
|
159
|
+
* a horizontal range with a symmetry is mirrored round (storedHorizontalAngle). Angles outside the file's range
|
|
160
|
+
* emit nothing. Type A and B profiles, whose angles mean something else, are stretched over the grid as before.
|
|
141
161
|
*
|
|
142
162
|
* @param {IESProfile} profile
|
|
143
163
|
* @param {number} width - texture width in samples (horizontal/U axis)
|
|
@@ -147,20 +167,34 @@ export function parseIES( text, name = 'ies' ) {
|
|
|
147
167
|
export function resampleIESToGrid( profile, width, height ) {
|
|
148
168
|
|
|
149
169
|
const data = new Uint8Array( width * height );
|
|
150
|
-
const { verticalAngles: vA, horizontalAngles: hA, candela, maxCandela } = profile;
|
|
170
|
+
const { verticalAngles: vA, horizontalAngles: hA, candela, maxCandela, photometricType } = profile;
|
|
151
171
|
|
|
152
172
|
if ( maxCandela <= 0 ) return data; // dead profile → zeros
|
|
153
173
|
|
|
154
|
-
const
|
|
155
|
-
const
|
|
174
|
+
const typeC = photometricType === undefined || photometricType === 1;
|
|
175
|
+
const vMin = vA[ 0 ];
|
|
176
|
+
const vMax = vA[ vA.length - 1 ];
|
|
156
177
|
const hMin = hA[ 0 ];
|
|
157
|
-
const
|
|
178
|
+
const hMax = hA[ hA.length - 1 ];
|
|
179
|
+
const rotationallySymmetric = hA.length === 1 || hMax === hMin;
|
|
180
|
+
|
|
181
|
+
const at = ( v0, v1, vt, hDeg ) => {
|
|
182
|
+
|
|
183
|
+
if ( rotationallySymmetric ) return lerp( candela[ v0 ][ 0 ], candela[ v1 ][ 0 ], vt );
|
|
184
|
+
if ( hDeg < hMin || hDeg > hMax ) return 0;
|
|
185
|
+
const h0 = lowerBoundIdx( hA, hDeg );
|
|
186
|
+
const h1 = Math.min( h0 + 1, hA.length - 1 );
|
|
187
|
+
const hSpan = hA[ h1 ] - hA[ h0 ];
|
|
188
|
+
const ht = hSpan > 0 ? ( hDeg - hA[ h0 ] ) / hSpan : 0;
|
|
189
|
+
return lerp( lerp( candela[ v0 ][ h0 ], candela[ v0 ][ h1 ], ht ), lerp( candela[ v1 ][ h0 ], candela[ v1 ][ h1 ], ht ), vt );
|
|
190
|
+
|
|
191
|
+
};
|
|
158
192
|
|
|
159
193
|
for ( let py = 0; py < height; py ++ ) {
|
|
160
194
|
|
|
161
|
-
// V coordinate → vertical angle in degrees. Texture covers [0, vMaxDeg].
|
|
162
195
|
const tV = ( py + 0.5 ) / height;
|
|
163
|
-
const vDeg = tV *
|
|
196
|
+
const vDeg = typeC ? tV * 180 : vMin + tV * ( vMax - vMin );
|
|
197
|
+
if ( vDeg < vMin || vDeg > vMax ) continue;
|
|
164
198
|
const v0 = lowerBoundIdx( vA, vDeg );
|
|
165
199
|
const v1 = Math.min( v0 + 1, vA.length - 1 );
|
|
166
200
|
const vSpan = vA[ v1 ] - vA[ v0 ];
|
|
@@ -168,31 +202,9 @@ export function resampleIESToGrid( profile, width, height ) {
|
|
|
168
202
|
|
|
169
203
|
for ( let px = 0; px < width; px ++ ) {
|
|
170
204
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Single column → just bilerp on V axis.
|
|
176
|
-
cd = lerp( candela[ v0 ][ 0 ], candela[ v1 ][ 0 ], vt );
|
|
177
|
-
|
|
178
|
-
} else {
|
|
179
|
-
|
|
180
|
-
const tH = ( px + 0.5 ) / width;
|
|
181
|
-
const hDeg = hMin + tH * ( hMaxDeg - hMin );
|
|
182
|
-
const h0 = lowerBoundIdx( hA, hDeg );
|
|
183
|
-
const h1 = Math.min( h0 + 1, hA.length - 1 );
|
|
184
|
-
const hSpan = hA[ h1 ] - hA[ h0 ];
|
|
185
|
-
const ht = hSpan > 0 ? ( hDeg - hA[ h0 ] ) / hSpan : 0;
|
|
186
|
-
|
|
187
|
-
const c00 = candela[ v0 ][ h0 ];
|
|
188
|
-
const c10 = candela[ v0 ][ h1 ];
|
|
189
|
-
const c01 = candela[ v1 ][ h0 ];
|
|
190
|
-
const c11 = candela[ v1 ][ h1 ];
|
|
191
|
-
cd = lerp( lerp( c00, c10, ht ), lerp( c01, c11, ht ), vt );
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const norm = Math.min( 1, Math.max( 0, cd / maxCandela ) );
|
|
205
|
+
const tH = ( px + 0.5 ) / width;
|
|
206
|
+
const hDeg = typeC ? storedHorizontalAngle( tH * 360, hMin, hMax ) : hMin + tH * ( hMax - hMin );
|
|
207
|
+
const norm = Math.min( 1, Math.max( 0, at( v0, v1, vt, hDeg ) / maxCandela ) );
|
|
196
208
|
data[ py * width + px ] = Math.round( norm * 255 );
|
|
197
209
|
|
|
198
210
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* denormalised one. World bounds and the inverse transform are derived on demand.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from './BufferLayout.js';
|
|
13
13
|
|
|
14
14
|
const IDENTITY = Float64Array.from( [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] );
|
|
15
15
|
|
|
@@ -95,11 +95,44 @@ export class KernelManager {
|
|
|
95
95
|
* Register a pre-built compute node.
|
|
96
96
|
* @param {string} name - Kernel name (e.g. 'generate', 'extend')
|
|
97
97
|
* @param {ComputeNode} computeNode - Built via `Fn().compute([dx,dy,dz], [wgx,wgy,wgz])`
|
|
98
|
+
* @param {{eager?: boolean}} [options] - `eager: false` leaves it out of compile(): it compiles at its first dispatch
|
|
98
99
|
*/
|
|
99
|
-
register( name, computeNode ) {
|
|
100
|
+
register( name, computeNode, { eager = true } = {} ) {
|
|
100
101
|
|
|
101
102
|
this.kernels.set( name, computeNode );
|
|
102
|
-
this.timing.set( name, { compiledOnce: false, lastDispatchMs: 0 } );
|
|
103
|
+
this.timing.set( name, { compiledOnce: false, lastDispatchMs: 0, eager } );
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether compile() can run here: three yields between build steps through scheduler.yield or requestAnimationFrame,
|
|
109
|
+
* and Node has neither (nor a page to keep responsive).
|
|
110
|
+
*/
|
|
111
|
+
static get canCompileInBackground() {
|
|
112
|
+
|
|
113
|
+
return typeof globalThis.requestAnimationFrame === 'function' || typeof globalThis.scheduler?.yield === 'function';
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Compiles the eager kernels without blocking the page: three.js generates their WGSL in steps that yield and
|
|
119
|
+
* creates the GPU pipelines asynchronously. A dispatch after it compiles nothing.
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
*/
|
|
122
|
+
async compile() {
|
|
123
|
+
|
|
124
|
+
const eager = [ ...this.kernels ].filter( ( [ name ] ) => this.timing.get( name )?.eager );
|
|
125
|
+
const t0 = performance.now();
|
|
126
|
+
// One call per kernel, all at once: the GPU compiles them side by side rather than each after the last.
|
|
127
|
+
await Promise.all( eager.map( ( [ , node ] ) => this.renderer.compileComputeAsync( node ) ) );
|
|
128
|
+
for ( const [ name ] of eager ) {
|
|
129
|
+
|
|
130
|
+
const entry = this.timing.get( name );
|
|
131
|
+
if ( entry ) entry.compiledOnce = true;
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
log.info( `${eager.length} kernels compiled in the background in ${fmt.ms( performance.now() - t0 )}` );
|
|
103
136
|
|
|
104
137
|
}
|
|
105
138
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MATERIAL_DEFAULTS as D } from '../EngineDefaults.js';
|
|
2
|
+
import { MATERIAL_DATA_LAYOUT as M, normalizeAttenuationDistance } from './BufferLayout.js';
|
|
2
3
|
|
|
3
4
|
// The first 8 Matrix3 elements; the GPU rebuilds the 9th (always 1).
|
|
4
5
|
const IDENTITY_UV = [ 1, 0, 0, 0, 1, 0, 0, 0 ];
|
|
@@ -15,8 +16,7 @@ const TRANSFORMS = [
|
|
|
15
16
|
|
|
16
17
|
/** Floats no field writes; the packer gives them fixed values. */
|
|
17
18
|
export const MATERIAL_RESERVED_FLOATS = Object.freeze( [
|
|
18
|
-
M.VISIBLE, M.
|
|
19
|
-
M.SPECULAR_COLOR_MAP_INDEX + 1, M.SPECULAR_COLOR_MAP_INDEX + 2, M.SPECULAR_COLOR_MAP_INDEX + 3,
|
|
19
|
+
M.VISIBLE, M.DISPLACEMENT_MAP_INDEX + 1, M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 1, M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 2,
|
|
20
20
|
] );
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -25,7 +25,7 @@ export const MATERIAL_RESERVED_FLOATS = Object.freeze( [
|
|
|
25
25
|
*/
|
|
26
26
|
export const UNIT_RANGE_PROPERTIES = Object.freeze( new Set( [
|
|
27
27
|
'metalness', 'roughness', 'transmission', 'opacity', 'clearcoat', 'clearcoatRoughness',
|
|
28
|
-
'sheen', 'sheenRoughness', 'specularIntensity', 'iridescence', 'subsurface', 'anisotropy',
|
|
28
|
+
'sheen', 'sheenRoughness', 'specularIntensity', 'iridescence', 'subsurface', 'anisotropy', 'diffuseTransmission',
|
|
29
29
|
] ) );
|
|
30
30
|
|
|
31
31
|
export const clampUnit = ( value ) => Math.min( Math.max( value, 0 ), 1 );
|
|
@@ -116,6 +116,7 @@ export function packMaterial( data, base, mat ) {
|
|
|
116
116
|
u( M.SHEEN_ROUGHNESS, mat.sheenRoughness, D.sheenRoughness );
|
|
117
117
|
|
|
118
118
|
writeTriple( data, base + M.SHEEN_COLOR, mat.sheenColor, D.sheenColor );
|
|
119
|
+
u( M.DIFFUSE_TRANSMISSION, mat.diffuseTransmission, D.diffuseTransmission );
|
|
119
120
|
|
|
120
121
|
u( M.SPECULAR_INTENSITY, mat.specularIntensity, D.specularIntensity );
|
|
121
122
|
writeTriple( data, base + M.SPECULAR_COLOR, mat.specularColor, D.specularColor );
|
|
@@ -157,12 +158,13 @@ export function packMaterial( data, base, mat ) {
|
|
|
157
158
|
data[ base + M.SPECULAR_INTENSITY_MAP_INDEX ] = mapIndex( mat.specularIntensityMap );
|
|
158
159
|
|
|
159
160
|
data[ base + M.SPECULAR_COLOR_MAP_INDEX ] = mapIndex( mat.specularColorMap );
|
|
161
|
+
writeTriple( data, base + M.DIFFUSE_TRANSMISSION_COLOR, mat.diffuseTransmissionColor, D.diffuseTransmissionColor );
|
|
162
|
+
data[ base + M.DIFFUSE_TRANSMISSION_MAP_INDEX ] = mapIndex( mat.diffuseTransmissionMap );
|
|
163
|
+
data[ base + M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX ] = mapIndex( mat.diffuseTransmissionColorMap );
|
|
160
164
|
|
|
161
165
|
data[ base + M.VISIBLE ] = 1;
|
|
162
|
-
data[ base + M.SHEEN_COLOR + 3 ] = 1;
|
|
163
166
|
data[ base + M.DISPLACEMENT_MAP_INDEX + 1 ] = 0;
|
|
164
|
-
data[ base + M.
|
|
165
|
-
data[ base + M.
|
|
166
|
-
data[ base + M.SPECULAR_COLOR_MAP_INDEX + 3 ] = 0;
|
|
167
|
+
data[ base + M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 1 ] = 0;
|
|
168
|
+
data[ base + M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 2 ] = 0;
|
|
167
169
|
|
|
168
170
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { MeshPhysicalMaterial, Color, DoubleSide } from 'three';
|
|
12
|
+
import { namedScattering, dipoleReflectance, albedoForReflectance } from './PBRTScattering.js';
|
|
12
13
|
|
|
13
14
|
// Normal-incidence reflectance approximations for pbrt's named conductor spectra.
|
|
14
15
|
const METAL_ALBEDO = {
|
|
@@ -65,6 +66,13 @@ export function pString( params, name, dflt ) {
|
|
|
65
66
|
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
export function pBool( params, name, dflt ) {
|
|
70
|
+
|
|
71
|
+
const v = params[ name ]?.value?.[ 0 ];
|
|
72
|
+
return v === undefined ? dflt : v === true || v === 'true';
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
68
76
|
/**
|
|
69
77
|
* Resolve a spectrum/color/float-valued parameter to an RGB triple and/or a
|
|
70
78
|
* texture. Returns `{ rgb, texture }` — exactly one is typically non-null.
|
|
@@ -144,15 +152,191 @@ function namedMetalKey( s ) {
|
|
|
144
152
|
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
function
|
|
155
|
+
// A float parameter's value, or a float texture's mean (`ctx.floatTextureMean`); null when absent or unreadable.
|
|
156
|
+
async function floatOrMean( params, name, ctx ) {
|
|
157
|
+
|
|
158
|
+
const p = params[ name ];
|
|
159
|
+
if ( ! p ) return null;
|
|
160
|
+
if ( typeof p.value[ 0 ] === 'number' ) return p.value[ 0 ];
|
|
161
|
+
if ( p.type === 'texture' ) return ( await ctx.floatTextureMean?.( p.value[ 0 ] ) ) ?? null;
|
|
162
|
+
return null;
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The engine's roughness for pbrt's `roughness` or anisotropic `uroughness`/`vroughness` (their α averaged), under
|
|
168
|
+
* `prefix` for a layer (`conductor.`, `interface.`); a textured roughness counts as its mean. pbrt-v4 takes GGX
|
|
169
|
+
* α = √roughness (`remaproughness`, the default) or α = roughness; the engine's GGX takes α = roughness². Passing
|
|
170
|
+
* pbrt's number through made `remaproughness false` 0.1 an α of 0.01, a near mirror. `dflt` is the engine's.
|
|
171
|
+
*/
|
|
172
|
+
const remapsRoughness = ( params ) => {
|
|
173
|
+
|
|
174
|
+
const remap = params.remaproughness?.value?.[ 0 ];
|
|
175
|
+
return remap !== false && remap !== 'false';
|
|
176
|
+
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
async function resolveRoughness( params, dflt, ctx, prefix = '' ) {
|
|
180
|
+
|
|
181
|
+
const remap = remapsRoughness( params );
|
|
182
|
+
const alpha = ( r ) => ( remap ? Math.sqrt( Math.max( r, 0 ) ) : r );
|
|
183
|
+
let a = null;
|
|
184
|
+
const r = await floatOrMean( params, `${prefix}roughness`, ctx );
|
|
185
|
+
if ( r !== null ) a = alpha( r );
|
|
186
|
+
else {
|
|
187
|
+
|
|
188
|
+
const u = await floatOrMean( params, `${prefix}uroughness`, ctx );
|
|
189
|
+
const v = await floatOrMean( params, `${prefix}vroughness`, ctx );
|
|
190
|
+
if ( u !== null ) a = v !== null ? ( alpha( u ) + alpha( v ) ) / 2 : alpha( u );
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return a === null ? dflt : Math.sqrt( Math.max( a, 0 ) );
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Sets `target` (`roughness` or `clearcoatRoughness`) as resolveRoughness does, except that a textured roughness becomes
|
|
200
|
+
* a roughness map with the remap baked in (`ctx.bakeFloatMap`), the value 1 multiplying it.
|
|
201
|
+
*/
|
|
202
|
+
async function applyRoughness( mat, target, params, dflt, ctx, prefix = '' ) {
|
|
203
|
+
|
|
204
|
+
const single = params[ `${prefix}roughness` ];
|
|
205
|
+
const keys = single ? [ `${prefix}roughness` ] : [ `${prefix}uroughness`, `${prefix}vroughness` ].filter( ( key ) => params[ key ] );
|
|
206
|
+
if ( ctx.bakeFloatMap && keys.some( ( key ) => params[ key ].type === 'texture' ) ) {
|
|
207
|
+
|
|
208
|
+
const remap = remapsRoughness( params );
|
|
209
|
+
const alpha = ( r ) => ( remap ? Math.sqrt( Math.max( r, 0 ) ) : Math.max( r, 0 ) );
|
|
210
|
+
const map = await ctx.bakeFloatMap( params, keys, ( values ) => Math.sqrt( values.reduce( ( sum, r ) => sum + alpha( r ), 0 ) / values.length ) );
|
|
211
|
+
if ( map ) {
|
|
212
|
+
|
|
213
|
+
mat[ `${target}Map` ] = map;
|
|
214
|
+
mat[ target ] = 1;
|
|
215
|
+
return;
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
mat[ target ] = await resolveRoughness( params, dflt, ctx, prefix );
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// A spectrum parameter as one RGB; a texture counts as its mean (`ctx.textureMeanRGB`).
|
|
226
|
+
async function spectrumMean( params, key, ctx, dflt ) {
|
|
149
227
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
228
|
+
const p = params[ key ];
|
|
229
|
+
if ( p?.type === 'texture' ) return ( await ctx.textureMeanRGB?.( p.value[ 0 ] ) ) ?? dflt;
|
|
230
|
+
return ( await resolveSpectrum( params, key, ctx, dflt ) ).rgb ?? dflt;
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// pbrt's default subsurface medium (materials.cpp), mm⁻¹.
|
|
235
|
+
const SUBSURFACE_SIGMA_A = [ 0.0011, 0.0024, 0.014 ];
|
|
236
|
+
const SUBSURFACE_SIGMA_S = [ 2.55, 3.21, 3.77 ];
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* pbrt's `subsurface` as the engine's random walk under a dielectric boundary of `eta`: σt = σa + σs (times `scale`),
|
|
240
|
+
* single-scattering albedo σs / σt, mean free path 1 / σt. The coefficients come as pbrt takes them — a named medium
|
|
241
|
+
* (g 0), `sigma_a` and `sigma_s`, or a `reflectance` and `mfp` (scaled) inverted through the dipole — and default to
|
|
242
|
+
* pbrt's. The base colour is the reflectance the dipole gives, for what reads it as an albedo.
|
|
243
|
+
*/
|
|
244
|
+
async function applySubsurface( mat, params, ctx ) {
|
|
245
|
+
|
|
246
|
+
const eta = pFloat( params, 'eta', 1.33 );
|
|
247
|
+
const scale = pFloat( params, 'scale', 1 );
|
|
248
|
+
const scaled = ( rgb ) => rgb.map( ( v ) => Math.max( 0, v * scale ) );
|
|
249
|
+
let g = pFloat( params, 'g', 0 );
|
|
250
|
+
let sigmaA, sigmaS;
|
|
251
|
+
|
|
252
|
+
const name = pString( params, 'name', '' );
|
|
253
|
+
const named = name ? namedScattering( name ) : null;
|
|
254
|
+
if ( name && ! named ) ctx.warn( `subsurface: no medium named "${name}" — using pbrt's default` );
|
|
255
|
+
|
|
256
|
+
if ( named ) {
|
|
257
|
+
|
|
258
|
+
sigmaA = scaled( named.sigmaA );
|
|
259
|
+
sigmaS = scaled( named.sigmaS );
|
|
260
|
+
g = 0;
|
|
261
|
+
|
|
262
|
+
} else if ( params.sigma_a && params.sigma_s ) {
|
|
263
|
+
|
|
264
|
+
sigmaA = scaled( await spectrumMean( params, 'sigma_a', ctx, SUBSURFACE_SIGMA_A ) );
|
|
265
|
+
sigmaS = scaled( await spectrumMean( params, 'sigma_s', ctx, SUBSURFACE_SIGMA_S ) );
|
|
266
|
+
|
|
267
|
+
} else if ( params.reflectance && ! name ) {
|
|
268
|
+
|
|
269
|
+
const reflectance = await spectrumMean( params, 'reflectance', ctx, [ 0.5, 0.5, 0.5 ] );
|
|
270
|
+
const mfp = scaled( params.mfp ? await spectrumMean( params, 'mfp', ctx, [ 1, 1, 1 ] ) : [ 1, 1, 1 ] );
|
|
271
|
+
sigmaA = [];
|
|
272
|
+
sigmaS = [];
|
|
273
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
274
|
+
|
|
275
|
+
const sigmaT = 1 / Math.max( mfp[ c ], 1e-9 );
|
|
276
|
+
// The reduced albedo σ′s / ( σa + σ′s ), σ′s = ( 1 − g ) σs, back to σs at this σt.
|
|
277
|
+
const reduced = albedoForReflectance( reflectance[ c ], eta );
|
|
278
|
+
sigmaS[ c ] = reduced * sigmaT / ( 1 - g + reduced * g );
|
|
279
|
+
sigmaA[ c ] = sigmaT - sigmaS[ c ];
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
} else {
|
|
284
|
+
|
|
285
|
+
sigmaA = scaled( SUBSURFACE_SIGMA_A );
|
|
286
|
+
sigmaS = scaled( SUBSURFACE_SIGMA_S );
|
|
287
|
+
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const sigmaT = sigmaA.map( ( a, c ) => a + sigmaS[ c ] );
|
|
291
|
+
mat.subsurface = 1;
|
|
292
|
+
mat.subsurfaceColor = new Color( ...sigmaT.map( ( t, c ) => ( t > 0 ? sigmaS[ c ] / t : 0 ) ) );
|
|
293
|
+
mat.subsurfaceRadius = sigmaT.map( ( t ) => ( t > 0 ? 1 / t : 1e6 ) );
|
|
294
|
+
mat.subsurfaceRadiusScale = 1;
|
|
295
|
+
mat.subsurfaceAnisotropy = Math.max( - 0.99, Math.min( 0.99, g ) );
|
|
296
|
+
mat.ior = eta;
|
|
297
|
+
mat.metalness = 0;
|
|
298
|
+
mat.color.setRGB( ...sigmaT.map( ( t, c ) => {
|
|
299
|
+
|
|
300
|
+
const reducedS = ( 1 - g ) * sigmaS[ c ];
|
|
301
|
+
return t > 0 ? dipoleReflectance( reducedS / ( sigmaA[ c ] + reducedS ), eta ) : 0;
|
|
302
|
+
|
|
303
|
+
} ) );
|
|
304
|
+
await applyRoughness( mat, 'roughness', params, 0, ctx );
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// A conductor's colour: `reflectance`, else normal-incidence Fresnel of `eta`/`k` (under `prefix` for a coated
|
|
309
|
+
// conductor). pbrt's default is copper.
|
|
310
|
+
async function applyConductorColor( mat, params, ctx, prefix, applyAlbedo, setColor ) {
|
|
311
|
+
|
|
312
|
+
const refl = await resolveSpectrum( params, 'reflectance', ctx, null );
|
|
313
|
+
const etaP = params[ `${prefix}eta` ], kP = params[ `${prefix}k` ];
|
|
314
|
+
const etaNamed = etaP && etaP.type === 'spectrum' && typeof etaP.value[ 0 ] === 'string';
|
|
315
|
+
|
|
316
|
+
if ( refl.rgb || refl.texture ) {
|
|
317
|
+
|
|
318
|
+
applyAlbedo( refl );
|
|
319
|
+
|
|
320
|
+
} else if ( etaP && kP && ! etaNamed ) {
|
|
321
|
+
|
|
322
|
+
// Normal-incidence reflectance from complex IOR: ((η-1)²+k²)/((η+1)²+k²).
|
|
323
|
+
const eta = ( await resolveSpectrum( params, `${prefix}eta`, ctx, [ 0.2, 0.92, 1.1 ] ) ).rgb;
|
|
324
|
+
const k = ( await resolveSpectrum( params, `${prefix}k`, ctx, [ 3.9, 2.45, 2.14 ] ) ).rgb;
|
|
325
|
+
const fr = ( n, kk ) => ( ( n - 1 ) ** 2 + kk ** 2 ) / ( ( n + 1 ) ** 2 + kk ** 2 );
|
|
326
|
+
setColor( [ fr( eta[ 0 ], k[ 0 ] ), fr( eta[ 1 ], k[ 1 ] ), fr( eta[ 2 ], k[ 2 ] ) ] );
|
|
327
|
+
|
|
328
|
+
} else if ( etaP ) {
|
|
329
|
+
|
|
330
|
+
// Named conductor spectrum → metal albedo table.
|
|
331
|
+
setColor( ( await resolveSpectrum( params, `${prefix}eta`, ctx, DEFAULT_METAL ) ).rgb || DEFAULT_METAL );
|
|
332
|
+
|
|
333
|
+
} else {
|
|
334
|
+
|
|
335
|
+
setColor( METAL_ALBEDO.cu ); // pbrt-v4 default conductor is copper
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
mat.metalness = 1;
|
|
156
340
|
|
|
157
341
|
}
|
|
158
342
|
|
|
@@ -193,9 +377,11 @@ export async function buildMaterial( def, ctx ) {
|
|
|
193
377
|
|
|
194
378
|
case 'diffuse': {
|
|
195
379
|
|
|
380
|
+
// Lambertian: no dielectric specular layer, which MeshPhysicalMaterial has by default.
|
|
196
381
|
applyAlbedo( await resolveSpectrum( params, 'reflectance', ctx, [ 0.5, 0.5, 0.5 ] ) );
|
|
197
382
|
mat.roughness = 1;
|
|
198
383
|
mat.metalness = 0;
|
|
384
|
+
mat.specularIntensity = 0;
|
|
199
385
|
break;
|
|
200
386
|
|
|
201
387
|
}
|
|
@@ -203,35 +389,19 @@ export async function buildMaterial( def, ctx ) {
|
|
|
203
389
|
case 'conductor':
|
|
204
390
|
case 'metal': {
|
|
205
391
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if ( refl.rgb || refl.texture ) {
|
|
211
|
-
|
|
212
|
-
applyAlbedo( refl );
|
|
213
|
-
|
|
214
|
-
} else if ( etaP && kP && ! etaNamed ) {
|
|
215
|
-
|
|
216
|
-
// Normal-incidence reflectance from complex IOR: ((η-1)²+k²)/((η+1)²+k²).
|
|
217
|
-
const eta = ( await resolveSpectrum( params, 'eta', ctx, [ 0.2, 0.92, 1.1 ] ) ).rgb;
|
|
218
|
-
const k = ( await resolveSpectrum( params, 'k', ctx, [ 3.9, 2.45, 2.14 ] ) ).rgb;
|
|
219
|
-
const fr = ( n, kk ) => ( ( n - 1 ) ** 2 + kk ** 2 ) / ( ( n + 1 ) ** 2 + kk ** 2 );
|
|
220
|
-
setColor( [ fr( eta[ 0 ], k[ 0 ] ), fr( eta[ 1 ], k[ 1 ] ), fr( eta[ 2 ], k[ 2 ] ) ] );
|
|
221
|
-
|
|
222
|
-
} else if ( etaP ) {
|
|
223
|
-
|
|
224
|
-
// Named conductor spectrum → metal albedo table.
|
|
225
|
-
setColor( ( await resolveSpectrum( params, 'eta', ctx, DEFAULT_METAL ) ).rgb || DEFAULT_METAL );
|
|
226
|
-
|
|
227
|
-
} else {
|
|
392
|
+
await applyConductorColor( mat, params, ctx, '', applyAlbedo, setColor );
|
|
393
|
+
await applyRoughness( mat, 'roughness', params, 0.1, ctx );
|
|
394
|
+
break;
|
|
228
395
|
|
|
229
|
-
|
|
396
|
+
}
|
|
230
397
|
|
|
231
|
-
|
|
398
|
+
case 'coatedconductor': {
|
|
232
399
|
|
|
233
|
-
|
|
234
|
-
|
|
400
|
+
// A conductor under a dielectric coat; pbrt's roughnesses default to 0 for both.
|
|
401
|
+
await applyConductorColor( mat, params, ctx, 'conductor.', applyAlbedo, setColor );
|
|
402
|
+
await applyRoughness( mat, 'roughness', params, 0, ctx, 'conductor.' );
|
|
403
|
+
mat.clearcoat = 1;
|
|
404
|
+
await applyRoughness( mat, 'clearcoatRoughness', params, 0, ctx, 'interface.' );
|
|
235
405
|
break;
|
|
236
406
|
|
|
237
407
|
}
|
|
@@ -243,7 +413,7 @@ export async function buildMaterial( def, ctx ) {
|
|
|
243
413
|
mat.metalness = 0;
|
|
244
414
|
mat.color.setRGB( 1, 1, 1 );
|
|
245
415
|
mat.ior = pFloat( params, 'eta', 1.5 );
|
|
246
|
-
mat
|
|
416
|
+
await applyRoughness( mat, 'roughness', params, 0, ctx );
|
|
247
417
|
mat.thickness = type === 'thindielectric' ? 0 : pFloat( params, 'thickness', 0 );
|
|
248
418
|
break;
|
|
249
419
|
|
|
@@ -255,18 +425,30 @@ export async function buildMaterial( def, ctx ) {
|
|
|
255
425
|
mat.roughness = 0.6;
|
|
256
426
|
mat.metalness = 0;
|
|
257
427
|
mat.clearcoat = 1;
|
|
258
|
-
mat
|
|
428
|
+
await applyRoughness( mat, 'clearcoatRoughness', params, 0, ctx );
|
|
259
429
|
break;
|
|
260
430
|
|
|
261
431
|
}
|
|
262
432
|
|
|
263
433
|
case 'diffusetransmission': {
|
|
264
434
|
|
|
435
|
+
// pbrt reflects R and transmits T, both diffusely and both × `scale`, clamped to [0, 1]; no specular layer.
|
|
436
|
+
// The engine splits its diffuse lobe by `diffuseTransmission`: reflecting (1 − dt) · base, transmitting
|
|
437
|
+
// dt · colour. dt = max(T) makes both exact whenever max(R) + max(T) ≤ 1.
|
|
438
|
+
const scale = pFloat( params, 'scale', 1 );
|
|
439
|
+
const unit = ( v ) => Math.min( 1, Math.max( 0, scale * v ) );
|
|
265
440
|
const trans = await resolveSpectrum( params, 'transmittance', ctx, [ 0.25, 0.25, 0.25 ] );
|
|
266
|
-
|
|
267
|
-
|
|
441
|
+
const T = trans.rgb ? trans.rgb.map( unit ) : [ 0.5, 0.5, 0.5 ];
|
|
442
|
+
const dt = Math.min( 0.99, Math.max( ...T ) );
|
|
443
|
+
const refl = await resolveSpectrum( params, 'reflectance', ctx, [ 0.25, 0.25, 0.25 ] );
|
|
444
|
+
const base = ( v ) => Math.min( 1, unit( v ) / ( 1 - dt ) );
|
|
445
|
+
applyAlbedo( { texture: refl.texture, rgb: refl.rgb ? refl.rgb.map( base ) : ( refl.texture ? [ 1, 1, 1 ].map( base ) : null ) } );
|
|
446
|
+
if ( trans.texture ) ctx.warn( 'diffusetransmission: a textured "transmittance" is not supported — using 0.5' );
|
|
447
|
+
mat.diffuseTransmission = dt;
|
|
448
|
+
mat.diffuseTransmissionColor = new Color().setRGB( ...T.map( ( v ) => ( dt > 0 ? Math.min( 1, v / dt ) : 1 ) ) );
|
|
268
449
|
mat.roughness = 1;
|
|
269
|
-
mat.
|
|
450
|
+
mat.metalness = 0;
|
|
451
|
+
mat.specularIntensity = 0;
|
|
270
452
|
break;
|
|
271
453
|
|
|
272
454
|
}
|
|
@@ -292,13 +474,18 @@ export async function buildMaterial( def, ctx ) {
|
|
|
292
474
|
// instead of warning per shape. Recursive: mix-in-mix terminates as the
|
|
293
475
|
// chain bottoms out at a non-mix.
|
|
294
476
|
const matNames = params.materials?.value || [];
|
|
295
|
-
|
|
477
|
+
let t = Math.max( 0, Math.min( 1, pFloat( params, 'amount', 0.5 ) ) );
|
|
296
478
|
const defA = matNames[ 0 ] ? ctx.namedMaterials?.get( matNames[ 0 ] ) : null;
|
|
297
479
|
const defB = matNames[ 1 ] ? ctx.namedMaterials?.get( matNames[ 1 ] ) : null;
|
|
298
480
|
|
|
299
481
|
if ( defA && defB ) {
|
|
300
482
|
|
|
301
483
|
const [ matA, matB ] = await Promise.all( [ buildMaterial( defA, ctx ), buildMaterial( defB, ctx ) ] );
|
|
484
|
+
// pbrt picks the second material where a texture amount says so; its colours are baked by that
|
|
485
|
+
// texture (`ctx.bakeMaterialMix`) and the rest is weighed by the texture's mean.
|
|
486
|
+
const amountP = params.amount;
|
|
487
|
+
const baked = amountP?.type === 'texture' ? await ctx.bakeMaterialMix?.( matA, matB, amountP.value[ 0 ] ) : null;
|
|
488
|
+
if ( baked ) t = baked.mean;
|
|
302
489
|
const lerp = ( a, b ) => a * ( 1 - t ) + b * t;
|
|
303
490
|
mat.color.lerpColors( matA.color, matB.color, t );
|
|
304
491
|
mat.roughness = lerp( matA.roughness, matB.roughness );
|
|
@@ -309,9 +496,25 @@ export async function buildMaterial( def, ctx ) {
|
|
|
309
496
|
mat.clearcoat = lerp( matA.clearcoat ?? 0, matB.clearcoat ?? 0 );
|
|
310
497
|
mat.clearcoatRoughness = lerp( matA.clearcoatRoughness ?? 0, matB.clearcoatRoughness ?? 0 );
|
|
311
498
|
mat.emissive.lerpColors( matA.emissive, matB.emissive, t );
|
|
499
|
+
const dtA = matA.diffuseTransmission ?? 0, dtB = matB.diffuseTransmission ?? 0;
|
|
500
|
+
if ( dtA > 0 || dtB > 0 ) {
|
|
501
|
+
|
|
502
|
+
mat.diffuseTransmission = lerp( dtA, dtB );
|
|
503
|
+
mat.diffuseTransmissionColor = new Color().lerpColors( matA.diffuseTransmissionColor ?? new Color( 1, 1, 1 ), matB.diffuseTransmissionColor ?? new Color( 1, 1, 1 ), t );
|
|
504
|
+
|
|
505
|
+
}
|
|
506
|
+
|
|
312
507
|
mat.emissiveIntensity = lerp( matA.emissiveIntensity ?? 0, matB.emissiveIntensity ?? 0 );
|
|
313
|
-
// Maps can't be lerped — pick the dominant side.
|
|
508
|
+
// Maps can't be lerped — pick the dominant side, unless the amount's own texture baked them together.
|
|
314
509
|
mat.map = ( t < 0.5 ? matA.map : matB.map ) || null;
|
|
510
|
+
mat.normalMap = ( t < 0.5 ? matA.normalMap : matB.normalMap ) || null;
|
|
511
|
+
if ( baked?.texture ) {
|
|
512
|
+
|
|
513
|
+
mat.map = baked.texture;
|
|
514
|
+
mat.color.setRGB( 1, 1, 1 );
|
|
515
|
+
|
|
516
|
+
}
|
|
517
|
+
|
|
315
518
|
break;
|
|
316
519
|
|
|
317
520
|
}
|
|
@@ -322,7 +525,13 @@ export async function buildMaterial( def, ctx ) {
|
|
|
322
525
|
|
|
323
526
|
}
|
|
324
527
|
|
|
325
|
-
case 'subsurface':
|
|
528
|
+
case 'subsurface': {
|
|
529
|
+
|
|
530
|
+
await applySubsurface( mat, params, ctx );
|
|
531
|
+
break;
|
|
532
|
+
|
|
533
|
+
}
|
|
534
|
+
|
|
326
535
|
case 'hair':
|
|
327
536
|
case 'measured': {
|
|
328
537
|
|
|
@@ -344,6 +553,9 @@ export async function buildMaterial( def, ctx ) {
|
|
|
344
553
|
|
|
345
554
|
}
|
|
346
555
|
|
|
556
|
+
const normalMap = pString( params, 'normalmap', null );
|
|
557
|
+
if ( normalMap && ctx.resolveNormalMap ) mat.normalMap = await ctx.resolveNormalMap( normalMap );
|
|
558
|
+
|
|
347
559
|
mat.roughness = Math.max( 0, Math.min( 1, mat.roughness ) );
|
|
348
560
|
return mat;
|
|
349
561
|
|