@needle-tools/materialx 1.0.1-next.df0e959 → 1.0.2-next.81f48e0
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 +5 -0
- package/README.md +1 -1
- package/codegen/register_types.ts +2 -0
- package/package.json +1 -1
- package/src/loader/loader.needle.ts +22 -33
- package/src/loader/loader.three.ts +123 -393
- package/src/materialx.helper.ts +477 -0
- package/src/materialx.material.ts +217 -0
- package/src/materialx.ts +125 -92
- package/src/materialx.types.d.ts +50 -0
- package/src/textureHelper.ts +6 -6
- package/src/utils.ts +39 -4
- package/src/helper.js +0 -490
|
@@ -1,85 +1,86 @@
|
|
|
1
1
|
import { Context } from "@needle-tools/engine";
|
|
2
|
-
import {
|
|
3
|
-
import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
|
-
import { getUniformValues } from "../helper.js";
|
|
2
|
+
import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture, DoubleSide, FrontSide } from "three";
|
|
3
|
+
import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
5
4
|
import { ready, MaterialXEnvironment, state } from "../materialx.js";
|
|
6
5
|
import { debug } from "../utils.js";
|
|
6
|
+
import { MaterialXMaterial } from "../materialx.material.js";
|
|
7
7
|
|
|
8
8
|
// TypeScript interfaces matching the C# data structures
|
|
9
|
-
|
|
9
|
+
export type MaterialX_root_extension = {
|
|
10
10
|
/** e.g. 1.39 */
|
|
11
11
|
version: string;
|
|
12
12
|
/** e.g. "Material" */
|
|
13
13
|
name: string;
|
|
14
14
|
/** MaterialX xml content */
|
|
15
15
|
mtlx: string;
|
|
16
|
+
/** MaterialX texture pointers */
|
|
17
|
+
textures: Array<{ name: string, pointer: string }>;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
export type MaterialX_material_extension = {
|
|
19
21
|
name: string; // Material name reference
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
type MaterialDefinition = {
|
|
25
|
+
name?: string; // Optional name for the material
|
|
26
|
+
doubleSided?: boolean; // Whether the material is double-sided
|
|
27
|
+
extensions?: {
|
|
28
|
+
[key: string]: any; // Extensions for the material, including MaterialX
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
// MaterialX loader extension for js GLTFLoader
|
|
23
33
|
export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
24
|
-
name = "NEEDLE_materials_mtlx";
|
|
25
|
-
|
|
26
|
-
private rootMaterialXData: MaterialX_root_extension | null = null;
|
|
27
|
-
private parsedDocument: any = null;
|
|
28
|
-
private documentParsePromise: Promise<any> | null = null;
|
|
29
|
-
private rootDataInitialized = false;
|
|
30
|
-
private environmentInitialized = false;
|
|
31
|
-
private generatedMaterials: ShaderMaterial[] = [];
|
|
34
|
+
readonly name = "NEEDLE_materials_mtlx";
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
if (debug) console.log("MaterialXLoader created for parser");
|
|
35
|
-
// Initialize MaterialX environment after MaterialX is ready
|
|
36
|
-
this.initializeEnvironment();
|
|
37
|
-
}
|
|
36
|
+
private readonly _generatedMaterials: MaterialXMaterial[] = [];
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
private async initializeEnvironment(): Promise<void> {
|
|
41
|
-
if (this.environmentInitialized) return;
|
|
38
|
+
private _documentReadyPromise: Promise<any> | null = null;
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
get materialX_root_data() {
|
|
41
|
+
return this.parser.json.extensions?.[this.name] as MaterialX_root_extension | null;
|
|
42
|
+
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
/** Generated materialX materials */
|
|
45
|
+
get materials() {
|
|
46
|
+
return this._generatedMaterials;
|
|
47
|
+
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
/**
|
|
50
|
+
* MaterialXLoader constructor
|
|
51
|
+
* @param parser The GLTFParser instance
|
|
52
|
+
* @param url The URL of the GLTF file
|
|
53
|
+
* @param context The context for the GLTF loading process
|
|
54
|
+
*/
|
|
55
|
+
constructor(private parser: GLTFParser, private url: string, private context: Context) {
|
|
56
|
+
if (debug) console.log("MaterialXLoader created for parser");
|
|
57
|
+
// Start loading of MaterialX environment if the root extension exists
|
|
58
|
+
if (this.materialX_root_data) {
|
|
59
|
+
ready();
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (this.
|
|
63
|
-
|
|
64
|
-
const gltfExtensions = this.parser.json.extensions;
|
|
65
|
-
if (gltfExtensions?.[this.name]) {
|
|
66
|
-
if (debug) console.log("[MaterialX] extension found in root:", gltfExtensions[this.name]);
|
|
67
|
-
|
|
68
|
-
const materialXExtension = gltfExtensions[this.name];
|
|
69
|
-
this.rootMaterialXData = materialXExtension as MaterialX_root_extension;
|
|
63
|
+
loadMaterial(materialIndex: number): Promise<Material> | null {
|
|
64
|
+
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
65
|
+
if (!materialDef?.extensions?.[this.name]) {
|
|
66
|
+
return null;
|
|
70
67
|
}
|
|
71
|
-
|
|
68
|
+
// Wrap the async implementation
|
|
69
|
+
return this._loadMaterialAsync(materialIndex);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
afterRoot = async (_gltf: GLTF) => {
|
|
73
|
+
// Initialize MaterialX lighting system with scene data
|
|
74
|
+
const environment = state.materialXEnvironment;
|
|
75
|
+
environment.initialize(this.context);
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
// Parse the MaterialX document once and cache it
|
|
75
|
-
private async
|
|
76
|
-
if (this.
|
|
77
|
-
return this.
|
|
79
|
+
private async _materialXDocumentReady(): Promise<any> {
|
|
80
|
+
if (this._documentReadyPromise) {
|
|
81
|
+
return this._documentReadyPromise;
|
|
78
82
|
}
|
|
79
|
-
|
|
80
|
-
this.documentParsePromise = (async () => {
|
|
81
|
-
if (this.parsedDocument) return this.parsedDocument;
|
|
82
|
-
|
|
83
|
+
return this._documentReadyPromise = (async () => {
|
|
83
84
|
if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
|
|
84
85
|
|
|
85
86
|
// Ensure MaterialX is initialized
|
|
@@ -94,43 +95,27 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
94
95
|
doc.setDataLibrary(state.materialXStdLib);
|
|
95
96
|
|
|
96
97
|
// Parse all MaterialX XML strings from the root data
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const root = this.materialX_root_data
|
|
99
|
+
if (root) {
|
|
100
|
+
if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
|
|
101
|
+
await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
103
|
-
|
|
104
|
-
this.parsedDocument = doc;
|
|
105
105
|
return doc;
|
|
106
106
|
})();
|
|
107
|
-
|
|
108
|
-
return this.documentParsePromise;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
loadMaterial(materialIndex: number): Promise<Material> | null {
|
|
112
|
-
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
113
|
-
if (!materialDef?.extensions?.[this.name]) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Wrap the async implementation
|
|
118
|
-
return this._loadMaterialAsync(materialIndex);
|
|
119
107
|
}
|
|
120
108
|
|
|
121
109
|
private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
|
|
122
|
-
// Initialize root data first
|
|
123
|
-
this.initializeRootData();
|
|
124
110
|
|
|
125
|
-
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
126
|
-
if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
|
|
111
|
+
const materialDef = this.parser.json.materials?.[materialIndex] as MaterialDefinition;
|
|
112
|
+
if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions?.[this.name]);
|
|
127
113
|
|
|
128
114
|
// Handle different types of MaterialX data
|
|
129
|
-
const
|
|
115
|
+
const ext = materialDef.extensions?.[this.name] as MaterialX_material_extension;
|
|
130
116
|
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
return await this.createMaterialXMaterial(dataIndex);
|
|
117
|
+
if (ext) {
|
|
118
|
+
return this._createMaterialXMaterial(materialDef, ext);
|
|
134
119
|
}
|
|
135
120
|
|
|
136
121
|
// Return fallback material instead of null
|
|
@@ -139,29 +124,22 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
139
124
|
return fallbackMaterial;
|
|
140
125
|
}
|
|
141
126
|
|
|
142
|
-
private
|
|
143
|
-
private async createMaterialXMaterial(materialXData: MaterialX_material_extension): Promise<Material> {
|
|
127
|
+
private async _createMaterialXMaterial(material_def: MaterialDefinition, material_extension: MaterialX_material_extension): Promise<Material> {
|
|
144
128
|
try {
|
|
145
|
-
if (debug) console.log(`Creating MaterialX material: ${
|
|
146
|
-
|
|
147
|
-
// Ensure MaterialX is initialized and document is parsed
|
|
148
|
-
await ready();
|
|
129
|
+
if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
|
|
149
130
|
|
|
150
|
-
|
|
151
|
-
this.rootDocument = this.parseRootDocument();
|
|
152
|
-
}
|
|
153
|
-
const doc = await this.rootDocument;
|
|
131
|
+
const doc = await this._materialXDocumentReady();
|
|
154
132
|
|
|
155
133
|
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
156
134
|
console.warn("[MaterialX] WASM module not ready, returning fallback material");
|
|
157
135
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
158
|
-
fallbackMaterial.userData.materialX =
|
|
159
|
-
fallbackMaterial.name = `MaterialX_Fallback_${
|
|
136
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
137
|
+
fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
|
|
160
138
|
return fallbackMaterial;
|
|
161
139
|
}
|
|
162
140
|
|
|
163
141
|
// Find the renderable element following MaterialX example pattern exactly
|
|
164
|
-
let renderableElement = null;
|
|
142
|
+
let renderableElement: any = null;
|
|
165
143
|
let foundRenderable = false;
|
|
166
144
|
|
|
167
145
|
if (debug) console.log("[MaterialX] document", doc);
|
|
@@ -179,7 +157,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
179
157
|
if (debug) console.log('[MaterialX] Scan material: ', i, materialName);
|
|
180
158
|
|
|
181
159
|
// Find the matching material
|
|
182
|
-
if (materialName ==
|
|
160
|
+
if (materialName == material_extension.name) {
|
|
183
161
|
renderableElement = materialNode;
|
|
184
162
|
foundRenderable = true;
|
|
185
163
|
if (debug) console.log('[MaterialX] -- add material: ', materialName);
|
|
@@ -236,17 +214,18 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
236
214
|
*/
|
|
237
215
|
|
|
238
216
|
if (!renderableElement) {
|
|
239
|
-
console.warn(
|
|
217
|
+
console.warn(`[MaterialX] No renderable element found in MaterialX document (${material_extension.name})`);
|
|
240
218
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
241
|
-
fallbackMaterial.
|
|
242
|
-
fallbackMaterial.
|
|
219
|
+
fallbackMaterial.color.set(0xff00ff);
|
|
220
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
221
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
|
|
243
222
|
return fallbackMaterial;
|
|
244
223
|
}
|
|
245
224
|
|
|
246
225
|
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
247
226
|
|
|
248
227
|
// Check transparency and set context options like the reference
|
|
249
|
-
|
|
228
|
+
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
250
229
|
state.materialXGenContext.getOptions().hwTransparency = isTransparent;
|
|
251
230
|
|
|
252
231
|
// Generate shaders using the element's name path
|
|
@@ -254,317 +233,68 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
254
233
|
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
255
234
|
|
|
256
235
|
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
let vertexShader = shader.getSourceCode("vertex").replace(/^#version.*$/gm, '').trim();
|
|
261
|
-
let fragmentShader = shader.getSourceCode("pixel").replace(/^#version.*$/gm, '').trim();
|
|
262
|
-
|
|
263
|
-
// MaterialX uses different attribute names than js defaults,
|
|
264
|
-
// so we patch the MaterialX shaders to match the js standard names.
|
|
265
|
-
// Otherwise, we'd have to modify the mesh attributes (see original MaterialX for reference).
|
|
266
|
-
|
|
267
|
-
// Patch vertexShader
|
|
268
|
-
vertexShader = vertexShader.replace(/\bi_position\b/g, 'position');
|
|
269
|
-
vertexShader = vertexShader.replace(/\bi_normal\b/g, 'normal');
|
|
270
|
-
vertexShader = vertexShader.replace(/\bi_texcoord_0\b/g, 'uv');
|
|
271
|
-
vertexShader = vertexShader.replace(/\bi_texcoord_1\b/g, 'uv1');
|
|
272
|
-
vertexShader = vertexShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
273
|
-
vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
|
|
274
|
-
|
|
275
|
-
// Patch fragmentShader
|
|
276
|
-
fragmentShader = fragmentShader.replace(/\bi_position\b/g, 'position');
|
|
277
|
-
fragmentShader = fragmentShader.replace(/\bi_normal\b/g, 'normal');
|
|
278
|
-
fragmentShader = fragmentShader.replace(/\bi_texcoord_0\b/g, 'uv');
|
|
279
|
-
fragmentShader = fragmentShader.replace(/\bi_texcoord_1\b/g, 'uv1');
|
|
280
|
-
fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
281
|
-
fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
|
|
282
|
-
|
|
283
|
-
// Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
|
|
284
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
|
|
285
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
|
|
286
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
|
|
287
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
|
|
288
|
-
vertexShader = vertexShader.replace(/in\s+vec4\s+tangent;/g, '');
|
|
289
|
-
vertexShader = vertexShader.replace(/in\s+vec4\s+color;/g, '');
|
|
290
|
-
|
|
291
|
-
// Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
|
|
292
|
-
// TODO what if we actually have a 3-component UV? Not sure what three.js does then
|
|
293
|
-
vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
|
|
294
|
-
|
|
295
|
-
// Add tonemapping and colorspace handling
|
|
296
|
-
// Replace `out vec4 out1;` with `out vec4 gl_FragColor;`
|
|
297
|
-
fragmentShader = fragmentShader.replace(/out\s+vec4\s+out1;/, 'layout(location = 0) out vec4 pc_fragColor;\n#define gl_FragColor pc_fragColor');
|
|
298
|
-
// Replace `out1 = vec4(<CAPTURE>)` with `gl_FragColor = vec4(<CAPTURE>)` and tonemapping/colorspace handling
|
|
299
|
-
fragmentShader = fragmentShader.replace(/^\s*out1\s*=\s*vec4\((.*)\);/gm,
|
|
300
|
-
`
|
|
301
|
-
gl_FragColor = vec4($1);
|
|
302
|
-
#include <tonemapping_fragment>
|
|
303
|
-
#include <colorspace_fragment>`);
|
|
304
|
-
|
|
305
|
-
if (debug) {
|
|
306
|
-
console.group("[MaterialX]: ", materialXData.name);
|
|
307
|
-
console.log("Vertex shader length:", vertexShader.length, vertexShader);
|
|
308
|
-
console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
|
|
309
|
-
console.groupEnd();
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Extract uniforms from both vertex and pixel stages
|
|
313
|
-
const loadingManager = new LoadingManager();
|
|
314
|
-
loadingManager.setURLModifier((url: string) => {
|
|
315
|
-
if (debug) console.log("Loading URL:", url);
|
|
316
|
-
// Find the texture in the textures in the parser and load it from there
|
|
317
|
-
return url;
|
|
318
|
-
});
|
|
319
|
-
let textureLoader = new TextureLoader(loadingManager);
|
|
320
|
-
|
|
321
|
-
// Override the textureLoader.load method to use the parser's loadTexture directly,
|
|
322
|
-
// since we want to load the textures from the glTF document and not from disk.
|
|
323
|
-
// That loader does its own caching too.
|
|
324
|
-
textureLoader.load = (url: string, onLoad?: (texture: Texture) => void, onProgress?: (event: ProgressEvent) => void, onError?: (err: Error) => void): Texture => {
|
|
325
|
-
|
|
326
|
-
// Return a checkerboard texture for now
|
|
327
|
-
const checkerboardTexture = new Texture();
|
|
328
|
-
checkerboardTexture.image = new Image();
|
|
329
|
-
checkerboardTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAG0lEQVR4nGJqWH9q9e8XjA/VrL8UfQIEAAD//zn2CCX5UcsdAAAAAElFTkSuQmCC";
|
|
330
|
-
checkerboardTexture.needsUpdate = true;
|
|
331
|
-
// Pixelated filtering
|
|
332
|
-
checkerboardTexture.magFilter = NearestFilter;
|
|
333
|
-
checkerboardTexture.minFilter = NearestFilter;
|
|
334
|
-
|
|
335
|
-
new Promise(() => {
|
|
336
|
-
// Find the index of the texture in the parser
|
|
337
|
-
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
338
|
-
|
|
339
|
-
// Find in the root extension; there's the textures dictionary:
|
|
340
|
-
// a mapping from the texture name used in MaterialX to the glTF texture index
|
|
341
|
-
const ext = this.parser.json.extensions?.[this.name];
|
|
342
|
-
const textures = ext?.textures || [];
|
|
343
|
-
|
|
344
|
-
const index = textures.findIndex(tex => {
|
|
345
|
-
if (debug) console.log("[MaterialX] Checking texture:", tex.name, "against URL:", filenameWithoutExt);
|
|
346
|
-
return tex.name === filenameWithoutExt;
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
if (index < 0) {
|
|
350
|
-
console.warn("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
|
|
351
|
-
onError?.(new Error(`Texture not found: ${filenameWithoutExt}`));
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
this.parser.getDependency("texture", index).then(tex => {
|
|
355
|
-
if (debug) console.log("[MaterialX] Texture loaded:", tex);
|
|
356
|
-
// update the checkerboard texture with the loaded texture
|
|
357
|
-
checkerboardTexture.image = tex.image;
|
|
358
|
-
checkerboardTexture.needsUpdate = true;
|
|
359
|
-
onLoad?.(tex);
|
|
360
|
-
}).catch(err => {
|
|
361
|
-
onError?.(err);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
return checkerboardTexture;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
const searchPath = ""; // Could be derived from the asset path if needed
|
|
369
|
-
const flipV = false; // Set based on your geometry requirements
|
|
370
|
-
|
|
371
|
-
let uniforms = {
|
|
372
|
-
...getUniformValues(shader.getStage('vertex'), textureLoader, searchPath, flipV),
|
|
373
|
-
...getUniformValues(shader.getStage('pixel'), textureLoader, searchPath, flipV),
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
// Get lighting and environment data from MaterialX environment
|
|
377
|
-
const environment = state.materialXEnvironment;
|
|
378
|
-
// const lights = environment.getLights() || [];
|
|
379
|
-
const lightData = environment.getLightData() || null;
|
|
380
|
-
const radianceTexture = environment.getRadianceTexture() || null;
|
|
381
|
-
const irradianceTexture = environment.getIrradianceTexture() || null;
|
|
382
|
-
const getLightRotation = () => {
|
|
383
|
-
// Placeholder for light rotation logic, if needed
|
|
384
|
-
return new Matrix4();
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
if (debug) console.log({ lightData, radianceTexture, irradianceTexture });
|
|
388
|
-
|
|
389
|
-
Object.assign(uniforms, {
|
|
390
|
-
u_numActiveLightSources: { value: lightData?.length || 0, type: 'i' },
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
if (lightData?.length) {
|
|
394
|
-
Object.assign(uniforms, {
|
|
395
|
-
u_lightData: { value: lightData, type: 'v4v' },
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const mips = Math.trunc(Math.log2(Math.max(radianceTexture?.width ?? 0, radianceTexture?.height ?? 0))) + 1;
|
|
400
|
-
if (debug) console.log("[MaterialX] Radiance texture mips:", mips, "for texture size:", radianceTexture?.width, "x", radianceTexture?.height);
|
|
401
|
-
Object.assign(uniforms, {
|
|
402
|
-
u_envMatrix: { value: getLightRotation() },
|
|
403
|
-
u_envRadiance: { value: radianceTexture, type: 't' },
|
|
404
|
-
u_envRadianceMips: { value: 8, type: 'i' },
|
|
405
|
-
// TODO we need to figure out how we can set a PMREM here... doing many texture samples is prohibitively expensive
|
|
406
|
-
u_envRadianceSamples: { value: 8, type: 'i' },
|
|
407
|
-
u_envIrradiance: { value: irradianceTexture, type: 't' },
|
|
408
|
-
u_refractionEnv: { value: true },
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// console.log("NEW MATERIAL UNIFORMS", uniforms);
|
|
412
|
-
|
|
413
|
-
// Debug: Log the actual shaders to see what we're working with
|
|
414
|
-
// console.log("Generated vertex shader:", vertexShader.substring(0, 500) + "...");
|
|
415
|
-
// console.log("Generated fragment shader:", fragmentShader.substring(0, 500) + "...");
|
|
416
|
-
|
|
417
|
-
// Create js RawShaderMaterial (with GLSL3 for MaterialX compatibility)
|
|
418
|
-
const shaderMaterial = new ShaderMaterial({
|
|
419
|
-
uniforms: uniforms,
|
|
420
|
-
vertexShader: vertexShader,
|
|
421
|
-
fragmentShader: fragmentShader,
|
|
422
|
-
glslVersion: GLSL3,
|
|
236
|
+
const shaderMaterial = new MaterialXMaterial({
|
|
237
|
+
name: material_extension.name,
|
|
238
|
+
shader,
|
|
423
239
|
transparent: isTransparent,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// Update standard transformation matrices
|
|
453
|
-
if (uniforms.u_worldMatrix) {
|
|
454
|
-
if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
|
|
455
|
-
uniforms.u_worldMatrix.value = object.matrixWorld;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (uniforms.u_viewProjectionMatrix) {
|
|
459
|
-
if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
|
|
460
|
-
uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (uniforms.u_viewPosition) {
|
|
464
|
-
if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
|
|
465
|
-
uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (uniforms.u_worldInverseTransposeMatrix) {
|
|
469
|
-
if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
|
|
470
|
-
uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
|
|
471
|
-
}
|
|
240
|
+
side: material_def.doubleSided ? DoubleSide : FrontSide,
|
|
241
|
+
loaders: {
|
|
242
|
+
cacheKey: this.url,
|
|
243
|
+
getTexture: async url => {
|
|
244
|
+
// Find the index of the texture in the parser
|
|
245
|
+
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
246
|
+
|
|
247
|
+
// Resolve the texture from the MaterialX root extension
|
|
248
|
+
const ext = this.materialX_root_data;
|
|
249
|
+
if (ext) {
|
|
250
|
+
const textures = ext.textures || [];
|
|
251
|
+
let index = -1;
|
|
252
|
+
for (const texture of textures) {
|
|
253
|
+
// Find the texture by name and use the pointer string to get the index
|
|
254
|
+
if (texture.name === filenameWithoutExt) {
|
|
255
|
+
const ptr = texture.pointer;
|
|
256
|
+
const indexStr = ptr.substring("/textures/".length);
|
|
257
|
+
index = parseInt(indexStr);
|
|
258
|
+
|
|
259
|
+
if (isNaN(index) || index < 0) {
|
|
260
|
+
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
472
268
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
269
|
+
if (index < 0) {
|
|
270
|
+
console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
return this.parser.getDependency("texture", index).then(tex => {
|
|
274
|
+
if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
|
|
275
|
+
return tex;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
480
280
|
}
|
|
481
|
-
|
|
482
|
-
// Mark uniforms as needing update
|
|
483
|
-
shaderMaterial.uniformsNeedUpdate = true;
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
|
|
487
|
-
|
|
488
|
-
// Add debugging to see if the material compiles correctly
|
|
489
|
-
if (debug) console.log("[MaterialX] material created successfully:", shaderMaterial.name);
|
|
490
|
-
// if (debug) console.log("Material uniforms keys:", Object.keys(shaderMaterial.uniforms || {}));
|
|
491
|
-
// if (debug) console.log("Material transparent:", shaderMaterial.transparent);
|
|
492
|
-
// if (debug) console.log("Material side:", shaderMaterial.side);
|
|
493
|
-
|
|
281
|
+
});
|
|
494
282
|
// Track this material for later lighting updates
|
|
495
|
-
this.
|
|
283
|
+
this._generatedMaterials.push(shaderMaterial);
|
|
496
284
|
|
|
285
|
+
// Add debugging to see if the material compiles correctly
|
|
286
|
+
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
497
287
|
return shaderMaterial;
|
|
498
288
|
|
|
499
289
|
} catch (error) {
|
|
500
290
|
// This is a wasm error (an int) that we need to resolve
|
|
501
|
-
console.error(
|
|
291
|
+
console.error(`[MaterialX] Error creating MaterialX material (${material_extension.name}):`, error);
|
|
502
292
|
// Return a fallback material with stored MaterialX data
|
|
503
293
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
504
|
-
fallbackMaterial.
|
|
505
|
-
fallbackMaterial.
|
|
294
|
+
fallbackMaterial.color.set(0xff00ff);
|
|
295
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
296
|
+
fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
|
|
506
297
|
return fallbackMaterial;
|
|
507
298
|
}
|
|
508
299
|
}
|
|
509
|
-
|
|
510
|
-
private identityMatrix: Matrix4 = new Matrix4().identity();
|
|
511
|
-
// Update lighting uniforms for all generated MaterialX materials
|
|
512
|
-
updateLightingFromEnvironment(environment: MaterialXEnvironment): void {
|
|
513
|
-
|
|
514
|
-
// Get lighting data from environment
|
|
515
|
-
const lightData = environment.getLightData() || null;
|
|
516
|
-
const lightCount = environment.getLightCount() || 0;
|
|
517
|
-
const radianceTexture = environment.getRadianceTexture() || null;
|
|
518
|
-
const irradianceTexture = environment.getIrradianceTexture() || null;
|
|
519
|
-
|
|
520
|
-
if (debug) console.log(`[MaterialX] Updating lighting for ${this.generatedMaterials.length} MaterialX materials`, {
|
|
521
|
-
lightData, radianceTexture, irradianceTexture,
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
// Update each generated material's lighting uniforms
|
|
525
|
-
this.generatedMaterials.forEach((material, _index) => {
|
|
526
|
-
if (!material.uniforms) return;
|
|
527
|
-
|
|
528
|
-
// Update light count
|
|
529
|
-
if (material.uniforms.u_numActiveLightSources && lightCount >= 0) {
|
|
530
|
-
material.uniforms.u_numActiveLightSources.value = lightCount;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Update light data if we have lights
|
|
534
|
-
if (lightData) {
|
|
535
|
-
if (!material.uniforms.u_lightData) {
|
|
536
|
-
material.uniforms.u_lightData = { value: null };
|
|
537
|
-
}
|
|
538
|
-
material.uniforms.u_lightData.value = lightData;
|
|
539
|
-
if (debug) console.log("[MaterialX] Updated light data for material", material.name, lightData, material.uniforms,);
|
|
540
|
-
}
|
|
541
|
-
else if(debug) console.warn("[MaterialX] No light data available to update uniforms for material", material.name);
|
|
542
|
-
|
|
543
|
-
// Update environment uniforms
|
|
544
|
-
if (material.uniforms.u_envMatrix) {
|
|
545
|
-
material.uniforms.u_envMatrix.value = this.identityMatrix;
|
|
546
|
-
}
|
|
547
|
-
if (material.uniforms.u_envRadiance) {
|
|
548
|
-
material.uniforms.u_envRadiance.value = radianceTexture;
|
|
549
|
-
}
|
|
550
|
-
if (material.uniforms.u_envRadianceMips) {
|
|
551
|
-
material.uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(radianceTexture?.source.data.width ?? 0, radianceTexture?.source.data.height ?? 0))) + 1;
|
|
552
|
-
}
|
|
553
|
-
if (material.uniforms.u_envIrradiance) {
|
|
554
|
-
material.uniforms.u_envIrradiance.value = irradianceTexture;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Mark uniforms as needing update
|
|
558
|
-
// console.log("Light data in uniforms", material.uniforms, material.fragmentShader);
|
|
559
|
-
material.uniformsNeedUpdate = true;
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
/* TODO not working yet
|
|
563
|
-
this.generatedMaterials.forEach((material, index) => {
|
|
564
|
-
console.log("Regenerating shaders for MaterialX material:", index, material.name);
|
|
565
|
-
material.userData.regenerateShaders();
|
|
566
|
-
resetShaders(this.context);
|
|
567
|
-
});
|
|
568
|
-
*/
|
|
569
|
-
}
|
|
570
300
|
}
|