@needle-tools/materialx 1.6.0-next.2af5fc1 → 1.7.0-next.043f0c6
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 +19 -1
- package/LICENSE.txt +131 -0
- 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 +18 -7
- package/src/constants.js +4 -5
- 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 +2 -2
- package/src/materialx.helper.js +113 -47
- package/src/materialx.js +106 -29
- package/src/materialx.material.d.ts +11 -2
- package/src/materialx.material.js +614 -34
- package/src/materialx.types.d.ts +65 -10
- package/src/utils.js +16 -9
- package/src/utils.texture.d.ts +11 -0
- package/src/utils.texture.js +195 -38
- /package/bin/{SHA_0e7685f37737511f2816949b9486d511a5fa71bd → SHA_ab218c56f016a9a2d398e8d306f3aeb439ae9e9e} +0 -0
|
Binary file
|
package/bin/revision.json
CHANGED
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
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.043f0c6",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
7
|
"main": "index.js",
|
|
7
8
|
"types": "index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
10
12
|
"import": "./index.js",
|
|
11
|
-
"require": "./index.js"
|
|
12
|
-
"types": "./index.d.ts"
|
|
13
|
+
"require": "./index.js"
|
|
13
14
|
},
|
|
14
15
|
"./needle": {
|
|
16
|
+
"types": "./needle.d.ts",
|
|
15
17
|
"import": "./needle.js",
|
|
16
|
-
"require": "./needle.js"
|
|
17
|
-
"types": "./needle.d.ts"
|
|
18
|
+
"require": "./needle.js"
|
|
18
19
|
},
|
|
19
20
|
"./package.json": "./package.json",
|
|
20
21
|
"./codegen/register_types.ts": {
|
|
@@ -26,7 +27,10 @@
|
|
|
26
27
|
"three": ">=0.160.0"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
|
-
"test": "node --import=./tests/unit/register-json-loader.js --test tests/unit/**/*.test.js"
|
|
30
|
+
"test": "node --import=./tests/unit/register-json-loader.js --test tests/unit/**/*.test.js",
|
|
31
|
+
"test:runtime:node": "node tests/runtime/materialx-runtime-smoke.mjs",
|
|
32
|
+
"test:runtime:deno": "deno run --allow-read --no-lock --node-modules-dir=auto tests/runtime/materialx-runtime-smoke.mjs",
|
|
33
|
+
"test:runtime:bun": "bun tests/runtime/materialx-runtime-smoke.mjs"
|
|
30
34
|
},
|
|
31
35
|
"devDependencies": {
|
|
32
36
|
"@needle-tools/engine": "4.x",
|
|
@@ -35,6 +39,9 @@
|
|
|
35
39
|
"jsdom": "^29.0.1",
|
|
36
40
|
"three": "npm:@needle-tools/three@^0.169.5"
|
|
37
41
|
},
|
|
42
|
+
"overrides": {
|
|
43
|
+
"@needle-tools/gltf-progressive": "3.6.0-beta.2"
|
|
44
|
+
},
|
|
38
45
|
"files": [
|
|
39
46
|
"index.js",
|
|
40
47
|
"index.d.ts",
|
|
@@ -66,5 +73,9 @@
|
|
|
66
73
|
"pbr",
|
|
67
74
|
"3d",
|
|
68
75
|
"wasm"
|
|
69
|
-
]
|
|
76
|
+
],
|
|
77
|
+
"repository": {
|
|
78
|
+
"type": "git",
|
|
79
|
+
"url": "https://github.com/needle-tools/needle-engine-materialx.git"
|
|
80
|
+
}
|
|
70
81
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Node.js
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import pkg from '../package.json';
|
|
1
|
+
// Keep the runtime version tied to package.json. The import attribute is
|
|
2
|
+
// required by browsers and Node.js for JSON modules; bundlers also understand
|
|
3
|
+
// this shape.
|
|
4
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
6
5
|
export const VERSION = pkg.version;
|
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";
|
|
@@ -16,7 +16,7 @@ export interface LightData {
|
|
|
16
16
|
outer_angle: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function prepareEnvTexture(texture: THREE.Texture, capabilities
|
|
19
|
+
export function prepareEnvTexture(texture: THREE.Texture, capabilities?: any): THREE.Texture;
|
|
20
20
|
|
|
21
21
|
export function getLightRotation(): THREE.Matrix4;
|
|
22
22
|
|
|
@@ -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
|
@@ -21,7 +21,7 @@ const IMAGE_PATH_SEPARATOR = "/";
|
|
|
21
21
|
export function prepareEnvTexture(texture, capabilities) {
|
|
22
22
|
const newTexture = new THREE.DataTexture(texture.image.data, texture.image.width, texture.image.height, /** @type {any} */(texture.format), texture.type);
|
|
23
23
|
newTexture.wrapS = THREE.RepeatWrapping;
|
|
24
|
-
newTexture.anisotropy = capabilities
|
|
24
|
+
newTexture.anisotropy = capabilities?.getMaxAnisotropy?.() ?? 1;
|
|
25
25
|
newTexture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
26
26
|
newTexture.magFilter = THREE.LinearFilter;
|
|
27
27
|
newTexture.generateMipmaps = true;
|
|
@@ -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) {
|
|
@@ -77,10 +71,22 @@ function fromMatrix(matrix, dimension) {
|
|
|
77
71
|
* @property {(path: string) => Promise<THREE.Texture | null | void>} getTexture - Get a texture by path
|
|
78
72
|
*/
|
|
79
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} src
|
|
76
|
+
* @returns {HTMLImageElement | { src: string }}
|
|
77
|
+
*/
|
|
78
|
+
function createPortableImage(src) {
|
|
79
|
+
if (typeof Image !== 'undefined') {
|
|
80
|
+
const image = new Image();
|
|
81
|
+
image.src = src;
|
|
82
|
+
return image;
|
|
83
|
+
}
|
|
84
|
+
return { src };
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
const defaultTexture = new THREE.Texture();
|
|
81
88
|
defaultTexture.needsUpdate = true;
|
|
82
|
-
defaultTexture.image =
|
|
83
|
-
defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRFr6+vGqg52AAAAAxJREFUeJxjZGBEgQAAWAAJLpjsTQAAAABJRU5ErkJggg=="
|
|
89
|
+
defaultTexture.image = createPortableImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRFr6+vGqg52AAAAAxJREFUeJxjZGBEgQAAWAAJLpjsTQAAAABJRU5ErkJggg==");
|
|
84
90
|
// defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB5QTFRFAAAABAQEw8PD////v7+/vb29Xl5eQEBA+/v7PDw8GPBYkgAAAB1JREFUeJxjZGBgYFQSABIUMlxgDGMGBtaIAnIZAKwQCSDYUEZEAAAAAElFTkSuQmCC";
|
|
85
91
|
// defaultTexture.wrapS = THREE.RepeatWrapping;
|
|
86
92
|
// defaultTexture.wrapT = THREE.RepeatWrapping;
|
|
@@ -90,8 +96,7 @@ defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAA
|
|
|
90
96
|
|
|
91
97
|
const defaultNormalTexture = new THREE.Texture();
|
|
92
98
|
defaultNormalTexture.needsUpdate = true;
|
|
93
|
-
defaultNormalTexture.image =
|
|
94
|
-
defaultNormalTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAABJQTFRFgYH4gIH4gYH3gIH3gIH5gID4m94ORAAAADFJREFUeJxjZBBkfMdo9P/BB0aBj/8FGB0ufghgFGT4r8wo+P8rD2Pgo3sMjIz8jAwAMLoN0ZjS5hgAAAAASUVORK5CYII=";
|
|
99
|
+
defaultNormalTexture.image = createPortableImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAABJQTFRFgYH4gIH4gYH3gIH3gIH5gID4m94ORAAAADFJREFUeJxjZBBkfMdo9P/BB0aBj/8FGB0ufghgFGT4r8wo+P8rD2Pgo3sMjIz8jAwAMLoN0ZjS5hgAAAAASUVORK5CYII=");
|
|
95
100
|
|
|
96
101
|
/**
|
|
97
102
|
* @param {string} key
|
|
@@ -125,9 +130,10 @@ function addToCache(key, value) {
|
|
|
125
130
|
* @param {string} name
|
|
126
131
|
* @param {Callbacks} loaders
|
|
127
132
|
* @param {string} searchPath
|
|
133
|
+
* @param {Array<Promise<unknown>>} [pendingTextureLoads]
|
|
128
134
|
* @returns {THREE.Uniform}
|
|
129
135
|
*/
|
|
130
|
-
function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
136
|
+
function toThreeUniform(threeUniforms, uniforms, type, value, name, loaders, searchPath, pendingTextureLoads) {
|
|
131
137
|
|
|
132
138
|
const uniform = new THREE.Uniform(/** @type {any} */(null));
|
|
133
139
|
|
|
@@ -178,13 +184,22 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
178
184
|
if (cacheValue) {
|
|
179
185
|
if (debug) console.log('[MaterialX] Use cached texture: ', cacheKey, cacheValue);
|
|
180
186
|
if (cacheValue instanceof Promise) {
|
|
181
|
-
cacheValue.then(res => {
|
|
182
|
-
if (res)
|
|
187
|
+
const trackedLoad = cacheValue.then(res => {
|
|
188
|
+
if (res) {
|
|
189
|
+
uniform.value = res;
|
|
190
|
+
if (threeUniforms[name + "_flipY"]) threeUniforms[name + "_flipY"].value = !!res.flipY;
|
|
191
|
+
}
|
|
183
192
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
193
|
+
return res;
|
|
194
|
+
}).catch(err => {
|
|
195
|
+
console.error(`[MaterialX] Failed to load texture ${name} '${texturePath}'`, err);
|
|
196
|
+
return null;
|
|
184
197
|
});
|
|
198
|
+
pendingTextureLoads?.push(trackedLoad);
|
|
185
199
|
}
|
|
186
200
|
else {
|
|
187
201
|
uniform.value = cacheValue;
|
|
202
|
+
if (threeUniforms[name + "_flipY"]) threeUniforms[name + "_flipY"].value = !!cacheValue.flipY;
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
205
|
else {
|
|
@@ -197,9 +212,9 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
197
212
|
const promise = loaders.getTexture(texturePath)
|
|
198
213
|
?.then(res => {
|
|
199
214
|
if (res) {
|
|
200
|
-
res = res.clone(); // we need to clone the texture once to avoid colorSpace issues with other materials
|
|
201
215
|
res.colorSpace = THREE.LinearSRGBColorSpace;
|
|
202
216
|
setTextureParameters(res, name, uniforms);
|
|
217
|
+
res.needsUpdate = true;
|
|
203
218
|
}
|
|
204
219
|
return res;
|
|
205
220
|
})
|
|
@@ -210,15 +225,21 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
210
225
|
|
|
211
226
|
if (checkCache) addToCache(cacheKey, promise);
|
|
212
227
|
|
|
213
|
-
promise?.then(res => {
|
|
228
|
+
const trackedLoad = promise?.then(res => {
|
|
214
229
|
// Replace Promise cache entry with the resolved texture value.
|
|
215
230
|
// This avoids keeping long-lived promise/closure graphs in THREE.Cache.
|
|
216
231
|
if (checkCache && res) addToCache(cacheKey, res);
|
|
217
|
-
if (res)
|
|
232
|
+
if (res) {
|
|
233
|
+
uniform.value = /** @type {any} */ (res);
|
|
234
|
+
if (threeUniforms[name + "_flipY"]) threeUniforms[name + "_flipY"].value = !!res.flipY;
|
|
235
|
+
}
|
|
218
236
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
237
|
+
return res;
|
|
219
238
|
});
|
|
239
|
+
if (trackedLoad) pendingTextureLoads?.push(trackedLoad);
|
|
220
240
|
}
|
|
221
241
|
}
|
|
242
|
+
threeUniforms[name + "_flipY"] = new THREE.Uniform(!!uniform.value?.flipY);
|
|
222
243
|
break;
|
|
223
244
|
case 'samplerCube':
|
|
224
245
|
case 'string':
|
|
@@ -248,23 +269,69 @@ const valueTypeWarningMap = new Map();
|
|
|
248
269
|
* @param {number} mode
|
|
249
270
|
* @returns {THREE.Wrapping}
|
|
250
271
|
*/
|
|
272
|
+
function getAddressMode(mode) {
|
|
273
|
+
if (typeof mode === "number") return mode;
|
|
274
|
+
switch (String(mode ?? "").toLowerCase()) {
|
|
275
|
+
case "constant":
|
|
276
|
+
return 0;
|
|
277
|
+
case "clamp":
|
|
278
|
+
return 1;
|
|
279
|
+
case "periodic":
|
|
280
|
+
return 2;
|
|
281
|
+
case "mirror":
|
|
282
|
+
return 3;
|
|
283
|
+
default:
|
|
284
|
+
return 2;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get Three wrapping mode
|
|
290
|
+
* @param {unknown} mode
|
|
291
|
+
* @returns {THREE.Wrapping}
|
|
292
|
+
*/
|
|
251
293
|
function getWrapping(mode) {
|
|
252
|
-
|
|
253
|
-
|
|
294
|
+
switch (getAddressMode(mode)) {
|
|
295
|
+
case 0:
|
|
254
296
|
case 1:
|
|
255
|
-
|
|
256
|
-
break;
|
|
257
|
-
case 2:
|
|
258
|
-
wrap = THREE.RepeatWrapping;
|
|
259
|
-
break;
|
|
297
|
+
return THREE.ClampToEdgeWrapping;
|
|
260
298
|
case 3:
|
|
261
|
-
|
|
262
|
-
|
|
299
|
+
return THREE.MirroredRepeatWrapping;
|
|
300
|
+
case 2:
|
|
263
301
|
default:
|
|
264
|
-
|
|
265
|
-
|
|
302
|
+
return THREE.RepeatWrapping;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @param {unknown} mode
|
|
308
|
+
* @returns {number}
|
|
309
|
+
*/
|
|
310
|
+
function getFilterType(mode) {
|
|
311
|
+
if (typeof mode === "number") return mode;
|
|
312
|
+
switch (String(mode ?? "").toLowerCase()) {
|
|
313
|
+
case "closest":
|
|
314
|
+
return 0;
|
|
315
|
+
case "linear":
|
|
316
|
+
return 1;
|
|
317
|
+
default:
|
|
318
|
+
return -1;
|
|
266
319
|
}
|
|
267
|
-
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @param {any} uniforms
|
|
324
|
+
* @param {string} name
|
|
325
|
+
* @param {any} defaultValue
|
|
326
|
+
* @returns {any}
|
|
327
|
+
*/
|
|
328
|
+
function getUniformData(uniforms, name, defaultValue) {
|
|
329
|
+
const uniform = uniforms?.find?.(name);
|
|
330
|
+
const value = uniform?.getValue?.();
|
|
331
|
+
if (!value) return defaultValue;
|
|
332
|
+
if (typeof value.getData === "function") return value.getData();
|
|
333
|
+
if (typeof value.data === "function") return value.data();
|
|
334
|
+
return defaultValue;
|
|
268
335
|
}
|
|
269
336
|
|
|
270
337
|
/**
|
|
@@ -278,22 +345,18 @@ function setTextureParameters(texture, name, uniforms, generateMipmaps = true) {
|
|
|
278
345
|
const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
|
|
279
346
|
const base = name.substring(0, idx) || name;
|
|
280
347
|
|
|
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
|
-
}
|
|
348
|
+
texture.wrapS = getWrapping(getUniformData(uniforms, base + UADDRESS_MODE_SUFFIX, 2));
|
|
349
|
+
texture.wrapT = getWrapping(getUniformData(uniforms, base + VADDRESS_MODE_SUFFIX, 2));
|
|
290
350
|
|
|
291
|
-
const mxFilterType = uniforms
|
|
351
|
+
const mxFilterType = getFilterType(getUniformData(uniforms, base + FILTER_TYPE_SUFFIX, -1));
|
|
292
352
|
let minFilter = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
|
|
353
|
+
let magFilter = THREE.LinearFilter;
|
|
293
354
|
if (mxFilterType === 0) {
|
|
294
355
|
minFilter = /** @type {any} */ (generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter);
|
|
356
|
+
magFilter = THREE.NearestFilter;
|
|
295
357
|
}
|
|
296
358
|
texture.minFilter = minFilter;
|
|
359
|
+
texture.magFilter = magFilter;
|
|
297
360
|
}
|
|
298
361
|
|
|
299
362
|
/**
|
|
@@ -528,9 +591,12 @@ export function getLightData(lights, genContext) {
|
|
|
528
591
|
* @param {any} shaderStage
|
|
529
592
|
* @param {Callbacks} loaders
|
|
530
593
|
* @param {string} searchPath
|
|
594
|
+
* @param {Array<Promise<unknown>>} [pendingTextureLoads]
|
|
531
595
|
* @returns {Object<string, THREE.Uniform>}
|
|
532
596
|
*/
|
|
533
|
-
export function getUniformValues(shaderStage, loaders, searchPath) {
|
|
597
|
+
export function getUniformValues(shaderStage, loaders, searchPath, pendingTextureLoads) {
|
|
598
|
+
loaders ??= { getTexture: async () => null };
|
|
599
|
+
searchPath ??= "";
|
|
534
600
|
/** @type {Object<string, THREE.Uniform>} */
|
|
535
601
|
const threeUniforms = {};
|
|
536
602
|
|
|
@@ -545,7 +611,7 @@ export function getUniformValues(shaderStage, loaders, searchPath) {
|
|
|
545
611
|
const value = variable.getValue()?.getData();
|
|
546
612
|
const uniformName = variable.getVariable();
|
|
547
613
|
const type = variable.getType().getName();
|
|
548
|
-
threeUniforms[uniformName] = toThreeUniform(uniforms, type, value, uniformName, loaders, searchPath);
|
|
614
|
+
threeUniforms[uniformName] = toThreeUniform(threeUniforms, uniforms, type, value, uniformName, loaders, searchPath, pendingTextureLoads);
|
|
549
615
|
if (debug) console.log("Adding uniform", { path: variable.getPath(), type: type, name: uniformName, value: threeUniforms[uniformName], },);
|
|
550
616
|
}
|
|
551
617
|
}
|