@needle-tools/materialx 1.6.0 → 1.7.0-next.0d06218
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/CHANGELOG.md +15 -1
- package/README.md +33 -1
- package/bin/JsMaterialXCore.js +5 -13
- package/bin/JsMaterialXCore.wasm +0 -0
- package/bin/JsMaterialXGenShader.data.txt +2127 -2453
- package/bin/JsMaterialXGenShader.js +5 -13
- package/bin/JsMaterialXGenShader.wasm +0 -0
- package/bin/revision.json +3 -3
- package/package.json +6 -2
- package/src/index.d.ts +1 -2
- package/src/loader/loader.three.d.ts +10 -1
- package/src/loader/loader.three.js +26 -19
- package/src/materialx.d.ts +9 -5
- package/src/materialx.helper.d.ts +1 -1
- package/src/materialx.helper.js +97 -42
- package/src/materialx.js +83 -24
- package/src/materialx.material.d.ts +11 -2
- package/src/materialx.material.js +512 -10
- package/src/materialx.types.d.ts +65 -10
- package/src/utils.texture.d.ts +11 -0
- package/src/utils.texture.js +194 -37
- /package/bin/{SHA_0e7685f37737511f2816949b9486d511a5fa71bd → SHA_ab218c56f016a9a2d398e8d306f3aeb439ae9e9e} +0 -0
|
Binary file
|
package/bin/revision.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/materialx",
|
|
3
3
|
"description": "MaterialX material support for three.js and Needle Engine – render physically based MaterialX shaders in the browser via WebAssembly",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.7.0-next.0d06218",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
@@ -66,5 +66,9 @@
|
|
|
66
66
|
"pbr",
|
|
67
67
|
"3d",
|
|
68
68
|
"wasm"
|
|
69
|
-
]
|
|
69
|
+
],
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "https://github.com/needle-tools/needle-engine-materialx.git"
|
|
73
|
+
}
|
|
70
74
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ready, type MaterialXContext, preloadWasm } from "./materialx.js";
|
|
1
|
+
export { ready, type MaterialXContext, type MaterialXEnvironmentRadianceMode, preloadWasm } from "./materialx.js";
|
|
2
2
|
export { MaterialXEnvironment } from "./materialx.js";
|
|
3
3
|
export { MaterialXMaterial } from "./materialx.material.js";
|
|
4
4
|
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
@@ -10,4 +10,3 @@ declare const Experimental_API: {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export { Experimental_API };
|
|
13
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Material, MaterialParameters } from "three";
|
|
2
2
|
import { GLTFLoader, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
3
|
import { MaterialXContext } from "../materialx.js";
|
|
4
|
+
import type { MaterialXEnvironmentRadianceMode } from "../materialx.js";
|
|
4
5
|
import { MaterialXMaterial } from "../materialx.material.js";
|
|
5
6
|
import { Callbacks } from "../materialx.helper.js";
|
|
6
7
|
|
|
@@ -39,6 +40,14 @@ export interface MaterialXLoaderOptions {
|
|
|
39
40
|
cacheKey?: string;
|
|
40
41
|
/** Parameters for the MaterialX loader */
|
|
41
42
|
parameters?: Pick<MaterialParameters, "precision">;
|
|
43
|
+
/** Environment radiance backend. Defaults to direct Three.js CubeUV PMREM sampling. */
|
|
44
|
+
environmentRadianceMode?: MaterialXEnvironmentRadianceMode;
|
|
45
|
+
/** Match Three.js glossy specular antialiasing. Defaults to true. */
|
|
46
|
+
specularAntialiasing?: boolean;
|
|
47
|
+
/** Flip texcoord V at the MaterialX texcoord node output. Defaults to false for standalone .mtlx creation and true for GLTFLoader integration. */
|
|
48
|
+
hwTexcoordVerticalFlip?: boolean;
|
|
49
|
+
/** Flip texcoord V inside MaterialX file texture sampling. Defaults to false for standalone .mtlx creation and true for GLTFLoader integration. */
|
|
50
|
+
fileTextureVerticalFlip?: boolean;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
export declare class MaterialXLoader implements GLTFLoaderPlugin {
|
|
@@ -71,7 +80,7 @@ export declare function useNeedleMaterialX(
|
|
|
71
80
|
export declare function createMaterialXMaterial(
|
|
72
81
|
mtlx: string,
|
|
73
82
|
materialNodeName: string | number,
|
|
74
|
-
loaders
|
|
83
|
+
loaders?: Callbacks,
|
|
75
84
|
options?: MaterialXLoaderOptions,
|
|
76
85
|
context?: MaterialXContext
|
|
77
86
|
): Promise<Material>;
|
|
@@ -61,7 +61,11 @@ export class MaterialXLoader {
|
|
|
61
61
|
*/
|
|
62
62
|
constructor(parser, options, context) {
|
|
63
63
|
this.parser = parser;
|
|
64
|
-
this.options =
|
|
64
|
+
this.options = {
|
|
65
|
+
...options,
|
|
66
|
+
hwTexcoordVerticalFlip: options?.hwTexcoordVerticalFlip ?? true,
|
|
67
|
+
fileTextureVerticalFlip: options?.fileTextureVerticalFlip ?? true,
|
|
68
|
+
};
|
|
65
69
|
this.context = context;
|
|
66
70
|
|
|
67
71
|
if (debug) console.log("MaterialXLoader created for parser");
|
|
@@ -182,7 +186,7 @@ export function useNeedleMaterialX(loader, options, context) {
|
|
|
182
186
|
/**
|
|
183
187
|
* Parse the MaterialX document once and cache it
|
|
184
188
|
* @param {string} mtlx
|
|
185
|
-
* @returns {Promise<
|
|
189
|
+
* @returns {Promise<import("../materialx.types.js").MaterialX.Document>}
|
|
186
190
|
*/
|
|
187
191
|
async function load(mtlx) {
|
|
188
192
|
// Ensure MaterialX is initialized
|
|
@@ -202,7 +206,7 @@ async function load(mtlx) {
|
|
|
202
206
|
/**
|
|
203
207
|
* @param {string} mtlx
|
|
204
208
|
* @param {string | number} materialNodeNameOrIndex
|
|
205
|
-
* @param {import('../materialx.helper.js').Callbacks} loaders
|
|
209
|
+
* @param {import('../materialx.helper.js').Callbacks} [loaders]
|
|
206
210
|
* @param {MaterialXLoaderOptions} [options]
|
|
207
211
|
* @param {import('../materialx.js').MaterialXContext} [context]
|
|
208
212
|
* @returns {Promise<Material>}
|
|
@@ -210,6 +214,10 @@ async function load(mtlx) {
|
|
|
210
214
|
export async function createMaterialXMaterial(mtlx, materialNodeNameOrIndex, loaders, options, context) {
|
|
211
215
|
try {
|
|
212
216
|
if (debug) console.log(`Creating MaterialX material: ${materialNodeNameOrIndex}`);
|
|
217
|
+
loaders ??= {
|
|
218
|
+
getTexture: async () => null,
|
|
219
|
+
};
|
|
220
|
+
|
|
213
221
|
|
|
214
222
|
const doc = await load(mtlx);
|
|
215
223
|
|
|
@@ -234,7 +242,7 @@ export async function createMaterialXMaterial(mtlx, materialNodeNameOrIndex, loa
|
|
|
234
242
|
for (let i = 0; i < materialNodes.length; ++i) {
|
|
235
243
|
const materialNode = materialNodes[i];
|
|
236
244
|
if (materialNode) {
|
|
237
|
-
const name = materialNode.getNamePath();
|
|
245
|
+
const name = materialNode.getNamePath?.();
|
|
238
246
|
if (debug) console.log(`[MaterialX] Scan material[${i}]: ${name}`);
|
|
239
247
|
|
|
240
248
|
// Find the matching material
|
|
@@ -320,25 +328,21 @@ export async function createMaterialXMaterial(mtlx, materialNodeNameOrIndex, loa
|
|
|
320
328
|
: (state.materialXModule.isTransparentSurface(renderableElement, target) ? "blend" : "opaque");
|
|
321
329
|
const isMask = alphaMode === "mask";
|
|
322
330
|
const isBlend = alphaMode === "blend";
|
|
331
|
+
const renderAsTransparent = isBlend;
|
|
323
332
|
// Both MASK and BLEND need alpha handling in the generated shader.
|
|
324
333
|
const needsAlpha = isMask || isBlend;
|
|
325
334
|
|
|
326
|
-
// Emscripten's getOptions() returns a temporary wrapper
|
|
327
|
-
//
|
|
328
|
-
// matter for generation in a single block right before generate().
|
|
335
|
+
// Emscripten's getOptions() returns a temporary wrapper; set all options
|
|
336
|
+
// that matter for generation in one block right before generate().
|
|
329
337
|
{
|
|
330
338
|
const opts = state.materialXGenContext.getOptions();
|
|
331
339
|
// MASK and BLEND need alpha handling in the generated shader.
|
|
332
340
|
opts.hwTransparency = needsAlpha;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// convention. With hwTexcoordVerticalFlip flipping UV data, texture
|
|
339
|
-
// sampling needs to flip back to match. fileTextureVerticalFlip does
|
|
340
|
-
// this inside mx_transform_uv.
|
|
341
|
-
opts.fileTextureVerticalFlip = true;
|
|
341
|
+
opts.hwTexcoordVerticalFlip = options?.hwTexcoordVerticalFlip ?? false;
|
|
342
|
+
opts.fileTextureVerticalFlip = options?.fileTextureVerticalFlip ?? false;
|
|
343
|
+
opts.hwSpecularEnvironmentMethod = options?.environmentRadianceMode === "materialx-fis"
|
|
344
|
+
? state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS
|
|
345
|
+
: state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_PREFILTER;
|
|
342
346
|
}
|
|
343
347
|
|
|
344
348
|
// Generate shaders using the element's name path
|
|
@@ -348,19 +352,22 @@ export async function createMaterialXMaterial(mtlx, materialNodeNameOrIndex, loa
|
|
|
348
352
|
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
349
353
|
|
|
350
354
|
const shaderMaterial = new MaterialXMaterial({
|
|
351
|
-
name: materialNodeNameOrIndex
|
|
355
|
+
name: typeof elementName === "string" ? elementName : `MaterialX_${materialNodeNameOrIndex}`,
|
|
352
356
|
shaderName: null, //shaderInfo?.originalName || shaderInfo?.name || null,
|
|
353
357
|
shader,
|
|
354
358
|
context: context || {},
|
|
359
|
+
environmentRadianceMode: options?.environmentRadianceMode,
|
|
360
|
+
specularAntialiasing: options?.specularAntialiasing,
|
|
355
361
|
parameters: {
|
|
356
|
-
// MASK
|
|
357
|
-
transparent:
|
|
362
|
+
// MASK uses discard; BLEND uses Three.js transparent sorting and blending.
|
|
363
|
+
transparent: renderAsTransparent,
|
|
358
364
|
// For MASK mode, set alphaTest so Three.js enables alpha testing
|
|
359
365
|
alphaTest: isMask ? 0.0001 : 0,
|
|
360
366
|
...options?.parameters,
|
|
361
367
|
},
|
|
362
368
|
loaders: loaders,
|
|
363
369
|
});
|
|
370
|
+
await shaderMaterial.ready;
|
|
364
371
|
|
|
365
372
|
// Add debugging to see if the material compiles correctly
|
|
366
373
|
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
package/src/materialx.d.ts
CHANGED
|
@@ -34,13 +34,14 @@ export type MaterialXContext = {
|
|
|
34
34
|
type EnvironmentTextureSet = {
|
|
35
35
|
radianceTexture: Texture | null;
|
|
36
36
|
irradianceTexture: Texture | null;
|
|
37
|
+
dispose?: () => void;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export declare const state: {
|
|
40
41
|
materialXModule: MX.MODULE | null;
|
|
41
|
-
materialXGenerator:
|
|
42
|
-
materialXGenContext:
|
|
43
|
-
materialXStdLib:
|
|
42
|
+
materialXGenerator: MX.MODULE["HwShaderGenerator"] | null;
|
|
43
|
+
materialXGenContext: MX.GenContext | null;
|
|
44
|
+
materialXStdLib: MX.StandardLibrary | null;
|
|
44
45
|
materialXInitPromise: Promise<void> | null;
|
|
45
46
|
};
|
|
46
47
|
|
|
@@ -83,8 +84,11 @@ export declare class MaterialXEnvironment {
|
|
|
83
84
|
|
|
84
85
|
private _pmremGenerator: any | null;
|
|
85
86
|
private _renderer: WebGLRenderer | null;
|
|
86
|
-
private _texturesCache: Map<Texture | null, EnvironmentTextureSet
|
|
87
|
+
private _texturesCache: Map<Texture | null, Map<string, EnvironmentTextureSet>>;
|
|
87
88
|
private _initialize(renderer: WebGLRenderer): Promise<boolean>;
|
|
88
|
-
private _getTextures(texture: Texture | null | undefined): EnvironmentTextureSet;
|
|
89
|
+
private _getTextures(texture: Texture | null | undefined, radianceMode?: MaterialXEnvironmentRadianceMode): EnvironmentTextureSet;
|
|
90
|
+
private _getPMREMGenerator(): any;
|
|
89
91
|
private updateLighting(collectLights?: boolean): void;
|
|
90
92
|
}
|
|
93
|
+
|
|
94
|
+
export type MaterialXEnvironmentRadianceMode = "three-pmrem" | "materialx-prefiltered" | "materialx-fis";
|
|
@@ -28,6 +28,6 @@ export function registerLights(mx: any, genContext: any): Promise<void>;
|
|
|
28
28
|
|
|
29
29
|
export function getLightData(lights: Array<THREE.Light>, genContext: any): { lightData: LightData[], lightCount: number };
|
|
30
30
|
|
|
31
|
-
export function getUniformValues(shaderStage: any, loaders
|
|
31
|
+
export function getUniformValues(shaderStage: any, loaders?: Callbacks, searchPath?: string): Record<string, THREE.Uniform>;
|
|
32
32
|
|
|
33
33
|
export function generateMaterialPropertiesForUniforms(material: THREE.ShaderMaterial, shaderStage: any): void;
|
package/src/materialx.helper.js
CHANGED
|
@@ -36,17 +36,11 @@ export function prepareEnvTexture(texture, capabilities) {
|
|
|
36
36
|
* @returns {Array<number>}
|
|
37
37
|
*/
|
|
38
38
|
function fromVector(value, dimension) {
|
|
39
|
-
|
|
40
|
-
if (value)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
outValue = [];
|
|
45
|
-
for (let i = 0; i < dimension; ++i)
|
|
46
|
-
outValue.push(0.0);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return outValue;
|
|
39
|
+
if (!value) return Array(dimension).fill(0.0);
|
|
40
|
+
if (typeof value.data === "function") return [...value.data()];
|
|
41
|
+
if (typeof value.getData === "function") return fromVector(value.getData(), dimension);
|
|
42
|
+
if (typeof value !== "string" && typeof value[Symbol.iterator] === "function") return [...value];
|
|
43
|
+
return Array(dimension).fill(0.0);
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
/**
|
|
@@ -56,7 +50,7 @@ function fromVector(value, dimension) {
|
|
|
56
50
|
* @returns {Array<number>}
|
|
57
51
|
*/
|
|
58
52
|
function fromMatrix(matrix, dimension) {
|
|
59
|
-
const vec =
|
|
53
|
+
const vec = [];
|
|
60
54
|
if (matrix) {
|
|
61
55
|
for (let i = 0; i < matrix.numRows(); ++i) {
|
|
62
56
|
for (let k = 0; k < matrix.numColumns(); ++k) {
|
|
@@ -125,9 +119,10 @@ function addToCache(key, value) {
|
|
|
125
119
|
* @param {string} name
|
|
126
120
|
* @param {Callbacks} loaders
|
|
127
121
|
* @param {string} searchPath
|
|
122
|
+
* @param {Array<Promise<unknown>>} [pendingTextureLoads]
|
|
128
123
|
* @returns {THREE.Uniform}
|
|
129
124
|
*/
|
|
130
|
-
function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
125
|
+
function toThreeUniform(threeUniforms, uniforms, type, value, name, loaders, searchPath, pendingTextureLoads) {
|
|
131
126
|
|
|
132
127
|
const uniform = new THREE.Uniform(/** @type {any} */(null));
|
|
133
128
|
|
|
@@ -178,13 +173,22 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
178
173
|
if (cacheValue) {
|
|
179
174
|
if (debug) console.log('[MaterialX] Use cached texture: ', cacheKey, cacheValue);
|
|
180
175
|
if (cacheValue instanceof Promise) {
|
|
181
|
-
cacheValue.then(res => {
|
|
182
|
-
if (res)
|
|
176
|
+
const trackedLoad = cacheValue.then(res => {
|
|
177
|
+
if (res) {
|
|
178
|
+
uniform.value = res;
|
|
179
|
+
if (threeUniforms[name + "_flipY"]) threeUniforms[name + "_flipY"].value = !!res.flipY;
|
|
180
|
+
}
|
|
183
181
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
182
|
+
return res;
|
|
183
|
+
}).catch(err => {
|
|
184
|
+
console.error(`[MaterialX] Failed to load texture ${name} '${texturePath}'`, err);
|
|
185
|
+
return null;
|
|
184
186
|
});
|
|
187
|
+
pendingTextureLoads?.push(trackedLoad);
|
|
185
188
|
}
|
|
186
189
|
else {
|
|
187
190
|
uniform.value = cacheValue;
|
|
191
|
+
if (threeUniforms[name + "_flipY"]) threeUniforms[name + "_flipY"].value = !!cacheValue.flipY;
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
else {
|
|
@@ -197,9 +201,9 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
197
201
|
const promise = loaders.getTexture(texturePath)
|
|
198
202
|
?.then(res => {
|
|
199
203
|
if (res) {
|
|
200
|
-
res = res.clone(); // we need to clone the texture once to avoid colorSpace issues with other materials
|
|
201
204
|
res.colorSpace = THREE.LinearSRGBColorSpace;
|
|
202
205
|
setTextureParameters(res, name, uniforms);
|
|
206
|
+
res.needsUpdate = true;
|
|
203
207
|
}
|
|
204
208
|
return res;
|
|
205
209
|
})
|
|
@@ -210,15 +214,21 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
210
214
|
|
|
211
215
|
if (checkCache) addToCache(cacheKey, promise);
|
|
212
216
|
|
|
213
|
-
promise?.then(res => {
|
|
217
|
+
const trackedLoad = promise?.then(res => {
|
|
214
218
|
// Replace Promise cache entry with the resolved texture value.
|
|
215
219
|
// This avoids keeping long-lived promise/closure graphs in THREE.Cache.
|
|
216
220
|
if (checkCache && res) addToCache(cacheKey, res);
|
|
217
|
-
if (res)
|
|
221
|
+
if (res) {
|
|
222
|
+
uniform.value = /** @type {any} */ (res);
|
|
223
|
+
if (threeUniforms[name + "_flipY"]) threeUniforms[name + "_flipY"].value = !!res.flipY;
|
|
224
|
+
}
|
|
218
225
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
226
|
+
return res;
|
|
219
227
|
});
|
|
228
|
+
if (trackedLoad) pendingTextureLoads?.push(trackedLoad);
|
|
220
229
|
}
|
|
221
230
|
}
|
|
231
|
+
threeUniforms[name + "_flipY"] = new THREE.Uniform(!!uniform.value?.flipY);
|
|
222
232
|
break;
|
|
223
233
|
case 'samplerCube':
|
|
224
234
|
case 'string':
|
|
@@ -248,23 +258,69 @@ const valueTypeWarningMap = new Map();
|
|
|
248
258
|
* @param {number} mode
|
|
249
259
|
* @returns {THREE.Wrapping}
|
|
250
260
|
*/
|
|
261
|
+
function getAddressMode(mode) {
|
|
262
|
+
if (typeof mode === "number") return mode;
|
|
263
|
+
switch (String(mode ?? "").toLowerCase()) {
|
|
264
|
+
case "constant":
|
|
265
|
+
return 0;
|
|
266
|
+
case "clamp":
|
|
267
|
+
return 1;
|
|
268
|
+
case "periodic":
|
|
269
|
+
return 2;
|
|
270
|
+
case "mirror":
|
|
271
|
+
return 3;
|
|
272
|
+
default:
|
|
273
|
+
return 2;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get Three wrapping mode
|
|
279
|
+
* @param {unknown} mode
|
|
280
|
+
* @returns {THREE.Wrapping}
|
|
281
|
+
*/
|
|
251
282
|
function getWrapping(mode) {
|
|
252
|
-
|
|
253
|
-
|
|
283
|
+
switch (getAddressMode(mode)) {
|
|
284
|
+
case 0:
|
|
254
285
|
case 1:
|
|
255
|
-
|
|
256
|
-
break;
|
|
257
|
-
case 2:
|
|
258
|
-
wrap = THREE.RepeatWrapping;
|
|
259
|
-
break;
|
|
286
|
+
return THREE.ClampToEdgeWrapping;
|
|
260
287
|
case 3:
|
|
261
|
-
|
|
262
|
-
|
|
288
|
+
return THREE.MirroredRepeatWrapping;
|
|
289
|
+
case 2:
|
|
263
290
|
default:
|
|
264
|
-
|
|
265
|
-
|
|
291
|
+
return THREE.RepeatWrapping;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @param {unknown} mode
|
|
297
|
+
* @returns {number}
|
|
298
|
+
*/
|
|
299
|
+
function getFilterType(mode) {
|
|
300
|
+
if (typeof mode === "number") return mode;
|
|
301
|
+
switch (String(mode ?? "").toLowerCase()) {
|
|
302
|
+
case "closest":
|
|
303
|
+
return 0;
|
|
304
|
+
case "linear":
|
|
305
|
+
return 1;
|
|
306
|
+
default:
|
|
307
|
+
return -1;
|
|
266
308
|
}
|
|
267
|
-
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {any} uniforms
|
|
313
|
+
* @param {string} name
|
|
314
|
+
* @param {any} defaultValue
|
|
315
|
+
* @returns {any}
|
|
316
|
+
*/
|
|
317
|
+
function getUniformData(uniforms, name, defaultValue) {
|
|
318
|
+
const uniform = uniforms?.find?.(name);
|
|
319
|
+
const value = uniform?.getValue?.();
|
|
320
|
+
if (!value) return defaultValue;
|
|
321
|
+
if (typeof value.getData === "function") return value.getData();
|
|
322
|
+
if (typeof value.data === "function") return value.data();
|
|
323
|
+
return defaultValue;
|
|
268
324
|
}
|
|
269
325
|
|
|
270
326
|
/**
|
|
@@ -278,22 +334,18 @@ function setTextureParameters(texture, name, uniforms, generateMipmaps = true) {
|
|
|
278
334
|
const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
|
|
279
335
|
const base = name.substring(0, idx) || name;
|
|
280
336
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
texture.wrapS = getWrapping(uaddressmode);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (uniforms.find(base + VADDRESS_MODE_SUFFIX)) {
|
|
287
|
-
const vaddressmode = uniforms.find(base + VADDRESS_MODE_SUFFIX).getValue().getData();
|
|
288
|
-
texture.wrapT = getWrapping(vaddressmode);
|
|
289
|
-
}
|
|
337
|
+
texture.wrapS = getWrapping(getUniformData(uniforms, base + UADDRESS_MODE_SUFFIX, 2));
|
|
338
|
+
texture.wrapT = getWrapping(getUniformData(uniforms, base + VADDRESS_MODE_SUFFIX, 2));
|
|
290
339
|
|
|
291
|
-
const mxFilterType = uniforms
|
|
340
|
+
const mxFilterType = getFilterType(getUniformData(uniforms, base + FILTER_TYPE_SUFFIX, -1));
|
|
292
341
|
let minFilter = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
|
|
342
|
+
let magFilter = THREE.LinearFilter;
|
|
293
343
|
if (mxFilterType === 0) {
|
|
294
344
|
minFilter = /** @type {any} */ (generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter);
|
|
345
|
+
magFilter = THREE.NearestFilter;
|
|
295
346
|
}
|
|
296
347
|
texture.minFilter = minFilter;
|
|
348
|
+
texture.magFilter = magFilter;
|
|
297
349
|
}
|
|
298
350
|
|
|
299
351
|
/**
|
|
@@ -528,9 +580,12 @@ export function getLightData(lights, genContext) {
|
|
|
528
580
|
* @param {any} shaderStage
|
|
529
581
|
* @param {Callbacks} loaders
|
|
530
582
|
* @param {string} searchPath
|
|
583
|
+
* @param {Array<Promise<unknown>>} [pendingTextureLoads]
|
|
531
584
|
* @returns {Object<string, THREE.Uniform>}
|
|
532
585
|
*/
|
|
533
|
-
export function getUniformValues(shaderStage, loaders, searchPath) {
|
|
586
|
+
export function getUniformValues(shaderStage, loaders, searchPath, pendingTextureLoads) {
|
|
587
|
+
loaders ??= { getTexture: async () => null };
|
|
588
|
+
searchPath ??= "";
|
|
534
589
|
/** @type {Object<string, THREE.Uniform>} */
|
|
535
590
|
const threeUniforms = {};
|
|
536
591
|
|
|
@@ -545,7 +600,7 @@ export function getUniformValues(shaderStage, loaders, searchPath) {
|
|
|
545
600
|
const value = variable.getValue()?.getData();
|
|
546
601
|
const uniformName = variable.getVariable();
|
|
547
602
|
const type = variable.getType().getName();
|
|
548
|
-
threeUniforms[uniformName] = toThreeUniform(uniforms, type, value, uniformName, loaders, searchPath);
|
|
603
|
+
threeUniforms[uniformName] = toThreeUniform(threeUniforms, uniforms, type, value, uniformName, loaders, searchPath, pendingTextureLoads);
|
|
549
604
|
if (debug) console.log("Adding uniform", { path: variable.getPath(), type: type, name: uniformName, value: threeUniforms[uniformName], },);
|
|
550
605
|
}
|
|
551
606
|
}
|
package/src/materialx.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import MaterialX from "../bin/JsMaterialXGenShader.js";
|
|
2
2
|
import { debug, waitForNetworkIdle } from "./utils.js";
|
|
3
|
-
import { renderPMREMToEquirect } from "./utils.texture.js";
|
|
4
|
-
import { Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
|
|
3
|
+
import { renderPMREMToEquirect, renderPMREMToPrefilteredEquirect } from "./utils.texture.js";
|
|
4
|
+
import { CubeUVReflectionMapping, Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
|
|
5
5
|
import { registerLights, getLightData } from "./materialx.helper.js";
|
|
6
6
|
import { whiteTexture } from "./utils.texture.js";
|
|
7
7
|
import { VERSION } from "./constants.js";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Accept Texture instances from any Three.js copy. Tooling like the fidelity
|
|
11
|
+
* renderer can host scenes with a different Three.js module instance than this
|
|
12
|
+
* package, so `instanceof Texture` is too strict here.
|
|
13
|
+
* @param {unknown} value
|
|
14
|
+
* @returns {value is Texture}
|
|
15
|
+
*/
|
|
16
|
+
function isTextureLike(value) {
|
|
17
|
+
return !!value && typeof value === "object" && /** @type {{ isTexture?: unknown }} */(value).isTexture === true;
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
|
|
10
21
|
/**
|
|
11
22
|
* Preloads the MaterialX WebAssembly module.
|
|
@@ -51,7 +62,7 @@ export async function ready() {
|
|
|
51
62
|
|
|
52
63
|
// NOTE: This must be a plain string literal (not a template) so that the
|
|
53
64
|
// makeFilesLocal Vite plugin can statically detect and localize this URL.
|
|
54
|
-
const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.
|
|
65
|
+
const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.7.0/";
|
|
55
66
|
|
|
56
67
|
/** @type {Array<string>} */
|
|
57
68
|
let urls;
|
|
@@ -121,7 +132,7 @@ export async function ready() {
|
|
|
121
132
|
// SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
|
|
122
133
|
// SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
|
|
123
134
|
// SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
|
|
124
|
-
state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.
|
|
135
|
+
state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_PREFILTER;
|
|
125
136
|
|
|
126
137
|
// TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
|
|
127
138
|
// TRANSMISSION_OPACITY: Use opacity for transmission rendering.
|
|
@@ -161,6 +172,7 @@ export async function ready() {
|
|
|
161
172
|
* @typedef {Object} EnvironmentTextureSet
|
|
162
173
|
* @property {Texture | null} radianceTexture
|
|
163
174
|
* @property {Texture | null} irradianceTexture
|
|
175
|
+
* @property {() => void} [dispose]
|
|
164
176
|
*/
|
|
165
177
|
|
|
166
178
|
/**
|
|
@@ -285,9 +297,10 @@ export class MaterialXEnvironment {
|
|
|
285
297
|
this._pmremGenerator?.dispose();
|
|
286
298
|
this._pmremGenerator = null;
|
|
287
299
|
this._renderer = null;
|
|
288
|
-
for (const
|
|
289
|
-
textureSet.
|
|
290
|
-
|
|
300
|
+
for (const textureModeMap of this._texturesCache.values()) {
|
|
301
|
+
for (const textureSet of textureModeMap.values()) {
|
|
302
|
+
textureSet.dispose?.();
|
|
303
|
+
}
|
|
291
304
|
}
|
|
292
305
|
this._texturesCache.clear();
|
|
293
306
|
}
|
|
@@ -304,25 +317,26 @@ export class MaterialXEnvironment {
|
|
|
304
317
|
* @param {import("./materialx.material.js").MaterialXMaterial} material
|
|
305
318
|
*/
|
|
306
319
|
getTextures(material) {
|
|
320
|
+
const radianceMode = material.environmentRadianceMode ?? "three-pmrem";
|
|
307
321
|
if (material.envMap) {
|
|
308
322
|
// If the material has its own envMap, we don't use the irradiance texture
|
|
309
|
-
return this._getTextures(material.envMap);
|
|
323
|
+
return this._getTextures(material.envMap, radianceMode);
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
// Use the scene background for lighting if no environment is available
|
|
313
327
|
// If we don't do this we don't see the correct lighting for scenes exported with 'Environment Lighting: Color' and 'Environment Reflections: Skybox'
|
|
314
328
|
const skybox = this._scene.environment || this._scene.background;
|
|
315
|
-
if (skybox
|
|
316
|
-
return this._getTextures(skybox);
|
|
329
|
+
if (isTextureLike(skybox)) {
|
|
330
|
+
return this._getTextures(skybox, radianceMode);
|
|
317
331
|
}
|
|
318
|
-
return this._getTextures(null);
|
|
332
|
+
return this._getTextures(null, radianceMode);
|
|
319
333
|
}
|
|
320
334
|
|
|
321
335
|
/** @type {PMREMGenerator | null} */
|
|
322
336
|
_pmremGenerator = null;
|
|
323
337
|
/** @type {WebGLRenderer | null} */
|
|
324
338
|
_renderer = null;
|
|
325
|
-
/** @type {Map<Texture | null, EnvironmentTextureSet
|
|
339
|
+
/** @type {Map<Texture | null, Map<string, EnvironmentTextureSet>>} */
|
|
326
340
|
_texturesCache = new Map();
|
|
327
341
|
|
|
328
342
|
/**
|
|
@@ -331,7 +345,6 @@ export class MaterialXEnvironment {
|
|
|
331
345
|
*/
|
|
332
346
|
async _initialize(renderer) {
|
|
333
347
|
this._isInitialized = false;
|
|
334
|
-
this._pmremGenerator = new PMREMGenerator(renderer);
|
|
335
348
|
this._renderer = renderer;
|
|
336
349
|
this.updateLighting(true);
|
|
337
350
|
this._isInitialized = true;
|
|
@@ -340,30 +353,65 @@ export class MaterialXEnvironment {
|
|
|
340
353
|
|
|
341
354
|
/**
|
|
342
355
|
* @param {Texture | null | undefined} texture
|
|
343
|
-
* @
|
|
356
|
+
* @param {"three-pmrem" | "materialx-prefiltered" | "materialx-fis"} [radianceMode]
|
|
357
|
+
* @returns {EnvironmentTextureSet}
|
|
344
358
|
*/
|
|
345
|
-
_getTextures(texture) {
|
|
359
|
+
_getTextures(texture, radianceMode = "three-pmrem") {
|
|
346
360
|
|
|
347
361
|
// Fallback to white texture if no texture is provided
|
|
348
362
|
if (!texture) {
|
|
349
363
|
texture = whiteTexture;
|
|
350
364
|
}
|
|
351
365
|
|
|
366
|
+
const cacheKey = texture || null;
|
|
367
|
+
let textureModeMap = this._texturesCache.get(cacheKey);
|
|
368
|
+
if (!textureModeMap) {
|
|
369
|
+
textureModeMap = new Map();
|
|
370
|
+
this._texturesCache.set(cacheKey, textureModeMap);
|
|
371
|
+
}
|
|
372
|
+
|
|
352
373
|
/** @type {EnvironmentTextureSet | undefined} */
|
|
353
|
-
let res =
|
|
374
|
+
let res = textureModeMap.get(radianceMode);
|
|
354
375
|
if (res) {
|
|
355
376
|
return res;
|
|
356
377
|
}
|
|
357
378
|
|
|
358
|
-
|
|
379
|
+
const isPmremTexture = texture.mapping === CubeUVReflectionMapping || texture.isRenderTargetTexture === true;
|
|
380
|
+
|
|
381
|
+
if (this._scene && this._renderer && texture) {
|
|
359
382
|
if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
383
|
+
let radianceRenderTarget;
|
|
384
|
+
let irradianceRenderTarget;
|
|
385
|
+
|
|
386
|
+
if (isPmremTexture) {
|
|
387
|
+
// Scene.environment is often already PMREM-processed (CubeUV layout).
|
|
388
|
+
// Running PMREMGenerator on it again corrupts the sampling layout.
|
|
389
|
+
radianceRenderTarget = radianceMode === "materialx-prefiltered"
|
|
390
|
+
? renderPMREMToPrefilteredEquirect(this._renderer, texture)
|
|
391
|
+
: radianceMode === "materialx-fis"
|
|
392
|
+
? renderPMREMToEquirect(this._renderer, texture, 0.0, 1024, 512)
|
|
393
|
+
: null;
|
|
394
|
+
irradianceRenderTarget = renderPMREMToEquirect(this._renderer, texture, 1.0, 32, 16);
|
|
395
|
+
} else {
|
|
396
|
+
const target = this._getPMREMGenerator().fromEquirectangular(texture);
|
|
397
|
+
radianceRenderTarget = radianceMode === "materialx-prefiltered"
|
|
398
|
+
? renderPMREMToPrefilteredEquirect(this._renderer, target.texture, undefined, undefined, target.height)
|
|
399
|
+
: radianceMode === "three-pmrem"
|
|
400
|
+
? target
|
|
401
|
+
: null;
|
|
402
|
+
irradianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 1.0, 32, 16, target.height);
|
|
403
|
+
if (radianceMode !== "three-pmrem") {
|
|
404
|
+
target.dispose();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
364
408
|
res = {
|
|
365
|
-
radianceTexture: radianceRenderTarget
|
|
366
|
-
irradianceTexture: irradianceRenderTarget.texture
|
|
409
|
+
radianceTexture: radianceRenderTarget?.texture ?? texture,
|
|
410
|
+
irradianceTexture: irradianceRenderTarget.texture,
|
|
411
|
+
dispose: () => {
|
|
412
|
+
radianceRenderTarget?.dispose();
|
|
413
|
+
irradianceRenderTarget?.dispose();
|
|
414
|
+
},
|
|
367
415
|
}
|
|
368
416
|
}
|
|
369
417
|
else {
|
|
@@ -372,10 +420,21 @@ export class MaterialXEnvironment {
|
|
|
372
420
|
irradianceTexture: null
|
|
373
421
|
}
|
|
374
422
|
}
|
|
375
|
-
|
|
423
|
+
textureModeMap.set(radianceMode, res);
|
|
376
424
|
return res;
|
|
377
425
|
}
|
|
378
426
|
|
|
427
|
+
/**
|
|
428
|
+
* @returns {PMREMGenerator}
|
|
429
|
+
*/
|
|
430
|
+
_getPMREMGenerator() {
|
|
431
|
+
if (!this._renderer) {
|
|
432
|
+
throw new Error("[MaterialX] Cannot create PMREMGenerator before renderer initialization.");
|
|
433
|
+
}
|
|
434
|
+
this._pmremGenerator ??= new PMREMGenerator(this._renderer);
|
|
435
|
+
return this._pmremGenerator;
|
|
436
|
+
}
|
|
437
|
+
|
|
379
438
|
/**
|
|
380
439
|
* @param {boolean} collectLights
|
|
381
440
|
*/
|