@needle-tools/materialx 1.0.1-next.b9467c8 → 1.0.1-next.b9638d9
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 +0 -5
- package/codegen/register_types.ts +0 -4
- package/index.ts +1 -1
- package/package.json +3 -18
- package/src/{materialx.helper.ts → helper.js} +190 -162
- package/src/index.ts +2 -2
- package/src/loader/loader.needle.ts +40 -27
- package/src/loader/loader.three.ts +395 -152
- package/src/materialx.ts +128 -161
- 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,79 +1,91 @@
|
|
|
1
1
|
import { Context } from "@needle-tools/engine";
|
|
2
|
-
import { Material, MeshStandardMaterial, Texture, NearestFilter,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { RawShaderMaterial, 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";
|
|
5
|
+
import { initializeMaterialX, 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
name: string;
|
|
14
|
-
/** MaterialX xml content */
|
|
15
|
-
mtlx: string;
|
|
16
|
-
/** MaterialX texture pointers */
|
|
17
|
-
textures: Array<{ name: string, pointer: string }>;
|
|
9
|
+
interface MaterialXData {
|
|
10
|
+
version: string; // e.g. "1.39"
|
|
11
|
+
name: string; // e.g. "Material"
|
|
12
|
+
mtlx: string; // MaterialX XML content
|
|
18
13
|
}
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
name: string;
|
|
15
|
+
interface MaterialXDataIndex {
|
|
16
|
+
name: string; // Material name reference
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
// MaterialX loader extension for js GLTFLoader
|
|
25
20
|
export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
26
|
-
|
|
21
|
+
name = "NEEDLE_materials_mtlx";
|
|
27
22
|
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Generated materialX materials */
|
|
37
|
-
get materials() {
|
|
38
|
-
return this._generatedMaterials;
|
|
39
|
-
}
|
|
23
|
+
private rootMaterialXData: MaterialXData | null = null;
|
|
24
|
+
private parsedDocument: any = null;
|
|
25
|
+
private documentParsePromise: Promise<any> | null = null;
|
|
26
|
+
private rootDataInitialized = false;
|
|
27
|
+
private environmentInitialized = false;
|
|
28
|
+
private generatedMaterials: RawShaderMaterial[] = [];
|
|
40
29
|
|
|
41
30
|
constructor(private parser: GLTFParser, private context: Context) {
|
|
42
31
|
if (debug) console.log("MaterialXLoader created for parser");
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
ready();
|
|
46
|
-
}
|
|
32
|
+
// Initialize MaterialX environment after MaterialX is ready
|
|
33
|
+
this.initializeEnvironment();
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
36
|
+
// Initialize MaterialX environment - called once after MaterialX is ready
|
|
37
|
+
private async initializeEnvironment(): Promise<void> {
|
|
38
|
+
if (this.environmentInitialized) return;
|
|
39
|
+
|
|
40
|
+
if (debug) console.log("MaterialXLoader: Initializing MaterialX environment...");
|
|
41
|
+
|
|
42
|
+
// Ensure MaterialX is initialized first
|
|
43
|
+
await initializeMaterialX();
|
|
44
|
+
|
|
45
|
+
// Set up environment with context
|
|
46
|
+
const environment = state.materialXEnvironment;
|
|
47
|
+
environment.setContext(this.context);
|
|
48
|
+
|
|
49
|
+
// Initialize the environment from context (properly awaited)
|
|
50
|
+
try {
|
|
51
|
+
await environment.initializeFromContext();
|
|
52
|
+
this.environmentInitialized = true;
|
|
53
|
+
if (debug) console.log("MaterialXLoader: Environment initialized successfully");
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn("MaterialXLoader: Failed to initialize MaterialX environment:", error);
|
|
53
56
|
}
|
|
54
|
-
// Wrap the async implementation
|
|
55
|
-
return this._loadMaterialAsync(materialIndex);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// Initialize root data from parser.json.extensions once
|
|
60
|
+
private initializeRootData(): void {
|
|
61
|
+
if (this.rootDataInitialized) return;
|
|
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;
|
|
69
|
+
}
|
|
70
|
+
this.rootDataInitialized = true;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
// Parse the MaterialX document once and cache it
|
|
65
|
-
private async
|
|
66
|
-
if (this.
|
|
67
|
-
return this.
|
|
74
|
+
private async parseRootDocument(): Promise<any> {
|
|
75
|
+
if (this.documentParsePromise) {
|
|
76
|
+
return this.documentParsePromise;
|
|
68
77
|
}
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
|
|
79
|
+
this.documentParsePromise = (async () => {
|
|
80
|
+
if (this.parsedDocument) return this.parsedDocument;
|
|
81
|
+
|
|
82
|
+
if (debug) console.log("Parsing MaterialX root document...");
|
|
71
83
|
|
|
72
84
|
// Ensure MaterialX is initialized
|
|
73
|
-
await
|
|
85
|
+
await initializeMaterialX();
|
|
74
86
|
|
|
75
87
|
if (!state.materialXModule) {
|
|
76
|
-
throw new Error("
|
|
88
|
+
throw new Error("MaterialX module failed to initialize");
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
// Create MaterialX document and parse ALL the XML data from root
|
|
@@ -81,28 +93,43 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
81
93
|
doc.setDataLibrary(state.materialXStdLib);
|
|
82
94
|
|
|
83
95
|
// Parse all MaterialX XML strings from the root data
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
|
|
96
|
+
if (this.rootMaterialXData) {
|
|
97
|
+
if (debug) console.log(`Parsing MaterialX XML for: ${this.rootMaterialXData.name}`);
|
|
98
|
+
await state.materialXModule.readFromXmlString(doc, this.rootMaterialXData.mtlx, "");
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
if (debug) console.log("
|
|
101
|
+
if (debug) console.log("MaterialX root document parsed successfully");
|
|
102
|
+
|
|
103
|
+
this.parsedDocument = doc;
|
|
91
104
|
return doc;
|
|
92
105
|
})();
|
|
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);
|
|
93
118
|
}
|
|
94
119
|
|
|
95
120
|
private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
|
|
121
|
+
// Initialize root data first
|
|
122
|
+
this.initializeRootData();
|
|
96
123
|
|
|
97
124
|
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
98
|
-
if (debug) console.log("
|
|
125
|
+
if (debug) console.log("MaterialX extension found in material:", materialDef.extensions[this.name]);
|
|
99
126
|
|
|
100
127
|
// Handle different types of MaterialX data
|
|
101
|
-
const dataIndex = materialDef.extensions[this.name] as
|
|
128
|
+
const dataIndex = materialDef.extensions[this.name] as MaterialXDataIndex;
|
|
102
129
|
|
|
103
130
|
if (dataIndex) {
|
|
104
131
|
// Create a new material and process MaterialX - AWAIT THIS!
|
|
105
|
-
return await this.
|
|
132
|
+
return await this.createMaterialXMaterial(dataIndex);
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
// Return fallback material instead of null
|
|
@@ -111,29 +138,36 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
111
138
|
return fallbackMaterial;
|
|
112
139
|
}
|
|
113
140
|
|
|
114
|
-
private
|
|
141
|
+
private rootDocument: Promise<any> | null = null;
|
|
142
|
+
private async createMaterialXMaterial(materialXData: MaterialXDataIndex): Promise<Material> {
|
|
115
143
|
try {
|
|
116
|
-
if (debug) console.log(`Creating MaterialX material: ${
|
|
144
|
+
if (debug) console.log(`Creating MaterialX material: ${materialXData.name}`);
|
|
145
|
+
|
|
146
|
+
// Ensure MaterialX is initialized and document is parsed
|
|
147
|
+
await initializeMaterialX();
|
|
117
148
|
|
|
118
|
-
|
|
149
|
+
if (!this.rootDocument) {
|
|
150
|
+
this.rootDocument = this.parseRootDocument();
|
|
151
|
+
}
|
|
152
|
+
const doc = await this.rootDocument;
|
|
119
153
|
|
|
120
154
|
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
121
|
-
console.warn("
|
|
155
|
+
console.warn("MaterialX WASM module not ready, returning fallback material");
|
|
122
156
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
123
|
-
fallbackMaterial.userData.materialX =
|
|
124
|
-
fallbackMaterial.name = `MaterialX_Fallback_${
|
|
157
|
+
fallbackMaterial.userData.materialX = materialXData;
|
|
158
|
+
fallbackMaterial.name = `MaterialX_Fallback_${materialXData.name}`;
|
|
125
159
|
return fallbackMaterial;
|
|
126
160
|
}
|
|
127
161
|
|
|
128
162
|
// Find the renderable element following MaterialX example pattern exactly
|
|
129
|
-
let renderableElement
|
|
163
|
+
let renderableElement = null;
|
|
130
164
|
let foundRenderable = false;
|
|
131
165
|
|
|
132
|
-
if (debug) console.log("
|
|
166
|
+
if (debug) console.log("Mtlx doc", doc);
|
|
133
167
|
|
|
134
168
|
// Search for material nodes first (following the reference pattern)
|
|
135
169
|
const materialNodes = doc.getMaterialNodes();
|
|
136
|
-
if (debug) console.log(`
|
|
170
|
+
if (debug) console.log(`Found ${materialNodes.length} material nodes in document`, materialNodes);
|
|
137
171
|
|
|
138
172
|
// Handle both array and vector-like APIs
|
|
139
173
|
const materialNodesLength = materialNodes.length;
|
|
@@ -141,13 +175,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
141
175
|
const materialNode = materialNodes[i];
|
|
142
176
|
if (materialNode) {
|
|
143
177
|
const materialName = materialNode.getNamePath();
|
|
144
|
-
if (debug) console.log('
|
|
178
|
+
if (debug) console.log('Scan material: ', i, materialName);
|
|
145
179
|
|
|
146
180
|
// Find the matching material
|
|
147
|
-
if (materialName ==
|
|
181
|
+
if (materialName == materialXData.name) {
|
|
148
182
|
renderableElement = materialNode;
|
|
149
183
|
foundRenderable = true;
|
|
150
|
-
if (debug) console.log('
|
|
184
|
+
if (debug) console.log('-- add material: ', materialName);
|
|
151
185
|
break;
|
|
152
186
|
}
|
|
153
187
|
}
|
|
@@ -201,112 +235,321 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
201
235
|
*/
|
|
202
236
|
|
|
203
237
|
if (!renderableElement) {
|
|
204
|
-
console.warn("
|
|
238
|
+
console.warn("No renderable element found in MaterialX document");
|
|
205
239
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
206
|
-
fallbackMaterial.userData.materialX =
|
|
207
|
-
fallbackMaterial.name = `MaterialX_NoRenderable_${
|
|
240
|
+
fallbackMaterial.userData.materialX = materialXData;
|
|
241
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${materialXData.name}`;
|
|
208
242
|
return fallbackMaterial;
|
|
209
243
|
}
|
|
210
244
|
|
|
211
|
-
if (debug) console.log("
|
|
245
|
+
if (debug) console.log("Using renderable element for shader generation");
|
|
212
246
|
|
|
213
247
|
// Check transparency and set context options like the reference
|
|
214
|
-
|
|
248
|
+
let isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
215
249
|
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
|
|
216
252
|
|
|
217
253
|
// Generate shaders using the element's name path
|
|
218
|
-
if (debug) console.log("
|
|
254
|
+
if (debug) console.log("Generating MaterialX shaders...");
|
|
219
255
|
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
220
256
|
|
|
221
257
|
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
|
-
|
|
258
|
+
|
|
259
|
+
// Get vertex and fragment shader source
|
|
260
|
+
// Remove #version directive for newer js. It's added by RawShaderMaterial glslVersion.
|
|
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;
|
|
292
340
|
}
|
|
293
|
-
|
|
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' },
|
|
294
378
|
});
|
|
295
|
-
|
|
296
|
-
|
|
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,
|
|
410
|
+
transparent: isTransparent,
|
|
411
|
+
// Only set blend settings if actually transparent
|
|
412
|
+
...(isTransparent && {
|
|
413
|
+
blendEquation: AddEquation,
|
|
414
|
+
blendSrc: OneMinusSrcAlphaFactor,
|
|
415
|
+
blendDst: SrcAlphaFactor,
|
|
416
|
+
}),
|
|
417
|
+
side: DoubleSide,
|
|
418
|
+
// Add some debug settings
|
|
419
|
+
depthTest: true,
|
|
420
|
+
depthWrite: !isTransparent
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Add helper matrices for uniform updates (similar to MaterialX example)
|
|
424
|
+
const normalMat = new Matrix3();
|
|
425
|
+
const viewProjMat = new Matrix4();
|
|
426
|
+
const worldViewPos = new Vector3();
|
|
427
|
+
|
|
428
|
+
// Store helper objects on the material for uniform updates
|
|
429
|
+
shaderMaterial.userData.materialX = materialXData;
|
|
430
|
+
shaderMaterial.userData.updateUniforms = (object: Object3D, camera: Camera) => {
|
|
431
|
+
const uniforms = shaderMaterial.uniforms;
|
|
432
|
+
|
|
433
|
+
if (!uniforms) return;
|
|
434
|
+
|
|
435
|
+
// TODO remove. Not sure why this is needed, but without it
|
|
436
|
+
// we currently get some "swimming" where matrices are not up to date.
|
|
437
|
+
camera.updateMatrixWorld(true);
|
|
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
|
+
}
|
|
459
|
+
|
|
460
|
+
// Update time uniforms
|
|
461
|
+
const time = performance.now() / 1000.0;
|
|
462
|
+
if (uniforms.u_time) {
|
|
463
|
+
uniforms.u_time.value = time;
|
|
464
|
+
}
|
|
465
|
+
if (uniforms.u_frame) {
|
|
466
|
+
uniforms.u_frame.value = time;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Mark uniforms as needing update
|
|
470
|
+
shaderMaterial.uniformsNeedUpdate = true;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
|
|
297
474
|
|
|
298
475
|
// Add debugging to see if the material compiles correctly
|
|
299
|
-
if (debug) console.log("
|
|
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
|
+
|
|
481
|
+
// Track this material for later lighting updates
|
|
482
|
+
this.generatedMaterials.push(shaderMaterial);
|
|
483
|
+
|
|
300
484
|
return shaderMaterial;
|
|
301
485
|
|
|
302
486
|
} catch (error) {
|
|
303
487
|
// This is a wasm error (an int) that we need to resolve
|
|
304
|
-
console.error("
|
|
488
|
+
console.error("Error creating MaterialX material:", error);
|
|
305
489
|
// Return a fallback material with stored MaterialX data
|
|
306
490
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
307
|
-
fallbackMaterial.userData.materialX =
|
|
308
|
-
fallbackMaterial.name = `MaterialX_Error_${
|
|
491
|
+
fallbackMaterial.userData.materialX = materialXData;
|
|
492
|
+
fallbackMaterial.name = `MaterialX_Error_${materialXData.name}`;
|
|
309
493
|
return fallbackMaterial;
|
|
310
494
|
}
|
|
311
495
|
}
|
|
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
|
+
}
|
|
312
555
|
}
|