@needle-tools/materialx 1.5.0 → 1.5.1

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 CHANGED
@@ -4,6 +4,16 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.5.1] – 2026-03-22
8
+
9
+ ### Fixed
10
+ - UV patching: always wrap vec3 targets regardless of source UV declaration type
11
+ - Vertex color: wrap vec3→vec4 for color attributes (Three.js provides vec3)
12
+ - Shader test page uses proper environment/lighting matching other test pages
13
+
14
+ ### Added
15
+ - Playwright e2e tests validating 111+ MaterialX materials for shader compilation
16
+
7
17
  ## [1.5.0] – 2026-03-20
8
18
 
9
19
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Needle MaterialX – MaterialX Materials for three.js & the Web
2
2
 
3
- Web runtime for [MaterialX](https://materialx.org/) materials in [Needle Engine](https://needle.tools) and [three.js](https://threejs.org/). Renders physically based MaterialX shaders in the browser using WebAssembly — load `.mtlx` files or glTF assets with the `NEEDLE_materials_mtlx` extension.
3
+ Web runtime for [MaterialX](https://materialx.org/) materials in [Needle Engine](https://needle.tools) and [three.js](https://threejs.org/). Renders physically based MaterialX shaders in the browser using WebAssembly — load `.mtlx` files or glTF assets with the `NEEDLE_materials_mtlx` extension (created with Needle Engine's Unity integration and ShaderGraph).
4
4
 
5
5
  - MaterialX to WebGL/WebGPU shader generation via WASM
6
6
  - Vertex displacement support (procedural noise, texture-based, animatable)
@@ -25,16 +25,22 @@ Web runtime for [MaterialX](https://materialx.org/) materials in [Needle Engine]
25
25
 
26
26
  ### Use with Needle Engine
27
27
 
28
- **Needle Engine has built in support for MaterialX shaders.** No changes to your code are necessary. The MaterialX module will import lazily when needed.
28
+ **Needle Engine has built in support for MaterialX shaders.**
29
+ No changes to your code are necessary. The MaterialX module will import lazily when needed.
29
30
 
30
31
  ### Use with three.js
31
32
 
32
33
  ```ts
33
34
  import { useNeedleMaterialX } from "@needle-tools/materialx";
34
- // Call the function with your GLTFLoader instance
35
+ // Call the function with your GLTFLoader instance to add the GLTFLoader plugin
35
36
  useNeedleMaterialX(<yourGltfLoaderInstance>);
36
37
  ```
37
38
 
39
+ ### Accessing Materials
40
+ MaterialX shaders will create `MaterialXMaterial` materials at runtime. These are custom three.js ShaderMaterials and assigned to objects like any other three.js material.
41
+
42
+ [Learn more in the Needle Engine documentation](https://engine.needle.tools/docs/how-to-guides/export/materialx.html)
43
+
38
44
  ## WASM
39
45
 
40
46
  ### Default (CDN)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
3
  "description": "MaterialX material support for three.js and Needle Engine – render physically based MaterialX shaders in the browser via WebAssembly",
4
- "version": "1.5.0",
4
+ "version": "1.5.1",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
@@ -122,6 +122,13 @@ export class MaterialXMaterial extends ShaderMaterial {
122
122
  fragmentShader = fragmentShader.replace(/\bu_envLightIntensity\b/g, 'envMapIntensity');
123
123
 
124
124
  // Capture some vertex shader properties
125
+ // Detect whether each UV was originally vec2 or vec3 before removing declarations.
126
+ // Three.js always provides vec2 attributes, so vec3 assignments need wrapping.
127
+ const uv_is_vec2 = vertexShader.includes('in vec2 uv;');
128
+ const uv1_is_vec2 = vertexShader.includes('in vec2 uv1;');
129
+ const uv2_is_vec2 = vertexShader.includes('in vec2 uv2;');
130
+ const uv3_is_vec2 = vertexShader.includes('in vec2 uv3;');
131
+
125
132
  // Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
126
133
  vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
127
134
  vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
@@ -137,20 +144,41 @@ export class MaterialXMaterial extends ShaderMaterial {
137
144
  vertexShader = vertexShader.replace(/in\s+vec4\s+tangent;/g, '');
138
145
  var hasColor = vertexShader.includes('in vec4 color;');
139
146
  vertexShader = vertexShader.replace(/in\s+vec4\s+color;/g, '');
147
+ // Three.js provides `color` as vec3 but MaterialX declares it as vec4.
148
+ // Wrap assignments to vec4 targets: `color_0 = color;` → `color_0 = vec4(color, 1.0);`
149
+ if (hasColor) {
150
+ vertexShader = vertexShader.replace(/\bvec4 (\w+) = color;/g, 'vec4 $1 = vec4(color, 1.0);');
151
+ vertexShader = vertexShader.replace(/(\w+) = color;/g, (match, name) => {
152
+ if (match.includes('vec4')) return match;
153
+ const isVec4 = new RegExp(`\\bvec4\\s+${name}\\b`).test(vertexShader);
154
+ return isVec4 ? `${name} = vec4(color, 1.0);` : match;
155
+ });
156
+ }
140
157
 
141
- // Patch uv vec2→vec3. After removing `in vecN uv;` declarations above,
142
- // Three.js always provides uv/uv1/uv2/uv3 as vec2 attributes. Any vec3
143
- // assignment from these must be wrapped. This applies unconditionally
144
- // even if MaterialX originally declared them as vec2, compound displacement
145
- // functions may still reference them as vec3 internally.
146
- vertexShader = vertexShader.replace(/\bvec3 (\w+) = uv;/g, 'vec3 $1 = vec3(uv, 0.0);');
147
- vertexShader = vertexShader.replace(/\bvec3 (\w+) = uv1;/g, 'vec3 $1 = vec3(uv1, 0.0);');
148
- vertexShader = vertexShader.replace(/\bvec3 (\w+) = uv2;/g, 'vec3 $1 = vec3(uv2, 0.0);');
149
- vertexShader = vertexShader.replace(/\bvec3 (\w+) = uv3;/g, 'vec3 $1 = vec3(uv3, 0.0);');
150
- // Also handle non-declaration assignments (e.g. `texcoord_0 = uv;`)
151
- vertexShader = vertexShader.replace(/(\w+) = uv;/g, (match, name) => {
152
- return match.includes('vec3') ? match : `${name} = vec3(uv, 0.0);`;
153
- });
158
+ // Patch uv vec2→vec3. Three.js always provides uv/uv1/uv2/uv3 as vec2
159
+ // attributes. When MaterialX originally declared them as vec3, any
160
+ // assignment from these attributes to a vec3 variable needs wrapping.
161
+ // When the UV was originally vec2, all assignments are already compatible.
162
+ // Three.js always provides uv/uv1/uv2/uv3 as vec2 attributes.
163
+ // When the generated shader assigns them to vec3 variables, we need to wrap.
164
+ // This applies regardless of the original declaration type, because Three.js
165
+ // always delivers vec2.
166
+ /** @param {string} shader @param {string} uvName */
167
+ function patchUvAssignments(shader, uvName) {
168
+ // 1. Declaration assignments: `vec3 x = <uv>;` → `vec3 x = vec3(<uv>, 0.0);`
169
+ shader = shader.replace(new RegExp(`\\bvec3 (\\w+) = ${uvName};`, 'g'), `vec3 $1 = vec3(${uvName}, 0.0);`);
170
+ // 2. Non-declaration assignments: `x = <uv>;` → wrap only when target is vec3
171
+ shader = shader.replace(new RegExp(`(\\w+) = ${uvName};`, 'g'), (match, name) => {
172
+ if (match.includes('vec3')) return match; // already handled
173
+ const isVec3 = new RegExp(`\\bvec3\\s+${name}\\b`).test(shader);
174
+ return isVec3 ? `${name} = vec3(${uvName}, 0.0);` : match;
175
+ });
176
+ return shader;
177
+ }
178
+ vertexShader = patchUvAssignments(vertexShader, 'uv');
179
+ vertexShader = patchUvAssignments(vertexShader, 'uv1');
180
+ vertexShader = patchUvAssignments(vertexShader, 'uv2');
181
+ vertexShader = patchUvAssignments(vertexShader, 'uv3');
154
182
 
155
183
  // Patch units – seems MaterialX uses different units and we end up with wrong light values?
156
184
  // result.direction = light.position - position;