@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 +1 -1
- package/src/helper.js +74 -58
- package/src/loader/loader.needle.ts +9 -2
- package/src/loader/loader.three.ts +97 -68
- package/src/materialx.ts +43 -48
package/package.json
CHANGED
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
|
-
|
|
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
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
console.
|
|
373
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
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.
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
439
|
+
lightCount = lightData.length;
|
|
423
440
|
|
|
424
441
|
// If we don't have enough entries in lightData, fill with empty lights
|
|
425
|
-
|
|
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.
|
|
432
|
-
intensity: 0
|
|
433
|
-
decay_rate: 2
|
|
434
|
-
inner_angle: 0
|
|
435
|
-
outer_angle: 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.
|
|
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
|
-
|
|
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
|
|
2
|
-
import { ShaderMaterial, Material, MeshStandardMaterial, LoadingManager, TextureLoader, Texture, NearestFilter, Matrix4, GLSL3, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, DoubleSide, Matrix3, Vector3, Object3D, Camera
|
|
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
|
-
|
|
27
|
-
private
|
|
26
|
+
private rootMaterialXData: MaterialX_root_extension | null = null;
|
|
27
|
+
private parsedDocument: any = null;
|
|
28
|
+
private documentParsePromise: Promise<any> | null = null;
|
|
29
|
+
private rootDataInitialized = false;
|
|
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
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
46
|
-
if (this.
|
|
47
|
-
return this.
|
|
75
|
+
private async parseRootDocument(): Promise<any> {
|
|
76
|
+
if (this.documentParsePromise) {
|
|
77
|
+
return this.documentParsePromise;
|
|
48
78
|
}
|
|
49
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
387
|
+
if (debug) console.log({ lightData, radianceTexture, irradianceTexture });
|
|
353
388
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
498
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
179
|
+
let envMap = this._context.scene.environment;
|
|
161
180
|
while (!envMap) {
|
|
162
|
-
await delay(
|
|
163
|
-
envMap =
|
|
181
|
+
await delay(200);
|
|
182
|
+
envMap = this._context.scene.environment;
|
|
164
183
|
}
|
|
165
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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 } =
|
|
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
|
}
|