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