@needle-tools/materialx 1.0.1-next.2ca9014 → 1.0.1-next.64f3b67

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.
@@ -0,0 +1,227 @@
1
+ import { Camera, DoubleSide, FrontSide, GLSL3, Matrix3, Matrix4, Mesh, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
2
+ import { debug } from "./utils.js";
3
+ import { MaterialXEnvironment, state } from "./materialx.js";
4
+ import { getUniformValues, Loaders } from "./materialx.helper.js";
5
+ import { Context } from "@needle-tools/engine";
6
+
7
+
8
+ // Add helper matrices for uniform updates (similar to MaterialX example)
9
+ const identityMatrix = new Matrix4();
10
+ const normalMat = new Matrix3();
11
+ const viewProjMat = new Matrix4();
12
+ const worldViewPos = new Vector3();
13
+
14
+ declare type MaterialXMaterialInitParameters = {
15
+ name: string,
16
+ shader: any,
17
+ loaders: Loaders,
18
+ transparent?: boolean,
19
+ }
20
+
21
+ export class MaterialXMaterial extends ShaderMaterial {
22
+
23
+ // copy(source: MaterialXMaterial): this {
24
+ // super.copy(source);
25
+ // this.name = source.name;
26
+ // this.uniforms = { ...source.uniforms }; // Shallow copy of uniforms
27
+ // this.envMapIntensity = source.envMapIntensity;
28
+ // this.envMap = source.envMap;
29
+ // this.updateUniforms = source.updateUniforms; // Copy the update function
30
+ // return this;
31
+ // }
32
+
33
+ constructor(init?: MaterialXMaterialInitParameters) {
34
+
35
+ // TODO: we need to properly copy the uniforms and other properties from the source material
36
+ if (!init) {
37
+ super();
38
+ return;
39
+ }
40
+
41
+ // Get vertex and fragment shader source, and remove #version directive for newer js.
42
+ // It's added by three.js glslVersion.
43
+ let vertexShader = init.shader.getSourceCode("vertex");
44
+ let fragmentShader = init.shader.getSourceCode("pixel");
45
+
46
+ vertexShader = vertexShader.replace(/^#version.*$/gm, '').trim();
47
+ fragmentShader = fragmentShader.replace(/^#version.*$/gm, '').trim();
48
+
49
+ // MaterialX uses different attribute names than js defaults,
50
+ // so we patch the MaterialX shaders to match the js standard names.
51
+ // Otherwise, we'd have to modify the mesh attributes (see original MaterialX for reference).
52
+
53
+ // Patch vertexShader
54
+ vertexShader = vertexShader.replace(/\bi_position\b/g, 'position');
55
+ vertexShader = vertexShader.replace(/\bi_normal\b/g, 'normal');
56
+ vertexShader = vertexShader.replace(/\bi_texcoord_0\b/g, 'uv');
57
+ vertexShader = vertexShader.replace(/\bi_texcoord_1\b/g, 'uv1');
58
+ vertexShader = vertexShader.replace(/\bi_tangent\b/g, 'tangent');
59
+ vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
60
+
61
+ // Patch fragmentShader
62
+ fragmentShader = fragmentShader.replace(/\bi_position\b/g, 'position');
63
+ fragmentShader = fragmentShader.replace(/\bi_normal\b/g, 'normal');
64
+ fragmentShader = fragmentShader.replace(/\bi_texcoord_0\b/g, 'uv');
65
+ fragmentShader = fragmentShader.replace(/\bi_texcoord_1\b/g, 'uv1');
66
+ fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
67
+ fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
68
+
69
+ // Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
70
+ vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
71
+ vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
72
+ vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
73
+ vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
74
+ vertexShader = vertexShader.replace(/in\s+vec4\s+tangent;/g, '');
75
+ vertexShader = vertexShader.replace(/in\s+vec4\s+color;/g, '');
76
+
77
+ // Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
78
+ // TODO what if we actually have a 3-component UV? Not sure what three.js does then
79
+ vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
80
+
81
+ // Patch units – seems MaterialX uses different units and we end up with wrong light values?
82
+ // result.direction = light.position - position;
83
+ fragmentShader = fragmentShader.replace(
84
+ /result\.direction\s*=\s*light\.position\s*-\s*position;/g,
85
+ 'result.direction = (light.position - position) * 10.0 / 1.0;');
86
+
87
+ // Add tonemapping and colorspace handling
88
+ // Replace `out vec4 out1;` with `out vec4 gl_FragColor;`
89
+ fragmentShader = fragmentShader.replace(
90
+ /out\s+vec4\s+out1;/,
91
+ 'layout(location = 0) out vec4 pc_fragColor;\n#define gl_FragColor pc_fragColor');
92
+
93
+ // Replace `out1 = vec4(<CAPTURE>)` with `gl_FragColor = vec4(<CAPTURE>)` and tonemapping/colorspace handling
94
+ fragmentShader = fragmentShader.replace(/^\s*out1\s*=\s*vec4\((.*)\);/gm, `
95
+ gl_FragColor = vec4($1);
96
+ #include <tonemapping_fragment>
97
+ #include <colorspace_fragment>`);
98
+
99
+ const searchPath = ""; // Could be derived from the asset path if needed
100
+ const flipV = false; // Set based on your geometry requirements
101
+ const isTransparent = init.transparent ?? false;
102
+ super({
103
+ name: init.name,
104
+ uniforms: {
105
+ ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath, flipV),
106
+ ...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath, flipV),
107
+ },
108
+ vertexShader: vertexShader,
109
+ fragmentShader: fragmentShader,
110
+ glslVersion: GLSL3,
111
+ transparent: isTransparent,
112
+ side: FrontSide,
113
+ depthTest: true,
114
+ depthWrite: !isTransparent,
115
+ });
116
+
117
+
118
+
119
+ Object.assign(this.uniforms, {
120
+ u_envMatrix: { value: new Matrix4() },
121
+ u_envRadiance: { value: null, type: 't' },
122
+ u_envRadianceMips: { value: 8, type: 'i' },
123
+ // TODO we need to figure out how we can set a PMREM here... doing many texture samples is prohibitively expensive
124
+ u_envRadianceSamples: { value: 8, type: 'i' },
125
+ u_envIrradiance: { value: null, type: 't' },
126
+ u_refractionEnv: { value: true },
127
+ u_numActiveLightSources: { value: 0 },
128
+ u_lightData: { value: [] }, // Array of light data
129
+ });
130
+
131
+ if (debug) {
132
+ // Get lighting and environment data from MaterialX environment
133
+ console.group("[MaterialX]: ", name);
134
+ console.log("Vertex shader length:", vertexShader.length, vertexShader);
135
+ console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
136
+ console.groupEnd();
137
+ }
138
+
139
+ }
140
+
141
+
142
+ envMapIntensity: number = 1.0; // Default intensity for environment map
143
+ envMap: Texture | null = null; // Environment map texture, can be set externally
144
+ updateUniforms = (context: Context, environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
145
+
146
+ const uniforms = this.uniforms;
147
+
148
+ if (!uniforms) return;
149
+
150
+ // TODO remove. Not sure why this is needed, but without it
151
+ // we currently get some "swimming" where matrices are not up to date.
152
+ camera.updateMatrixWorld(true);
153
+
154
+ // Update standard transformation matrices
155
+ if (uniforms.u_worldMatrix) {
156
+ if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
157
+ uniforms.u_worldMatrix.value = object.matrixWorld;
158
+ }
159
+
160
+ if (uniforms.u_viewProjectionMatrix) {
161
+ if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
162
+ uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
163
+ }
164
+
165
+ if (uniforms.u_viewPosition) {
166
+ if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
167
+ uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
168
+ }
169
+
170
+ if (uniforms.u_worldInverseTransposeMatrix) {
171
+ if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
172
+ uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
173
+ }
174
+
175
+ // Update time uniforms
176
+ if (uniforms.u_time) {
177
+ uniforms.u_time.value = context.time.time;
178
+ }
179
+ if (uniforms.u_frame) {
180
+ uniforms.u_frame.value = context.time.frame;
181
+ }
182
+
183
+ // Update light uniforms
184
+ this.updateEnvironmentUniforms(environment);
185
+
186
+ this.uniformsNeedUpdate = true;
187
+ }
188
+
189
+ private updateEnvironmentUniforms = (environment: MaterialXEnvironment) => {
190
+
191
+ // Get lighting data from environment
192
+ const lightData = environment.lightData || null;
193
+ const lightCount = environment.lightCount || 0;
194
+ const textures = environment.getTextures(this) || null;
195
+
196
+ // Update each generated material's lighting uniforms
197
+ if (!this.uniforms) return;
198
+
199
+ // Update light count
200
+ if (this.uniforms.u_numActiveLightSources && lightCount >= 0) {
201
+ this.uniforms.u_numActiveLightSources.value = lightCount;
202
+ }
203
+
204
+ // Update light data if we have lights
205
+ if (lightData) {
206
+ this.uniforms.u_lightData.value = lightData;
207
+ if (debug) console.log("[MaterialX] Updated light data for material", this.name, lightData, this.uniforms,);
208
+ }
209
+ else if (debug) console.warn("[MaterialX] No light data available to update uniforms for material", this.name);
210
+
211
+ // Update environment uniforms
212
+ if (this.uniforms.u_envMatrix) {
213
+ this.uniforms.u_envMatrix.value = identityMatrix;
214
+ }
215
+ if (this.uniforms.u_envRadiance) {
216
+ this.uniforms.u_envRadiance.value = textures.radianceTexture || null;
217
+ }
218
+ if (this.uniforms.u_envRadianceMips) {
219
+ this.uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(textures.radianceTexture?.source.data.width ?? 0, textures.radianceTexture?.source.data.height ?? 0))) + 1;
220
+ }
221
+ if (this.uniforms.u_envIrradiance) {
222
+ this.uniforms.u_envIrradiance.value = textures.irradianceTexture;
223
+ }
224
+
225
+ this.uniformsNeedUpdate = true;
226
+ }
227
+ }
package/src/materialx.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { Context, delay, isDevEnvironment, ObjectUtils, GameObject } from "@needle-tools/engine";
1
+ import { Context, delay, isDevEnvironment, ObjectUtils, GameObject, onBeforeRender } from "@needle-tools/engine";
2
+ import type { MaterialX as MX } from "./materialx.types.js";
2
3
  import MaterialX from "../bin/JsMaterialXGenShader.js";
3
4
  import { debug } from "./utils.js";
4
5
  import { renderPMREMToEquirect } from "./textureHelper.js";
5
- import { Light, MeshBasicMaterial, Object3D, PMREMGenerator } from "three";
6
- import { registerLights } from "./helper.js";
6
+ import { Light, Material, MeshBasicMaterial, Object3D, PMREMGenerator, Texture } from "three";
7
+ import { registerLights, getLightData } from "./materialx.helper.js";
8
+ import type { MaterialXMaterial } from "./materialx.material.js";
7
9
 
8
10
 
9
- // Global MaterialX module instance - initialized lazily
10
11
  export const state = new class {
11
- materialXModule: any = null;
12
+ materialXModule: MX.MODULE | null = null;
12
13
  materialXGenerator: any = null;
13
14
  materialXGenContext: any = null;
14
15
  materialXStdLib: any = null;
@@ -22,14 +23,14 @@ export const state = new class {
22
23
  }
23
24
  }
24
25
 
25
- // Initialize MaterialX WASM module lazily
26
- export async function initializeMaterialX(): Promise<void> {
26
+ /** Initialize the MaterialX module. Must be awaited before trying to create materials */
27
+ export async function ready(): Promise<void> {
27
28
  if (state.materialXInitPromise) {
28
29
  return state.materialXInitPromise;
29
30
  }
30
31
  return state.materialXInitPromise = (async () => {
31
32
  if (state.materialXModule) return; // Already initialized
32
- if (debug) console.log("Initializing MaterialX WASM module...");
33
+ if (debug) console.log("[MaterialX] Initializing WASM module...");
33
34
  try {
34
35
 
35
36
  const urls: Array<string> = await Promise.all([
@@ -44,7 +45,7 @@ export async function initializeMaterialX(): Promise<void> {
44
45
 
45
46
  const module = await MaterialX({
46
47
  locateFile: (path: string, scriptDirectory: string) => {
47
- if (debug) console.debug("MaterialX locateFile called:", { path, scriptDirectory });
48
+ if (debug) console.debug("[MaterialX] locateFile called:", { path, scriptDirectory });
48
49
 
49
50
  if (path.includes("JsMaterialXCore.wasm")) {
50
51
  return JsMaterialXCore; // Use the URL for the core WASM file
@@ -59,8 +60,8 @@ export async function initializeMaterialX(): Promise<void> {
59
60
  return scriptDirectory + path;
60
61
  },
61
62
  });
62
- if (debug) console.log("MaterialXLoader module loaded", module);
63
- state.materialXModule = module;
63
+ if (debug) console.log("[MaterialX] module loaded", module);
64
+ state.materialXModule = module as MX.MODULE
64
65
 
65
66
  // Initialize shader generator and context
66
67
  state.materialXGenerator = module.EsslShaderGenerator.create();
@@ -72,14 +73,13 @@ export async function initializeMaterialX(): Promise<void> {
72
73
  tempDoc.setDataLibrary(state.materialXStdLib);
73
74
 
74
75
  // TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
75
- const options = state.materialXGenContext.getOptions();
76
76
  state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
77
-
77
+
78
78
  // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
79
79
  // SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
80
80
  // SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
81
81
  state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
82
-
82
+
83
83
  // TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
84
84
  // TRANSMISSION_OPACITY: Use opacity for transmission rendering.
85
85
  // state.materialXGenContext.getOptions().hwTransmissionRenderMethod = state.materialXModule.HwTransmissionRenderMethod.TRANSMISSION_REFRACTION;
@@ -96,137 +96,166 @@ export async function initializeMaterialX(): Promise<void> {
96
96
  state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
97
97
 
98
98
  // This prewarms the shader generation context to have all light types
99
- await registerLights(state.materialXModule, [], state.materialXGenContext);
99
+ await registerLights(state.materialXModule, state.materialXGenContext);
100
100
 
101
- if (debug) console.log("MaterialX generator initialized successfully");
101
+ if (debug) console.log("[MaterialX] generator initialized successfully");
102
102
  } catch (error) {
103
- console.error("Failed to load MaterialX module:", error);
103
+ console.error("[MaterialX] Failed to load MaterialX module:", error);
104
104
  throw error;
105
105
  }
106
106
  })();
107
107
  }
108
108
 
109
+ type EnvironmentTextureSet = {
110
+ radianceTexture: Texture | null;
111
+ irradianceTexture: Texture | null;
112
+ }
113
+
109
114
  // MaterialX Environment Manager - handles lighting and environment setup
110
115
  export class MaterialXEnvironment {
111
- private lightData: any = null;
112
- private lightCount: number = 0;
113
- private radianceTexture: any = null;
114
- private irradianceTexture: any = null;
115
- private context: Context | null = null;
116
- private initialized: boolean = false;
116
+ private _context: Context | null = null;
117
+ private _lights: Array<Light> = [];
118
+ private _lightData: any = null;
119
+ private _lightCount: number = 0;
120
+ private _initializePromise: Promise<boolean> | null = null;
117
121
 
118
- constructor() {
119
- if (debug) console.log("MaterialX Environment created");
120
- }
122
+ private _unsubscribehook: (() => void) | null = null;
121
123
 
122
- setContext(context: Context) {
123
- this.context = context;
124
+ constructor() {
125
+ if (debug) console.log("[MaterialX] Environment created");
124
126
  }
125
127
 
126
128
  // Initialize with Needle Engine context
127
- async initializeFromContext(): Promise<void> {
128
- if (!this.context) {
129
- console.warn("No Needle context available for MaterialX environment initialization");
130
- return;
129
+ async initialize(context: Context): Promise<boolean> {
130
+ if (this._initializePromise) {
131
+ return this._initializePromise;
131
132
  }
133
+ return this._initializePromise = this._initialize(context);
134
+ }
132
135
 
133
- // Prevent multiple initializations
134
- if (this.initialized) {
135
- if (debug) console.log("MaterialX environment already initialized, skipping");
136
- return;
136
+ get lightData() { return this._lightData; }
137
+ get lightCount() { return this._lightCount || 0; }
138
+ getTextures(material: MaterialXMaterial) {
139
+ if (material.envMap) {
140
+ // If the material has its own envMap, we don't use the irradiance texture
141
+ return this._getTextures(material.envMap);
137
142
  }
143
+ return this._getTextures(this._context?.scene.environment);
144
+ }
138
145
 
139
- // Clean up previous textures if they exist
140
- if (this.radianceTexture) {
141
- if (debug) console.log("Disposing previous radiance texture");
142
- this.radianceTexture.dispose();
143
- this.radianceTexture = null;
144
- }
145
- if (this.irradianceTexture) {
146
- if (debug) console.log("Disposing previous irradiance texture");
147
- this.irradianceTexture.dispose();
148
- this.irradianceTexture = null;
149
- }
146
+ private _pmremGenerator: PMREMGenerator | null = null;
147
+ private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
148
+
149
+ private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
150
150
 
151
- // Get renderer from context
152
- const renderer = this.context.renderer;
151
+ this._context = context;
152
+ this._pmremGenerator = new PMREMGenerator(context.renderer);
153
+
154
+ this._unsubscribehook?.();
155
+ this._unsubscribehook = onBeforeRender(() => {
156
+ this.updateLighting(false);
157
+ this._getTextures(context.scene.environment);
158
+ })
153
159
 
154
160
  // TODO remove this delay; we should wait for the scene lighting to be ready
155
161
  // and then update the uniforms
156
- let envMap = this.context.scene.environment;
157
- while (!envMap) {
158
- await delay(200);
159
- envMap = this.context.scene.environment;
160
- }
161
- var pmrem = new PMREMGenerator(renderer);
162
- const target = pmrem.fromEquirectangular(envMap);
163
-
164
- const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
165
- const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
166
-
167
- this.radianceTexture = radianceRenderTarget.texture;
168
- this.irradianceTexture = irradianceRenderTarget.texture;
169
-
170
- // Clean up PMREM generator and its render target
171
- target.dispose();
172
- pmrem.dispose();
173
-
174
- if (debug) {
175
- console.log({ radiance: this.radianceTexture, irradiance: this.irradianceTexture });
176
- // Show both of them on cubes in the scene
177
- const unlitMat = new MeshBasicMaterial();
178
- const radianceMat = unlitMat.clone();
179
- radianceMat.map = this.radianceTexture;
180
- const radianceCube = ObjectUtils.createPrimitive("Cube", { material: radianceMat });
181
- const irradianceMat = unlitMat.clone();
182
- irradianceMat.map = this.irradianceTexture;
183
- const irradianceCube = ObjectUtils.createPrimitive("Cube", { material: irradianceMat });
184
- this.context.scene.add(radianceCube);
185
- this.context.scene.add(irradianceCube);
186
- radianceCube.position.set(2, 0, 0);
187
- radianceCube.scale.y = 0.00001;
188
- irradianceCube.position.set(-2, 0, 0);
189
- irradianceCube.scale.y = 0.00001;
190
- // await this.initializeLighting(defaultLightRigXml, renderer);
191
- console.log("MaterialX environment initialized from Needle context", this, this.context.scene);
162
+ while (!context.scene.environment) {
163
+ await delay(5);
192
164
  }
165
+ this._getTextures(context.scene.environment);
166
+
167
+ // if (debug) {
168
+ // console.log({ radiance: this._radianceTexture, irradiance: this._irradianceTexture });
169
+ // // Show both of them on cubes in the scene
170
+ // const unlitMat = new MeshBasicMaterial();
171
+ // unlitMat.side = 2;
172
+ // const radianceMat = unlitMat.clone();
173
+ // radianceMat.map = this._radianceTexture;
174
+ // const radianceCube = ObjectUtils.createPrimitive("Quad", { material: radianceMat });
175
+ // const irradianceMat = unlitMat.clone();
176
+ // irradianceMat.map = this._irradianceTexture;
177
+ // const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
178
+ // context.scene.add(radianceCube);
179
+ // context.scene.add(irradianceCube);
180
+ // radianceCube.position.set(2, 0, 0);
181
+ // irradianceCube.position.set(-2, 0, 0);
182
+ // console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
183
+ // }
184
+
185
+ this.updateLighting(true);
193
186
 
194
- // Find lights in scene
195
- let lights = new Array<Light>();
196
- this.context.scene.traverse((object: Object3D) => {
197
- if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
198
- lights.push(object as Light);
199
- });
187
+ // Mark as initialized
188
+ return true;
189
+ }
200
190
 
201
- const { lightData, lightCount } = await registerLights(state.materialXModule, lights, state.materialXGenContext);
202
- this.lightData = lightData;
203
- this.lightCount = lightCount;
191
+ // Reset the environment to allow re-initialization
192
+ reset() {
193
+ if (debug) console.log("[MaterialX] Resetting environment");
194
+ this._initializePromise = null;
195
+ this._lights = [];
196
+ this._lightData = null;
197
+ this._lightCount = 0;
198
+ this._pmremGenerator?.dispose();
199
+ this._pmremGenerator = null;
200
+ for(const textureSet of this._texturesCache.values()) {
201
+ textureSet.radianceTexture?.dispose();
202
+ textureSet.irradianceTexture?.dispose();
203
+ }
204
+ this._texturesCache.clear();
204
205
 
205
- // Mark as initialized
206
- this.initialized = true;
206
+ this._unsubscribehook?.();
207
+ this._unsubscribehook = null;
207
208
  }
208
209
 
209
- getLightData() { return this.lightData; }
210
- getLightCount() { return this.lightCount; }
211
- getRadianceTexture() { return this.radianceTexture; }
212
- getIrradianceTexture() { return this.irradianceTexture; }
210
+ private _getTextures(texture: Texture | null | undefined): {
211
+ radianceTexture: Texture | null,
212
+ irradianceTexture: Texture | null
213
+ } {
213
214
 
214
- setRadianceTexture(texture: any) { this.radianceTexture = texture; }
215
- setIrradianceTexture(texture: any) { this.irradianceTexture = texture; }
215
+ let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
216
+ if (res) {
217
+ return res;
218
+ }
216
219
 
217
- // Reset the environment to allow re-initialization
218
- reset() {
219
- if (debug) console.log("Resetting MaterialX environment");
220
- if (this.radianceTexture) {
221
- this.radianceTexture.dispose();
222
- this.radianceTexture = null;
220
+ if (this._context && this._pmremGenerator && texture) {
221
+ if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
222
+
223
+ const target = this._pmremGenerator.fromEquirectangular(texture);
224
+ const radianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 0.0, 1024, 512, target.height);
225
+ const irradianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 1.0, 32, 16, target.height);
226
+ target.dispose();
227
+ res = {
228
+ radianceTexture: radianceRenderTarget.texture,
229
+ irradianceTexture: irradianceRenderTarget.texture
230
+ }
223
231
  }
224
- if (this.irradianceTexture) {
225
- this.irradianceTexture.dispose();
226
- this.irradianceTexture = null;
232
+ else {
233
+ res = {
234
+ radianceTexture: null,
235
+ irradianceTexture: null
236
+ }
237
+ }
238
+ this._texturesCache.set(texture || null, res);
239
+ return res;
240
+ }
241
+
242
+ private updateLighting = (collectLights: boolean = false) => {
243
+ if (!this._context) return;
244
+
245
+ // Find lights in scene
246
+ if (collectLights) {
247
+ const lights = new Array<Light>();
248
+ this._context.scene.traverse((object: Object3D) => {
249
+ if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
250
+ lights.push(object as Light);
251
+ });
252
+ this._lights = lights;
253
+ }
254
+
255
+ if (state.materialXGenContext) {
256
+ const { lightData, lightCount } = getLightData(this._lights, state.materialXGenContext);
257
+ this._lightData = lightData;
258
+ this._lightCount = lightCount;
227
259
  }
228
- this.initialized = false;
229
- // this.lights = [];
230
- this.lightData = null;
231
260
  }
232
261
  }
@@ -0,0 +1,50 @@
1
+
2
+
3
+
4
+ export namespace MaterialX {
5
+
6
+ export type MODULE = {
7
+ ShaderInterfaceType: any;
8
+ HwSpecularEnvironmentMethod: any;
9
+ HwShaderGenerator: {
10
+ bindLightShader(def: any, id: number, genContext: GenContext): void;
11
+ unbindLightShaders(context: any): void;
12
+ };
13
+ createDocument(): Document;
14
+ readFromXmlString(doc: Document, xml: string, unknown: string): void;
15
+ loadStandardLibraries(genContext: GenContext): StandardLibrary;
16
+ isTransparentSurface(renderableElement: any, target: string): boolean;
17
+ }
18
+
19
+
20
+ export type GenContext = {
21
+ }
22
+
23
+ export type StandardLibrary = {
24
+
25
+ }
26
+
27
+ // https://github.com/AcademySoftwareFoundation/MaterialX/blob/b74787db6544283dc32afc8085ebc93cabe937cb/source/MaterialXGenShader/ShaderStage.h#L56
28
+ export type ShaderStage = {
29
+ getUniformBlocks(): Record<string, any>;
30
+ }
31
+
32
+ export type Document = {
33
+ setDataLibrary(lib: StandardLibrary): void;
34
+ importLibrary(lib: Document): void;
35
+
36
+ getNodes(): Node[];
37
+ }
38
+
39
+ export type Node = {
40
+ getType(): string;
41
+ }
42
+
43
+ export type Matrix = {
44
+ numRows(): number;
45
+ numColumns(): number;
46
+ get size(): number;
47
+ getItem(row: number, col: number): number;
48
+ }
49
+
50
+ }
@@ -40,7 +40,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
40
40
  } else {
41
41
  imageHeight = 256; // Final fallback
42
42
  }
43
-
43
+
44
44
  const maxMip = Math.log2(imageHeight) - 2;
45
45
  const cubeUVHeight = imageHeight;
46
46
  const cubeUVWidth = 3 * Math.max(Math.pow(2, maxMip), 7 * 16);
@@ -129,14 +129,14 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
129
129
  const currentAutoClear = renderer.autoClear;
130
130
  const currentXrEnabled = renderer.xr.enabled;
131
131
  const currentShadowMapEnabled = renderer.shadowMap.enabled;
132
-
132
+
133
133
  renderTarget.texture.generateMipmaps = true;
134
-
134
+
135
135
  try {
136
136
  // Disable XR and shadow mapping during our render to avoid interference
137
137
  renderer.xr.enabled = false;
138
138
  renderer.shadowMap.enabled = false;
139
-
139
+
140
140
  // Render to our target
141
141
  renderer.autoClear = true;
142
142
  renderer.setRenderTarget(renderTarget);
@@ -148,7 +148,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
148
148
  renderer.autoClear = currentAutoClear;
149
149
  renderer.xr.enabled = currentXrEnabled;
150
150
  renderer.shadowMap.enabled = currentShadowMapEnabled;
151
-
151
+
152
152
  // Clean up temporary objects
153
153
  geometry.dispose();
154
154
  material.dispose();
@@ -159,7 +159,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
159
159
  renderTarget.texture.mapping = EquirectangularReflectionMapping;
160
160
 
161
161
  // Log mipmap infos
162
- if (debug) console.log('PMREM to Equirect Render Target:', {
162
+ if (debug) console.log('[MaterialX] PMREM to Equirect Render Target:', {
163
163
  width: renderTarget.width,
164
164
  height: renderTarget.height,
165
165
  mipmaps: renderTarget.texture.mipmaps?.length,