@needle-tools/materialx 1.0.1-next.b9638d9 → 1.0.1-next.c315a2f
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/README.md +5 -0
- package/codegen/register_types.ts +4 -0
- package/index.ts +1 -1
- package/package.json +18 -3
- package/src/index.ts +2 -2
- package/src/loader/loader.needle.ts +27 -40
- package/src/loader/loader.three.ts +129 -394
- package/src/materialx.helper.ts +477 -0
- package/src/materialx.material.ts +216 -0
- package/src/materialx.ts +161 -128
- 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 -457
|
@@ -1,91 +1,85 @@
|
|
|
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
|
-
}
|
|
35
|
-
|
|
36
|
-
// Initialize MaterialX environment - called once after MaterialX is ready
|
|
37
|
-
private async initializeEnvironment(): Promise<void> {
|
|
38
|
-
if (this.environmentInitialized) return;
|
|
30
|
+
private _documentReadyPromise: Promise<any> | null = null;
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
55
|
-
|
|
41
|
+
/**
|
|
42
|
+
* MaterialXLoader constructor
|
|
43
|
+
* @param parser The GLTFParser instance
|
|
44
|
+
* @param url The URL of the GLTF file
|
|
45
|
+
* @param context The context for the GLTF loading process
|
|
46
|
+
*/
|
|
47
|
+
constructor(private parser: GLTFParser, private url: string, private context: Context) {
|
|
48
|
+
if (debug) console.log("MaterialXLoader created for parser");
|
|
49
|
+
// Start loading of MaterialX environment if the root extension exists
|
|
50
|
+
if (this.materialX_root_data) {
|
|
51
|
+
ready();
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
54
|
|
|
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;
|
|
55
|
+
loadMaterial(materialIndex: number): Promise<Material> | null {
|
|
56
|
+
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
57
|
+
if (!materialDef?.extensions?.[this.name]) {
|
|
58
|
+
return null;
|
|
69
59
|
}
|
|
70
|
-
|
|
60
|
+
// Wrap the async implementation
|
|
61
|
+
return this._loadMaterialAsync(materialIndex);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
afterRoot = async (_gltf: GLTF) => {
|
|
65
|
+
// Initialize MaterialX lighting system with scene data
|
|
66
|
+
const environment = state.materialXEnvironment;
|
|
67
|
+
environment.initialize(this.context);
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
// Parse the MaterialX document once and cache it
|
|
74
|
-
private async
|
|
75
|
-
if (this.
|
|
76
|
-
return this.
|
|
71
|
+
private async _materialXDocumentReady(): Promise<any> {
|
|
72
|
+
if (this._documentReadyPromise) {
|
|
73
|
+
return this._documentReadyPromise;
|
|
77
74
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (this.parsedDocument) return this.parsedDocument;
|
|
81
|
-
|
|
82
|
-
if (debug) console.log("Parsing MaterialX root document...");
|
|
75
|
+
return this._documentReadyPromise = (async () => {
|
|
76
|
+
if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
|
|
83
77
|
|
|
84
78
|
// Ensure MaterialX is initialized
|
|
85
|
-
await
|
|
79
|
+
await ready();
|
|
86
80
|
|
|
87
81
|
if (!state.materialXModule) {
|
|
88
|
-
throw new Error("MaterialX module failed to initialize");
|
|
82
|
+
throw new Error("[MaterialX] module failed to initialize");
|
|
89
83
|
}
|
|
90
84
|
|
|
91
85
|
// Create MaterialX document and parse ALL the XML data from root
|
|
@@ -93,43 +87,28 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
93
87
|
doc.setDataLibrary(state.materialXStdLib);
|
|
94
88
|
|
|
95
89
|
// Parse all MaterialX XML strings from the root data
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
const root = this.materialX_root_data
|
|
91
|
+
if (root) {
|
|
92
|
+
if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
|
|
93
|
+
await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
|
|
99
94
|
}
|
|
100
95
|
|
|
101
|
-
if (debug) console.log("MaterialX root document parsed successfully");
|
|
102
|
-
|
|
103
|
-
this.parsedDocument = doc;
|
|
96
|
+
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
104
97
|
return doc;
|
|
105
98
|
})();
|
|
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
99
|
}
|
|
119
100
|
|
|
120
101
|
private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
|
|
121
|
-
// Initialize root data first
|
|
122
|
-
this.initializeRootData();
|
|
123
102
|
|
|
124
103
|
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
125
|
-
if (debug) console.log("MaterialX extension found in material:", materialDef.extensions[this.name]);
|
|
104
|
+
if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
|
|
126
105
|
|
|
127
106
|
// Handle different types of MaterialX data
|
|
128
|
-
const dataIndex = materialDef.extensions[this.name] as
|
|
107
|
+
const dataIndex = materialDef.extensions[this.name] as MaterialX_material_extension;
|
|
129
108
|
|
|
130
109
|
if (dataIndex) {
|
|
131
110
|
// Create a new material and process MaterialX - AWAIT THIS!
|
|
132
|
-
return await this.
|
|
111
|
+
return await this._createMaterialXMaterial(dataIndex);
|
|
133
112
|
}
|
|
134
113
|
|
|
135
114
|
// Return fallback material instead of null
|
|
@@ -138,36 +117,29 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
138
117
|
return fallbackMaterial;
|
|
139
118
|
}
|
|
140
119
|
|
|
141
|
-
private
|
|
142
|
-
private async createMaterialXMaterial(materialXData: MaterialXDataIndex): Promise<Material> {
|
|
120
|
+
private async _createMaterialXMaterial(material_extension: MaterialX_material_extension): Promise<Material> {
|
|
143
121
|
try {
|
|
144
|
-
if (debug) console.log(`Creating MaterialX material: ${
|
|
122
|
+
if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
|
|
145
123
|
|
|
146
|
-
|
|
147
|
-
await initializeMaterialX();
|
|
148
|
-
|
|
149
|
-
if (!this.rootDocument) {
|
|
150
|
-
this.rootDocument = this.parseRootDocument();
|
|
151
|
-
}
|
|
152
|
-
const doc = await this.rootDocument;
|
|
124
|
+
const doc = await this._materialXDocumentReady();
|
|
153
125
|
|
|
154
126
|
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
155
|
-
console.warn("MaterialX WASM module not ready, returning fallback material");
|
|
127
|
+
console.warn("[MaterialX] WASM module not ready, returning fallback material");
|
|
156
128
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
157
|
-
fallbackMaterial.userData.materialX =
|
|
158
|
-
fallbackMaterial.name = `MaterialX_Fallback_${
|
|
129
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
130
|
+
fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
|
|
159
131
|
return fallbackMaterial;
|
|
160
132
|
}
|
|
161
133
|
|
|
162
134
|
// Find the renderable element following MaterialX example pattern exactly
|
|
163
|
-
let renderableElement = null;
|
|
135
|
+
let renderableElement: any = null;
|
|
164
136
|
let foundRenderable = false;
|
|
165
137
|
|
|
166
|
-
if (debug) console.log("
|
|
138
|
+
if (debug) console.log("[MaterialX] document", doc);
|
|
167
139
|
|
|
168
140
|
// Search for material nodes first (following the reference pattern)
|
|
169
141
|
const materialNodes = doc.getMaterialNodes();
|
|
170
|
-
if (debug) console.log(`Found ${materialNodes.length} material nodes in document`, materialNodes);
|
|
142
|
+
if (debug) console.log(`[MaterialX] Found ${materialNodes.length} material nodes in document`, materialNodes);
|
|
171
143
|
|
|
172
144
|
// Handle both array and vector-like APIs
|
|
173
145
|
const materialNodesLength = materialNodes.length;
|
|
@@ -175,13 +147,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
175
147
|
const materialNode = materialNodes[i];
|
|
176
148
|
if (materialNode) {
|
|
177
149
|
const materialName = materialNode.getNamePath();
|
|
178
|
-
if (debug) console.log('Scan material: ', i, materialName);
|
|
150
|
+
if (debug) console.log('[MaterialX] Scan material: ', i, materialName);
|
|
179
151
|
|
|
180
152
|
// Find the matching material
|
|
181
|
-
if (materialName ==
|
|
153
|
+
if (materialName == material_extension.name) {
|
|
182
154
|
renderableElement = materialNode;
|
|
183
155
|
foundRenderable = true;
|
|
184
|
-
if (debug) console.log('-- add material: ', materialName);
|
|
156
|
+
if (debug) console.log('[MaterialX] -- add material: ', materialName);
|
|
185
157
|
break;
|
|
186
158
|
}
|
|
187
159
|
}
|
|
@@ -235,321 +207,84 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
235
207
|
*/
|
|
236
208
|
|
|
237
209
|
if (!renderableElement) {
|
|
238
|
-
console.warn("No renderable element found in MaterialX document");
|
|
210
|
+
console.warn("[MaterialX] No renderable element found in MaterialX document");
|
|
239
211
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
240
|
-
fallbackMaterial.userData.materialX =
|
|
241
|
-
fallbackMaterial.name = `MaterialX_NoRenderable_${
|
|
212
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
213
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
|
|
242
214
|
return fallbackMaterial;
|
|
243
215
|
}
|
|
244
216
|
|
|
245
|
-
if (debug) console.log("Using renderable element for shader generation");
|
|
217
|
+
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
246
218
|
|
|
247
219
|
// Check transparency and set context options like the reference
|
|
248
|
-
|
|
220
|
+
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
249
221
|
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
222
|
|
|
253
223
|
// Generate shaders using the element's name path
|
|
254
|
-
if (debug) console.log("Generating MaterialX shaders...");
|
|
224
|
+
if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
|
|
255
225
|
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
256
226
|
|
|
257
227
|
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,
|
|
228
|
+
const shaderMaterial = new MaterialXMaterial({
|
|
229
|
+
name: material_extension.name,
|
|
230
|
+
shader,
|
|
410
231
|
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
|
-
// Update standard transformation matrices
|
|
440
|
-
if (uniforms.u_worldMatrix) {
|
|
441
|
-
if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
|
|
442
|
-
uniforms.u_worldMatrix.value = object.matrixWorld;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (uniforms.u_viewProjectionMatrix) {
|
|
446
|
-
if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
|
|
447
|
-
uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (uniforms.u_viewPosition) {
|
|
451
|
-
if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
|
|
452
|
-
uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (uniforms.u_worldInverseTransposeMatrix) {
|
|
456
|
-
if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
|
|
457
|
-
uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
|
|
458
|
-
}
|
|
232
|
+
loaders: {
|
|
233
|
+
cacheKey: this.url,
|
|
234
|
+
getTexture: async url => {
|
|
235
|
+
// Find the index of the texture in the parser
|
|
236
|
+
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
237
|
+
|
|
238
|
+
// Resolve the texture from the MaterialX root extension
|
|
239
|
+
const ext = this.materialX_root_data;
|
|
240
|
+
if (ext) {
|
|
241
|
+
const textures = ext.textures || [];
|
|
242
|
+
let index = -1;
|
|
243
|
+
for (const texture of textures) {
|
|
244
|
+
// Find the texture by name and use the pointer string to get the index
|
|
245
|
+
if (texture.name === filenameWithoutExt) {
|
|
246
|
+
const ptr = texture.pointer;
|
|
247
|
+
const indexStr = ptr.substring("/textures/".length);
|
|
248
|
+
index = parseInt(indexStr);
|
|
249
|
+
|
|
250
|
+
if (isNaN(index) || index < 0) {
|
|
251
|
+
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
459
259
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
260
|
+
if (index < 0) {
|
|
261
|
+
console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
return this.parser.getDependency("texture", index).then(tex => {
|
|
265
|
+
if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
|
|
266
|
+
return tex;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
467
271
|
}
|
|
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
|
-
|
|
272
|
+
});
|
|
481
273
|
// Track this material for later lighting updates
|
|
482
|
-
this.
|
|
274
|
+
this._generatedMaterials.push(shaderMaterial);
|
|
483
275
|
|
|
276
|
+
// Add debugging to see if the material compiles correctly
|
|
277
|
+
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
484
278
|
return shaderMaterial;
|
|
485
279
|
|
|
486
280
|
} catch (error) {
|
|
487
281
|
// This is a wasm error (an int) that we need to resolve
|
|
488
|
-
console.error("Error creating MaterialX material:", error);
|
|
282
|
+
console.error("[MaterialX] Error creating MaterialX material:", error);
|
|
489
283
|
// Return a fallback material with stored MaterialX data
|
|
490
284
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
491
|
-
fallbackMaterial.userData.materialX =
|
|
492
|
-
fallbackMaterial.name = `MaterialX_Error_${
|
|
285
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
286
|
+
fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
|
|
493
287
|
return fallbackMaterial;
|
|
494
288
|
}
|
|
495
289
|
}
|
|
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
290
|
}
|