@needle-tools/materialx 1.0.1-next.c1bbe8d → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.0.1-next.c1bbe8d",
3
+ "version": "1.0.1-next.df0e959",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "exports": {
package/src/helper.js CHANGED
@@ -291,22 +291,24 @@ export function findLights(doc)
291
291
  return lights;
292
292
  }
293
293
 
294
- let lightTypesBound = {};
295
-
296
294
  /**
297
295
  * Register lights in shader generation context
298
296
  * @param {Object} mx MaterialX Module
297
+ * @param {Array.<mx.Node>} lights Light nodes
299
298
  * @param {mx.GenContext} genContext Shader generation context
300
299
  * @returns {Array.<mx.Node>}
301
300
  */
302
- export async function registerLights(mx, genContext)
301
+ export async function registerLights(mx, lights, genContext)
303
302
  {
304
- lightTypesBound = {};
305
- const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
306
-
307
303
  mx.HwShaderGenerator.unbindLightShaders(genContext);
304
+ // TODO Remove, not sure why we need that – something resets the value inbetween calls to registerLights
305
+ genContext.getOptions().hwMaxActiveLightSources = 4;
308
306
 
307
+ const lightTypesBound = {};
308
+ const lightData = [];
309
309
  let lightId = 1;
310
+ let lightCount = 0;
311
+ const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
310
312
 
311
313
  // All light types so that we have NodeDefs for them
312
314
  const defaultLightRigXml = `<?xml version="1.0"?>
@@ -331,8 +333,7 @@ export async function registerLights(mx, genContext)
331
333
  document.setDataLibrary(stdlib);
332
334
  document.importLibrary(lightRigDoc);
333
335
  const defaultLights = findLights(document);
334
- // if (debug)
335
- console.log("Default lights in MaterialX document", defaultLights);
336
+ if (debug) console.log("Default lights in MaterialX document", defaultLights);
336
337
 
337
338
  // Loading a document seems to reset this option for some reason, so we set it again
338
339
  genContext.getOptions().hwMaxActiveLightSources = maxLightCount;
@@ -357,34 +358,52 @@ export async function registerLights(mx, genContext)
357
358
  }
358
359
 
359
360
  if (debug) console.log("Light types bound in MaterialX context", lightTypesBound);
360
- }
361
361
 
362
- // Converts Three.js light type to MaterialX node name
363
- function threeLightTypeToMaterialXNodeName(threeLightType) {
364
- switch (threeLightType) {
365
- case 'PointLight':
366
- return 'ND_point_light';
367
- case 'DirectionalLight':
368
- return 'ND_directional_light';
369
- case 'SpotLight':
370
- return 'ND_spot_light';
371
- default:
372
- console.warn('MaterialX: Unsupported light type: ' + threeLightType);
373
- return 'ND_point_light'; // Default to point light
362
+ // MaterialX light nodes
363
+ for (let light of lights)
364
+ {
365
+ // Skip if light does not have a node definition
366
+ if (!("getNodeDef" in light)) continue;
367
+
368
+ let nodeDef = light.getNodeDef();
369
+ let nodeName = nodeDef.getName();
370
+ if (!lightTypesBound[nodeName])
371
+ {
372
+ if (debug) console.log("bind light shader for node", { nodeName, lightId, nodeDef });
373
+ lightTypesBound[nodeName] = lightId;
374
+ mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
375
+ }
376
+
377
+ const lightDirection = light.getValueElement("direction").getValue().getData().data();
378
+ const lightColor = light.getValueElement("color").getValue().getData().data();
379
+ const lightIntensity = light.getValueElement("intensity").getValue().getData();
380
+
381
+ let rotatedLightDirection = new THREE.Vector3(...lightDirection)
382
+ rotatedLightDirection.transformDirection(getLightRotation())
383
+
384
+ lightData.push({
385
+ type: lightTypesBound[nodeName],
386
+ direction: rotatedLightDirection,
387
+ color: new THREE.Vector3(...lightColor),
388
+ intensity: lightIntensity,
389
+ });
374
390
  }
375
- };
376
391
 
377
- /**
378
- * Update light data for shader uniforms
379
- * @param {Object} mx MaterialX Module
380
- * @param {Array.<mx.Node>} lights Light nodes
381
- * @param {mx.GenContext} genContext Shader generation context
382
- * @returns {{ lightData: Array<any>, lightCount: number }}
383
- */
384
- export function getLightData(mx, lights, genContext)
385
- {
386
- const lightData = [];
387
- const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
392
+ const threeLightTypeToMaterialXNodeName = (threeLightType) => {
393
+ switch (threeLightType) {
394
+ case 'PointLight':
395
+ return 'ND_point_light';
396
+ case 'DirectionalLight':
397
+ return 'ND_directional_light';
398
+ case 'SpotLight':
399
+ return 'ND_spot_light';
400
+ default:
401
+ console.warn('MaterialX: Unsupported light type: ' + threeLightType);
402
+ return 'ND_point_light'; // Default to point light
403
+ }
404
+ };
405
+
406
+ if (debug) console.log("Registering lights in MaterialX context", lights, lightData);
388
407
 
389
408
  // Three.js lights
390
409
  for (let light of lights) {
@@ -396,49 +415,48 @@ export function getLightData(mx, lights, genContext)
396
415
  const lightDefinitionName = threeLightTypeToMaterialXNodeName(light.type);
397
416
 
398
417
  if (!lightTypesBound[lightDefinitionName])
399
- console.error("MaterialX: Light type not registered in context. Make sure to register light types before using them.", lightDefinitionName);
418
+ {
419
+ lightTypesBound[lightDefinitionName] = lightId;
420
+ const nodeDef = null;
421
+ mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
422
+ }
400
423
 
401
424
  const wp = light.getWorldPosition(new THREE.Vector3());
402
425
  const wd = getWorldDirection(light, new THREE.Vector3(0,0,-1));
403
-
404
- // console.log("Registering light", light.penumbra);
405
-
406
426
  lightData.push({
407
427
  type: lightTypesBound[lightDefinitionName],
408
428
  position: wp.clone(),
409
429
  direction: wd.clone(),
410
- color: new THREE.Color().fromArray(light.color.toArray()),
411
- // Luminous efficacy for converting radiant power in watts (W) to luminous flux in lumens (lm) at a wavelength of 555 nm.
412
- // Also, three.js lights don't have PI scale baked in, but MaterialX does, so we need to divide by PI for point and spot lights.
413
- intensity: light.intensity * (light.isPointLight ? 683.0 / 3.1415 : light.isSpotLight ? 683.0 / 3.1415: 1.0),
414
- decay_rate: 2.0,
415
- // Approximations for testing – the relevant light has 61.57986...129.4445 as inner/outer spot angle
416
- inner_angle: 0.9,
417
- outer_angle: 0.4,
430
+ color: new THREE.Vector3().fromArray(light.color.toArray()),
431
+ intensity: light.intensity, // Scale intensity for spot lights
432
+ decay_rate: 2.0, // physically-based default decay rate
433
+ inner_angle: 1.0,
434
+ outer_angle: 2.0,
418
435
  });
419
436
  }
420
437
 
421
438
  // Count the number of lights that are not empty
422
- const lightCount = lightData.length;
439
+ lightCount = lightData.length;
423
440
 
424
441
  // If we don't have enough entries in lightData, fill with empty lights
425
- while (lightData.length < maxLightCount)
442
+ if (lightData.length < maxLightCount)
426
443
  {
427
444
  const emptyLight = {
428
445
  type: 0, // Default light type
429
446
  position: new THREE.Vector3(0, 0, 0),
430
447
  direction: new THREE.Vector3(0, 0, -1),
431
- color: new THREE.Color(0, 0, 0),
432
- intensity: 0.0,
433
- decay_rate: 2.0,
434
- inner_angle: 0.0,
435
- outer_angle: 0.0,
448
+ color: new THREE.Vector3(0, 0, 0),
449
+ intensity: 0,
450
+ decay_rate: 2,
451
+ inner_angle: 0,
452
+ outer_angle: 0,
436
453
  };
437
- lightData.push(emptyLight);
454
+ while (lightData.length < maxLightCount) {
455
+ lightData.push(emptyLight);
456
+ }
438
457
  }
439
458
 
440
- if (debug)
441
- console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
459
+ if (debug) console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
442
460
 
443
461
  return { lightData, lightCount };
444
462
  }
@@ -450,12 +468,11 @@ export function getLightData(mx, lights, genContext)
450
468
  */
451
469
  export function getUniformValues(shaderStage, textureLoader, searchPath, flipY)
452
470
  {
453
- const threeUniforms = {};
471
+ let threeUniforms = {};
454
472
 
455
473
  const uniformBlocks = Object.values(shaderStage.getUniformBlocks());
456
474
  uniformBlocks.forEach(uniforms =>
457
475
  {
458
- // TODO Seems struct uniforms (like in LightData) end up here as well, we should filter those out.
459
476
  if (!uniforms.empty())
460
477
  {
461
478
  for (let i = 0; i < uniforms.size(); ++i)
@@ -463,7 +480,6 @@ export function getUniformValues(shaderStage, textureLoader, searchPath, flipY)
463
480
  const variable = uniforms.get(i);
464
481
  const value = variable.getValue()?.getData();
465
482
  const name = variable.getVariable();
466
- if (debug) console.log("Adding uniform", { name, value, type: variable.getType().getName() });
467
483
  threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
468
484
  textureLoader, searchPath, flipY));
469
485
  }
@@ -38,8 +38,16 @@ export class MaterialXUniformUpdate extends Component {
38
38
 
39
39
  const camera = this.context.mainCamera;
40
40
  if (!camera) return;
41
-
41
+
42
42
  MaterialXUniformUpdate.updateMaterial(material, gameObject, camera);
43
+
44
+ // If this is a Group, we need to update all direct children
45
+ if ((gameObject as any as Group).isGroup) {
46
+ gameObject.children.forEach((child: Object3D) => {
47
+ if (child instanceof Mesh && child.material)
48
+ MaterialXUniformUpdate.updateMaterial(child.material, child, camera);
49
+ });
50
+ }
43
51
  }
44
52
  }
45
53
 
@@ -81,7 +89,6 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
81
89
  // Initialize MaterialX lighting system with scene data
82
90
  const environment = state.materialXEnvironment;
83
91
  environment.initializeFromContext(context).then(() => {
84
- console.warn("[MaterialX] Environment initialized...");
85
92
  this.loader?.updateLightingFromEnvironment(environment);
86
93
  });
87
94
  };
@@ -1,5 +1,5 @@
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";
1
+ import { Context } from "@needle-tools/engine";
2
+ import { ShaderMaterial, Material, MeshStandardMaterial, LoadingManager, TextureLoader, Texture, NearestFilter, Matrix4, GLSL3, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, DoubleSide, Matrix3, Vector3, Object3D, Camera } from "three";
3
3
  import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
4
4
  import { getUniformValues } from "../helper.js";
5
5
  import { ready, MaterialXEnvironment, state } from "../materialx.js";
@@ -23,30 +23,63 @@ interface MaterialX_material_extension {
23
23
  export class MaterialXLoader implements GLTFLoaderPlugin {
24
24
  name = "NEEDLE_materials_mtlx";
25
25
 
26
- // private rootMaterialXData: MaterialX_root_extension | null = null;
27
- private _documentReadyPromise: Promise<any> | null = null;
26
+ private rootMaterialXData: MaterialX_root_extension | null = null;
27
+ private parsedDocument: any = null;
28
+ private documentParsePromise: Promise<any> | null = null;
29
+ private rootDataInitialized = false;
28
30
  private environmentInitialized = false;
29
31
  private generatedMaterials: ShaderMaterial[] = [];
30
32
 
31
- get materialX_root_data() {
32
- return this.parser.json.extensions?.[this.name] as MaterialX_root_extension | null;
33
- }
34
-
35
33
  constructor(private parser: GLTFParser, private context: Context) {
36
34
  if (debug) console.log("MaterialXLoader created for parser");
37
- // Start loading of MaterialX environment if the root extension exists
38
- const hasMaterialXExtension = this.parser.json.extensions?.[this.name] != null;
39
- if (hasMaterialXExtension) {
40
- ready();
35
+ // Initialize MaterialX environment after MaterialX is ready
36
+ this.initializeEnvironment();
37
+ }
38
+
39
+ // Initialize MaterialX environment - called once after MaterialX is ready
40
+ private async initializeEnvironment(): Promise<void> {
41
+ if (this.environmentInitialized) return;
42
+
43
+ if (debug) console.log("[MaterialX] MaterialXLoader: Initializing MaterialX environment...");
44
+
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);
57
+ }
58
+ }
59
+
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;
41
70
  }
71
+ this.rootDataInitialized = true;
42
72
  }
43
73
 
44
74
  // Parse the MaterialX document once and cache it
45
- private async _materialXDocumentReady(): Promise<any> {
46
- if (this._documentReadyPromise) {
47
- return this._documentReadyPromise;
75
+ private async parseRootDocument(): Promise<any> {
76
+ if (this.documentParsePromise) {
77
+ return this.documentParsePromise;
48
78
  }
49
- return this._documentReadyPromise = (async () => {
79
+
80
+ this.documentParsePromise = (async () => {
81
+ if (this.parsedDocument) return this.parsedDocument;
82
+
50
83
  if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
51
84
 
52
85
  // Ensure MaterialX is initialized
@@ -61,15 +94,18 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
61
94
  doc.setDataLibrary(state.materialXStdLib);
62
95
 
63
96
  // Parse all MaterialX XML strings from the root data
64
- const root = this.materialX_root_data
65
- if (root) {
66
- if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
67
- 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, "");
68
100
  }
69
101
 
70
102
  if (debug) console.log("[MaterialX] root document parsed successfully");
103
+
104
+ this.parsedDocument = doc;
71
105
  return doc;
72
106
  })();
107
+
108
+ return this.documentParsePromise;
73
109
  }
74
110
 
75
111
  loadMaterial(materialIndex: number): Promise<Material> | null {
@@ -83,6 +119,8 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
83
119
  }
84
120
 
85
121
  private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
122
+ // Initialize root data first
123
+ this.initializeRootData();
86
124
 
87
125
  const materialDef = this.parser.json.materials?.[materialIndex];
88
126
  if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
@@ -101,11 +139,18 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
101
139
  return fallbackMaterial;
102
140
  }
103
141
 
142
+ private rootDocument: Promise<any> | null = null;
104
143
  private async createMaterialXMaterial(materialXData: MaterialX_material_extension): Promise<Material> {
105
144
  try {
106
145
  if (debug) console.log(`Creating MaterialX material: ${materialXData.name}`);
107
146
 
108
- const doc = await this._materialXDocumentReady();
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;
109
154
 
110
155
  if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
111
156
  console.warn("[MaterialX] WASM module not ready, returning fallback material");
@@ -201,7 +246,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
201
246
  if (debug) console.log("[MaterialX] Using renderable element for shader generation");
202
247
 
203
248
  // Check transparency and set context options like the reference
204
- const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
249
+ let isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
205
250
  state.materialXGenContext.getOptions().hwTransparency = isTransparent;
206
251
 
207
252
  // Generate shaders using the element's name path
@@ -247,18 +292,9 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
247
292
  // TODO what if we actually have a 3-component UV? Not sure what three.js does then
248
293
  vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
249
294
 
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
295
  // Add tonemapping and colorspace handling
257
296
  // 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
-
297
+ fragmentShader = fragmentShader.replace(/out\s+vec4\s+out1;/, 'layout(location = 0) out vec4 pc_fragColor;\n#define gl_FragColor pc_fragColor');
262
298
  // Replace `out1 = vec4(<CAPTURE>)` with `gl_FragColor = vec4(<CAPTURE>)` and tonemapping/colorspace handling
263
299
  fragmentShader = fragmentShader.replace(/^\s*out1\s*=\s*vec4\((.*)\);/gm,
264
300
  `
@@ -280,8 +316,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
280
316
  // Find the texture in the textures in the parser and load it from there
281
317
  return url;
282
318
  });
283
-
284
- const textureLoader = new TextureLoader(loadingManager);
319
+ let textureLoader = new TextureLoader(loadingManager);
285
320
 
286
321
  // Override the textureLoader.load method to use the parser's loadTexture directly,
287
322
  // since we want to load the textures from the glTF document and not from disk.
@@ -333,7 +368,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
333
368
  const searchPath = ""; // Could be derived from the asset path if needed
334
369
  const flipV = false; // Set based on your geometry requirements
335
370
 
336
- const uniforms = {
371
+ let uniforms = {
337
372
  ...getUniformValues(shader.getStage('vertex'), textureLoader, searchPath, flipV),
338
373
  ...getUniformValues(shader.getStage('pixel'), textureLoader, searchPath, flipV),
339
374
  };
@@ -349,12 +384,20 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
349
384
  return new Matrix4();
350
385
  };
351
386
 
352
- if (debug) console.log("Lights", { lightData, radianceTexture, irradianceTexture });
387
+ if (debug) console.log({ lightData, radianceTexture, irradianceTexture });
353
388
 
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);
389
+ Object.assign(uniforms, {
390
+ u_numActiveLightSources: { value: lightData?.length || 0, type: 'i' },
391
+ });
392
+
393
+ if (lightData?.length) {
394
+ Object.assign(uniforms, {
395
+ u_lightData: { value: lightData, type: 'v4v' },
396
+ });
357
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);
358
401
  Object.assign(uniforms, {
359
402
  u_envMatrix: { value: getLightRotation() },
360
403
  u_envRadiance: { value: radianceTexture, type: 't' },
@@ -363,7 +406,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
363
406
  u_envRadianceSamples: { value: 8, type: 'i' },
364
407
  u_envIrradiance: { value: irradianceTexture, type: 't' },
365
408
  u_refractionEnv: { value: true },
366
- u_lightData: { value: [] },
367
409
  });
368
410
 
369
411
  // console.log("NEW MATERIAL UNIFORMS", uniforms);
@@ -437,28 +479,10 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
437
479
  uniforms.u_frame.value = time;
438
480
  }
439
481
 
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
482
  // Mark uniforms as needing update
454
483
  shaderMaterial.uniformsNeedUpdate = true;
455
484
  };
456
485
 
457
- this.context.pre_update_callbacks.push(() => {
458
- const environment = state.materialXEnvironment;
459
- environment.updateLighting(false);
460
- });
461
-
462
486
  shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
463
487
 
464
488
  // Add debugging to see if the material compiles correctly
@@ -493,18 +517,14 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
493
517
  const radianceTexture = environment.getRadianceTexture() || null;
494
518
  const irradianceTexture = environment.getIrradianceTexture() || null;
495
519
 
496
- if (debug) {
497
- console.log(`[MaterialX] Updating lighting for ${this.generatedMaterials.length} MaterialX materials`, {
498
- lightData, radianceTexture, irradianceTexture,
499
- });
500
- }
520
+ if (debug) console.log(`[MaterialX] Updating lighting for ${this.generatedMaterials.length} MaterialX materials`, {
521
+ lightData, radianceTexture, irradianceTexture,
522
+ });
501
523
 
502
524
  // Update each generated material's lighting uniforms
503
525
  this.generatedMaterials.forEach((material, _index) => {
504
526
  if (!material.uniforms) return;
505
527
 
506
- console.warn(material.name, material.uniforms, lightCount)
507
-
508
528
  // Update light count
509
529
  if (material.uniforms.u_numActiveLightSources && lightCount >= 0) {
510
530
  material.uniforms.u_numActiveLightSources.value = lightCount;
@@ -512,11 +532,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
512
532
 
513
533
  // Update light data if we have lights
514
534
  if (lightData) {
515
- material.uniforms.u_lightData ??= new Uniform(null)
535
+ if (!material.uniforms.u_lightData) {
536
+ material.uniforms.u_lightData = { value: null };
537
+ }
516
538
  material.uniforms.u_lightData.value = lightData;
517
539
  if (debug) console.log("[MaterialX] Updated light data for material", material.name, lightData, material.uniforms,);
518
540
  }
519
- else if (debug) console.warn("[MaterialX] No light data available to update uniforms for material", material.name);
541
+ else if(debug) console.warn("[MaterialX] No light data available to update uniforms for material", material.name);
520
542
 
521
543
  // Update environment uniforms
522
544
  if (material.uniforms.u_envMatrix) {
@@ -535,7 +557,14 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
535
557
  // Mark uniforms as needing update
536
558
  // console.log("Light data in uniforms", material.uniforms, material.fragmentShader);
537
559
  material.uniformsNeedUpdate = true;
538
- console.log(material)
539
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
+ */
540
569
  }
541
570
  }
package/src/materialx.ts CHANGED
@@ -3,7 +3,7 @@ import MaterialX from "../bin/JsMaterialXGenShader.js";
3
3
  import { debug } from "./utils.js";
4
4
  import { renderPMREMToEquirect } from "./textureHelper.js";
5
5
  import { Light, MeshBasicMaterial, Object3D, PMREMGenerator, Texture } from "three";
6
- import { registerLights, getLightData } from "./helper.js";
6
+ import { registerLights } from "./helper.js";
7
7
 
8
8
 
9
9
  export const state = new class {
@@ -71,6 +71,7 @@ export async function ready(): Promise<void> {
71
71
  tempDoc.setDataLibrary(state.materialXStdLib);
72
72
 
73
73
  // TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
74
+ const options = state.materialXGenContext.getOptions();
74
75
  state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
75
76
 
76
77
  // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
@@ -94,8 +95,7 @@ export async function ready(): Promise<void> {
94
95
  state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
95
96
 
96
97
  // This prewarms the shader generation context to have all light types
97
- await registerLights(state.materialXModule, state.materialXGenContext);
98
- // getLightData(state.materialXModule, [], state.materialXGenContext);
98
+ await registerLights(state.materialXModule, [], state.materialXGenContext);
99
99
 
100
100
  if (debug) console.log("[MaterialX] generator initialized successfully");
101
101
  } catch (error) {
@@ -108,7 +108,6 @@ export async function ready(): Promise<void> {
108
108
  // MaterialX Environment Manager - handles lighting and environment setup
109
109
  export class MaterialXEnvironment {
110
110
  private _context: Context | null = null;
111
- private _lights: Array<Light> = [];
112
111
  private _lightData: any = null;
113
112
  private _lightCount: number = 0;
114
113
  private _radianceTexture: Texture | null = null;
@@ -121,28 +120,45 @@ export class MaterialXEnvironment {
121
120
 
122
121
  // Initialize with Needle Engine context
123
122
  async initializeFromContext(context: Context): Promise<boolean> {
123
+
124
+ // Prevent multiple initializations
124
125
  if (this._initializePromise) {
126
+ if (debug) console.log("[MaterialX] environment already initialized, skipping");
125
127
  return this._initializePromise;
126
128
  }
129
+
127
130
  return this._initializePromise = this._initialize(context);
128
131
  }
129
132
 
130
133
  getLightData() { return this._lightData; }
131
134
  getLightCount() { return this._lightCount; }
132
-
135
+
133
136
  setRadianceTexture(texture: Texture) { this._radianceTexture = texture; }
134
137
  getRadianceTexture() { return this._radianceTexture; }
135
138
 
136
139
  getIrradianceTexture() { return this._irradianceTexture; }
137
140
  setIrradianceTexture(texture: Texture) { this._irradianceTexture = texture; }
138
141
 
142
+ // Reset the environment to allow re-initialization
143
+ reset() {
144
+ if (debug) console.log("[MaterialX] Resetting environment");
145
+ if (this._radianceTexture) {
146
+ this._radianceTexture.dispose();
147
+ this._radianceTexture = null;
148
+ }
149
+ if (this._irradianceTexture) {
150
+ this._irradianceTexture.dispose();
151
+ this._irradianceTexture = null;
152
+ }
153
+ this._initializePromise = null;
154
+ // this.lights = [];
155
+ this._lightData = null;
156
+ }
157
+
139
158
  private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
140
159
 
141
160
  this._context = context;
142
161
 
143
- // Get renderer from context
144
- const renderer = context.renderer;
145
-
146
162
  // Clean up previous textures if they exist
147
163
  if (this._radianceTexture) {
148
164
  if (debug) console.log("[MaterialX] Disposing previous radiance texture");
@@ -155,17 +171,22 @@ export class MaterialXEnvironment {
155
171
  this._irradianceTexture = null;
156
172
  }
157
173
 
174
+ // Get renderer from context
175
+ const renderer = this._context.renderer;
176
+
158
177
  // TODO remove this delay; we should wait for the scene lighting to be ready
159
178
  // and then update the uniforms
160
- let envMap = context.scene.environment;
179
+ let envMap = this._context.scene.environment;
161
180
  while (!envMap) {
162
- await delay(1000);
163
- envMap = context.scene.environment;
181
+ await delay(200);
182
+ envMap = this._context.scene.environment;
164
183
  }
165
- const pmrem = new PMREMGenerator(renderer);
184
+ var pmrem = new PMREMGenerator(renderer);
166
185
  const target = pmrem.fromEquirectangular(envMap);
186
+
167
187
  const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
168
188
  const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
189
+
169
190
  this._radianceTexture = radianceRenderTarget.texture;
170
191
  this._irradianceTexture = irradianceRenderTarget.texture;
171
192
 
@@ -184,50 +205,24 @@ export class MaterialXEnvironment {
184
205
  const irradianceMat = unlitMat.clone();
185
206
  irradianceMat.map = this._irradianceTexture;
186
207
  const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
187
- context.scene.add(radianceCube);
188
- context.scene.add(irradianceCube);
208
+ this._context.scene.add(radianceCube);
209
+ this._context.scene.add(irradianceCube);
189
210
  radianceCube.position.set(2, 0, 0);
190
211
  irradianceCube.position.set(-2, 0, 0);
212
+ // await this.initializeLighting(defaultLightRigXml, renderer);
191
213
  console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
192
214
  }
193
215
 
194
- this.updateLighting(true);
195
-
196
- // Mark as initialized
197
- return true;
198
- }
199
-
200
- updateLighting(collectLights: boolean) {
201
- if (!this._context) return;
202
-
203
216
  // Find lights in scene
204
- if (collectLights) {
205
- const lights = new Array<Light>();
206
- this._context.scene.traverse((object: Object3D) => {
207
- if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
208
- lights.push(object as Light);
209
- });
210
- this._lights = lights;
211
- }
217
+ let lights = new Array<Light>();
218
+ this._context.scene.traverse((object: Object3D) => {
219
+ if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
220
+ lights.push(object as Light);
221
+ });
212
222
 
213
- const { lightData, lightCount } = getLightData(state.materialXModule, this._lights, state.materialXGenContext);
223
+ const { lightData, lightCount } = await registerLights(state.materialXModule, lights, state.materialXGenContext);
214
224
  this._lightData = lightData;
215
225
  this._lightCount = lightCount;
216
- }
217
-
218
- // Reset the environment to allow re-initialization
219
- reset() {
220
- if (debug) console.log("[MaterialX] Resetting environment");
221
- if (this._radianceTexture) {
222
- this._radianceTexture.dispose();
223
- this._radianceTexture = null;
224
- }
225
- if (this._irradianceTexture) {
226
- this._irradianceTexture.dispose();
227
- this._irradianceTexture = null;
228
- }
229
- this._initializePromise = null;
230
- this._lights = [];
231
- this._lightData = null;
226
+ return true;
232
227
  }
233
228
  }