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

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/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+ All notable changes to this package will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.0.1] - 2025-07-08
8
+ - Initial release
package/README.md CHANGED
@@ -1 +1,23 @@
1
+ # Needle MaterialX
2
+
3
+ ## Installation
4
+ `npm i @needle-tools/materialx@stable`
5
+
6
+ ## How to use
7
+
8
+ To use with Needle Engine simply import the module
9
+
10
+ ```ts
11
+ import "@needle-tools/materialx"
12
+ ```
13
+
14
+ <br />
15
+
16
+ # Contact ✒️
17
+ <b>[🌵 Needle](https://needle.tools)</b> •
18
+ [Github](https://github.com/needle-tools) •
19
+ [Twitter](https://twitter.com/NeedleTools) •
20
+ [Discord](https://discord.needle.tools) •
21
+ [Forum](https://forum.needle.tools) •
22
+ [Youtube](https://www.youtube.com/@needle-tools)
1
23
 
package/package.json CHANGED
@@ -1,20 +1,38 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-next.2ca9014",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
- "dependencies": {},
6
+ "exports": {
7
+ ".": {
8
+ "import": "./index.ts",
9
+ "require": "./index.js"
10
+ },
11
+ "./package.json": "./package.json"
12
+ },
7
13
  "peerDependencies": {
8
14
  "@needle-tools/engine": "4.x",
9
15
  "three": "npm:@needle-tools/three@^0.169.5"
10
16
  },
11
17
  "devDependencies": {
12
18
  "@needle-tools/engine": "4.x",
19
+ "@types/three": "0.169.0",
13
20
  "three": "npm:@needle-tools/three@^0.169.5",
14
- "@types/three": "0.169.0"
21
+ "vite": "^7.0.3"
15
22
  },
16
23
  "publishConfig": {
17
24
  "access": "public",
18
25
  "registry": "https://registry.npmjs.org/"
19
- }
26
+ },
27
+ "keywords": [
28
+ "needle",
29
+ "materialx",
30
+ "material",
31
+ "shader",
32
+ "threejs",
33
+ "three.js",
34
+ "webgl",
35
+ "mtlx",
36
+ "rendering"
37
+ ]
20
38
  }
package/src/helper.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  //
5
5
 
6
- import { getParam } from '@needle-tools/engine';
6
+ import { getParam, getWorldDirection } from '@needle-tools/engine';
7
7
  import * as THREE from 'three';
8
8
 
9
9
  const debug = getParam("debugmaterialx");
@@ -301,10 +301,14 @@ export function findLights(doc)
301
301
  export async function registerLights(mx, lights, genContext)
302
302
  {
303
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;
304
306
 
305
307
  const lightTypesBound = {};
306
308
  const lightData = [];
307
309
  let lightId = 1;
310
+ let lightCount = 0;
311
+ const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
308
312
 
309
313
  // All light types so that we have NodeDefs for them
310
314
  const defaultLightRigXml = `<?xml version="1.0"?>
@@ -331,6 +335,9 @@ export async function registerLights(mx, lights, genContext)
331
335
  const defaultLights = findLights(document);
332
336
  if (debug) console.log("Default lights in MaterialX document", defaultLights);
333
337
 
338
+ // Loading a document seems to reset this option for some reason, so we set it again
339
+ genContext.getOptions().hwMaxActiveLightSources = maxLightCount;
340
+
334
341
  // Register types only – we get these from the default light rig XML above
335
342
  // This is needed to ensure that the light shaders are bound for each light type
336
343
  for (let light of defaultLights)
@@ -414,18 +421,44 @@ export async function registerLights(mx, lights, genContext)
414
421
  mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
415
422
  }
416
423
 
424
+ const wp = light.getWorldPosition(new THREE.Vector3());
425
+ const wd = getWorldDirection(light, new THREE.Vector3(0,0,-1));
417
426
  lightData.push({
418
427
  type: lightTypesBound[lightDefinitionName],
419
- direction: light.direction?.clone() || new THREE.Vector3(0, -1, 0),
428
+ position: wp.clone(),
429
+ direction: wd.clone(),
420
430
  color: new THREE.Vector3().fromArray(light.color.toArray()),
421
- intensity: light.intensity,
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,
422
435
  });
423
436
  }
424
437
 
425
- // Make sure max light count is large enough
426
- genContext.getOptions().hwMaxActiveLightSources = Math.max(genContext.getOptions().hwMaxActiveLightSources, lightData.length);
438
+ // Count the number of lights that are not empty
439
+ lightCount = lightData.length;
440
+
441
+ // If we don't have enough entries in lightData, fill with empty lights
442
+ if (lightData.length < maxLightCount)
443
+ {
444
+ const emptyLight = {
445
+ type: 0, // Default light type
446
+ position: new THREE.Vector3(0, 0, 0),
447
+ direction: new THREE.Vector3(0, 0, -1),
448
+ color: new THREE.Vector3(0, 0, 0),
449
+ intensity: 0,
450
+ decay_rate: 2,
451
+ inner_angle: 0,
452
+ outer_angle: 0,
453
+ };
454
+ while (lightData.length < maxLightCount) {
455
+ lightData.push(emptyLight);
456
+ }
457
+ }
458
+
459
+ if (debug) console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
427
460
 
428
- return lightData;
461
+ return { lightData, lightCount };
429
462
  }
430
463
 
431
464
  /**
@@ -1,5 +1,5 @@
1
1
  import { Context } from "@needle-tools/engine";
2
- import { RawShaderMaterial, Material, MeshStandardMaterial, LoadingManager, TextureLoader, Texture, NearestFilter, Matrix4, GLSL3, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, DoubleSide, Matrix3, Vector3, Object3D, Camera } from "three";
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 { initializeMaterialX, MaterialXEnvironment, state } from "../materialx.js";
@@ -25,7 +25,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
25
25
  private documentParsePromise: Promise<any> | null = null;
26
26
  private rootDataInitialized = false;
27
27
  private environmentInitialized = false;
28
- private generatedMaterials: RawShaderMaterial[] = [];
28
+ private generatedMaterials: ShaderMaterial[] = [];
29
29
 
30
30
  constructor(private parser: GLTFParser, private context: Context) {
31
31
  if (debug) console.log("MaterialXLoader created for parser");
@@ -247,23 +247,23 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
247
247
  // Check transparency and set context options like the reference
248
248
  let isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
249
249
  state.materialXGenContext.getOptions().hwTransparency = isTransparent;
250
- state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
251
- state.materialXGenContext.getOptions().hwSrgbEncodeOutput = true; // Like the reference
252
-
250
+
253
251
  // Generate shaders using the element's name path
254
252
  if (debug) console.log("Generating MaterialX shaders...");
255
253
  const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
256
254
 
257
255
  const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
258
256
 
259
- // Get vertex and fragment shader source
260
- // Remove #version directive for newer js. It's added by RawShaderMaterial glslVersion.
257
+ // Get vertex and fragment shader source, and remove #version directive for newer js.
258
+ // It's added by three.js glslVersion.
261
259
  let vertexShader = shader.getSourceCode("vertex").replace(/^#version.*$/gm, '').trim();
262
260
  let fragmentShader = shader.getSourceCode("pixel").replace(/^#version.*$/gm, '').trim();
263
261
 
264
262
  // MaterialX uses different attribute names than js defaults,
265
263
  // so we patch the MaterialX shaders to match the js standard names.
266
- // Otherwise, we'd have to modify the mesh attributes (see below).
264
+ // Otherwise, we'd have to modify the mesh attributes (see original MaterialX for reference).
265
+
266
+ // Patch vertexShader
267
267
  vertexShader = vertexShader.replace(/\bi_position\b/g, 'position');
268
268
  vertexShader = vertexShader.replace(/\bi_normal\b/g, 'normal');
269
269
  vertexShader = vertexShader.replace(/\bi_texcoord_0\b/g, 'uv');
@@ -271,6 +271,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
271
271
  vertexShader = vertexShader.replace(/\bi_tangent\b/g, 'tangent');
272
272
  vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
273
273
 
274
+ // Patch fragmentShader
274
275
  fragmentShader = fragmentShader.replace(/\bi_position\b/g, 'position');
275
276
  fragmentShader = fragmentShader.replace(/\bi_normal\b/g, 'normal');
276
277
  fragmentShader = fragmentShader.replace(/\bi_texcoord_0\b/g, 'uv');
@@ -278,16 +279,27 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
278
279
  fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
279
280
  fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
280
281
 
281
- // From the original code:
282
-
283
- /* PATCHING THE SHADER IS BETTER – THIS IS JUST FOR REFERENCE
284
- // Use default MaterialX naming convention.
285
- var startStreamTime = performance.now();
286
- child.geometry.attributes.i_position = child.geometry.attributes.position;
287
- child.geometry.attributes.i_normal = child.geometry.attributes.normal;
288
- child.geometry.attributes.i_tangent = child.geometry.attributes.tangent;
289
- child.geometry.attributes.i_texcoord_0 = child.geometry.attributes.uv;
290
- */
282
+ // Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
283
+ vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
284
+ vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
285
+ vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
286
+ vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
287
+ vertexShader = vertexShader.replace(/in\s+vec4\s+tangent;/g, '');
288
+ vertexShader = vertexShader.replace(/in\s+vec4\s+color;/g, '');
289
+
290
+ // Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
291
+ // TODO what if we actually have a 3-component UV? Not sure what three.js does then
292
+ vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
293
+
294
+ // Add tonemapping and colorspace handling
295
+ // Replace `out vec4 out1;` with `out vec4 gl_FragColor;`
296
+ fragmentShader = fragmentShader.replace(/out\s+vec4\s+out1;/, 'layout(location = 0) out vec4 pc_fragColor;\n#define gl_FragColor pc_fragColor');
297
+ // Replace `out1 = vec4(<CAPTURE>)` with `gl_FragColor = vec4(<CAPTURE>)` and tonemapping/colorspace handling
298
+ fragmentShader = fragmentShader.replace(/^\s*out1\s*=\s*vec4\((.*)\);/gm,
299
+ `
300
+ gl_FragColor = vec4($1);
301
+ #include <tonemapping_fragment>
302
+ #include <colorspace_fragment>`);
291
303
 
292
304
  if (debug) {
293
305
  console.group("Material: ", materialXData.name);
@@ -402,7 +414,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
402
414
  // console.log("Generated fragment shader:", fragmentShader.substring(0, 500) + "...");
403
415
 
404
416
  // Create js RawShaderMaterial (with GLSL3 for MaterialX compatibility)
405
- const shaderMaterial = new RawShaderMaterial({
417
+ const shaderMaterial = new ShaderMaterial({
406
418
  uniforms: uniforms,
407
419
  vertexShader: vertexShader,
408
420
  fragmentShader: fragmentShader,
@@ -499,8 +511,8 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
499
511
  updateLightingFromEnvironment(environment: MaterialXEnvironment): void {
500
512
 
501
513
  // Get lighting data from environment
502
- // const lights = environment.getLights() || [];
503
514
  const lightData = environment.getLightData() || null;
515
+ const lightCount = environment.getLightCount() || 0;
504
516
  const radianceTexture = environment.getRadianceTexture() || null;
505
517
  const irradianceTexture = environment.getIrradianceTexture() || null;
506
518
 
@@ -513,8 +525,8 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
513
525
  if (!material.uniforms) return;
514
526
 
515
527
  // Update light count
516
- if (material.uniforms.u_numActiveLightSources && lightData) {
517
- material.uniforms.u_numActiveLightSources.value = lightData.length;
528
+ if (material.uniforms.u_numActiveLightSources && lightCount >= 0) {
529
+ material.uniforms.u_numActiveLightSources.value = lightCount;
518
530
  }
519
531
 
520
532
  // Update light data if we have lights
@@ -523,6 +535,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
523
535
  material.uniforms.u_lightData = { value: null };
524
536
  }
525
537
  material.uniforms.u_lightData.value = lightData;
538
+ if (debug) console.log("Updated light data for material", material.name, lightData, material.uniforms, );
526
539
  }
527
540
 
528
541
  // Update environment uniforms
package/src/materialx.ts CHANGED
@@ -1,28 +1,10 @@
1
- import { Context, delay, isDevEnvironment, ObjectUtils } from "@needle-tools/engine";
1
+ import { Context, delay, isDevEnvironment, ObjectUtils, GameObject } from "@needle-tools/engine";
2
2
  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 } from "three";
6
6
  import { registerLights } from "./helper.js";
7
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
8
 
27
9
  // Global MaterialX module instance - initialized lazily
28
10
  export const state = new class {
@@ -40,7 +22,6 @@ export const state = new class {
40
22
  }
41
23
  }
42
24
 
43
-
44
25
  // Initialize MaterialX WASM module lazily
45
26
  export async function initializeMaterialX(): Promise<void> {
46
27
  if (state.materialXInitPromise) {
@@ -50,7 +31,34 @@ export async function initializeMaterialX(): Promise<void> {
50
31
  if (state.materialXModule) return; // Already initialized
51
32
  if (debug) console.log("Initializing MaterialX WASM module...");
52
33
  try {
53
- const module = await MaterialX(materialXConfig);
34
+
35
+ const urls: Array<string> = await Promise.all([
36
+ /** @ts-ignore */
37
+ import(`../bin/JsMaterialXCore.wasm?url`).then(m => m.default || m),
38
+ /** @ts-ignore */
39
+ import(`../bin/JsMaterialXGenShader.wasm?url`).then(m => m.default || m),
40
+ /** @ts-ignore */
41
+ import(`../bin/JsMaterialXGenShader.data.txt?url`).then(m => m.default || m),
42
+ ]);
43
+ const [JsMaterialXCore, JsMaterialXGenShader, JsMaterialXGenShader_data] = urls;
44
+
45
+ const module = await MaterialX({
46
+ locateFile: (path: string, scriptDirectory: string) => {
47
+ if (debug) console.debug("MaterialX locateFile called:", { path, scriptDirectory });
48
+
49
+ if (path.includes("JsMaterialXCore.wasm")) {
50
+ return JsMaterialXCore; // Use the URL for the core WASM file
51
+ }
52
+ else if (path.includes("JsMaterialXGenShader.wasm")) {
53
+ return JsMaterialXGenShader; // Use the URL for the shader WASM file
54
+ }
55
+ else if (path.includes("JsMaterialXGenShader.data")) {
56
+ return JsMaterialXGenShader_data; // Use the URL for the shader data file
57
+ }
58
+
59
+ return scriptDirectory + path;
60
+ },
61
+ });
54
62
  if (debug) console.log("MaterialXLoader module loaded", module);
55
63
  state.materialXModule = module;
56
64
 
@@ -63,16 +71,29 @@ export async function initializeMaterialX(): Promise<void> {
63
71
  state.materialXStdLib = module.loadStandardLibraries(state.materialXGenContext);
64
72
  tempDoc.setDataLibrary(state.materialXStdLib);
65
73
 
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>`;
74
+ // 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
+ 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
99
  await registerLights(state.materialXModule, [], state.materialXGenContext);
@@ -87,8 +108,8 @@ export async function initializeMaterialX(): Promise<void> {
87
108
 
88
109
  // MaterialX Environment Manager - handles lighting and environment setup
89
110
  export class MaterialXEnvironment {
90
- private lights: any[] = [];
91
111
  private lightData: any = null;
112
+ private lightCount: number = 0;
92
113
  private radianceTexture: any = null;
93
114
  private irradianceTexture: any = null;
94
115
  private context: Context | null = null;
@@ -102,18 +123,6 @@ export class MaterialXEnvironment {
102
123
  this.context = context;
103
124
  }
104
125
 
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;
111
- }
112
-
113
- registerLights(materialXModule, this.lights, materialXGenContext);
114
- }
115
- */
116
-
117
126
  // Initialize with Needle Engine context
118
127
  async initializeFromContext(): Promise<void> {
119
128
  if (!this.context) {
@@ -185,17 +194,20 @@ export class MaterialXEnvironment {
185
194
  // Find lights in scene
186
195
  let lights = new Array<Light>();
187
196
  this.context.scene.traverse((object: Object3D) => {
188
- if ((object as Light).isLight) lights.push(object as Light);
197
+ if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
198
+ lights.push(object as Light);
189
199
  });
190
200
 
191
- this.lightData = await registerLights(state.materialXModule, lights, state.materialXGenContext);
201
+ const { lightData, lightCount } = await registerLights(state.materialXModule, lights, state.materialXGenContext);
202
+ this.lightData = lightData;
203
+ this.lightCount = lightCount;
192
204
 
193
205
  // Mark as initialized
194
206
  this.initialized = true;
195
207
  }
196
208
 
197
- // getLights() { return this.lights; }
198
209
  getLightData() { return this.lightData; }
210
+ getLightCount() { return this.lightCount; }
199
211
  getRadianceTexture() { return this.radianceTexture; }
200
212
  getIrradianceTexture() { return this.irradianceTexture; }
201
213
 
package/bin/README.md DELETED
@@ -1,5 +0,0 @@
1
- Source: https://github.com/AcademySoftwareFoundation/MaterialX/tree/gh-pages
2
-
3
- Edits:
4
-
5
- - `JsMaterialXGenShader.js` added `export default MaterialX;` at bottom