@needle-tools/materialx 1.1.1-next.623fc20 → 1.1.1-next.714bc32
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/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/loader/loader.three.ts +212 -208
- package/src/materialx.helper.ts +47 -40
- package/src/materialx.material.ts +48 -18
- package/src/materialx.ts +6 -1
- package/src/utils.ts +4 -0
- /package/src/{textureHelper.ts → utils.texture.ts} +0 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
export { ready, type MaterialXContext } from "./materialx.js";
|
|
2
2
|
export { MaterialXMaterial } from "./materialx.material.js";
|
|
3
3
|
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import { createMaterialXMaterial } from "./loader/loader.three.js";
|
|
7
|
+
|
|
8
|
+
const Experimental_API = {
|
|
9
|
+
createMaterialXMaterial
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export { Experimental_API }
|
|
@@ -3,6 +3,7 @@ import { GLTFLoader, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loa
|
|
|
3
3
|
import { ready, state, MaterialXContext } from "../materialx.js";
|
|
4
4
|
import { debug } from "../utils.js";
|
|
5
5
|
import { MaterialXMaterial } from "../materialx.material.js";
|
|
6
|
+
import { Callbacks as callbacks } from "../materialx.helper.js";
|
|
6
7
|
|
|
7
8
|
// TypeScript interfaces matching the C# data structures
|
|
8
9
|
export type MaterialX_root_extension = {
|
|
@@ -90,36 +91,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
90
91
|
return this._loadMaterialAsync(materialIndex);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
// Parse the MaterialX document once and cache it
|
|
94
|
-
private async _materialXDocumentReady(): Promise<any> {
|
|
95
|
-
if (this._documentReadyPromise) {
|
|
96
|
-
return this._documentReadyPromise;
|
|
97
|
-
}
|
|
98
|
-
return this._documentReadyPromise = (async () => {
|
|
99
|
-
if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
|
|
100
|
-
|
|
101
|
-
// Ensure MaterialX is initialized
|
|
102
|
-
await ready();
|
|
103
|
-
|
|
104
|
-
if (!state.materialXModule) {
|
|
105
|
-
throw new Error("[MaterialX] module failed to initialize");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Create MaterialX document and parse ALL the XML data from root
|
|
109
|
-
const doc = state.materialXModule.createDocument();
|
|
110
|
-
doc.setDataLibrary(state.materialXStdLib);
|
|
111
|
-
|
|
112
|
-
// Parse all MaterialX XML strings from the root data
|
|
113
|
-
const root = this.materialX_root_data
|
|
114
|
-
if (root) {
|
|
115
|
-
if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
|
|
116
|
-
await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
120
|
-
return doc;
|
|
121
|
-
})();
|
|
122
|
-
}
|
|
123
94
|
|
|
124
95
|
private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
|
|
125
96
|
|
|
@@ -129,8 +100,61 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
129
100
|
// Handle different types of MaterialX data
|
|
130
101
|
const ext = materialDef.extensions?.[this.name] as MaterialX_material_extension;
|
|
131
102
|
|
|
132
|
-
|
|
133
|
-
|
|
103
|
+
const mtlx = this.materialX_root_data?.mtlx;
|
|
104
|
+
|
|
105
|
+
if (ext && mtlx) {
|
|
106
|
+
|
|
107
|
+
const materialOptions: MaterialXMaterialOptions = {
|
|
108
|
+
...this.options,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!materialOptions.parameters) materialOptions.parameters = {};
|
|
112
|
+
|
|
113
|
+
if (materialOptions.parameters?.side === undefined && materialDef.doubleSided !== undefined) {
|
|
114
|
+
materialOptions.parameters.side = materialDef.doubleSided ? DoubleSide : FrontSide;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return createMaterialXMaterial(mtlx, ext.name, {
|
|
118
|
+
cacheKey: this.options.cacheKey || "",
|
|
119
|
+
getTexture: async url => {
|
|
120
|
+
// Find the index of the texture in the parser
|
|
121
|
+
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
122
|
+
|
|
123
|
+
// Resolve the texture from the MaterialX root extension
|
|
124
|
+
if (this.materialX_root_data) {
|
|
125
|
+
const textures = this.materialX_root_data.textures || [];
|
|
126
|
+
let index = -1;
|
|
127
|
+
for (const texture of textures) {
|
|
128
|
+
// Find the texture by name and use the pointer string to get the index
|
|
129
|
+
if (texture.name === filenameWithoutExt) {
|
|
130
|
+
const ptr = texture.pointer;
|
|
131
|
+
const indexStr = ptr.substring("/textures/".length);
|
|
132
|
+
index = parseInt(indexStr);
|
|
133
|
+
|
|
134
|
+
if (isNaN(index) || index < 0) {
|
|
135
|
+
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (index < 0) {
|
|
145
|
+
console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
return this.parser.getDependency("texture", index);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}, materialOptions, this.context)
|
|
153
|
+
// Cache and return the generated material
|
|
154
|
+
.then(mat => {
|
|
155
|
+
if (mat instanceof MaterialXMaterial) this._generatedMaterials.push(mat);
|
|
156
|
+
return mat;
|
|
157
|
+
})
|
|
134
158
|
}
|
|
135
159
|
|
|
136
160
|
// Return fallback material instead of null
|
|
@@ -139,199 +163,179 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
139
163
|
return fallbackMaterial;
|
|
140
164
|
}
|
|
141
165
|
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
|
|
166
|
+
}
|
|
145
167
|
|
|
146
|
-
const doc = await this._materialXDocumentReady();
|
|
147
168
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
fallbackMaterial.userData.materialX = material_extension;
|
|
152
|
-
fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
|
|
153
|
-
return fallbackMaterial;
|
|
154
|
-
}
|
|
169
|
+
type MaterialXMaterialOptions = {
|
|
170
|
+
parameters?: MaterialParameters;
|
|
171
|
+
}
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Add the MaterialXLoader to the GLTFLoader instance.
|
|
175
|
+
*/
|
|
176
|
+
export function useNeedleMaterialX(loader: GLTFLoader, options?: MaterialXLoaderOptions, context?: MaterialXContext) {
|
|
177
|
+
loader.register(p => {
|
|
178
|
+
const loader = new MaterialXLoader(p, options || {}, context || {});
|
|
179
|
+
return loader;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// Parse the MaterialX document once and cache it
|
|
187
|
+
async function load(mtlx: string): Promise<any> {
|
|
188
|
+
// Ensure MaterialX is initialized
|
|
189
|
+
await ready();
|
|
190
|
+
if (!state.materialXModule) {
|
|
191
|
+
throw new Error("[MaterialX] module failed to initialize");
|
|
192
|
+
}
|
|
193
|
+
// Create MaterialX document and parse ALL the XML data from root
|
|
194
|
+
const doc = state.materialXModule.createDocument();
|
|
195
|
+
doc.setDataLibrary(state.materialXStdLib);
|
|
196
|
+
// Parse all MaterialX XML strings from the root data
|
|
197
|
+
await state.materialXModule.readFromXmlString(doc, mtlx, "");
|
|
198
|
+
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
199
|
+
return doc;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function createMaterialXMaterial(mtlx: string, materialNodeName: string, loaders: callbacks, options?: MaterialXLoaderOptions, context?: MaterialXContext): Promise<Material> {
|
|
203
|
+
try {
|
|
204
|
+
if (debug) console.log(`Creating MaterialX material: ${materialNodeName}`);
|
|
205
|
+
|
|
206
|
+
const doc = await load(mtlx);
|
|
207
|
+
|
|
208
|
+
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
209
|
+
console.warn("[MaterialX] WASM module not ready, returning fallback material");
|
|
210
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
211
|
+
fallbackMaterial.name = `MaterialX_Fallback_${materialNodeName}`;
|
|
212
|
+
return fallbackMaterial;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Find the renderable element following MaterialX example pattern exactly
|
|
216
|
+
let renderableElement: any = null;
|
|
217
|
+
let foundRenderable = false;
|
|
218
|
+
|
|
219
|
+
if (debug) console.log("[MaterialX] document", doc);
|
|
220
|
+
|
|
221
|
+
// Search for material nodes first (following the reference pattern)
|
|
222
|
+
const materialNodes = doc.getMaterialNodes();
|
|
223
|
+
if (debug) console.log(`[MaterialX] Found ${materialNodes.length} material nodes in document`, materialNodes);
|
|
224
|
+
|
|
225
|
+
// Handle both array and vector-like APIs
|
|
226
|
+
for (let i = 0; i < materialNodes.length; ++i) {
|
|
227
|
+
const materialNode = materialNodes[i];
|
|
228
|
+
if (materialNode) {
|
|
229
|
+
const name = materialNode.getNamePath();
|
|
230
|
+
if (debug) console.log(`[MaterialX] Scan material[${i}]: ${name}`);
|
|
231
|
+
|
|
232
|
+
// Find the matching material
|
|
233
|
+
if (materialNodes.length === 1 || name == materialNodeName) {
|
|
234
|
+
materialNodeName = name;
|
|
235
|
+
renderableElement = materialNode;
|
|
236
|
+
foundRenderable = true;
|
|
237
|
+
if (debug) console.log(`[MaterialX] Use material node: '${name}'`);
|
|
238
|
+
break;
|
|
181
239
|
}
|
|
182
240
|
}
|
|
241
|
+
}
|
|
183
242
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
243
|
+
/*
|
|
244
|
+
// If no material nodes found, search nodeGraphs
|
|
245
|
+
if (!foundRenderable) {
|
|
246
|
+
const nodeGraphs = doc.getNodeGraphs();
|
|
247
|
+
console.log(`Found ${nodeGraphs.length} node graphs in document`);
|
|
248
|
+
const nodeGraphsLength = nodeGraphs.length;
|
|
249
|
+
for (let i = 0; i < nodeGraphsLength; ++i) {
|
|
250
|
+
const nodeGraph = nodeGraphs[i];
|
|
251
|
+
if (nodeGraph) {
|
|
252
|
+
// Skip any nodegraph that has nodedef or sourceUri
|
|
253
|
+
if ((nodeGraph as any).hasAttribute('nodedef') || (nodeGraph as any).hasSourceUri()) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
// Skip any nodegraph that is connected to something downstream
|
|
257
|
+
if ((nodeGraph as any).getDownstreamPorts().length > 0) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const outputs = (nodeGraph as any).getOutputs();
|
|
261
|
+
for (let j = 0; j < outputs.length; ++j) {
|
|
262
|
+
const output = outputs[j];
|
|
263
|
+
if (output && !foundRenderable) {
|
|
264
|
+
renderableElement = output;
|
|
265
|
+
foundRenderable = true;
|
|
266
|
+
break;
|
|
209
267
|
}
|
|
210
|
-
if (foundRenderable) break;
|
|
211
268
|
}
|
|
269
|
+
if (foundRenderable) break;
|
|
212
270
|
}
|
|
213
271
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// If still no element found, search document outputs
|
|
275
|
+
if (!foundRenderable) {
|
|
276
|
+
const outputs = doc.getOutputs();
|
|
277
|
+
console.log(`Found ${outputs.length} output nodes in document`);
|
|
278
|
+
const outputsLength = outputs.length;
|
|
279
|
+
for (let i = 0; i < outputsLength; ++i) {
|
|
280
|
+
const output = outputs[i];
|
|
281
|
+
if (output && !foundRenderable) {
|
|
282
|
+
renderableElement = output;
|
|
283
|
+
foundRenderable = true;
|
|
284
|
+
break;
|
|
227
285
|
}
|
|
228
286
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (!renderableElement) {
|
|
232
|
-
console.warn(`[MaterialX] No renderable element found in MaterialX document (${material_extension.name})`);
|
|
233
|
-
const fallbackMaterial = new MeshStandardMaterial();
|
|
234
|
-
fallbackMaterial.color.set(0xff00ff);
|
|
235
|
-
fallbackMaterial.userData.materialX = material_extension;
|
|
236
|
-
fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
|
|
237
|
-
return fallbackMaterial;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
241
|
-
|
|
242
|
-
// Check transparency and set context options like the reference
|
|
243
|
-
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
244
|
-
state.materialXGenContext.getOptions().hwTransparency = isTransparent;
|
|
245
|
-
|
|
246
|
-
// Generate shaders using the element's name path
|
|
247
|
-
if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
|
|
248
|
-
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
249
|
-
|
|
250
|
-
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
251
|
-
|
|
252
|
-
const rootExtension = this.materialX_root_data;
|
|
253
|
-
|
|
254
|
-
const shaderInfo = rootExtension && material_extension.shader !== undefined && material_extension.shader >= 0
|
|
255
|
-
? rootExtension.shaders?.[material_extension.shader]
|
|
256
|
-
: null;
|
|
257
|
-
|
|
258
|
-
const shaderMaterial = new MaterialXMaterial({
|
|
259
|
-
name: material_extension.name,
|
|
260
|
-
shaderName: shaderInfo?.originalName || shaderInfo?.name || null,
|
|
261
|
-
shader,
|
|
262
|
-
context: this.context,
|
|
263
|
-
parameters: {
|
|
264
|
-
transparent: isTransparent,
|
|
265
|
-
side: material_def.doubleSided ? DoubleSide : FrontSide,
|
|
266
|
-
...this.options.parameters,
|
|
267
|
-
},
|
|
268
|
-
loaders: {
|
|
269
|
-
cacheKey: this.options.cacheKey || "",
|
|
270
|
-
getTexture: async url => {
|
|
271
|
-
// Find the index of the texture in the parser
|
|
272
|
-
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
273
|
-
|
|
274
|
-
// Resolve the texture from the MaterialX root extension
|
|
275
|
-
if (rootExtension) {
|
|
276
|
-
const textures = rootExtension.textures || [];
|
|
277
|
-
let index = -1;
|
|
278
|
-
for (const texture of textures) {
|
|
279
|
-
// Find the texture by name and use the pointer string to get the index
|
|
280
|
-
if (texture.name === filenameWithoutExt) {
|
|
281
|
-
const ptr = texture.pointer;
|
|
282
|
-
const indexStr = ptr.substring("/textures/".length);
|
|
283
|
-
index = parseInt(indexStr);
|
|
284
|
-
|
|
285
|
-
if (isNaN(index) || index < 0) {
|
|
286
|
-
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
287
|
+
}
|
|
288
|
+
*/
|
|
294
289
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
return this.parser.getDependency("texture", index).then(tex => {
|
|
300
|
-
if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
|
|
301
|
-
return tex;
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
// Track this material for later lighting updates
|
|
309
|
-
this._generatedMaterials.push(shaderMaterial);
|
|
310
|
-
|
|
311
|
-
// Add debugging to see if the material compiles correctly
|
|
312
|
-
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
313
|
-
return shaderMaterial;
|
|
314
|
-
|
|
315
|
-
} catch (error) {
|
|
316
|
-
// This is a wasm error (an int) that we need to resolve
|
|
317
|
-
console.error(`[MaterialX] Error creating MaterialX material (${material_extension.name}):`, error);
|
|
318
|
-
// Return a fallback material with stored MaterialX data
|
|
290
|
+
if (!renderableElement) {
|
|
291
|
+
console.warn(`[MaterialX] No renderable element found in MaterialX document (${name})`);
|
|
319
292
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
320
293
|
fallbackMaterial.color.set(0xff00ff);
|
|
321
|
-
fallbackMaterial.
|
|
322
|
-
fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
|
|
294
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${name}`;
|
|
323
295
|
return fallbackMaterial;
|
|
324
296
|
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
297
|
|
|
298
|
+
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
328
299
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
300
|
+
// Check transparency and set context options like the reference
|
|
301
|
+
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
302
|
+
state.materialXGenContext.getOptions().hwTransparency = isTransparent;
|
|
303
|
+
|
|
304
|
+
// Generate shaders using the element's name path
|
|
305
|
+
if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
|
|
306
|
+
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
307
|
+
|
|
308
|
+
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
309
|
+
|
|
310
|
+
// const rootExtension = this.materialX_root_data;
|
|
311
|
+
|
|
312
|
+
// const shaderInfo = rootExtension && material_extension.shader !== undefined && material_extension.shader >= 0
|
|
313
|
+
// ? rootExtension.shaders?.[material_extension.shader]
|
|
314
|
+
// : null;
|
|
315
|
+
|
|
316
|
+
const shaderMaterial = new MaterialXMaterial({
|
|
317
|
+
name: materialNodeName,
|
|
318
|
+
shaderName: null, //shaderInfo?.originalName || shaderInfo?.name || null,
|
|
319
|
+
shader,
|
|
320
|
+
context: context || {},
|
|
321
|
+
parameters: {
|
|
322
|
+
transparent: isTransparent,
|
|
323
|
+
...options?.parameters,
|
|
324
|
+
},
|
|
325
|
+
loaders: loaders,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Add debugging to see if the material compiles correctly
|
|
329
|
+
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
330
|
+
return shaderMaterial;
|
|
331
|
+
|
|
332
|
+
} catch (error) {
|
|
333
|
+
// This is a wasm error (an int) that we need to resolve
|
|
334
|
+
console.error(`[MaterialX] Error creating MaterialX material (${name}):`, error);
|
|
335
|
+
// Return a fallback material with stored MaterialX data
|
|
336
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
337
|
+
fallbackMaterial.color.set(0xff00ff);
|
|
338
|
+
fallbackMaterial.name = `MaterialX_Error_${name}`;
|
|
339
|
+
return fallbackMaterial;
|
|
340
|
+
}
|
|
337
341
|
}
|
package/src/materialx.helper.ts
CHANGED
|
@@ -70,17 +70,17 @@ function fromMatrix(matrix: MaterialX.Matrix, dimension: MaterialX.Matrix["size"
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
export type
|
|
73
|
+
export type Callbacks = {
|
|
74
74
|
/**
|
|
75
75
|
* Cache key for the loaders, used to identify and reuse textures
|
|
76
76
|
*/
|
|
77
|
-
readonly cacheKey
|
|
77
|
+
readonly cacheKey?: string;
|
|
78
78
|
/**
|
|
79
79
|
* Get a texture by path
|
|
80
80
|
* @param {string} path - The path to the texture
|
|
81
81
|
* @return {Promise<THREE.Texture>} - A promise that resolves to the texture
|
|
82
82
|
*/
|
|
83
|
-
readonly getTexture: (path: string) => Promise<THREE.Texture>;
|
|
83
|
+
readonly getTexture: (path: string) => Promise<THREE.Texture | null | void>;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const defaultTexture = new THREE.Texture();
|
|
@@ -119,7 +119,7 @@ function addToCache(key: string, value: any): void {
|
|
|
119
119
|
/**
|
|
120
120
|
* Get Three uniform from MaterialX value
|
|
121
121
|
*/
|
|
122
|
-
function toThreeUniform(uniforms: any, type: string, value: any, name: string, loaders:
|
|
122
|
+
function toThreeUniform(uniforms: any, type: string, value: any, name: string, loaders: Callbacks, searchPath: string): THREE.Uniform {
|
|
123
123
|
|
|
124
124
|
const uniform = new THREE.Uniform<any>(null);
|
|
125
125
|
|
|
@@ -165,7 +165,7 @@ function toThreeUniform(uniforms: any, type: string, value: any, name: string, l
|
|
|
165
165
|
checkCache = true;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
const cacheKey = `${loaders.cacheKey}-${texturePath}
|
|
168
|
+
const cacheKey = loaders.cacheKey?.length ? `${loaders.cacheKey}-${texturePath}` : texturePath;
|
|
169
169
|
const cacheValue = checkCache ? tryGetFromCache(cacheKey) : null;
|
|
170
170
|
if (cacheValue) {
|
|
171
171
|
if (debug) console.log('[MaterialX] Use cached texture: ', cacheKey, cacheValue);
|
|
@@ -180,27 +180,29 @@ function toThreeUniform(uniforms: any, type: string, value: any, name: string, l
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
else {
|
|
183
|
-
if (name.toLowerCase().includes("normal")) {
|
|
184
|
-
uniform.value = defaultNormalTexture;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
uniform.value = defaultTexture;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
183
|
if (debug) console.log('[MaterialX] Load texture:', texturePath);
|
|
184
|
+
|
|
185
|
+
if (name.toLowerCase().includes("normal")) uniform.value = defaultNormalTexture;
|
|
186
|
+
else uniform.value = defaultTexture;
|
|
187
|
+
const defaultValue = uniform.value;
|
|
191
188
|
// Save the loading promise in the cache
|
|
192
|
-
const promise = loaders.getTexture(texturePath)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
189
|
+
const promise = loaders.getTexture(texturePath)
|
|
190
|
+
?.then(res => {
|
|
191
|
+
if (res) {
|
|
192
|
+
res = res.clone(); // we need to clone the texture once to avoid colorSpace issues with other materials
|
|
193
|
+
res.colorSpace = THREE.LinearSRGBColorSpace;
|
|
194
|
+
setTextureParameters(res, name, uniforms);
|
|
195
|
+
}
|
|
196
|
+
return res;
|
|
197
|
+
})
|
|
198
|
+
.catch(err => {
|
|
199
|
+
console.error(`[MaterialX] Failed to load texture ${name} '${texturePath}'`, err);
|
|
200
|
+
return defaultValue;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (checkCache) addToCache(cacheKey, promise);
|
|
204
|
+
|
|
205
|
+
promise?.then(res => {
|
|
204
206
|
if (res) uniform.value = res;
|
|
205
207
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
206
208
|
});
|
|
@@ -454,7 +456,7 @@ export function getLightData(lights: Array<THREE.Light>, genContext: any): { lig
|
|
|
454
456
|
/**
|
|
455
457
|
* Get uniform values for a shader
|
|
456
458
|
*/
|
|
457
|
-
export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders:
|
|
459
|
+
export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Callbacks, searchPath: string) {
|
|
458
460
|
const threeUniforms = {};
|
|
459
461
|
|
|
460
462
|
const uniformBlocks = shaderStage.getUniformBlocks()
|
|
@@ -468,8 +470,8 @@ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Lo
|
|
|
468
470
|
const value = variable.getValue()?.getData();
|
|
469
471
|
const uniformName = variable.getVariable();
|
|
470
472
|
const type = variable.getType().getName();
|
|
471
|
-
if (debug) console.log("Adding uniform", { path: variable.getPath(), name: uniformName, value: value, type: type });
|
|
472
473
|
threeUniforms[uniformName] = toThreeUniform(uniforms, type, value, uniformName, loaders, searchPath);
|
|
474
|
+
if (debug) console.log("Adding uniform", { path: variable.getPath(), type: type, name: uniformName, value: threeUniforms[uniformName], },);
|
|
473
475
|
}
|
|
474
476
|
}
|
|
475
477
|
}
|
|
@@ -501,22 +503,27 @@ export function generateMaterialPropertiesForUniforms(material: THREE.ShaderMate
|
|
|
501
503
|
break;
|
|
502
504
|
}
|
|
503
505
|
if (key) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
506
|
+
if (material.hasOwnProperty(key)) {
|
|
507
|
+
if (debug) console.warn(`[MaterialX] Uniform ${uniformName} already exists in material as property ${key}, skipping.`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
Object.defineProperty(material, key, {
|
|
511
|
+
get: function () {
|
|
512
|
+
return this.uniforms?.[uniformName].value
|
|
513
|
+
},
|
|
514
|
+
set: function (v) {
|
|
515
|
+
const uniforms = this.uniforms;
|
|
516
|
+
if (!uniforms || !uniforms[uniformName]) {
|
|
517
|
+
console.warn(`[MaterialX] Uniform ${uniformName} not found in ${this.name} uniforms`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
this.uniforms[uniformName].value = v;
|
|
521
|
+
this.uniformsNeedUpdate = true;
|
|
513
522
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
});
|
|
523
|
+
});
|
|
524
|
+
}
|
|
518
525
|
}
|
|
519
526
|
}
|
|
520
527
|
}
|
|
521
528
|
}
|
|
522
|
-
}
|
|
529
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BufferGeometry, Camera, FrontSide, GLSL3, Group, IUniform, MaterialParameters, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
2
2
|
import { debug, getFrame, getTime } from "./utils.js";
|
|
3
3
|
import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
|
|
4
|
-
import { generateMaterialPropertiesForUniforms, getUniformValues,
|
|
4
|
+
import { generateMaterialPropertiesForUniforms, getUniformValues, Callbacks } from "./materialx.helper.js";
|
|
5
5
|
import { cloneUniforms, cloneUniformsGroups } from "three/src/renderers/shaders/UniformsUtils.js";
|
|
6
6
|
|
|
7
7
|
|
|
@@ -13,10 +13,11 @@ declare type MaterialXMaterialInitParameters = {
|
|
|
13
13
|
name: string,
|
|
14
14
|
shaderName?: string | null, // Optional name of the shader
|
|
15
15
|
shader: any,
|
|
16
|
-
loaders:
|
|
16
|
+
loaders: Callbacks,
|
|
17
17
|
context: MaterialXContext,
|
|
18
18
|
// Optional parameters
|
|
19
19
|
parameters?: MaterialParameters,
|
|
20
|
+
debug?: boolean,
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
|
|
@@ -25,7 +26,7 @@ type Precision = "highp" | "mediump" | "lowp";
|
|
|
25
26
|
export class MaterialXMaterial extends ShaderMaterial {
|
|
26
27
|
|
|
27
28
|
/** The original name of the shader */
|
|
28
|
-
readonly shaderName
|
|
29
|
+
readonly shaderName: string | null = null;
|
|
29
30
|
|
|
30
31
|
copy(source: MaterialXMaterial): this {
|
|
31
32
|
super.copy(source);
|
|
@@ -43,6 +44,7 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
43
44
|
|
|
44
45
|
private _context: MaterialXContext | null = null;
|
|
45
46
|
private _shader: any;
|
|
47
|
+
private _needsTangents: boolean = false;
|
|
46
48
|
|
|
47
49
|
constructor(init?: MaterialXMaterialInitParameters) {
|
|
48
50
|
|
|
@@ -75,7 +77,8 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
75
77
|
vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
|
|
76
78
|
|
|
77
79
|
// Patch fragmentShader
|
|
78
|
-
const precision = init.parameters?.precision || "highp" as Precision;
|
|
80
|
+
const precision: Precision = init.parameters?.precision || "highp" as Precision;
|
|
81
|
+
vertexShader = vertexShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
|
|
79
82
|
fragmentShader = fragmentShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
|
|
80
83
|
fragmentShader = fragmentShader.replace(/#define M_FLOAT_EPS 1e-8/g, precision === "highp" ? `#define M_FLOAT_EPS 1e-8` : `#define M_FLOAT_EPS 1e-3`);
|
|
81
84
|
|
|
@@ -88,9 +91,16 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
88
91
|
fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
89
92
|
fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
|
|
90
93
|
|
|
94
|
+
// Capture some vertex shader properties
|
|
95
|
+
const uv_is_vec2 = vertexShader.includes('in vec2 uv;'); // check if uv is vec2; e.g. https://matlib.gpuopen.com/main/materials/all?material=da6ec531-f5c1-4790-ac14-8a5c51d0314e
|
|
96
|
+
const uv1_is_vec2 = vertexShader.includes('in vec2 uv1;');
|
|
97
|
+
const uv2_is_vec2 = vertexShader.includes('in vec2 uv2;');
|
|
98
|
+
const uv3_is_vec2 = vertexShader.includes('in vec2 uv3;');
|
|
99
|
+
|
|
91
100
|
// Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
|
|
92
101
|
vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
|
|
93
102
|
vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
|
|
103
|
+
vertexShader = vertexShader.replace(/in\s+vec2\s+uv;/g, '');
|
|
94
104
|
vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
|
|
95
105
|
var hasUv1 = vertexShader.includes('in vec3 uv1;');
|
|
96
106
|
vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
|
|
@@ -105,10 +115,10 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
105
115
|
|
|
106
116
|
// Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
|
|
107
117
|
// TODO what if we actually have a 3-component UV? Not sure what three.js does then
|
|
108
|
-
vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
|
|
109
|
-
vertexShader = vertexShader.replace(/texcoord_1 = uv1;/g, 'texcoord_1 = vec3(uv1, 0.0);');
|
|
110
|
-
vertexShader = vertexShader.replace(/texcoord_2 = uv2;/g, 'texcoord_2 = vec3(uv2, 0.0);');
|
|
111
|
-
vertexShader = vertexShader.replace(/texcoord_3 = uv3;/g, 'texcoord_3 = vec3(uv3, 0.0);');
|
|
118
|
+
if (!uv_is_vec2) vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
|
|
119
|
+
if (!uv1_is_vec2) vertexShader = vertexShader.replace(/texcoord_1 = uv1;/g, 'texcoord_1 = vec3(uv1, 0.0);');
|
|
120
|
+
if (!uv2_is_vec2) vertexShader = vertexShader.replace(/texcoord_2 = uv2;/g, 'texcoord_2 = vec3(uv2, 0.0);');
|
|
121
|
+
if (!uv3_is_vec2) vertexShader = vertexShader.replace(/texcoord_3 = uv3;/g, 'texcoord_3 = vec3(uv3, 0.0);');
|
|
112
122
|
|
|
113
123
|
// Patch units – seems MaterialX uses different units and we end up with wrong light values?
|
|
114
124
|
// result.direction = light.position - position;
|
|
@@ -148,9 +158,10 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
148
158
|
defines: defines,
|
|
149
159
|
...init.parameters, // Spread any additional parameters passed to the material
|
|
150
160
|
});
|
|
161
|
+
this.shaderName = init.shaderName || null;
|
|
151
162
|
this._context = init.context;
|
|
152
163
|
this._shader = init.shader;
|
|
153
|
-
this.
|
|
164
|
+
this._needsTangents = vertexShader.includes('in vec4 tangent;') || vertexShader.includes('in vec3 tangent;');
|
|
154
165
|
|
|
155
166
|
Object.assign(this.uniforms, {
|
|
156
167
|
...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
|
|
@@ -161,6 +172,9 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
161
172
|
u_viewPosition: { value: new Vector3() },
|
|
162
173
|
u_worldInverseTransposeMatrix: { value: new Matrix4() },
|
|
163
174
|
|
|
175
|
+
// u_shadowMatrix: { value: new Matrix4() },
|
|
176
|
+
// u_shadowMap: { value: null, type: 't' }, // Shadow map
|
|
177
|
+
|
|
164
178
|
u_envMatrix: { value: new Matrix4() },
|
|
165
179
|
u_envRadiance: { value: null, type: 't' },
|
|
166
180
|
u_envRadianceMips: { value: 8, type: 'i' },
|
|
@@ -176,7 +190,7 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
176
190
|
generateMaterialPropertiesForUniforms(this, init.shader.getStage('vertex'));
|
|
177
191
|
|
|
178
192
|
|
|
179
|
-
if (debug) {
|
|
193
|
+
if (debug || init.debug) {
|
|
180
194
|
// Get lighting and environment data from MaterialX environment
|
|
181
195
|
console.group("[MaterialX]: ", this.name);
|
|
182
196
|
console.log(`Vertex shader length: ${vertexShader.length}\n`, vertexShader);
|
|
@@ -185,20 +199,28 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
185
199
|
}
|
|
186
200
|
}
|
|
187
201
|
|
|
188
|
-
|
|
202
|
+
private _missingTangentsWarned: boolean = false;
|
|
203
|
+
onBeforeRender(renderer: WebGLRenderer, _scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D, _group: Group): void {
|
|
204
|
+
if (this._needsTangents && !geometry.attributes.tangent) {
|
|
205
|
+
if (!this._missingTangentsWarned) {
|
|
206
|
+
this._missingTangentsWarned = true;
|
|
207
|
+
console.warn(`[MaterialX] Tangents are required for this material (${this.name}) but not present in the geometry.`);
|
|
208
|
+
// TODO: can we compute tangents here?
|
|
209
|
+
}
|
|
210
|
+
}
|
|
189
211
|
const time = this._context?.getTime?.() || getTime();
|
|
190
212
|
const frame = this._context?.getFrame?.() || getFrame();
|
|
191
213
|
const env = MaterialXEnvironment.get(_scene);
|
|
192
214
|
if (env) {
|
|
193
|
-
env.update(frame, _scene,
|
|
194
|
-
this.updateUniforms(env, object, camera, time, frame);
|
|
215
|
+
env.update(frame, _scene, renderer);
|
|
216
|
+
this.updateUniforms(env, renderer, object, camera, time, frame);
|
|
195
217
|
}
|
|
196
218
|
}
|
|
197
219
|
|
|
198
220
|
|
|
199
221
|
envMapIntensity: number = 1.0; // Default intensity for environment map
|
|
200
222
|
envMap: Texture | null = null; // Environment map texture, can be set externally
|
|
201
|
-
updateUniforms = (environment: MaterialXEnvironment, object: Object3D, camera: Camera, time?: number, frame?: number) => {
|
|
223
|
+
updateUniforms = (environment: MaterialXEnvironment, renderer: WebGLRenderer, object: Object3D, camera: Camera, time?: number, frame?: number) => {
|
|
202
224
|
|
|
203
225
|
const uniforms = this.uniforms as Uniforms;
|
|
204
226
|
|
|
@@ -213,15 +235,23 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
213
235
|
uniforms.u_viewProjectionMatrix.needsUpdate = true;
|
|
214
236
|
}
|
|
215
237
|
|
|
238
|
+
if (uniforms.u_worldInverseTransposeMatrix) {
|
|
239
|
+
uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
|
|
240
|
+
uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
216
243
|
if (uniforms.u_viewPosition) {
|
|
217
244
|
uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
|
|
218
245
|
uniforms.u_viewPosition.needsUpdate = true;
|
|
219
246
|
}
|
|
220
247
|
|
|
221
|
-
if (uniforms.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
248
|
+
// if (uniforms.u_shadowMap) {
|
|
249
|
+
// const light = environment.lights?.[2] || null;
|
|
250
|
+
// uniforms.u_shadowMatrix.value = light?.shadow?.matrix.clone().premultiply(object.matrixWorld.clone()).invert();
|
|
251
|
+
// uniforms.u_shadowMap.value = light.shadow?.map || null;
|
|
252
|
+
// uniforms.u_shadowMap.needsUpdate = true;
|
|
253
|
+
// console.log("[MaterialX] Renderer shadow map updated", light);
|
|
254
|
+
// }
|
|
225
255
|
|
|
226
256
|
// Update time uniforms
|
|
227
257
|
if (uniforms.u_time) {
|
package/src/materialx.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MaterialX as MX } from "./materialx.types.js";
|
|
2
2
|
import MaterialX from "../bin/JsMaterialXGenShader.js";
|
|
3
3
|
import { debug } from "./utils.js";
|
|
4
|
-
import { renderPMREMToEquirect } from "./
|
|
4
|
+
import { renderPMREMToEquirect } from "./utils.texture.js";
|
|
5
5
|
import { Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
|
|
6
6
|
import { registerLights, getLightData, LightData } from "./materialx.helper.js";
|
|
7
7
|
import type { MaterialXMaterial } from "./materialx.material.js";
|
|
@@ -95,6 +95,8 @@ export async function ready(): Promise<void> {
|
|
|
95
95
|
// Set a reasonable default for max active lights
|
|
96
96
|
state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
|
|
97
97
|
|
|
98
|
+
// state.materialXGenContext.getOptions().hwShadowMap = true;
|
|
99
|
+
|
|
98
100
|
// This prewarms the shader generation context to have all light types
|
|
99
101
|
await registerLights(state.materialXModule, state.materialXGenContext);
|
|
100
102
|
|
|
@@ -209,6 +211,9 @@ export class MaterialXEnvironment {
|
|
|
209
211
|
this._texturesCache.clear();
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
get lights() {
|
|
215
|
+
return this._lights;
|
|
216
|
+
}
|
|
212
217
|
get lightData() {
|
|
213
218
|
return this._lightData;
|
|
214
219
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// import { BufferGeometry } from 'three';
|
|
2
|
+
// import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
export function getParam(name: string) {
|
|
2
6
|
const urlParams = new URLSearchParams(window.location.search);
|
|
3
7
|
const param = urlParams.get(name);
|
|
File without changes
|