@needle-tools/materialx 1.0.4-next.6620f9d → 1.0.4-next.90b66fb
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/index.ts +1 -1
- package/package.json +2 -3
- package/src/index.ts +4 -5
- package/src/loader/loader.needle.ts +10 -52
- package/src/loader/loader.three.ts +5 -11
- package/src/materialx.helper.ts +3 -3
- package/src/materialx.material.ts +61 -39
- package/src/materialx.ts +81 -83
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/materialx",
|
|
3
|
-
"version": "1.0.4-next.
|
|
3
|
+
"version": "1.0.4-next.90b66fb",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@needle-tools/engine": "4.x",
|
|
19
19
|
"@types/three": "0.169.0",
|
|
20
|
-
"three": "npm:@needle-tools/three@^0.169.5"
|
|
21
|
-
"vite": "^7.0.3"
|
|
20
|
+
"three": "npm:@needle-tools/three@^0.169.5"
|
|
22
21
|
},
|
|
23
22
|
"publishConfig": {
|
|
24
23
|
"access": "public",
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export { ready, getMaterialXEnvironment };
|
|
1
|
+
export { ready, type MaterialXContext } from "./materialx.js";
|
|
2
|
+
export { MaterialXMaterial } from "./materialx.material.js";
|
|
3
|
+
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
4
|
+
export { registerNeedleLoader } from "./loader/loader.needle.js";
|
|
@@ -1,46 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { Context, GLTF, addCustomExtensionPlugin, Component, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
|
|
3
|
+
import { Context, GLTF, addCustomExtensionPlugin, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
|
|
5
4
|
import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
6
5
|
import type { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
7
6
|
import { MaterialXLoader } from "./loader.three.js";
|
|
8
7
|
import { debug } from "../utils.js";
|
|
9
|
-
import { MaterialXEnvironment, state } from "../materialx.js";
|
|
10
|
-
import { MaterialXMaterial } from "../materialx.material.js";
|
|
11
|
-
|
|
12
|
-
//@dont-generate-component
|
|
13
|
-
export class MaterialXUniformUpdate extends Component {
|
|
14
|
-
|
|
15
|
-
onEnable(): void {
|
|
16
|
-
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
onDisable(): void {
|
|
20
|
-
this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
onBeforeRenderThree = () => {
|
|
24
|
-
// Update uniforms or perform any pre-render logic here
|
|
25
|
-
const gameObject = this.gameObject as any as Mesh;
|
|
26
|
-
const material = gameObject?.material;
|
|
27
|
-
|
|
28
|
-
const camera = this.context.mainCamera;
|
|
29
|
-
if (!camera) return;
|
|
30
|
-
|
|
31
|
-
const env = state.materialXEnvironment;
|
|
32
|
-
|
|
33
|
-
if (Array.isArray(material)) {
|
|
34
|
-
for (const entry of material) {
|
|
35
|
-
if (entry && entry instanceof MaterialXMaterial) {
|
|
36
|
-
entry.updateUniforms(this.context, env, this.gameObject, camera);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
} else if (material instanceof MaterialXMaterial) {
|
|
40
|
-
material.updateUniforms(this.context, env, this.gameObject, camera);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
8
|
|
|
45
9
|
export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
46
10
|
readonly name = "MaterialXLoaderPlugin";
|
|
@@ -53,7 +17,12 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
53
17
|
// Register the MaterialX loader extension
|
|
54
18
|
// Environment initialization is now handled in the MaterialXLoader constructor
|
|
55
19
|
loader.register(p => {
|
|
56
|
-
this.loader = new MaterialXLoader(p, url,
|
|
20
|
+
this.loader = new MaterialXLoader(p, url, {
|
|
21
|
+
getTime: () => context.time.time,
|
|
22
|
+
getFrame: ()=> context.time.frame,
|
|
23
|
+
getScene: () => context.scene,
|
|
24
|
+
getRenderer: () => context.renderer,
|
|
25
|
+
});
|
|
57
26
|
return this.loader;
|
|
58
27
|
});
|
|
59
28
|
};
|
|
@@ -65,25 +34,14 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
65
34
|
if (!this.loader?.materialX_root_data) {
|
|
66
35
|
return;
|
|
67
36
|
}
|
|
68
|
-
// Set up onBeforeRender callbacks for objects with MaterialX materials
|
|
69
|
-
// This ensures uniforms are updated properly during rendering
|
|
70
|
-
gltf.scene.traverse((child) => {
|
|
71
|
-
if ((child as any).isMesh) {
|
|
72
|
-
const mesh = child as Mesh;
|
|
73
|
-
const material = mesh.material as Material;
|
|
74
|
-
|
|
75
|
-
if (material instanceof MaterialXMaterial) {
|
|
76
|
-
if (debug) console.log("[MaterialX] Adding MaterialX uniform update component to:", child.name);
|
|
77
|
-
child.addComponent(MaterialXUniformUpdate);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
37
|
|
|
82
38
|
if (debug) console.log("[MaterialX] Loaded: ", this.loader);
|
|
39
|
+
|
|
40
|
+
|
|
83
41
|
};
|
|
84
42
|
|
|
85
43
|
onExport = (_exporter: GLTFExporter, _context: Context) => {
|
|
86
|
-
console.
|
|
44
|
+
console.warn("[MaterialX] Export is not supported");
|
|
87
45
|
// TODO: Add MaterialX export functionality if needed
|
|
88
46
|
};
|
|
89
47
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ready, MaterialXEnvironment, state } from "../materialx.js";
|
|
1
|
+
import { Material, MeshStandardMaterial, DoubleSide, FrontSide } from "three";
|
|
2
|
+
import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { ready, state, MaterialXContext } from "../materialx.js";
|
|
5
4
|
import { debug } from "../utils.js";
|
|
6
5
|
import { MaterialXMaterial } from "../materialx.material.js";
|
|
7
6
|
|
|
@@ -52,7 +51,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
52
51
|
* @param url The URL of the GLTF file
|
|
53
52
|
* @param context The context for the GLTF loading process
|
|
54
53
|
*/
|
|
55
|
-
constructor(private parser: GLTFParser, private url: string, private context:
|
|
54
|
+
constructor(private parser: GLTFParser, private url: string, private context: MaterialXContext) {
|
|
56
55
|
if (debug) console.log("MaterialXLoader created for parser");
|
|
57
56
|
// Start loading of MaterialX environment if the root extension exists
|
|
58
57
|
if (this.materialX_root_data) {
|
|
@@ -69,12 +68,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
69
68
|
return this._loadMaterialAsync(materialIndex);
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
afterRoot = async (_gltf: GLTF) => {
|
|
73
|
-
// Initialize MaterialX lighting system with scene data
|
|
74
|
-
const environment = state.materialXEnvironment;
|
|
75
|
-
environment.initialize(this.context);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
71
|
// Parse the MaterialX document once and cache it
|
|
79
72
|
private async _materialXDocumentReady(): Promise<any> {
|
|
80
73
|
if (this._documentReadyPromise) {
|
|
@@ -238,6 +231,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
238
231
|
shader,
|
|
239
232
|
transparent: isTransparent,
|
|
240
233
|
side: material_def.doubleSided ? DoubleSide : FrontSide,
|
|
234
|
+
context: this.context,
|
|
241
235
|
loaders: {
|
|
242
236
|
cacheKey: this.url,
|
|
243
237
|
getTexture: async url => {
|
package/src/materialx.helper.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
//
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getWorldDirection } from '@needle-tools/engine';
|
|
7
7
|
import * as THREE from 'three';
|
|
8
8
|
import { debug, debugUpdate } from './utils';
|
|
9
9
|
import { MaterialX } from './materialx.types';
|
|
@@ -216,7 +216,7 @@ function toThreeUniform(uniforms: any, type: string, value: any, name: string, l
|
|
|
216
216
|
const key = type + ':' + name;
|
|
217
217
|
if (!valueTypeWarningMap.has(key)) {
|
|
218
218
|
valueTypeWarningMap.set(key, true);
|
|
219
|
-
console.warn(
|
|
219
|
+
console.warn(`MaterialX: Unsupported uniform type: ${type} for uniform: ${name}`);
|
|
220
220
|
}
|
|
221
221
|
break;
|
|
222
222
|
}
|
|
@@ -469,7 +469,7 @@ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Lo
|
|
|
469
469
|
const value = variable.getValue()?.getData();
|
|
470
470
|
const name = variable.getVariable();
|
|
471
471
|
if (debug) console.log("Adding uniform", { path: variable.getPath(), name: name, value: value, type: variable.getType().getName() });
|
|
472
|
-
threeUniforms[name] = toThreeUniform(uniforms, variable.getType().getName(), value, name, loaders, searchPath)
|
|
472
|
+
threeUniforms[name] = toThreeUniform(uniforms, variable.getType().getName(), value, name, loaders, searchPath);
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BufferGeometry, Camera, FrontSide, GLSL3, Group, IUniform, MaterialParameters, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
2
2
|
import { debug } from "./utils.js";
|
|
3
|
-
import { MaterialXEnvironment } from "./materialx.js";
|
|
4
|
-
import {
|
|
5
|
-
import { Context } from "@needle-tools/engine";
|
|
3
|
+
import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
|
|
4
|
+
import { getUniformValues, Loaders } from "./materialx.helper.js";
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
// Add helper matrices for uniform updates (similar to MaterialX example)
|
|
@@ -17,19 +16,20 @@ declare type MaterialXMaterialInitParameters = {
|
|
|
17
16
|
loaders: Loaders,
|
|
18
17
|
transparent?: boolean,
|
|
19
18
|
side?: MaterialParameters['side'],
|
|
19
|
+
context: MaterialXContext,
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
|
|
23
|
+
|
|
22
24
|
export class MaterialXMaterial extends ShaderMaterial {
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// return this;
|
|
32
|
-
// }
|
|
26
|
+
copy(source: MaterialXMaterial): this {
|
|
27
|
+
super.copy(source);
|
|
28
|
+
this._context = source._context;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private _context: MaterialXContext | null = null;
|
|
33
33
|
|
|
34
34
|
constructor(init?: MaterialXMaterialInitParameters) {
|
|
35
35
|
|
|
@@ -114,6 +114,12 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
114
114
|
Object.assign(this.uniforms, {
|
|
115
115
|
...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
|
|
116
116
|
...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath),
|
|
117
|
+
|
|
118
|
+
u_worldMatrix: { value: new Matrix4() },
|
|
119
|
+
u_viewProjectionMatrix: { value: new Matrix4() },
|
|
120
|
+
u_viewPosition: { value: new Vector3() },
|
|
121
|
+
u_worldInverseTransposeMatrix: { value: new Matrix4() },
|
|
122
|
+
|
|
117
123
|
u_envMatrix: { value: new Matrix4() },
|
|
118
124
|
u_envRadiance: { value: null, type: 't' },
|
|
119
125
|
u_envRadianceMips: { value: 8, type: 'i' },
|
|
@@ -122,9 +128,11 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
122
128
|
u_envIrradiance: { value: null, type: 't' },
|
|
123
129
|
u_refractionEnv: { value: true },
|
|
124
130
|
u_numActiveLightSources: { value: 0 },
|
|
125
|
-
u_lightData: { value: [], needsUpdate: false }, // Array of light data
|
|
131
|
+
u_lightData: { value: [], needsUpdate: false }, // Array of light data. We need to set needsUpdate to false until we actually update it
|
|
126
132
|
});
|
|
127
133
|
|
|
134
|
+
this._context = init.context;
|
|
135
|
+
|
|
128
136
|
if (debug) {
|
|
129
137
|
// Get lighting and environment data from MaterialX environment
|
|
130
138
|
console.group("[MaterialX]: ", name);
|
|
@@ -132,15 +140,24 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
132
140
|
console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
|
|
133
141
|
console.groupEnd();
|
|
134
142
|
}
|
|
143
|
+
}
|
|
135
144
|
|
|
145
|
+
onBeforeRender(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, _geometry: BufferGeometry, object: Object3D, _group: Group): void {
|
|
146
|
+
if (this._context) {
|
|
147
|
+
const env = MaterialXEnvironment.get(this._context);
|
|
148
|
+
if (env) {
|
|
149
|
+
env.update(this._context.getFrame(), this._context.getScene());
|
|
150
|
+
this.updateUniforms(env, object, camera);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
136
153
|
}
|
|
137
154
|
|
|
138
155
|
|
|
139
156
|
envMapIntensity: number = 1.0; // Default intensity for environment map
|
|
140
157
|
envMap: Texture | null = null; // Environment map texture, can be set externally
|
|
141
|
-
updateUniforms = (
|
|
158
|
+
updateUniforms = (environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
|
|
142
159
|
|
|
143
|
-
const uniforms = this.uniforms;
|
|
160
|
+
const uniforms = this.uniforms as Uniforms;
|
|
144
161
|
|
|
145
162
|
// TODO remove. Not sure why this is needed, but without it
|
|
146
163
|
// we currently get some "swimming" where matrices are not up to date.
|
|
@@ -148,31 +165,33 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
148
165
|
|
|
149
166
|
// Update standard transformation matrices
|
|
150
167
|
if (uniforms.u_worldMatrix) {
|
|
151
|
-
if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
|
|
152
168
|
uniforms.u_worldMatrix.value = object.matrixWorld;
|
|
169
|
+
uniforms.u_worldMatrix.needsUpdate = true;
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
if (uniforms.u_viewProjectionMatrix) {
|
|
156
|
-
if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
|
|
157
173
|
uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
174
|
+
uniforms.u_viewProjectionMatrix.needsUpdate = true;
|
|
158
175
|
}
|
|
159
176
|
|
|
160
177
|
if (uniforms.u_viewPosition) {
|
|
161
|
-
if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
|
|
162
178
|
uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
|
|
179
|
+
uniforms.u_viewPosition.needsUpdate = true;
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
if (uniforms.u_worldInverseTransposeMatrix) {
|
|
166
|
-
if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
|
|
167
183
|
uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
|
|
184
|
+
uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
|
|
168
185
|
}
|
|
169
186
|
|
|
170
187
|
// Update time uniforms
|
|
171
|
-
if (
|
|
172
|
-
uniforms.u_time
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
uniforms.u_frame
|
|
188
|
+
if (this._context) {
|
|
189
|
+
if (uniforms.u_time) {
|
|
190
|
+
uniforms.u_time.value = this._context.getTime();
|
|
191
|
+
}
|
|
192
|
+
if (uniforms.u_frame) {
|
|
193
|
+
uniforms.u_frame.value = this._context.getFrame();
|
|
194
|
+
}
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
// Update light uniforms
|
|
@@ -183,37 +202,40 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
183
202
|
|
|
184
203
|
private updateEnvironmentUniforms = (environment: MaterialXEnvironment) => {
|
|
185
204
|
|
|
205
|
+
const uniforms = this.uniforms as Uniforms;
|
|
206
|
+
|
|
186
207
|
// Get lighting data from environment
|
|
187
208
|
const lightData = environment.lightData || null;
|
|
188
209
|
const lightCount = environment.lightCount || 0;
|
|
189
210
|
const textures = environment.getTextures(this) || null;
|
|
190
211
|
|
|
191
212
|
// Update light count
|
|
192
|
-
if (
|
|
193
|
-
|
|
213
|
+
if (uniforms.u_numActiveLightSources && lightCount >= 0) {
|
|
214
|
+
uniforms.u_numActiveLightSources.value = lightCount;
|
|
194
215
|
}
|
|
195
216
|
|
|
196
217
|
// Update light data
|
|
197
218
|
if (lightData?.length) {
|
|
198
|
-
|
|
199
|
-
if ("needsUpdate" in
|
|
219
|
+
uniforms.u_lightData.value = lightData;
|
|
220
|
+
if ("needsUpdate" in uniforms.u_lightData && uniforms.u_lightData.needsUpdate === false) {
|
|
200
221
|
if (debug) console.debug(`[MaterialX] LightData assigned (${this.name}, ${this.uuid})`, lightData);
|
|
201
|
-
|
|
222
|
+
uniforms.u_lightData.needsUpdate = undefined;
|
|
202
223
|
}
|
|
203
224
|
}
|
|
204
225
|
|
|
205
226
|
// Update environment uniforms
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.uniforms.u_envRadiance.value = textures.radianceTexture;
|
|
227
|
+
if (uniforms.u_envRadiance) {
|
|
228
|
+
const prev = uniforms.u_envRadiance.value;
|
|
229
|
+
uniforms.u_envRadiance.value = textures.radianceTexture;
|
|
230
|
+
if (prev != textures.radianceTexture) uniforms.u_envRadiance.needsUpdate = true;
|
|
211
231
|
}
|
|
212
|
-
if (
|
|
213
|
-
|
|
232
|
+
if (uniforms.u_envRadianceMips) {
|
|
233
|
+
uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(textures.radianceTexture?.source.data.width ?? 0, textures.radianceTexture?.source.data.height ?? 0))) + 1;
|
|
214
234
|
}
|
|
215
|
-
if (
|
|
216
|
-
|
|
235
|
+
if (uniforms.u_envIrradiance) {
|
|
236
|
+
const prev = uniforms.u_envIrradiance.value;
|
|
237
|
+
uniforms.u_envIrradiance.value = textures.irradianceTexture;
|
|
238
|
+
if (prev != textures.irradianceTexture) uniforms.u_envIrradiance.needsUpdate = true;
|
|
217
239
|
}
|
|
218
240
|
|
|
219
241
|
this.uniformsNeedUpdate = true;
|
package/src/materialx.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { Context, delay, isDevEnvironment, ObjectUtils, GameObject, onBeforeRender } from "@needle-tools/engine";
|
|
2
1
|
import type { MaterialX as MX } from "./materialx.types.js";
|
|
3
2
|
import MaterialX from "../bin/JsMaterialXGenShader.js";
|
|
4
3
|
import { debug } from "./utils.js";
|
|
5
4
|
import { renderPMREMToEquirect } from "./textureHelper.js";
|
|
6
|
-
import { Light,
|
|
5
|
+
import { Light, Object3D, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
|
|
7
6
|
import { registerLights, getLightData, LightData } from "./materialx.helper.js";
|
|
8
7
|
import type { MaterialXMaterial } from "./materialx.material.js";
|
|
9
8
|
|
|
9
|
+
export type MaterialXContext = {
|
|
10
|
+
getTime(): number,
|
|
11
|
+
getFrame(): number,
|
|
12
|
+
getScene(): Scene,
|
|
13
|
+
getRenderer(): WebGLRenderer,
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
export const state = new class {
|
|
12
18
|
materialXModule: MX.MODULE | null = null;
|
|
@@ -14,16 +20,12 @@ export const state = new class {
|
|
|
14
20
|
materialXGenContext: any = null;
|
|
15
21
|
materialXStdLib: any = null;
|
|
16
22
|
materialXInitPromise: Promise<void> | null = null;
|
|
17
|
-
|
|
18
|
-
// Global MaterialX environment instance
|
|
19
|
-
private _materialXEnvironment: MaterialXEnvironment | null = null;
|
|
20
|
-
get materialXEnvironment() {
|
|
21
|
-
this._materialXEnvironment ??= new MaterialXEnvironment();
|
|
22
|
-
return this._materialXEnvironment;
|
|
23
|
-
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Wait for the MaterialX WASM module to be ready.
|
|
28
|
+
*/
|
|
27
29
|
export async function ready(): Promise<void> {
|
|
28
30
|
if (state.materialXInitPromise) {
|
|
29
31
|
return state.materialXInitPromise;
|
|
@@ -111,93 +113,72 @@ type EnvironmentTextureSet = {
|
|
|
111
113
|
irradianceTexture: Texture | null;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
type EnvironmentContext = Pick<MaterialXContext, "getRenderer" | "getScene">;
|
|
119
|
+
/**
|
|
120
|
+
* MaterialXEnvironment manages the environment settings for MaterialX materials.
|
|
121
|
+
*/
|
|
115
122
|
export class MaterialXEnvironment {
|
|
116
|
-
|
|
123
|
+
|
|
124
|
+
static get(context: EnvironmentContext): MaterialXEnvironment | null {
|
|
125
|
+
return this.getEnvironment(context);
|
|
126
|
+
}
|
|
127
|
+
private static _environments: WeakMap<EnvironmentContext, MaterialXEnvironment> = new Map();
|
|
128
|
+
private static getEnvironment(context: EnvironmentContext): MaterialXEnvironment {
|
|
129
|
+
if (this._environments.has(context)) {
|
|
130
|
+
return this._environments.get(context)!;
|
|
131
|
+
}
|
|
132
|
+
const env = new MaterialXEnvironment(context);
|
|
133
|
+
this._environments.set(context, env);
|
|
134
|
+
return env;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
117
138
|
private _lights: Array<Light> = [];
|
|
118
139
|
private _lightData: null | LightData[] = null;
|
|
119
140
|
private _lightCount: number = 0;
|
|
120
|
-
private _initializePromise: Promise<boolean> | null = null;
|
|
121
141
|
|
|
122
|
-
private
|
|
142
|
+
private _initializePromise: Promise<boolean> | null = null;
|
|
143
|
+
private _isInitialized: boolean = false;
|
|
144
|
+
private _lastUpdateFrame: number = -1;
|
|
123
145
|
|
|
124
|
-
constructor() {
|
|
146
|
+
constructor(private _context: EnvironmentContext) {
|
|
125
147
|
if (debug) console.log("[MaterialX] Environment created");
|
|
126
148
|
}
|
|
127
149
|
|
|
128
150
|
// Initialize with Needle Engine context
|
|
129
|
-
async initialize(
|
|
151
|
+
async initialize(): Promise<boolean> {
|
|
130
152
|
if (this._initializePromise) {
|
|
131
153
|
return this._initializePromise;
|
|
132
154
|
}
|
|
133
|
-
|
|
155
|
+
this._initializePromise = this._initialize();
|
|
156
|
+
return this._initializePromise;
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// }
|
|
141
|
-
return this._lightData;
|
|
142
|
-
}
|
|
143
|
-
get lightCount() { return this._lightCount || 0; }
|
|
144
|
-
getTextures(material: MaterialXMaterial) {
|
|
145
|
-
if (material.envMap) {
|
|
146
|
-
// If the material has its own envMap, we don't use the irradiance texture
|
|
147
|
-
return this._getTextures(material.envMap);
|
|
159
|
+
update(frame: number, scene: Scene) {
|
|
160
|
+
if (!this._initializePromise) {
|
|
161
|
+
this.initialize();
|
|
162
|
+
return;
|
|
148
163
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
private _pmremGenerator: PMREMGenerator | null = null;
|
|
153
|
-
private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
|
|
154
|
-
|
|
155
|
-
private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
|
|
156
|
-
|
|
157
|
-
this._context = context;
|
|
158
|
-
this._pmremGenerator = new PMREMGenerator(context.renderer);
|
|
159
|
-
|
|
160
|
-
this._unsubscribehook?.();
|
|
161
|
-
this._unsubscribehook = onBeforeRender(() => {
|
|
162
|
-
this.updateLighting(false);
|
|
163
|
-
this._getTextures(context.scene.environment);
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
// TODO remove this delay; we should wait for the scene lighting to be ready
|
|
167
|
-
// and then update the uniforms
|
|
168
|
-
while (!context.scene.environment) {
|
|
169
|
-
await delay(5);
|
|
164
|
+
if (!this._isInitialized) {
|
|
165
|
+
return;
|
|
170
166
|
}
|
|
171
|
-
this.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// const radianceMat = unlitMat.clone();
|
|
179
|
-
// radianceMat.map = this._radianceTexture;
|
|
180
|
-
// const radianceCube = ObjectUtils.createPrimitive("Quad", { material: radianceMat });
|
|
181
|
-
// const irradianceMat = unlitMat.clone();
|
|
182
|
-
// irradianceMat.map = this._irradianceTexture;
|
|
183
|
-
// const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
|
|
184
|
-
// context.scene.add(radianceCube);
|
|
185
|
-
// context.scene.add(irradianceCube);
|
|
186
|
-
// radianceCube.position.set(2, 0, 0);
|
|
187
|
-
// irradianceCube.position.set(-2, 0, 0);
|
|
188
|
-
// console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
|
|
189
|
-
// }
|
|
190
|
-
|
|
191
|
-
this.updateLighting(true);
|
|
192
|
-
|
|
193
|
-
// Mark as initialized
|
|
194
|
-
return true;
|
|
167
|
+
if (this._lastUpdateFrame === frame) {
|
|
168
|
+
// Already updated this frame
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this._lastUpdateFrame = frame;
|
|
172
|
+
this.updateLighting(false);
|
|
173
|
+
this._getTextures(scene.environment);
|
|
195
174
|
}
|
|
196
175
|
|
|
197
176
|
// Reset the environment to allow re-initialization
|
|
198
177
|
reset() {
|
|
199
178
|
if (debug) console.log("[MaterialX] Resetting environment");
|
|
200
179
|
this._initializePromise = null;
|
|
180
|
+
this._isInitialized = false;
|
|
181
|
+
this._lastUpdateFrame = -1;
|
|
201
182
|
this._lights = [];
|
|
202
183
|
this._lightData = null;
|
|
203
184
|
this._lightCount = 0;
|
|
@@ -208,16 +189,35 @@ export class MaterialXEnvironment {
|
|
|
208
189
|
textureSet.irradianceTexture?.dispose();
|
|
209
190
|
}
|
|
210
191
|
this._texturesCache.clear();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get lightData() {
|
|
195
|
+
return this._lightData;
|
|
196
|
+
}
|
|
197
|
+
get lightCount() { return this._lightCount || 0; }
|
|
198
|
+
getTextures(material: MaterialXMaterial) {
|
|
199
|
+
if (material.envMap) {
|
|
200
|
+
// If the material has its own envMap, we don't use the irradiance texture
|
|
201
|
+
return this._getTextures(material.envMap);
|
|
202
|
+
}
|
|
203
|
+
return this._getTextures(this._context?.getScene().environment);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private _pmremGenerator: PMREMGenerator | null = null;
|
|
207
|
+
private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
|
|
211
208
|
|
|
212
|
-
|
|
213
|
-
this.
|
|
209
|
+
private async _initialize(): Promise<boolean> {
|
|
210
|
+
this._isInitialized = false;
|
|
211
|
+
this._pmremGenerator = new PMREMGenerator(this._context.getRenderer());
|
|
212
|
+
this.updateLighting(true);
|
|
213
|
+
this._isInitialized = true;
|
|
214
|
+
return true;
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
private _getTextures(texture: Texture | null | undefined): {
|
|
217
218
|
radianceTexture: Texture | null,
|
|
218
219
|
irradianceTexture: Texture | null
|
|
219
220
|
} {
|
|
220
|
-
|
|
221
221
|
let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
|
|
222
222
|
if (res) {
|
|
223
223
|
return res;
|
|
@@ -225,10 +225,9 @@ export class MaterialXEnvironment {
|
|
|
225
225
|
|
|
226
226
|
if (this._context && this._pmremGenerator && texture) {
|
|
227
227
|
if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
|
|
228
|
-
|
|
229
228
|
const target = this._pmremGenerator.fromEquirectangular(texture);
|
|
230
|
-
const radianceRenderTarget = renderPMREMToEquirect(this._context.
|
|
231
|
-
const irradianceRenderTarget = renderPMREMToEquirect(this._context.
|
|
229
|
+
const radianceRenderTarget = renderPMREMToEquirect(this._context.getRenderer(), target.texture, 0.0, 1024, 512, target.height);
|
|
230
|
+
const irradianceRenderTarget = renderPMREMToEquirect(this._context.getRenderer(), target.texture, 1.0, 32, 16, target.height);
|
|
232
231
|
target.dispose();
|
|
233
232
|
res = {
|
|
234
233
|
radianceTexture: radianceRenderTarget.texture,
|
|
@@ -247,12 +246,11 @@ export class MaterialXEnvironment {
|
|
|
247
246
|
|
|
248
247
|
private updateLighting = (collectLights: boolean = false) => {
|
|
249
248
|
if (!this._context) return;
|
|
250
|
-
|
|
251
249
|
// Find lights in scene
|
|
252
250
|
if (collectLights) {
|
|
253
251
|
const lights = new Array<Light>();
|
|
254
|
-
this._context.
|
|
255
|
-
if ((object as Light).isLight &&
|
|
252
|
+
this._context.getScene().traverse((object: Object3D) => {
|
|
253
|
+
if ((object as Light).isLight && object.visible)
|
|
256
254
|
lights.push(object as Light);
|
|
257
255
|
});
|
|
258
256
|
this._lights = lights;
|