@needle-tools/materialx 1.0.0 → 1.0.1-next.19d0723

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,220 @@
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
+ // TODO remove. Not sure why this is needed, but without it
149
+ // we currently get some "swimming" where matrices are not up to date.
150
+ camera.updateMatrixWorld(true);
151
+
152
+ // Update standard transformation matrices
153
+ if (uniforms.u_worldMatrix) {
154
+ if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
155
+ uniforms.u_worldMatrix.value = object.matrixWorld;
156
+ }
157
+
158
+ if (uniforms.u_viewProjectionMatrix) {
159
+ if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
160
+ uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
161
+ }
162
+
163
+ if (uniforms.u_viewPosition) {
164
+ if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
165
+ uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
166
+ }
167
+
168
+ if (uniforms.u_worldInverseTransposeMatrix) {
169
+ if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
170
+ uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
171
+ }
172
+
173
+ // Update time uniforms
174
+ if (uniforms.u_time) {
175
+ uniforms.u_time.value = context.time.time;
176
+ }
177
+ if (uniforms.u_frame) {
178
+ uniforms.u_frame.value = context.time.frame;
179
+ }
180
+
181
+ // Update light uniforms
182
+ this.updateEnvironmentUniforms(environment);
183
+
184
+ this.uniformsNeedUpdate = true;
185
+ }
186
+
187
+ private updateEnvironmentUniforms = (environment: MaterialXEnvironment) => {
188
+
189
+ // Get lighting data from environment
190
+ const lightData = environment.lightData || null;
191
+ const lightCount = environment.lightCount || 0;
192
+ const textures = environment.getTextures(this) || null;
193
+
194
+ // Update light count
195
+ if (this.uniforms.u_numActiveLightSources && lightCount >= 0) {
196
+ this.uniforms.u_numActiveLightSources.value = lightCount;
197
+ }
198
+
199
+ // Update light data
200
+ if (lightData) {
201
+ this.uniforms.u_lightData.value = lightData;
202
+ }
203
+
204
+ // Update environment uniforms
205
+ if (this.uniforms.u_envMatrix) {
206
+ this.uniforms.u_envMatrix.value = identityMatrix;
207
+ }
208
+ if (this.uniforms.u_envRadiance) {
209
+ this.uniforms.u_envRadiance.value = textures.radianceTexture || null;
210
+ }
211
+ if (this.uniforms.u_envRadianceMips) {
212
+ this.uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(textures.radianceTexture?.source.data.width ?? 0, textures.radianceTexture?.source.data.height ?? 0))) + 1;
213
+ }
214
+ if (this.uniforms.u_envIrradiance) {
215
+ this.uniforms.u_envIrradiance.value = textures.irradianceTexture;
216
+ }
217
+
218
+ this.uniformsNeedUpdate = true;
219
+ }
220
+ }
package/src/materialx.ts CHANGED
@@ -1,32 +1,15 @@
1
- import { Context, delay, isDevEnvironment, ObjectUtils } 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";
7
-
8
- // Configure MaterialX with the correct path for its data files
9
- const materialXConfig = {
10
- locateFile: (path: string, scriptDirectory: string) => {
11
- if (debug) console.debug("MaterialX locateFile called:", { path, scriptDirectory });
12
-
13
- // Return the correct path for MaterialX data files
14
- if (path.endsWith('.data') || path.endsWith('.wasm')) {
15
- // For Vite dev server, we need to use the correct module path
16
- const correctPath = new URL(`../bin/${path}`, import.meta.url).href;
17
- if (debug) console.log("Resolved path:", correctPath);
18
- return correctPath;
19
- }
20
- return scriptDirectory + path;
21
- },
22
- // Add buffer allocation to handle the data file properly
23
- wasmBinary: null,
24
- wasmMemory: null
25
- };
26
-
27
- // Global MaterialX module instance - initialized lazily
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";
9
+
10
+
28
11
  export const state = new class {
29
- materialXModule: any = null;
12
+ materialXModule: MX.MODULE | null = null;
30
13
  materialXGenerator: any = null;
31
14
  materialXGenContext: any = null;
32
15
  materialXStdLib: any = null;
@@ -40,19 +23,45 @@ export const state = new class {
40
23
  }
41
24
  }
42
25
 
43
-
44
- // Initialize MaterialX WASM module lazily
45
- 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> {
46
28
  if (state.materialXInitPromise) {
47
29
  return state.materialXInitPromise;
48
30
  }
49
31
  return state.materialXInitPromise = (async () => {
50
32
  if (state.materialXModule) return; // Already initialized
51
- if (debug) console.log("Initializing MaterialX WASM module...");
33
+ if (debug) console.log("[MaterialX] Initializing WASM module...");
52
34
  try {
53
- const module = await MaterialX(materialXConfig);
54
- if (debug) console.log("MaterialXLoader module loaded", module);
55
- state.materialXModule = module;
35
+
36
+ const urls: Array<string> = await Promise.all([
37
+ /** @ts-ignore */
38
+ import(`../bin/JsMaterialXCore.wasm?url`).then(m => m.default || m),
39
+ /** @ts-ignore */
40
+ import(`../bin/JsMaterialXGenShader.wasm?url`).then(m => m.default || m),
41
+ /** @ts-ignore */
42
+ import(`../bin/JsMaterialXGenShader.data.txt?url`).then(m => m.default || m),
43
+ ]);
44
+ const [JsMaterialXCore, JsMaterialXGenShader, JsMaterialXGenShader_data] = urls;
45
+
46
+ const module = await MaterialX({
47
+ locateFile: (path: string, scriptDirectory: string) => {
48
+ if (debug) console.debug("[MaterialX] locateFile called:", { path, scriptDirectory });
49
+
50
+ if (path.includes("JsMaterialXCore.wasm")) {
51
+ return JsMaterialXCore; // Use the URL for the core WASM file
52
+ }
53
+ else if (path.includes("JsMaterialXGenShader.wasm")) {
54
+ return JsMaterialXGenShader; // Use the URL for the shader WASM file
55
+ }
56
+ else if (path.includes("JsMaterialXGenShader.data")) {
57
+ return JsMaterialXGenShader_data; // Use the URL for the shader data file
58
+ }
59
+
60
+ return scriptDirectory + path;
61
+ },
62
+ });
63
+ if (debug) console.log("[MaterialX] module loaded", module);
64
+ state.materialXModule = module as MX.MODULE
56
65
 
57
66
  // Initialize shader generator and context
58
67
  state.materialXGenerator = module.EsslShaderGenerator.create();
@@ -63,158 +72,190 @@ export async function initializeMaterialX(): Promise<void> {
63
72
  state.materialXStdLib = module.loadStandardLibraries(state.materialXGenContext);
64
73
  tempDoc.setDataLibrary(state.materialXStdLib);
65
74
 
66
- // Initialize basic lighting with default light rig
67
- const defaultLightRigXml = `<?xml version="1.0"?>
68
- <materialx version="1.39">
69
- <!-- Default directional light -->
70
- <directional_light name="default_light" type="lightshader">
71
- <input name="direction" type="vector3" value="0.0, -1.0, -0.5" />
72
- <input name="color" type="color3" value="1.0, 1.0, 1.0" />
73
- <input name="intensity" type="float" value="1.0" />
74
- </directional_light>
75
- </materialx>`;
75
+ // TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
76
+ state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
77
+
78
+ // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
79
+ // SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
80
+ // SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
81
+ state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
82
+
83
+ // TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
84
+ // TRANSMISSION_OPACITY: Use opacity for transmission rendering.
85
+ // state.materialXGenContext.getOptions().hwTransmissionRenderMethod = state.materialXModule.HwTransmissionRenderMethod.TRANSMISSION_REFRACTION;
86
+
87
+ // Turned off because we're doing color space conversion the three.js way
88
+ state.materialXGenContext.getOptions().hwSrgbEncodeOutput = false;
89
+
90
+ // Enables the generation of a prefiltered environment map.
91
+ // TODO Would be great to use but requires setting more uniforms (like u_envPrefilterMip).
92
+ // When set to true, the u_envRadiance map is expected to be a prefiltered environment map.
93
+ // state.materialXGenContext.getOptions().hwWriteEnvPrefilter = true;
94
+
95
+ // Set a reasonable default for max active lights
96
+ state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
76
97
 
77
98
  // This prewarms the shader generation context to have all light types
78
- await registerLights(state.materialXModule, [], state.materialXGenContext);
99
+ await registerLights(state.materialXModule, state.materialXGenContext);
79
100
 
80
- if (debug) console.log("MaterialX generator initialized successfully");
101
+ if (debug) console.log("[MaterialX] generator initialized successfully");
81
102
  } catch (error) {
82
- console.error("Failed to load MaterialX module:", error);
103
+ console.error("[MaterialX] Failed to load MaterialX module:", error);
83
104
  throw error;
84
105
  }
85
106
  })();
86
107
  }
87
108
 
109
+ type EnvironmentTextureSet = {
110
+ radianceTexture: Texture | null;
111
+ irradianceTexture: Texture | null;
112
+ }
113
+
88
114
  // MaterialX Environment Manager - handles lighting and environment setup
89
115
  export class MaterialXEnvironment {
90
- private lights: any[] = [];
91
- private lightData: any = null;
92
- private radianceTexture: any = null;
93
- private irradianceTexture: any = null;
94
- private context: Context | null = null;
95
- 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;
121
+
122
+ private _unsubscribehook: (() => void) | null = null;
96
123
 
97
124
  constructor() {
98
- if (debug) console.log("MaterialX Environment created");
125
+ if (debug) console.log("[MaterialX] Environment created");
99
126
  }
100
127
 
101
- setContext(context: Context) {
102
- this.context = context;
128
+ // Initialize with Needle Engine context
129
+ async initialize(context: Context): Promise<boolean> {
130
+ if (this._initializePromise) {
131
+ return this._initializePromise;
132
+ }
133
+ return this._initializePromise = this._initialize(context);
103
134
  }
104
135
 
105
- /*
106
- // Initialize MaterialX lighting system based on the reference implementation
107
- async initializeLighting(lightRigXml: string, renderer?: any, radianceTexture?: any, irradianceTexture?: any): Promise<void> {
108
- if (!materialXModule || !materialXGenContext) {
109
- console.warn("MaterialX module not initialized, skipping lighting setup");
110
- 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);
111
142
  }
112
-
113
- registerLights(materialXModule, this.lights, materialXGenContext);
143
+ return this._getTextures(this._context?.scene.environment);
114
144
  }
115
- */
116
145
 
117
- // Initialize with Needle Engine context
118
- async initializeFromContext(): Promise<void> {
119
- if (!this.context) {
120
- console.warn("No Needle context available for MaterialX environment initialization");
121
- return;
122
- }
146
+ private _pmremGenerator: PMREMGenerator | null = null;
147
+ private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
123
148
 
124
- // Prevent multiple initializations
125
- if (this.initialized) {
126
- if (debug) console.log("MaterialX environment already initialized, skipping");
127
- return;
128
- }
149
+ private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
129
150
 
130
- // Clean up previous textures if they exist
131
- if (this.radianceTexture) {
132
- if (debug) console.log("Disposing previous radiance texture");
133
- this.radianceTexture.dispose();
134
- this.radianceTexture = null;
135
- }
136
- if (this.irradianceTexture) {
137
- if (debug) console.log("Disposing previous irradiance texture");
138
- this.irradianceTexture.dispose();
139
- this.irradianceTexture = null;
140
- }
151
+ this._context = context;
152
+ this._pmremGenerator = new PMREMGenerator(context.renderer);
141
153
 
142
- // Get renderer from context
143
- const renderer = this.context.renderer;
154
+ this._unsubscribehook?.();
155
+ this._unsubscribehook = onBeforeRender(() => {
156
+ this.updateLighting(false);
157
+ this._getTextures(context.scene.environment);
158
+ })
144
159
 
145
160
  // TODO remove this delay; we should wait for the scene lighting to be ready
146
161
  // and then update the uniforms
147
- let envMap = this.context.scene.environment;
148
- while (!envMap) {
149
- await delay(200);
150
- envMap = this.context.scene.environment;
151
- }
152
- var pmrem = new PMREMGenerator(renderer);
153
- const target = pmrem.fromEquirectangular(envMap);
154
-
155
- const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
156
- const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
157
-
158
- this.radianceTexture = radianceRenderTarget.texture;
159
- this.irradianceTexture = irradianceRenderTarget.texture;
160
-
161
- // Clean up PMREM generator and its render target
162
- target.dispose();
163
- pmrem.dispose();
164
-
165
- if (debug) {
166
- console.log({ radiance: this.radianceTexture, irradiance: this.irradianceTexture });
167
- // Show both of them on cubes in the scene
168
- const unlitMat = new MeshBasicMaterial();
169
- const radianceMat = unlitMat.clone();
170
- radianceMat.map = this.radianceTexture;
171
- const radianceCube = ObjectUtils.createPrimitive("Cube", { material: radianceMat });
172
- const irradianceMat = unlitMat.clone();
173
- irradianceMat.map = this.irradianceTexture;
174
- const irradianceCube = ObjectUtils.createPrimitive("Cube", { material: irradianceMat });
175
- this.context.scene.add(radianceCube);
176
- this.context.scene.add(irradianceCube);
177
- radianceCube.position.set(2, 0, 0);
178
- radianceCube.scale.y = 0.00001;
179
- irradianceCube.position.set(-2, 0, 0);
180
- irradianceCube.scale.y = 0.00001;
181
- // await this.initializeLighting(defaultLightRigXml, renderer);
182
- console.log("MaterialX environment initialized from Needle context", this, this.context.scene);
162
+ while (!context.scene.environment) {
163
+ await delay(5);
183
164
  }
165
+ this._getTextures(context.scene.environment);
184
166
 
185
- // Find lights in scene
186
- let lights = new Array<Light>();
187
- this.context.scene.traverse((object: Object3D) => {
188
- if ((object as Light).isLight) lights.push(object as Light);
189
- });
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
+ // }
190
184
 
191
- this.lightData = await registerLights(state.materialXModule, lights, state.materialXGenContext);
185
+ this.updateLighting(true);
192
186
 
193
187
  // Mark as initialized
194
- this.initialized = true;
188
+ return true;
195
189
  }
196
190
 
197
- // getLights() { return this.lights; }
198
- getLightData() { return this.lightData; }
199
- getRadianceTexture() { return this.radianceTexture; }
200
- getIrradianceTexture() { return this.irradianceTexture; }
201
-
202
- setRadianceTexture(texture: any) { this.radianceTexture = texture; }
203
- setIrradianceTexture(texture: any) { this.irradianceTexture = texture; }
204
-
205
191
  // Reset the environment to allow re-initialization
206
192
  reset() {
207
- if (debug) console.log("Resetting MaterialX environment");
208
- if (this.radianceTexture) {
209
- this.radianceTexture.dispose();
210
- this.radianceTexture = null;
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();
205
+
206
+ this._unsubscribehook?.();
207
+ this._unsubscribehook = null;
208
+ }
209
+
210
+ private _getTextures(texture: Texture | null | undefined): {
211
+ radianceTexture: Texture | null,
212
+ irradianceTexture: Texture | null
213
+ } {
214
+
215
+ let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
216
+ if (res) {
217
+ return res;
211
218
  }
212
- if (this.irradianceTexture) {
213
- this.irradianceTexture.dispose();
214
- this.irradianceTexture = null;
219
+
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
+ }
231
+ }
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;
215
259
  }
216
- this.initialized = false;
217
- // this.lights = [];
218
- this.lightData = null;
219
260
  }
220
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
+ }