@needle-tools/materialx 1.6.0 → 1.7.0-next.57183d6

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/src/materialx.js CHANGED
@@ -1,11 +1,35 @@
1
1
  import MaterialX from "../bin/JsMaterialXGenShader.js";
2
2
  import { debug, waitForNetworkIdle } from "./utils.js";
3
- import { renderPMREMToEquirect } from "./utils.texture.js";
4
- import { Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
3
+ import { renderPMREMToEquirect, renderPMREMToPrefilteredEquirect } from "./utils.texture.js";
4
+ import { CubeUVReflectionMapping, Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture } from "three";
5
5
  import { registerLights, getLightData } from "./materialx.helper.js";
6
6
  import { whiteTexture } from "./utils.texture.js";
7
7
  import { VERSION } from "./constants.js";
8
8
 
9
+ /**
10
+ * Accept Texture instances from any Three.js copy. Tooling like the fidelity
11
+ * renderer can host scenes with a different Three.js module instance than this
12
+ * package, so `instanceof Texture` is too strict here.
13
+ * @param {unknown} value
14
+ * @returns {value is Texture}
15
+ */
16
+ function isTextureLike(value) {
17
+ return !!value && typeof value === "object" && /** @type {{ isTexture?: unknown }} */(value).isTexture === true;
18
+ }
19
+
20
+ function isNodeRuntime() {
21
+ return !!globalThis.process?.versions?.node && globalThis.process?.type !== "renderer";
22
+ }
23
+
24
+ async function getNodePackageAssetPaths() {
25
+ const { fileURLToPath } = await import(/* @vite-ignore */ "node:url");
26
+ return [
27
+ fileURLToPath(new URL("../bin/JsMaterialXCore.wasm", import.meta.url)),
28
+ fileURLToPath(new URL("../bin/JsMaterialXGenShader.wasm", import.meta.url)),
29
+ fileURLToPath(new URL("../bin/JsMaterialXGenShader.data.txt", import.meta.url)),
30
+ ];
31
+ }
32
+
9
33
 
10
34
  /**
11
35
  * Preloads the MaterialX WebAssembly module.
@@ -51,14 +75,17 @@ export async function ready() {
51
75
 
52
76
  // NOTE: This must be a plain string literal (not a template) so that the
53
77
  // makeFilesLocal Vite plugin can statically detect and localize this URL.
54
- const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.6.0/";
78
+ const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.7.0/";
55
79
 
56
80
  /** @type {Array<string>} */
57
81
  let urls;
58
82
 
59
83
  const location = globalThis.NEEDLE_MATERIALX_LOCATION;
60
84
 
61
- if (location === "package" || location === "bin/" || location === "./bin/" || location === "../bin/") {
85
+ if (isNodeRuntime()) {
86
+ urls = await getNodePackageAssetPaths();
87
+ }
88
+ else if (location === "package" || location === "bin/" || location === "./bin/" || location === "../bin/") {
62
89
  // Use local files from the @needle-tools/materialx npm package.
63
90
  // Vite's ?url suffix copies these files to the output directory
64
91
  // and returns their URL automatically — no CDN download needed.
@@ -121,7 +148,7 @@ export async function ready() {
121
148
  // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
122
149
  // SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
123
150
  // SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
124
- state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
151
+ state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_PREFILTER;
125
152
 
126
153
  // TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
127
154
  // TRANSMISSION_OPACITY: Use opacity for transmission rendering.
@@ -161,6 +188,7 @@ export async function ready() {
161
188
  * @typedef {Object} EnvironmentTextureSet
162
189
  * @property {Texture | null} radianceTexture
163
190
  * @property {Texture | null} irradianceTexture
191
+ * @property {() => void} [dispose]
164
192
  */
165
193
 
166
194
  /**
@@ -217,7 +245,7 @@ export class MaterialXEnvironment {
217
245
 
218
246
  /**
219
247
  * Initialize with Needle Engine context
220
- * @param {WebGLRenderer} renderer
248
+ * @param {import("three").WebGLRenderer} renderer
221
249
  * @returns {Promise<boolean>}
222
250
  */
223
251
  async initialize(renderer) {
@@ -231,7 +259,7 @@ export class MaterialXEnvironment {
231
259
  /**
232
260
  * @param {number} frame
233
261
  * @param {Scene} scene
234
- * @param {WebGLRenderer} renderer
262
+ * @param {import("three").WebGLRenderer} renderer
235
263
  */
236
264
  update(frame, scene, renderer) {
237
265
  if (!this._initializePromise) {
@@ -285,9 +313,10 @@ export class MaterialXEnvironment {
285
313
  this._pmremGenerator?.dispose();
286
314
  this._pmremGenerator = null;
287
315
  this._renderer = null;
288
- for (const textureSet of this._texturesCache.values()) {
289
- textureSet.radianceTexture?.dispose();
290
- textureSet.irradianceTexture?.dispose();
316
+ for (const textureModeMap of this._texturesCache.values()) {
317
+ for (const textureSet of textureModeMap.values()) {
318
+ textureSet.dispose?.();
319
+ }
291
320
  }
292
321
  this._texturesCache.clear();
293
322
  }
@@ -304,34 +333,34 @@ export class MaterialXEnvironment {
304
333
  * @param {import("./materialx.material.js").MaterialXMaterial} material
305
334
  */
306
335
  getTextures(material) {
336
+ const radianceMode = material.environmentRadianceMode ?? "three-pmrem";
307
337
  if (material.envMap) {
308
338
  // If the material has its own envMap, we don't use the irradiance texture
309
- return this._getTextures(material.envMap);
339
+ return this._getTextures(material.envMap, radianceMode);
310
340
  }
311
341
 
312
342
  // Use the scene background for lighting if no environment is available
313
343
  // If we don't do this we don't see the correct lighting for scenes exported with 'Environment Lighting: Color' and 'Environment Reflections: Skybox'
314
344
  const skybox = this._scene.environment || this._scene.background;
315
- if (skybox instanceof Texture) {
316
- return this._getTextures(skybox);
345
+ if (isTextureLike(skybox)) {
346
+ return this._getTextures(skybox, radianceMode);
317
347
  }
318
- return this._getTextures(null);
348
+ return this._getTextures(null, radianceMode);
319
349
  }
320
350
 
321
351
  /** @type {PMREMGenerator | null} */
322
352
  _pmremGenerator = null;
323
- /** @type {WebGLRenderer | null} */
353
+ /** @type {import("three").WebGLRenderer | null} */
324
354
  _renderer = null;
325
- /** @type {Map<Texture | null, EnvironmentTextureSet>} */
355
+ /** @type {Map<Texture | null, Map<string, EnvironmentTextureSet>>} */
326
356
  _texturesCache = new Map();
327
357
 
328
358
  /**
329
- * @param {WebGLRenderer} renderer
359
+ * @param {import("three").WebGLRenderer} renderer
330
360
  * @returns {Promise<boolean>}
331
361
  */
332
362
  async _initialize(renderer) {
333
363
  this._isInitialized = false;
334
- this._pmremGenerator = new PMREMGenerator(renderer);
335
364
  this._renderer = renderer;
336
365
  this.updateLighting(true);
337
366
  this._isInitialized = true;
@@ -340,30 +369,65 @@ export class MaterialXEnvironment {
340
369
 
341
370
  /**
342
371
  * @param {Texture | null | undefined} texture
343
- * @returns {{radianceTexture: Texture | null, irradianceTexture: Texture | null}}
372
+ * @param {"three-pmrem" | "materialx-prefiltered" | "materialx-fis"} [radianceMode]
373
+ * @returns {EnvironmentTextureSet}
344
374
  */
345
- _getTextures(texture) {
375
+ _getTextures(texture, radianceMode = "three-pmrem") {
346
376
 
347
377
  // Fallback to white texture if no texture is provided
348
378
  if (!texture) {
349
379
  texture = whiteTexture;
350
380
  }
351
381
 
382
+ const cacheKey = texture || null;
383
+ let textureModeMap = this._texturesCache.get(cacheKey);
384
+ if (!textureModeMap) {
385
+ textureModeMap = new Map();
386
+ this._texturesCache.set(cacheKey, textureModeMap);
387
+ }
388
+
352
389
  /** @type {EnvironmentTextureSet | undefined} */
353
- let res = this._texturesCache.get(texture || null);
390
+ let res = textureModeMap.get(radianceMode);
354
391
  if (res) {
355
392
  return res;
356
393
  }
357
394
 
358
- if (this._scene && this._pmremGenerator && this._renderer && texture) {
395
+ const isPmremTexture = texture.mapping === CubeUVReflectionMapping || texture.isRenderTargetTexture === true;
396
+
397
+ if (this._scene && this._renderer && texture) {
359
398
  if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
360
- const target = this._pmremGenerator.fromEquirectangular(texture);
361
- const radianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 0.0, 1024, 512, target.height);
362
- const irradianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 1.0, 32, 16, target.height);
363
- target.dispose();
399
+ let radianceRenderTarget;
400
+ let irradianceRenderTarget;
401
+
402
+ if (isPmremTexture) {
403
+ // Scene.environment is often already PMREM-processed (CubeUV layout).
404
+ // Running PMREMGenerator on it again corrupts the sampling layout.
405
+ radianceRenderTarget = radianceMode === "materialx-prefiltered"
406
+ ? renderPMREMToPrefilteredEquirect(this._renderer, texture)
407
+ : radianceMode === "materialx-fis"
408
+ ? renderPMREMToEquirect(this._renderer, texture, 0.0, 1024, 512)
409
+ : null;
410
+ irradianceRenderTarget = renderPMREMToEquirect(this._renderer, texture, 1.0, 32, 16);
411
+ } else {
412
+ const target = this._getPMREMGenerator().fromEquirectangular(texture);
413
+ radianceRenderTarget = radianceMode === "materialx-prefiltered"
414
+ ? renderPMREMToPrefilteredEquirect(this._renderer, target.texture, undefined, undefined, target.height)
415
+ : radianceMode === "three-pmrem"
416
+ ? target
417
+ : null;
418
+ irradianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 1.0, 32, 16, target.height);
419
+ if (radianceMode !== "three-pmrem") {
420
+ target.dispose();
421
+ }
422
+ }
423
+
364
424
  res = {
365
- radianceTexture: radianceRenderTarget.texture,
366
- irradianceTexture: irradianceRenderTarget.texture
425
+ radianceTexture: radianceRenderTarget?.texture ?? texture,
426
+ irradianceTexture: irradianceRenderTarget.texture,
427
+ dispose: () => {
428
+ radianceRenderTarget?.dispose();
429
+ irradianceRenderTarget?.dispose();
430
+ },
367
431
  }
368
432
  }
369
433
  else {
@@ -372,10 +436,21 @@ export class MaterialXEnvironment {
372
436
  irradianceTexture: null
373
437
  }
374
438
  }
375
- this._texturesCache.set(texture || null, res);
439
+ textureModeMap.set(radianceMode, res);
376
440
  return res;
377
441
  }
378
442
 
443
+ /**
444
+ * @returns {PMREMGenerator}
445
+ */
446
+ _getPMREMGenerator() {
447
+ if (!this._renderer) {
448
+ throw new Error("[MaterialX] Cannot create PMREMGenerator before renderer initialization.");
449
+ }
450
+ this._pmremGenerator ??= new PMREMGenerator(this._renderer);
451
+ return this._pmremGenerator;
452
+ }
453
+
379
454
  /**
380
455
  * @param {boolean} collectLights
381
456
  */
@@ -1,5 +1,6 @@
1
- import { BufferGeometry, Camera, Group, IUniform, MaterialParameters, Object3D, Scene, ShaderMaterial, Texture, WebGLRenderer } from "three";
1
+ import { BufferGeometry, Camera, Euler, Group, IUniform, MaterialParameters, Object3D, Scene, ShaderMaterial, Texture, WebGLRenderer } from "three";
2
2
  import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
3
+ import type { MaterialXEnvironmentRadianceMode } from "./materialx.js";
3
4
  import { Callbacks } from "./materialx.helper.js";
4
5
 
5
6
  declare type MaterialXMaterialInitParameters = {
@@ -9,6 +10,8 @@ declare type MaterialXMaterialInitParameters = {
9
10
  loaders: Callbacks;
10
11
  context: MaterialXContext;
11
12
  parameters?: MaterialParameters;
13
+ environmentRadianceMode?: MaterialXEnvironmentRadianceMode;
14
+ specularAntialiasing?: boolean;
12
15
  debug?: boolean;
13
16
  }
14
17
 
@@ -17,6 +20,7 @@ type Precision = "highp" | "mediump" | "lowp";
17
20
 
18
21
  export declare class MaterialXMaterial extends ShaderMaterial {
19
22
  readonly shaderName: string | null;
23
+ readonly ready: Promise<void>;
20
24
 
21
25
  copy(source: MaterialXMaterial): this;
22
26
 
@@ -31,7 +35,12 @@ export declare class MaterialXMaterial extends ShaderMaterial {
31
35
 
32
36
  envMapIntensity: number;
33
37
  envMap: Texture | null;
38
+ envMapRotation: Euler;
39
+ environmentRadianceMode: MaterialXEnvironmentRadianceMode;
40
+ specularAntialiasing: boolean;
34
41
  updateUniforms(environment: MaterialXEnvironment, _renderer: WebGLRenderer, object: Object3D, camera: Camera, time?: number, frame?: number): void;
35
42
 
36
- private updateEnvironmentUniforms(environment: MaterialXEnvironment): void;
43
+ private updateEnvironmentUniforms(environment: MaterialXEnvironment, scene: Scene): void;
37
44
  }
45
+
46
+ export declare const DEFAULT_ENVIRONMENT_RADIANCE_MODE: MaterialXEnvironmentRadianceMode;