@needle-tools/materialx 1.4.2 → 1.4.3
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 +9 -0
- package/README.md +34 -0
- package/bin/JsMaterialXCore.js +12 -9
- package/bin/JsMaterialXCore.wasm +0 -0
- package/bin/JsMaterialXGenShader.data.txt +26297 -24807
- package/bin/JsMaterialXGenShader.js +12 -11
- package/bin/JsMaterialXGenShader.wasm +0 -0
- package/bin/SHA_309ccca5d7788f90d773248c88498ddc203dc260 +0 -0
- package/bin/revision.json +5 -0
- package/package.json +4 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/materialx.d.ts +28 -0
- package/src/materialx.helper.d.ts +2 -0
- package/src/materialx.helper.js +46 -14
- package/src/materialx.js +42 -17
- package/src/materialx.material.js +192 -20
- package/src/utils.d.ts +3 -1
- package/src/vite-url-modules.d.ts +9 -0
- package/bin/README.md +0 -6
|
Binary file
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/materialx",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"three": ">=0.160.0"
|
|
26
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node --test tests/unit/**/*.test.js"
|
|
29
|
+
},
|
|
27
30
|
"devDependencies": {
|
|
28
31
|
"@needle-tools/engine": "4.x",
|
|
29
32
|
"@types/three": "0.169.0",
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createMaterialXMaterial } from "./loader/loader.three.js";
|
|
2
2
|
|
|
3
3
|
export { ready, preloadWasm } from "./materialx.js";
|
|
4
|
+
export { MaterialXEnvironment } from "./materialx.js";
|
|
4
5
|
export { MaterialXMaterial } from "./materialx.material.js";
|
|
5
6
|
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
6
7
|
|
package/src/materialx.d.ts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { Light, Scene, Texture, WebGLRenderer } from "three";
|
|
2
2
|
import type { MaterialX as MX } from "./materialx.types.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Override the base URL for MaterialX WASM files.
|
|
6
|
+
* Set this global **before** any MaterialX code runs to load WASM from a custom location.
|
|
7
|
+
*
|
|
8
|
+
* Special values:
|
|
9
|
+
* - `"package"`: Use local files bundled with the npm package
|
|
10
|
+
* - `"/custom/path/"`: Use a custom base URL (must end with `/`)
|
|
11
|
+
* - `undefined`: Use default CDN
|
|
12
|
+
*
|
|
13
|
+
* @default `https://cdn.needle.tools/static/materialx/<version>/`
|
|
14
|
+
* @example
|
|
15
|
+
* ```js
|
|
16
|
+
* // Use package-local files (same as "bin/")
|
|
17
|
+
* globalThis.NEEDLE_MATERIALX_LOCATION = "package";
|
|
18
|
+
*
|
|
19
|
+
* // Use custom CDN or self-hosted files
|
|
20
|
+
* globalThis.NEEDLE_MATERIALX_LOCATION = "/assets/materialx/";
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare global {
|
|
24
|
+
var NEEDLE_MATERIALX_LOCATION: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
4
27
|
export function preloadWasm(trigger: "immediately" | "network_idle"): Promise<void>;
|
|
5
28
|
|
|
6
29
|
export type MaterialXContext = {
|
|
@@ -47,6 +70,11 @@ export declare class MaterialXEnvironment {
|
|
|
47
70
|
initialize(renderer: WebGLRenderer): Promise<boolean>;
|
|
48
71
|
update(frame: number, scene: Scene, renderer: WebGLRenderer): void;
|
|
49
72
|
reset(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Re-collect lights from the scene and rebuild light data.
|
|
75
|
+
* Call this after adding/removing lights, toggling visibility, or changing light properties.
|
|
76
|
+
*/
|
|
77
|
+
refreshLights(): void;
|
|
50
78
|
|
|
51
79
|
get lights(): Array<Light>;
|
|
52
80
|
get lightData(): any[] | null;
|
|
@@ -22,6 +22,8 @@ export function getLightRotation(): THREE.Matrix4;
|
|
|
22
22
|
|
|
23
23
|
export function findLights(doc: any): Array<any>;
|
|
24
24
|
|
|
25
|
+
export function getLightTypeIds(): { directional: number, point: number, spot: number };
|
|
26
|
+
|
|
25
27
|
export function registerLights(mx: any, genContext: any): Promise<void>;
|
|
26
28
|
|
|
27
29
|
export function getLightData(lights: Array<THREE.Light>, genContext: any): { lightData: LightData[], lightCount: number };
|
package/src/materialx.helper.js
CHANGED
|
@@ -211,6 +211,9 @@ function toThreeUniform(uniforms, type, value, name, loaders, searchPath) {
|
|
|
211
211
|
if (checkCache) addToCache(cacheKey, promise);
|
|
212
212
|
|
|
213
213
|
promise?.then(res => {
|
|
214
|
+
// Replace Promise cache entry with the resolved texture value.
|
|
215
|
+
// This avoids keeping long-lived promise/closure graphs in THREE.Cache.
|
|
216
|
+
if (checkCache && res) addToCache(cacheKey, res);
|
|
214
217
|
if (res) uniform.value = /** @type {any} */ (res);
|
|
215
218
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
216
219
|
});
|
|
@@ -313,6 +316,19 @@ export function findLights(doc) {
|
|
|
313
316
|
/** @type {Object<string, number>} */
|
|
314
317
|
let lightTypesBound = {};
|
|
315
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Returns the current mapping of MaterialX light definition names to their type IDs.
|
|
321
|
+
* These IDs are used in the shader's LightData.type field.
|
|
322
|
+
* @returns {{ directional: number, point: number, spot: number }}
|
|
323
|
+
*/
|
|
324
|
+
export function getLightTypeIds() {
|
|
325
|
+
return {
|
|
326
|
+
directional: lightTypesBound['ND_directional_light'] || 1,
|
|
327
|
+
point: lightTypesBound['ND_point_light'] || 2,
|
|
328
|
+
spot: lightTypesBound['ND_spot_light'] || 3,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
316
332
|
/**
|
|
317
333
|
* Register lights in shader generation context
|
|
318
334
|
* @param {any} mx - MaterialX Module
|
|
@@ -373,6 +389,19 @@ export async function registerLights(mx, genContext) {
|
|
|
373
389
|
}
|
|
374
390
|
|
|
375
391
|
const _lightTypeWarnings = {}
|
|
392
|
+
const _emptyLightPosition = new THREE.Vector3(0, 0, 0);
|
|
393
|
+
const _emptyLightDirection = new THREE.Vector3(0, 0, -1);
|
|
394
|
+
const _emptyLightColor = new THREE.Color(0, 0, 0);
|
|
395
|
+
const _emptyLight = Object.freeze({
|
|
396
|
+
type: 0,
|
|
397
|
+
position: _emptyLightPosition,
|
|
398
|
+
direction: _emptyLightDirection,
|
|
399
|
+
color: _emptyLightColor,
|
|
400
|
+
intensity: 0.0,
|
|
401
|
+
decay_rate: 2.0,
|
|
402
|
+
inner_angle: 0.0,
|
|
403
|
+
outer_angle: 0.0,
|
|
404
|
+
});
|
|
376
405
|
/**
|
|
377
406
|
* Converts Three.js light type to MaterialX node name
|
|
378
407
|
* @param {string} threeLightType
|
|
@@ -434,8 +463,21 @@ export function getLightData(lights, genContext) {
|
|
|
434
463
|
}
|
|
435
464
|
|
|
436
465
|
const wp = light.getWorldPosition(new THREE.Vector3());
|
|
437
|
-
|
|
438
|
-
|
|
466
|
+
// For target-based lights (SpotLight, DirectionalLight), compute direction from position to target.
|
|
467
|
+
// Using getWorldQuaternion() doesn't work because Three.js target-based lights don't set rotation.
|
|
468
|
+
let wd;
|
|
469
|
+
const directionalOrSpot = (/** @type {THREE.DirectionalLight | THREE.SpotLight | null} */ (
|
|
470
|
+
(/** @type {THREE.DirectionalLight} */ (light)).isDirectionalLight || (/** @type {THREE.SpotLight} */ (light)).isSpotLight
|
|
471
|
+
? light
|
|
472
|
+
: null
|
|
473
|
+
));
|
|
474
|
+
if (directionalOrSpot) {
|
|
475
|
+
const targetPos = directionalOrSpot.target.getWorldPosition(new THREE.Vector3());
|
|
476
|
+
wd = targetPos.sub(wp).normalize();
|
|
477
|
+
} else {
|
|
478
|
+
const wq = light.getWorldQuaternion(new THREE.Quaternion());
|
|
479
|
+
wd = new THREE.Vector3(0, 0, -1).applyQuaternion(wq);
|
|
480
|
+
}
|
|
439
481
|
|
|
440
482
|
// Shader math from the generated MaterialX shader:
|
|
441
483
|
// float low = min(light.inner_angle, light.outer_angle);
|
|
@@ -452,7 +494,7 @@ export function getLightData(lights, genContext) {
|
|
|
452
494
|
type: lightTypesBound[lightDefinitionName],
|
|
453
495
|
position: wp.clone(),
|
|
454
496
|
direction: wd.clone(),
|
|
455
|
-
color:
|
|
497
|
+
color: light.color.clone(),
|
|
456
498
|
// Luminous efficacy for converting radiant power in watts (W) to luminous flux in lumens (lm) at a wavelength of 555 nm.
|
|
457
499
|
// Also, three.js lights don't have PI scale baked in, but MaterialX does, so we need to divide by PI for point and spot lights.
|
|
458
500
|
intensity: light.intensity * (/** @type {THREE.PointLight} */ (light).isPointLight ? 683.0 / 3.1415 : /** @type {THREE.SpotLight} */ (light).isSpotLight ? 683.0 / 3.1415 : 1.0),
|
|
@@ -468,17 +510,7 @@ export function getLightData(lights, genContext) {
|
|
|
468
510
|
|
|
469
511
|
// If we don't have enough entries in lightData, fill with empty lights
|
|
470
512
|
while (lightData.length < maxLightCount) {
|
|
471
|
-
|
|
472
|
-
type: 0, // Default light type
|
|
473
|
-
position: new THREE.Vector3(0, 0, 0),
|
|
474
|
-
direction: new THREE.Vector3(0, 0, -1),
|
|
475
|
-
color: new THREE.Color(0, 0, 0),
|
|
476
|
-
intensity: 0.0,
|
|
477
|
-
decay_rate: 2.0,
|
|
478
|
-
inner_angle: 0.0,
|
|
479
|
-
outer_angle: 0.0,
|
|
480
|
-
};
|
|
481
|
-
lightData.push(emptyLight);
|
|
513
|
+
lightData.push(_emptyLight);
|
|
482
514
|
}
|
|
483
515
|
|
|
484
516
|
if (debugUpdate) console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
|
package/src/materialx.js
CHANGED
|
@@ -48,29 +48,41 @@ export async function ready() {
|
|
|
48
48
|
if (debug) console.log("[MaterialX] Initializing WASM module...");
|
|
49
49
|
try {
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// NOTE: This must be a plain string literal (not a template) so that the
|
|
52
|
+
// makeFilesLocal Vite plugin can statically detect and localize this URL.
|
|
53
|
+
const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.4.3/";
|
|
53
54
|
|
|
54
55
|
/** @type {Array<string>} */
|
|
55
56
|
let urls;
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} else {
|
|
64
|
-
// For local paths, import the files as assets so Vite copies them to the dist folder
|
|
58
|
+
const location = globalThis.NEEDLE_MATERIALX_LOCATION;
|
|
59
|
+
|
|
60
|
+
if (location === "package" || location === "bin/" || location === "./bin/" || location === "../bin/") {
|
|
61
|
+
// Use local files from the @needle-tools/materialx npm package.
|
|
62
|
+
// Vite's ?url suffix copies these files to the output directory
|
|
63
|
+
// and returns their URL automatically — no CDN download needed.
|
|
65
64
|
urls = await Promise.all([
|
|
66
|
-
|
|
67
|
-
import(
|
|
68
|
-
|
|
69
|
-
import( /* @vite-ignore */ `../bin/JsMaterialXGenShader.wasm?url`).then(m => m.default || m),
|
|
70
|
-
/** @ts-ignore */
|
|
71
|
-
import( /* @vite-ignore */ `../bin/JsMaterialXGenShader.data.txt?url`).then(m => m.default || m),
|
|
65
|
+
import('../bin/JsMaterialXCore.wasm?url').then(m => m.default || m),
|
|
66
|
+
import('../bin/JsMaterialXGenShader.wasm?url').then(m => m.default || m),
|
|
67
|
+
import('../bin/JsMaterialXGenShader.data.txt?url').then(m => m.default || m),
|
|
72
68
|
]);
|
|
73
69
|
}
|
|
70
|
+
else if (location) {
|
|
71
|
+
// Custom path: use as base URL for CDN or self-hosted files
|
|
72
|
+
urls = [
|
|
73
|
+
location + "JsMaterialXCore.wasm",
|
|
74
|
+
location + "JsMaterialXGenShader.wasm",
|
|
75
|
+
location + "JsMaterialXGenShader.data.txt",
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Default: fetch from CDN (or from local files if makeFilesLocal rewrites this URL)
|
|
80
|
+
urls = [
|
|
81
|
+
defaultBaseUrl + "JsMaterialXCore.wasm",
|
|
82
|
+
defaultBaseUrl + "JsMaterialXGenShader.wasm",
|
|
83
|
+
defaultBaseUrl + "JsMaterialXGenShader.data.txt",
|
|
84
|
+
];
|
|
85
|
+
}
|
|
74
86
|
const [JsMaterialXCore, JsMaterialXGenShader, JsMaterialXGenShader_data] = urls;
|
|
75
87
|
|
|
76
88
|
const module = await MaterialX({
|
|
@@ -125,6 +137,7 @@ export async function ready() {
|
|
|
125
137
|
// Set a reasonable default for max active lights
|
|
126
138
|
state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
|
|
127
139
|
|
|
140
|
+
// We use Three.js shadow maps instead of MaterialX's own shadow implementation
|
|
128
141
|
// state.materialXGenContext.getOptions().hwShadowMap = true;
|
|
129
142
|
|
|
130
143
|
// This prewarms the shader generation context to have all light types
|
|
@@ -159,7 +172,7 @@ export class MaterialXEnvironment {
|
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
/** @type {WeakMap<Scene, MaterialXEnvironment>} */
|
|
162
|
-
static _environments = new
|
|
175
|
+
static _environments = new WeakMap();
|
|
163
176
|
|
|
164
177
|
/**
|
|
165
178
|
* @param {Scene} scene
|
|
@@ -370,6 +383,9 @@ export class MaterialXEnvironment {
|
|
|
370
383
|
if ((/** @type {Light} */ (object)).isLight && object.visible)
|
|
371
384
|
lights.push(/** @type {Light} */(object));
|
|
372
385
|
});
|
|
386
|
+
// Keep the same ordering strategy as Three.js (shadow casters first)
|
|
387
|
+
// and do this only when re-collecting lights to avoid per-frame sort allocations.
|
|
388
|
+
lights.sort((a, b) => (b.castShadow ? 1 : 0) - (a.castShadow ? 1 : 0));
|
|
373
389
|
this._lights = lights;
|
|
374
390
|
}
|
|
375
391
|
|
|
@@ -377,6 +393,15 @@ export class MaterialXEnvironment {
|
|
|
377
393
|
const { lightData, lightCount } = getLightData(this._lights, state.materialXGenContext);
|
|
378
394
|
this._lightData = lightData;
|
|
379
395
|
this._lightCount = lightCount;
|
|
396
|
+
// Note: Shadow data is now handled by Three.js when lights: true is set on the material
|
|
380
397
|
}
|
|
381
398
|
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Re-collect lights from the scene and rebuild light data.
|
|
402
|
+
* Call this after adding/removing lights, toggling visibility, or changing light properties.
|
|
403
|
+
*/
|
|
404
|
+
refreshLights() {
|
|
405
|
+
this.updateLighting(true);
|
|
406
|
+
}
|
|
382
407
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BufferGeometry, Camera, FrontSide, GLSL3, Group, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
1
|
+
import { BufferGeometry, Camera, FrontSide, GLSL3, Group, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, UniformsLib, Vector3, WebGLRenderer } from "three";
|
|
2
2
|
import { debug, getFrame, getTime } from "./utils.js";
|
|
3
3
|
import { MaterialXEnvironment } from "./materialx.js";
|
|
4
|
-
import { generateMaterialPropertiesForUniforms, getUniformValues } from "./materialx.helper.js";
|
|
5
|
-
import { cloneUniforms, cloneUniformsGroups } from "three/src/renderers/shaders/UniformsUtils.js";
|
|
4
|
+
import { generateMaterialPropertiesForUniforms, getUniformValues, getLightTypeIds } from "./materialx.helper.js";
|
|
5
|
+
import { cloneUniforms, cloneUniformsGroups, mergeUniforms } from "three/src/renderers/shaders/UniformsUtils.js";
|
|
6
6
|
|
|
7
7
|
// Add helper matrices for uniform updates (similar to MaterialX example)
|
|
8
8
|
const normalMat = new Matrix3();
|
|
@@ -35,8 +35,10 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
35
35
|
*/
|
|
36
36
|
copy(source) {
|
|
37
37
|
super.copy(source);
|
|
38
|
+
this.shaderName = source.shaderName;
|
|
38
39
|
this._context = source._context;
|
|
39
40
|
this._shader = source._shader;
|
|
41
|
+
this._needsTangents = source._needsTangents;
|
|
40
42
|
this.uniforms = cloneUniforms(source.uniforms);
|
|
41
43
|
this.uniformsGroups = cloneUniformsGroups(source.uniformsGroups);
|
|
42
44
|
this.envMapIntensity = source.envMapIntensity;
|
|
@@ -59,16 +61,21 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
59
61
|
*/
|
|
60
62
|
constructor(init) {
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
64
|
+
/** @type {import('three').ShaderMaterialParameters | undefined} */
|
|
65
|
+
let materialParameters = undefined;
|
|
66
|
+
/** @type {string} */
|
|
67
|
+
let vertexShader = "";
|
|
68
|
+
/** @type {string} */
|
|
69
|
+
let fragmentShader = "";
|
|
70
|
+
/** @type {Record<string, string>} */
|
|
71
|
+
let defines = {};
|
|
72
|
+
|
|
73
|
+
if (init) {
|
|
67
74
|
|
|
68
75
|
// Get vertex and fragment shader source, and remove #version directive for newer js.
|
|
69
76
|
// It's added by three.js glslVersion.
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
vertexShader = init.shader.getSourceCode("vertex");
|
|
78
|
+
fragmentShader = init.shader.getSourceCode("pixel");
|
|
72
79
|
|
|
73
80
|
vertexShader = vertexShader.replace(/^#version.*$/gm, '').trim();
|
|
74
81
|
fragmentShader = fragmentShader.replace(/^#version.*$/gm, '').trim();
|
|
@@ -108,6 +115,12 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
108
115
|
fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
109
116
|
fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
|
|
110
117
|
|
|
118
|
+
// Patch env intensity uniform to match Three.js naming convention.
|
|
119
|
+
// MaterialX generates `u_envLightIntensity`; Three.js uses `envMapIntensity`.
|
|
120
|
+
// This lets us combine material.envMapIntensity * scene.environmentIntensity
|
|
121
|
+
// the same way MeshStandardMaterial does.
|
|
122
|
+
fragmentShader = fragmentShader.replace(/\bu_envLightIntensity\b/g, 'envMapIntensity');
|
|
123
|
+
|
|
111
124
|
// Capture some vertex shader properties
|
|
112
125
|
const uv_is_vec2 = vertexShader.includes('in vec2 uv;'); // check if uv is vec2; e.g. https://matlib.gpuopen.com/main/materials/all?material=da6ec531-f5c1-4790-ac14-8a5c51d0314e
|
|
113
126
|
const uv1_is_vec2 = vertexShader.includes('in vec2 uv1;');
|
|
@@ -155,16 +168,154 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
155
168
|
#include <tonemapping_fragment>
|
|
156
169
|
#include <colorspace_fragment>`);
|
|
157
170
|
|
|
158
|
-
|
|
171
|
+
defines = {};
|
|
159
172
|
if (hasUv1) defines['USE_UV1'] = '';
|
|
160
173
|
if (hasUv2) defines['USE_UV2'] = '';
|
|
161
174
|
if (hasUv3) defines['USE_UV3'] = '';
|
|
162
175
|
if (hasTangent) defines['USE_TANGENT'] = '';
|
|
163
176
|
if (hasColor) defines['USE_COLOR'] = '';
|
|
164
177
|
|
|
165
|
-
|
|
178
|
+
// Add Three.js shadow support
|
|
179
|
+
// Insert shadow pars before main() in vertex shader
|
|
180
|
+
vertexShader = vertexShader.replace(
|
|
181
|
+
/void\s+main\s*\(\s*\)\s*\{/,
|
|
182
|
+
`#include <common>
|
|
183
|
+
#include <shadowmap_pars_vertex>
|
|
184
|
+
void main() {`
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Insert shadow vertex calculation at the end of vertex main (before the closing brace)
|
|
188
|
+
// We need to compute worldPosition and transformedNormal for shadow coords
|
|
189
|
+
// Note: Three.js shadowmap_vertex expects transformedNormal in VIEW space:
|
|
190
|
+
// it does `inverseTransformDirection(transformedNormal, viewMatrix)` to get world-space normal
|
|
191
|
+
vertexShader = vertexShader.replace(
|
|
192
|
+
/(\n\s*)\}(\s*)$/,
|
|
193
|
+
`$1 // Three.js shadow support
|
|
194
|
+
$1 vec4 worldPosition = u_worldMatrix * vec4(position, 1.0);
|
|
195
|
+
$1 vec3 transformedNormal = mat3(viewMatrix) * normalWorld;
|
|
196
|
+
$1 #include <shadowmap_vertex>
|
|
197
|
+
$1}$2`
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Insert shadow includes at the very beginning of the fragment shader (after precision)
|
|
201
|
+
// This ensures DirectionalLightShadow struct is defined before getMxShadow uses it
|
|
202
|
+
fragmentShader = fragmentShader.replace(
|
|
203
|
+
/(precision\s+\w+\s+float;)/,
|
|
204
|
+
`$1
|
|
205
|
+
|
|
206
|
+
#include <common>
|
|
207
|
+
#include <packing>
|
|
208
|
+
#include <shadowmap_pars_fragment>`
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Get MaterialX light type IDs for shadow dispatch
|
|
212
|
+
const lightTypeIds = getLightTypeIds();
|
|
213
|
+
|
|
214
|
+
// Generate GLSL helper functions that sample shadow maps using constant indices.
|
|
215
|
+
// Sampler arrays require constant integral expression indices in GLSL ES 3.0,
|
|
216
|
+
// so we use if/else chains with literal constants (guarded by preprocessor).
|
|
217
|
+
const MAX_SHADOW_LIGHTS = 4; // max shadow-casting lights per type
|
|
218
|
+
|
|
219
|
+
let dirShadowCases = '';
|
|
220
|
+
for (let i = 0; i < MAX_SHADOW_LIGHTS; i++) {
|
|
221
|
+
dirShadowCases += `
|
|
222
|
+
#if NUM_DIR_LIGHT_SHADOWS > ${i}
|
|
223
|
+
${i > 0 ? 'else ' : ''}if (idx == ${i}) {
|
|
224
|
+
DirectionalLightShadow s = directionalLightShadows[${i}];
|
|
225
|
+
return getShadow(directionalShadowMap[${i}], s.shadowMapSize, s.shadowIntensity, s.shadowBias, s.shadowRadius, vDirectionalShadowCoord[${i}]);
|
|
226
|
+
}
|
|
227
|
+
#endif`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let spotShadowCases = '';
|
|
231
|
+
for (let i = 0; i < MAX_SHADOW_LIGHTS; i++) {
|
|
232
|
+
spotShadowCases += `
|
|
233
|
+
#if NUM_SPOT_LIGHT_SHADOWS > ${i}
|
|
234
|
+
${i > 0 ? 'else ' : ''}if (idx == ${i}) {
|
|
235
|
+
SpotLightShadow s = spotLightShadows[${i}];
|
|
236
|
+
return getShadow(spotShadowMap[${i}], s.shadowMapSize, s.shadowIntensity, s.shadowBias, s.shadowRadius, vSpotLightCoord[${i}]);
|
|
237
|
+
}
|
|
238
|
+
#endif`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let pointShadowCases = '';
|
|
242
|
+
for (let i = 0; i < MAX_SHADOW_LIGHTS; i++) {
|
|
243
|
+
pointShadowCases += `
|
|
244
|
+
#if NUM_POINT_LIGHT_SHADOWS > ${i}
|
|
245
|
+
${i > 0 ? 'else ' : ''}if (idx == ${i}) {
|
|
246
|
+
PointLightShadow s = pointLightShadows[${i}];
|
|
247
|
+
return getPointShadow(pointShadowMap[${i}], s.shadowMapSize, s.shadowIntensity, s.shadowBias, s.shadowRadius, vPointShadowCoord[${i}], s.shadowCameraNear, s.shadowCameraFar);
|
|
248
|
+
}
|
|
249
|
+
#endif`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Insert getMxShadow helper function BEFORE sampleLightSource (so it's defined when used)
|
|
253
|
+
// Supports directional, spot, and point light shadows.
|
|
254
|
+
// Uses global per-type counters to track which shadow map index to use.
|
|
255
|
+
fragmentShader = fragmentShader.replace(
|
|
256
|
+
/void sampleLightSource\(LightData light, vec3 position, out lightshader result\)/,
|
|
257
|
+
`// MaterialX light type IDs (from registerLights)
|
|
258
|
+
#define MX_LIGHT_TYPE_DIRECTIONAL ${lightTypeIds.directional}
|
|
259
|
+
#define MX_LIGHT_TYPE_POINT ${lightTypeIds.point}
|
|
260
|
+
#define MX_LIGHT_TYPE_SPOT ${lightTypeIds.spot}
|
|
261
|
+
|
|
262
|
+
// Per-type shadow index counters (global so they persist across sampleLightSource calls)
|
|
263
|
+
int mxDirShadowIdx = 0;
|
|
264
|
+
int mxSpotShadowIdx = 0;
|
|
265
|
+
int mxPointShadowIdx = 0;
|
|
266
|
+
|
|
267
|
+
// Shadow sampling helpers using constant indices (required for sampler arrays in GLSL ES 3.0)
|
|
268
|
+
float sampleMxDirShadow(int idx) {
|
|
269
|
+
#ifdef USE_SHADOWMAP
|
|
270
|
+
#if NUM_DIR_LIGHT_SHADOWS > 0
|
|
271
|
+
${dirShadowCases}
|
|
272
|
+
#endif
|
|
273
|
+
#endif
|
|
274
|
+
return 1.0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
float sampleMxSpotShadow(int idx) {
|
|
278
|
+
#ifdef USE_SHADOWMAP
|
|
279
|
+
#if NUM_SPOT_LIGHT_SHADOWS > 0
|
|
280
|
+
${spotShadowCases}
|
|
281
|
+
#endif
|
|
282
|
+
#endif
|
|
283
|
+
return 1.0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
float sampleMxPointShadow(int idx) {
|
|
287
|
+
#ifdef USE_SHADOWMAP
|
|
288
|
+
#if NUM_POINT_LIGHT_SHADOWS > 0
|
|
289
|
+
${pointShadowCases}
|
|
290
|
+
#endif
|
|
291
|
+
#endif
|
|
292
|
+
return 1.0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
void sampleLightSource(LightData light, vec3 position, out lightshader result)`
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// Find the sampleLightSource function and add shadow + counter increment at the end.
|
|
299
|
+
// The per-type counters track which Three.js shadow map index to use for each light type.
|
|
300
|
+
// Lights must be sorted (shadow-casting first per type) to match Three.js shadow map ordering.
|
|
301
|
+
fragmentShader = fragmentShader.replace(
|
|
302
|
+
/(void sampleLightSource\(LightData light, vec3 position, out lightshader result\)\s*\{[\s\S]*?)(^\})/m,
|
|
303
|
+
`$1 // Apply Three.js shadow and increment per-type shadow counters
|
|
304
|
+
if (light.type == MX_LIGHT_TYPE_DIRECTIONAL) {
|
|
305
|
+
result.intensity *= sampleMxDirShadow(mxDirShadowIdx);
|
|
306
|
+
mxDirShadowIdx++;
|
|
307
|
+
} else if (light.type == MX_LIGHT_TYPE_SPOT) {
|
|
308
|
+
result.intensity *= sampleMxSpotShadow(mxSpotShadowIdx);
|
|
309
|
+
mxSpotShadowIdx++;
|
|
310
|
+
} else if (light.type == MX_LIGHT_TYPE_POINT) {
|
|
311
|
+
result.intensity *= sampleMxPointShadow(mxPointShadowIdx);
|
|
312
|
+
mxPointShadowIdx++;
|
|
313
|
+
}
|
|
314
|
+
$2`
|
|
315
|
+
);
|
|
316
|
+
|
|
166
317
|
const isTransparent = init.parameters?.transparent ?? false;
|
|
167
|
-
|
|
318
|
+
materialParameters = {
|
|
168
319
|
name: init.name,
|
|
169
320
|
uniforms: {},
|
|
170
321
|
vertexShader: vertexShader,
|
|
@@ -173,14 +324,28 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
173
324
|
depthTest: true,
|
|
174
325
|
depthWrite: !isTransparent,
|
|
175
326
|
defines: defines,
|
|
327
|
+
lights: true, // Enable Three.js light uniforms
|
|
176
328
|
...init.parameters, // Spread any additional parameters passed to the material
|
|
177
|
-
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
super(materialParameters);
|
|
333
|
+
|
|
334
|
+
// Constructor can be called without init during clone() paths.
|
|
335
|
+
if (!init) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const searchPath = ""; // Could be derived from the asset path if needed
|
|
178
340
|
this.shaderName = init.shaderName || null;
|
|
179
341
|
this._context = init.context;
|
|
180
342
|
this._shader = init.shader;
|
|
181
343
|
this._needsTangents = vertexShader.includes('in vec4 tangent;') || vertexShader.includes('in vec3 tangent;');
|
|
182
344
|
|
|
183
345
|
Object.assign(this.uniforms, {
|
|
346
|
+
// Three.js light uniforms (required when lights: true)
|
|
347
|
+
...UniformsLib.lights,
|
|
348
|
+
|
|
184
349
|
...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
|
|
185
350
|
...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath),
|
|
186
351
|
|
|
@@ -189,15 +354,13 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
189
354
|
u_viewPosition: { value: new Vector3() },
|
|
190
355
|
u_worldInverseTransposeMatrix: { value: new Matrix4() },
|
|
191
356
|
|
|
192
|
-
// u_shadowMatrix: { value: new Matrix4() },
|
|
193
|
-
// u_shadowMap: { value: null, type: 't' }, // Shadow map
|
|
194
|
-
|
|
195
357
|
u_envMatrix: { value: new Matrix4() },
|
|
196
358
|
u_envRadiance: { value: null, type: 't' },
|
|
197
359
|
u_envRadianceMips: { value: 8, type: 'i' },
|
|
198
360
|
// TODO we need to figure out how we can set a PMREM here... doing many texture samples is prohibitively expensive
|
|
199
361
|
u_envRadianceSamples: { value: 8, type: 'i' },
|
|
200
362
|
u_envIrradiance: { value: null, type: 't' },
|
|
363
|
+
envMapIntensity: { value: 1.0 },
|
|
201
364
|
u_refractionEnv: { value: true },
|
|
202
365
|
u_numActiveLightSources: { value: 0 },
|
|
203
366
|
u_lightData: { value: [], needsUpdate: false }, // Array of light data. We need to set needsUpdate to false until we actually update it
|
|
@@ -240,7 +403,7 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
240
403
|
const env = MaterialXEnvironment.get(_scene);
|
|
241
404
|
if (env) {
|
|
242
405
|
env.update(frame, _scene, renderer);
|
|
243
|
-
this.updateEnvironmentUniforms(env);
|
|
406
|
+
this.updateEnvironmentUniforms(env, _scene);
|
|
244
407
|
}
|
|
245
408
|
this.updateUniforms(renderer, object, camera, time, frame);
|
|
246
409
|
}
|
|
@@ -312,9 +475,10 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
312
475
|
|
|
313
476
|
/**
|
|
314
477
|
* @private
|
|
315
|
-
|
|
478
|
+
* @param {MaterialXEnvironment} environment
|
|
479
|
+
* @param {Scene} scene
|
|
316
480
|
*/
|
|
317
|
-
updateEnvironmentUniforms = (environment) => {
|
|
481
|
+
updateEnvironmentUniforms = (environment, scene) => {
|
|
318
482
|
|
|
319
483
|
const uniforms = this.uniforms;
|
|
320
484
|
|
|
@@ -354,6 +518,14 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
354
518
|
if (prev != textures.irradianceTexture) uniforms.u_envIrradiance.needsUpdate = true;
|
|
355
519
|
}
|
|
356
520
|
|
|
521
|
+
// Sync environment intensity: combine per-material envMapIntensity with scene.environmentIntensity
|
|
522
|
+
// (mirrors MeshStandardMaterial behaviour in Three.js)
|
|
523
|
+
if (uniforms.envMapIntensity) {
|
|
524
|
+
uniforms.envMapIntensity.value = (this.envMapIntensity ?? 1.0) * (scene.environmentIntensity ?? 1.0);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Note: Shadow uniforms are handled by Three.js when lights: true is set
|
|
528
|
+
|
|
357
529
|
this.uniformsNeedUpdate = true;
|
|
358
530
|
}
|
|
359
531
|
}
|
package/src/utils.d.ts
CHANGED
package/bin/README.md
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
Source: https://github.com/AcademySoftwareFoundation/MaterialX/tree/gh-pages
|
|
2
|
-
|
|
3
|
-
Edits:
|
|
4
|
-
|
|
5
|
-
- In `JsMaterialXGenShader.js` added `export default MaterialX;` at bottom
|
|
6
|
-
- Renamed `JsMaterialXGenShader.data` to `JsMaterialXGenShader.data.txt` so it can be loaded by vite etc
|