@needle-tools/materialx 1.6.0 → 1.7.0-next.043f0c6

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,37 @@
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 nodeUrlModule = "node:url";
26
+ const { fileURLToPath } = await import(/* @vite-ignore */ nodeUrlModule);
27
+ const binDirectory = ["..", "bin"].join("/") + "/";
28
+ return [
29
+ "JsMaterialXCore.wasm",
30
+ "JsMaterialXGenShader.wasm",
31
+ "JsMaterialXGenShader.data.txt",
32
+ ].map(fileName => fileURLToPath(new URL(binDirectory + fileName, import.meta.url)));
33
+ }
34
+
9
35
 
10
36
  /**
11
37
  * Preloads the MaterialX WebAssembly module.
@@ -51,14 +77,17 @@ export async function ready() {
51
77
 
52
78
  // NOTE: This must be a plain string literal (not a template) so that the
53
79
  // makeFilesLocal Vite plugin can statically detect and localize this URL.
54
- const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.6.0/";
80
+ const defaultBaseUrl = "https://cdn.needle.tools/static/materialx/1.7.0/";
55
81
 
56
82
  /** @type {Array<string>} */
57
83
  let urls;
58
84
 
59
85
  const location = globalThis.NEEDLE_MATERIALX_LOCATION;
60
86
 
61
- if (location === "package" || location === "bin/" || location === "./bin/" || location === "../bin/") {
87
+ if (isNodeRuntime()) {
88
+ urls = await getNodePackageAssetPaths();
89
+ }
90
+ else if (location === "package" || location === "bin/" || location === "./bin/" || location === "../bin/") {
62
91
  // Use local files from the @needle-tools/materialx npm package.
63
92
  // Vite's ?url suffix copies these files to the output directory
64
93
  // and returns their URL automatically — no CDN download needed.
@@ -121,7 +150,7 @@ export async function ready() {
121
150
  // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
122
151
  // SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
123
152
  // SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
124
- state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
153
+ state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_PREFILTER;
125
154
 
126
155
  // TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
127
156
  // TRANSMISSION_OPACITY: Use opacity for transmission rendering.
@@ -161,6 +190,7 @@ export async function ready() {
161
190
  * @typedef {Object} EnvironmentTextureSet
162
191
  * @property {Texture | null} radianceTexture
163
192
  * @property {Texture | null} irradianceTexture
193
+ * @property {() => void} [dispose]
164
194
  */
165
195
 
166
196
  /**
@@ -217,7 +247,7 @@ export class MaterialXEnvironment {
217
247
 
218
248
  /**
219
249
  * Initialize with Needle Engine context
220
- * @param {WebGLRenderer} renderer
250
+ * @param {import("three").WebGLRenderer} renderer
221
251
  * @returns {Promise<boolean>}
222
252
  */
223
253
  async initialize(renderer) {
@@ -231,7 +261,7 @@ export class MaterialXEnvironment {
231
261
  /**
232
262
  * @param {number} frame
233
263
  * @param {Scene} scene
234
- * @param {WebGLRenderer} renderer
264
+ * @param {import("three").WebGLRenderer} renderer
235
265
  */
236
266
  update(frame, scene, renderer) {
237
267
  if (!this._initializePromise) {
@@ -285,9 +315,10 @@ export class MaterialXEnvironment {
285
315
  this._pmremGenerator?.dispose();
286
316
  this._pmremGenerator = null;
287
317
  this._renderer = null;
288
- for (const textureSet of this._texturesCache.values()) {
289
- textureSet.radianceTexture?.dispose();
290
- textureSet.irradianceTexture?.dispose();
318
+ for (const textureModeMap of this._texturesCache.values()) {
319
+ for (const textureSet of textureModeMap.values()) {
320
+ textureSet.dispose?.();
321
+ }
291
322
  }
292
323
  this._texturesCache.clear();
293
324
  }
@@ -304,34 +335,34 @@ export class MaterialXEnvironment {
304
335
  * @param {import("./materialx.material.js").MaterialXMaterial} material
305
336
  */
306
337
  getTextures(material) {
338
+ const radianceMode = material.environmentRadianceMode ?? "three-pmrem";
307
339
  if (material.envMap) {
308
340
  // If the material has its own envMap, we don't use the irradiance texture
309
- return this._getTextures(material.envMap);
341
+ return this._getTextures(material.envMap, radianceMode);
310
342
  }
311
343
 
312
344
  // Use the scene background for lighting if no environment is available
313
345
  // 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
346
  const skybox = this._scene.environment || this._scene.background;
315
- if (skybox instanceof Texture) {
316
- return this._getTextures(skybox);
347
+ if (isTextureLike(skybox)) {
348
+ return this._getTextures(skybox, radianceMode);
317
349
  }
318
- return this._getTextures(null);
350
+ return this._getTextures(null, radianceMode);
319
351
  }
320
352
 
321
353
  /** @type {PMREMGenerator | null} */
322
354
  _pmremGenerator = null;
323
- /** @type {WebGLRenderer | null} */
355
+ /** @type {import("three").WebGLRenderer | null} */
324
356
  _renderer = null;
325
- /** @type {Map<Texture | null, EnvironmentTextureSet>} */
357
+ /** @type {Map<Texture | null, Map<string, EnvironmentTextureSet>>} */
326
358
  _texturesCache = new Map();
327
359
 
328
360
  /**
329
- * @param {WebGLRenderer} renderer
361
+ * @param {import("three").WebGLRenderer} renderer
330
362
  * @returns {Promise<boolean>}
331
363
  */
332
364
  async _initialize(renderer) {
333
365
  this._isInitialized = false;
334
- this._pmremGenerator = new PMREMGenerator(renderer);
335
366
  this._renderer = renderer;
336
367
  this.updateLighting(true);
337
368
  this._isInitialized = true;
@@ -340,30 +371,65 @@ export class MaterialXEnvironment {
340
371
 
341
372
  /**
342
373
  * @param {Texture | null | undefined} texture
343
- * @returns {{radianceTexture: Texture | null, irradianceTexture: Texture | null}}
374
+ * @param {"three-pmrem" | "materialx-prefiltered" | "materialx-fis"} [radianceMode]
375
+ * @returns {EnvironmentTextureSet}
344
376
  */
345
- _getTextures(texture) {
377
+ _getTextures(texture, radianceMode = "three-pmrem") {
346
378
 
347
379
  // Fallback to white texture if no texture is provided
348
380
  if (!texture) {
349
381
  texture = whiteTexture;
350
382
  }
351
383
 
384
+ const cacheKey = texture || null;
385
+ let textureModeMap = this._texturesCache.get(cacheKey);
386
+ if (!textureModeMap) {
387
+ textureModeMap = new Map();
388
+ this._texturesCache.set(cacheKey, textureModeMap);
389
+ }
390
+
352
391
  /** @type {EnvironmentTextureSet | undefined} */
353
- let res = this._texturesCache.get(texture || null);
392
+ let res = textureModeMap.get(radianceMode);
354
393
  if (res) {
355
394
  return res;
356
395
  }
357
396
 
358
- if (this._scene && this._pmremGenerator && this._renderer && texture) {
397
+ const isPmremTexture = texture.mapping === CubeUVReflectionMapping || texture.isRenderTargetTexture === true;
398
+
399
+ if (this._scene && this._renderer && texture) {
359
400
  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();
401
+ let radianceRenderTarget;
402
+ let irradianceRenderTarget;
403
+
404
+ if (isPmremTexture) {
405
+ // Scene.environment is often already PMREM-processed (CubeUV layout).
406
+ // Running PMREMGenerator on it again corrupts the sampling layout.
407
+ radianceRenderTarget = radianceMode === "materialx-prefiltered"
408
+ ? renderPMREMToPrefilteredEquirect(this._renderer, texture)
409
+ : radianceMode === "materialx-fis"
410
+ ? renderPMREMToEquirect(this._renderer, texture, 0.0, 1024, 512)
411
+ : null;
412
+ irradianceRenderTarget = renderPMREMToEquirect(this._renderer, texture, 1.0, 32, 16);
413
+ } else {
414
+ const target = this._getPMREMGenerator().fromEquirectangular(texture);
415
+ radianceRenderTarget = radianceMode === "materialx-prefiltered"
416
+ ? renderPMREMToPrefilteredEquirect(this._renderer, target.texture, undefined, undefined, target.height)
417
+ : radianceMode === "three-pmrem"
418
+ ? target
419
+ : null;
420
+ irradianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 1.0, 32, 16, target.height);
421
+ if (radianceMode !== "three-pmrem") {
422
+ target.dispose();
423
+ }
424
+ }
425
+
364
426
  res = {
365
- radianceTexture: radianceRenderTarget.texture,
366
- irradianceTexture: irradianceRenderTarget.texture
427
+ radianceTexture: radianceRenderTarget?.texture ?? texture,
428
+ irradianceTexture: irradianceRenderTarget.texture,
429
+ dispose: () => {
430
+ radianceRenderTarget?.dispose();
431
+ irradianceRenderTarget?.dispose();
432
+ },
367
433
  }
368
434
  }
369
435
  else {
@@ -372,10 +438,21 @@ export class MaterialXEnvironment {
372
438
  irradianceTexture: null
373
439
  }
374
440
  }
375
- this._texturesCache.set(texture || null, res);
441
+ textureModeMap.set(radianceMode, res);
376
442
  return res;
377
443
  }
378
444
 
445
+ /**
446
+ * @returns {PMREMGenerator}
447
+ */
448
+ _getPMREMGenerator() {
449
+ if (!this._renderer) {
450
+ throw new Error("[MaterialX] Cannot create PMREMGenerator before renderer initialization.");
451
+ }
452
+ this._pmremGenerator ??= new PMREMGenerator(this._renderer);
453
+ return this._pmremGenerator;
454
+ }
455
+
379
456
  /**
380
457
  * @param {boolean} collectLights
381
458
  */
@@ -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;