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