@needle-tools/materialx 1.0.1-next.c1bbe8d → 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/codegen/register_types.ts +2 -0
- package/package.json +1 -1
- package/src/loader/loader.needle.ts +25 -29
- package/src/loader/loader.three.ts +94 -345
- package/src/{helper.js → materialx.helper.ts} +180 -177
- package/src/materialx.material.ts +216 -0
- package/src/materialx.ts +109 -81
- package/src/materialx.types.d.ts +50 -0
- package/src/textureHelper.ts +6 -6
- package/src/utils.ts +39 -4
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { TypeStore } from "@needle-tools/engine"
|
|
3
3
|
|
|
4
4
|
// Import types
|
|
5
|
+
import { MaterialXMaterial } from "../src/materialx.material.js";
|
|
5
6
|
import { MaterialXUniformUpdate } from "../src/loader/loader.needle.js";
|
|
6
7
|
|
|
7
8
|
// Register types
|
|
9
|
+
TypeStore.add("MaterialXMaterial", MaterialXMaterial);
|
|
8
10
|
TypeStore.add("MaterialXUniformUpdate", MaterialXUniformUpdate);
|
package/package.json
CHANGED
|
@@ -6,40 +6,39 @@ import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
|
6
6
|
import type { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
7
7
|
import { MaterialXLoader } from "./loader.three.js";
|
|
8
8
|
import { debug } from "../utils.js";
|
|
9
|
-
import { state } from "../materialx.js";
|
|
9
|
+
import { MaterialXEnvironment, state } from "../materialx.js";
|
|
10
|
+
import { MaterialXMaterial } from "../materialx.material.js";
|
|
10
11
|
|
|
11
12
|
//@dont-generate-component
|
|
12
13
|
export class MaterialXUniformUpdate extends Component {
|
|
13
14
|
|
|
14
|
-
static updateMaterial(mat: Material | Material[], object: Object3D, camera: Camera) {
|
|
15
|
-
if (Array.isArray(mat)) {
|
|
16
|
-
mat.forEach(m => {
|
|
17
|
-
if (m.userData?.updateUniforms) {
|
|
18
|
-
m.userData.updateUniforms(object, camera);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
} else if (mat.userData?.updateUniforms) {
|
|
22
|
-
mat.userData.updateUniforms(object, camera);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
15
|
onEnable(): void {
|
|
27
|
-
this.context.addBeforeRenderListener(this.gameObject, this.
|
|
16
|
+
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
onDisable(): void {
|
|
31
|
-
this.context.removeBeforeRenderListener(this.gameObject, this.
|
|
20
|
+
this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
onBeforeRenderThree = () => {
|
|
35
24
|
// Update uniforms or perform any pre-render logic here
|
|
36
25
|
const gameObject = this.gameObject as any as Mesh;
|
|
37
26
|
const material = gameObject?.material;
|
|
38
27
|
|
|
39
28
|
const camera = this.context.mainCamera;
|
|
40
29
|
if (!camera) return;
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
|
|
31
|
+
const env = state.materialXEnvironment;
|
|
32
|
+
|
|
33
|
+
if (Array.isArray(material)) {
|
|
34
|
+
for (const entry of material) {
|
|
35
|
+
if (entry && entry instanceof MaterialXMaterial) {
|
|
36
|
+
entry.updateUniforms(this.context, env, this.gameObject, camera);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else if (material instanceof MaterialXMaterial) {
|
|
40
|
+
material.updateUniforms(this.context, env, this.gameObject, camera);
|
|
41
|
+
}
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
@@ -54,14 +53,18 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
54
53
|
// Register the MaterialX loader extension
|
|
55
54
|
// Environment initialization is now handled in the MaterialXLoader constructor
|
|
56
55
|
loader.register(p => {
|
|
57
|
-
this.loader = new MaterialXLoader(p, context);
|
|
56
|
+
this.loader = new MaterialXLoader(p, url, context);
|
|
58
57
|
return this.loader;
|
|
59
58
|
});
|
|
60
59
|
};
|
|
61
60
|
|
|
62
|
-
onLoaded = (url: string, gltf: GLTF,
|
|
63
|
-
if (debug) console.log("[MaterialX] MaterialXLoaderPlugin: glTF loaded", url, gltf.scene);
|
|
61
|
+
onLoaded = (url: string, gltf: GLTF, _context: Context) => {
|
|
62
|
+
if (debug) console.log("[MaterialX] MaterialXLoaderPlugin: glTF loaded", { url, scene: gltf.scene, materialX_root_data: this.loader?.materialX_root_data });
|
|
64
63
|
|
|
64
|
+
// If we don't have MaterialX data in the loaded glTF we don't need to do anything else here
|
|
65
|
+
if (!this.loader?.materialX_root_data) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
65
68
|
// Set up onBeforeRender callbacks for objects with MaterialX materials
|
|
66
69
|
// This ensures uniforms are updated properly during rendering
|
|
67
70
|
gltf.scene.traverse((child) => {
|
|
@@ -69,7 +72,7 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
69
72
|
const mesh = child as Mesh;
|
|
70
73
|
const material = mesh.material as Material;
|
|
71
74
|
|
|
72
|
-
if (material
|
|
75
|
+
if (material instanceof MaterialXMaterial) {
|
|
73
76
|
if (debug) console.log("[MaterialX] Adding MaterialX uniform update component to:", child.name);
|
|
74
77
|
child.addComponent(MaterialXUniformUpdate);
|
|
75
78
|
}
|
|
@@ -77,13 +80,6 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
if (debug) console.log("[MaterialX] Loaded: ", this.loader);
|
|
80
|
-
|
|
81
|
-
// Initialize MaterialX lighting system with scene data
|
|
82
|
-
const environment = state.materialXEnvironment;
|
|
83
|
-
environment.initializeFromContext(context).then(() => {
|
|
84
|
-
console.warn("[MaterialX] Environment initialized...");
|
|
85
|
-
this.loader?.updateLightingFromEnvironment(environment);
|
|
86
|
-
});
|
|
87
83
|
};
|
|
88
84
|
|
|
89
85
|
onExport = (_exporter: GLTFExporter, _context: Context) => {
|
|
@@ -1,46 +1,72 @@
|
|
|
1
|
-
import { Context
|
|
2
|
-
import {
|
|
3
|
-
import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
|
-
import { getUniformValues } from "../helper.js";
|
|
1
|
+
import { Context } from "@needle-tools/engine";
|
|
2
|
+
import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture } from "three";
|
|
3
|
+
import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
5
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
|
-
|
|
9
|
+
export type 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 }>;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
export type MaterialX_material_extension = {
|
|
19
21
|
name: string; // Material name reference
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// MaterialX loader extension for js GLTFLoader
|
|
23
25
|
export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
24
|
-
name = "NEEDLE_materials_mtlx";
|
|
26
|
+
readonly name = "NEEDLE_materials_mtlx";
|
|
27
|
+
|
|
28
|
+
private readonly _generatedMaterials: MaterialXMaterial[] = [];
|
|
25
29
|
|
|
26
|
-
// private rootMaterialXData: MaterialX_root_extension | null = null;
|
|
27
30
|
private _documentReadyPromise: Promise<any> | null = null;
|
|
28
|
-
private environmentInitialized = false;
|
|
29
|
-
private generatedMaterials: ShaderMaterial[] = [];
|
|
30
31
|
|
|
31
32
|
get materialX_root_data() {
|
|
32
33
|
return this.parser.json.extensions?.[this.name] as MaterialX_root_extension | null;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
/** Generated materialX materials */
|
|
37
|
+
get materials() {
|
|
38
|
+
return this._generatedMaterials;
|
|
39
|
+
}
|
|
40
|
+
|
|
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) {
|
|
36
48
|
if (debug) console.log("MaterialXLoader created for parser");
|
|
37
49
|
// Start loading of MaterialX environment if the root extension exists
|
|
38
|
-
|
|
39
|
-
if (hasMaterialXExtension) {
|
|
50
|
+
if (this.materialX_root_data) {
|
|
40
51
|
ready();
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
loadMaterial(materialIndex: number): Promise<Material> | null {
|
|
56
|
+
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
57
|
+
if (!materialDef?.extensions?.[this.name]) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
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);
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
// Parse the MaterialX document once and cache it
|
|
45
71
|
private async _materialXDocumentReady(): Promise<any> {
|
|
46
72
|
if (this._documentReadyPromise) {
|
|
@@ -72,16 +98,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
72
98
|
})();
|
|
73
99
|
}
|
|
74
100
|
|
|
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
|
-
|
|
85
101
|
private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
|
|
86
102
|
|
|
87
103
|
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
@@ -92,7 +108,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
92
108
|
|
|
93
109
|
if (dataIndex) {
|
|
94
110
|
// Create a new material and process MaterialX - AWAIT THIS!
|
|
95
|
-
return await this.
|
|
111
|
+
return await this._createMaterialXMaterial(dataIndex);
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
// Return fallback material instead of null
|
|
@@ -101,22 +117,22 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
101
117
|
return fallbackMaterial;
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
private async
|
|
120
|
+
private async _createMaterialXMaterial(material_extension: MaterialX_material_extension): Promise<Material> {
|
|
105
121
|
try {
|
|
106
|
-
if (debug) console.log(`Creating MaterialX material: ${
|
|
122
|
+
if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
|
|
107
123
|
|
|
108
124
|
const doc = await this._materialXDocumentReady();
|
|
109
125
|
|
|
110
126
|
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
111
127
|
console.warn("[MaterialX] WASM module not ready, returning fallback material");
|
|
112
128
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
113
|
-
fallbackMaterial.userData.materialX =
|
|
114
|
-
fallbackMaterial.name = `MaterialX_Fallback_${
|
|
129
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
130
|
+
fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
|
|
115
131
|
return fallbackMaterial;
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
// Find the renderable element following MaterialX example pattern exactly
|
|
119
|
-
let renderableElement = null;
|
|
135
|
+
let renderableElement: any = null;
|
|
120
136
|
let foundRenderable = false;
|
|
121
137
|
|
|
122
138
|
if (debug) console.log("[MaterialX] document", doc);
|
|
@@ -134,7 +150,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
134
150
|
if (debug) console.log('[MaterialX] Scan material: ', i, materialName);
|
|
135
151
|
|
|
136
152
|
// Find the matching material
|
|
137
|
-
if (materialName ==
|
|
153
|
+
if (materialName == material_extension.name) {
|
|
138
154
|
renderableElement = materialNode;
|
|
139
155
|
foundRenderable = true;
|
|
140
156
|
if (debug) console.log('[MaterialX] -- add material: ', materialName);
|
|
@@ -193,8 +209,8 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
193
209
|
if (!renderableElement) {
|
|
194
210
|
console.warn("[MaterialX] No renderable element found in MaterialX document");
|
|
195
211
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
196
|
-
fallbackMaterial.userData.materialX =
|
|
197
|
-
fallbackMaterial.name = `MaterialX_NoRenderable_${
|
|
212
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
213
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
|
|
198
214
|
return fallbackMaterial;
|
|
199
215
|
}
|
|
200
216
|
|
|
@@ -209,267 +225,56 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
209
225
|
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
210
226
|
|
|
211
227
|
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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;
|
|
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,
|
|
228
|
+
const shaderMaterial = new MaterialXMaterial({
|
|
229
|
+
name: material_extension.name,
|
|
230
|
+
shader,
|
|
381
231
|
transparent: isTransparent,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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;
|
|
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
|
-
}
|
|
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
|
+
}
|
|
447
259
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
+
}
|
|
451
271
|
}
|
|
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);
|
|
460
272
|
});
|
|
461
|
-
|
|
462
|
-
shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
|
|
463
|
-
|
|
464
|
-
// Add debugging to see if the material compiles correctly
|
|
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
273
|
// Track this material for later lighting updates
|
|
471
|
-
this.
|
|
274
|
+
this._generatedMaterials.push(shaderMaterial);
|
|
472
275
|
|
|
276
|
+
// Add debugging to see if the material compiles correctly
|
|
277
|
+
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
473
278
|
return shaderMaterial;
|
|
474
279
|
|
|
475
280
|
} catch (error) {
|
|
@@ -477,65 +282,9 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
477
282
|
console.error("[MaterialX] Error creating MaterialX material:", error);
|
|
478
283
|
// Return a fallback material with stored MaterialX data
|
|
479
284
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
480
|
-
fallbackMaterial.userData.materialX =
|
|
481
|
-
fallbackMaterial.name = `MaterialX_Error_${
|
|
285
|
+
fallbackMaterial.userData.materialX = material_extension;
|
|
286
|
+
fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
|
|
482
287
|
return fallbackMaterial;
|
|
483
288
|
}
|
|
484
289
|
}
|
|
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
|
-
}
|
|
541
290
|
}
|