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