@needle-tools/materialx 1.0.1-next.19d0723 → 1.0.1-next.2ca9014
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/README.md +1 -3
- package/codegen/register_types.ts +0 -4
- package/index.ts +1 -1
- package/package.json +2 -6
- package/src/{materialx.helper.ts → helper.js} +205 -144
- package/src/index.ts +2 -2
- package/src/loader/loader.needle.ts +40 -27
- package/src/loader/loader.three.ts +409 -153
- package/src/materialx.ts +114 -143
- package/src/textureHelper.ts +6 -6
- package/src/utils.ts +4 -39
- package/src/materialx.material.ts +0 -220
- package/src/materialx.types.d.ts +0 -50
package/src/materialx.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { Context, delay, isDevEnvironment, ObjectUtils, GameObject
|
|
2
|
-
import type { MaterialX as MX } from "./materialx.types.js";
|
|
1
|
+
import { Context, delay, isDevEnvironment, ObjectUtils, GameObject } from "@needle-tools/engine";
|
|
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,
|
|
7
|
-
import { registerLights
|
|
8
|
-
import type { MaterialXMaterial } from "./materialx.material.js";
|
|
5
|
+
import { Light, MeshBasicMaterial, Object3D, PMREMGenerator } from "three";
|
|
6
|
+
import { registerLights } from "./helper.js";
|
|
9
7
|
|
|
10
8
|
|
|
9
|
+
// Global MaterialX module instance - initialized lazily
|
|
11
10
|
export const state = new class {
|
|
12
|
-
materialXModule:
|
|
11
|
+
materialXModule: any = null;
|
|
13
12
|
materialXGenerator: any = null;
|
|
14
13
|
materialXGenContext: any = null;
|
|
15
14
|
materialXStdLib: any = null;
|
|
@@ -23,14 +22,14 @@ export const state = new class {
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
export async function
|
|
25
|
+
// Initialize MaterialX WASM module lazily
|
|
26
|
+
export async function initializeMaterialX(): Promise<void> {
|
|
28
27
|
if (state.materialXInitPromise) {
|
|
29
28
|
return state.materialXInitPromise;
|
|
30
29
|
}
|
|
31
30
|
return state.materialXInitPromise = (async () => {
|
|
32
31
|
if (state.materialXModule) return; // Already initialized
|
|
33
|
-
if (debug) console.log("
|
|
32
|
+
if (debug) console.log("Initializing MaterialX WASM module...");
|
|
34
33
|
try {
|
|
35
34
|
|
|
36
35
|
const urls: Array<string> = await Promise.all([
|
|
@@ -45,7 +44,7 @@ export async function ready(): Promise<void> {
|
|
|
45
44
|
|
|
46
45
|
const module = await MaterialX({
|
|
47
46
|
locateFile: (path: string, scriptDirectory: string) => {
|
|
48
|
-
if (debug) console.debug("
|
|
47
|
+
if (debug) console.debug("MaterialX locateFile called:", { path, scriptDirectory });
|
|
49
48
|
|
|
50
49
|
if (path.includes("JsMaterialXCore.wasm")) {
|
|
51
50
|
return JsMaterialXCore; // Use the URL for the core WASM file
|
|
@@ -60,8 +59,8 @@ export async function ready(): Promise<void> {
|
|
|
60
59
|
return scriptDirectory + path;
|
|
61
60
|
},
|
|
62
61
|
});
|
|
63
|
-
if (debug) console.log("
|
|
64
|
-
state.materialXModule = module
|
|
62
|
+
if (debug) console.log("MaterialXLoader module loaded", module);
|
|
63
|
+
state.materialXModule = module;
|
|
65
64
|
|
|
66
65
|
// Initialize shader generator and context
|
|
67
66
|
state.materialXGenerator = module.EsslShaderGenerator.create();
|
|
@@ -73,13 +72,14 @@ export async function ready(): Promise<void> {
|
|
|
73
72
|
tempDoc.setDataLibrary(state.materialXStdLib);
|
|
74
73
|
|
|
75
74
|
// TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
|
|
75
|
+
const options = state.materialXGenContext.getOptions();
|
|
76
76
|
state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
|
|
79
79
|
// SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
|
|
80
80
|
// SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
|
|
81
81
|
state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
// TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
|
|
84
84
|
// TRANSMISSION_OPACITY: Use opacity for transmission rendering.
|
|
85
85
|
// state.materialXGenContext.getOptions().hwTransmissionRenderMethod = state.materialXModule.HwTransmissionRenderMethod.TRANSMISSION_REFRACTION;
|
|
@@ -96,166 +96,137 @@ export async function ready(): Promise<void> {
|
|
|
96
96
|
state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
|
|
97
97
|
|
|
98
98
|
// This prewarms the shader generation context to have all light types
|
|
99
|
-
await registerLights(state.materialXModule, state.materialXGenContext);
|
|
99
|
+
await registerLights(state.materialXModule, [], state.materialXGenContext);
|
|
100
100
|
|
|
101
|
-
if (debug) console.log("
|
|
101
|
+
if (debug) console.log("MaterialX generator initialized successfully");
|
|
102
102
|
} catch (error) {
|
|
103
|
-
console.error("
|
|
103
|
+
console.error("Failed to load MaterialX module:", error);
|
|
104
104
|
throw error;
|
|
105
105
|
}
|
|
106
106
|
})();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
type EnvironmentTextureSet = {
|
|
110
|
-
radianceTexture: Texture | null;
|
|
111
|
-
irradianceTexture: Texture | null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
109
|
// MaterialX Environment Manager - handles lighting and environment setup
|
|
115
110
|
export class MaterialXEnvironment {
|
|
116
|
-
private
|
|
117
|
-
private
|
|
118
|
-
private
|
|
119
|
-
private
|
|
120
|
-
private
|
|
121
|
-
|
|
122
|
-
private _unsubscribehook: (() => void) | null = null;
|
|
111
|
+
private lightData: any = null;
|
|
112
|
+
private lightCount: number = 0;
|
|
113
|
+
private radianceTexture: any = null;
|
|
114
|
+
private irradianceTexture: any = null;
|
|
115
|
+
private context: Context | null = null;
|
|
116
|
+
private initialized: boolean = false;
|
|
123
117
|
|
|
124
118
|
constructor() {
|
|
125
|
-
if (debug) console.log("
|
|
119
|
+
if (debug) console.log("MaterialX Environment created");
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (this._initializePromise) {
|
|
131
|
-
return this._initializePromise;
|
|
132
|
-
}
|
|
133
|
-
return this._initializePromise = this._initialize(context);
|
|
122
|
+
setContext(context: Context) {
|
|
123
|
+
this.context = context;
|
|
134
124
|
}
|
|
135
125
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return this._getTextures(material.envMap);
|
|
126
|
+
// Initialize with Needle Engine context
|
|
127
|
+
async initializeFromContext(): Promise<void> {
|
|
128
|
+
if (!this.context) {
|
|
129
|
+
console.warn("No Needle context available for MaterialX environment initialization");
|
|
130
|
+
return;
|
|
142
131
|
}
|
|
143
|
-
return this._getTextures(this._context?.scene.environment);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private _pmremGenerator: PMREMGenerator | null = null;
|
|
147
|
-
private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
|
|
148
132
|
|
|
149
|
-
|
|
133
|
+
// Prevent multiple initializations
|
|
134
|
+
if (this.initialized) {
|
|
135
|
+
if (debug) console.log("MaterialX environment already initialized, skipping");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
150
138
|
|
|
151
|
-
|
|
152
|
-
|
|
139
|
+
// Clean up previous textures if they exist
|
|
140
|
+
if (this.radianceTexture) {
|
|
141
|
+
if (debug) console.log("Disposing previous radiance texture");
|
|
142
|
+
this.radianceTexture.dispose();
|
|
143
|
+
this.radianceTexture = null;
|
|
144
|
+
}
|
|
145
|
+
if (this.irradianceTexture) {
|
|
146
|
+
if (debug) console.log("Disposing previous irradiance texture");
|
|
147
|
+
this.irradianceTexture.dispose();
|
|
148
|
+
this.irradianceTexture = null;
|
|
149
|
+
}
|
|
153
150
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.updateLighting(false);
|
|
157
|
-
this._getTextures(context.scene.environment);
|
|
158
|
-
})
|
|
151
|
+
// Get renderer from context
|
|
152
|
+
const renderer = this.context.renderer;
|
|
159
153
|
|
|
160
154
|
// TODO remove this delay; we should wait for the scene lighting to be ready
|
|
161
155
|
// and then update the uniforms
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
let envMap = this.context.scene.environment;
|
|
157
|
+
while (!envMap) {
|
|
158
|
+
await delay(200);
|
|
159
|
+
envMap = this.context.scene.environment;
|
|
164
160
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this._lightData = null;
|
|
197
|
-
this._lightCount = 0;
|
|
198
|
-
this._pmremGenerator?.dispose();
|
|
199
|
-
this._pmremGenerator = null;
|
|
200
|
-
for(const textureSet of this._texturesCache.values()) {
|
|
201
|
-
textureSet.radianceTexture?.dispose();
|
|
202
|
-
textureSet.irradianceTexture?.dispose();
|
|
161
|
+
var pmrem = new PMREMGenerator(renderer);
|
|
162
|
+
const target = pmrem.fromEquirectangular(envMap);
|
|
163
|
+
|
|
164
|
+
const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
|
|
165
|
+
const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
|
|
166
|
+
|
|
167
|
+
this.radianceTexture = radianceRenderTarget.texture;
|
|
168
|
+
this.irradianceTexture = irradianceRenderTarget.texture;
|
|
169
|
+
|
|
170
|
+
// Clean up PMREM generator and its render target
|
|
171
|
+
target.dispose();
|
|
172
|
+
pmrem.dispose();
|
|
173
|
+
|
|
174
|
+
if (debug) {
|
|
175
|
+
console.log({ radiance: this.radianceTexture, irradiance: this.irradianceTexture });
|
|
176
|
+
// Show both of them on cubes in the scene
|
|
177
|
+
const unlitMat = new MeshBasicMaterial();
|
|
178
|
+
const radianceMat = unlitMat.clone();
|
|
179
|
+
radianceMat.map = this.radianceTexture;
|
|
180
|
+
const radianceCube = ObjectUtils.createPrimitive("Cube", { material: radianceMat });
|
|
181
|
+
const irradianceMat = unlitMat.clone();
|
|
182
|
+
irradianceMat.map = this.irradianceTexture;
|
|
183
|
+
const irradianceCube = ObjectUtils.createPrimitive("Cube", { material: irradianceMat });
|
|
184
|
+
this.context.scene.add(radianceCube);
|
|
185
|
+
this.context.scene.add(irradianceCube);
|
|
186
|
+
radianceCube.position.set(2, 0, 0);
|
|
187
|
+
radianceCube.scale.y = 0.00001;
|
|
188
|
+
irradianceCube.position.set(-2, 0, 0);
|
|
189
|
+
irradianceCube.scale.y = 0.00001;
|
|
190
|
+
// await this.initializeLighting(defaultLightRigXml, renderer);
|
|
191
|
+
console.log("MaterialX environment initialized from Needle context", this, this.context.scene);
|
|
203
192
|
}
|
|
204
|
-
this._texturesCache.clear();
|
|
205
|
-
|
|
206
|
-
this._unsubscribehook?.();
|
|
207
|
-
this._unsubscribehook = null;
|
|
208
|
-
}
|
|
209
193
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
194
|
+
// Find lights in scene
|
|
195
|
+
let lights = new Array<Light>();
|
|
196
|
+
this.context.scene.traverse((object: Object3D) => {
|
|
197
|
+
if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
|
|
198
|
+
lights.push(object as Light);
|
|
199
|
+
});
|
|
214
200
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
201
|
+
const { lightData, lightCount } = await registerLights(state.materialXModule, lights, state.materialXGenContext);
|
|
202
|
+
this.lightData = lightData;
|
|
203
|
+
this.lightCount = lightCount;
|
|
219
204
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const target = this._pmremGenerator.fromEquirectangular(texture);
|
|
224
|
-
const radianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 0.0, 1024, 512, target.height);
|
|
225
|
-
const irradianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 1.0, 32, 16, target.height);
|
|
226
|
-
target.dispose();
|
|
227
|
-
res = {
|
|
228
|
-
radianceTexture: radianceRenderTarget.texture,
|
|
229
|
-
irradianceTexture: irradianceRenderTarget.texture
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
res = {
|
|
234
|
-
radianceTexture: null,
|
|
235
|
-
irradianceTexture: null
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
this._texturesCache.set(texture || null, res);
|
|
239
|
-
return res;
|
|
205
|
+
// Mark as initialized
|
|
206
|
+
this.initialized = true;
|
|
240
207
|
}
|
|
241
208
|
|
|
242
|
-
|
|
243
|
-
|
|
209
|
+
getLightData() { return this.lightData; }
|
|
210
|
+
getLightCount() { return this.lightCount; }
|
|
211
|
+
getRadianceTexture() { return this.radianceTexture; }
|
|
212
|
+
getIrradianceTexture() { return this.irradianceTexture; }
|
|
244
213
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const lights = new Array<Light>();
|
|
248
|
-
this._context.scene.traverse((object: Object3D) => {
|
|
249
|
-
if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
|
|
250
|
-
lights.push(object as Light);
|
|
251
|
-
});
|
|
252
|
-
this._lights = lights;
|
|
253
|
-
}
|
|
214
|
+
setRadianceTexture(texture: any) { this.radianceTexture = texture; }
|
|
215
|
+
setIrradianceTexture(texture: any) { this.irradianceTexture = texture; }
|
|
254
216
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
217
|
+
// Reset the environment to allow re-initialization
|
|
218
|
+
reset() {
|
|
219
|
+
if (debug) console.log("Resetting MaterialX environment");
|
|
220
|
+
if (this.radianceTexture) {
|
|
221
|
+
this.radianceTexture.dispose();
|
|
222
|
+
this.radianceTexture = null;
|
|
223
|
+
}
|
|
224
|
+
if (this.irradianceTexture) {
|
|
225
|
+
this.irradianceTexture.dispose();
|
|
226
|
+
this.irradianceTexture = null;
|
|
259
227
|
}
|
|
228
|
+
this.initialized = false;
|
|
229
|
+
// this.lights = [];
|
|
230
|
+
this.lightData = null;
|
|
260
231
|
}
|
|
261
232
|
}
|
package/src/textureHelper.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
|
|
|
40
40
|
} else {
|
|
41
41
|
imageHeight = 256; // Final fallback
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
const maxMip = Math.log2(imageHeight) - 2;
|
|
45
45
|
const cubeUVHeight = imageHeight;
|
|
46
46
|
const cubeUVWidth = 3 * Math.max(Math.pow(2, maxMip), 7 * 16);
|
|
@@ -129,14 +129,14 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
|
|
|
129
129
|
const currentAutoClear = renderer.autoClear;
|
|
130
130
|
const currentXrEnabled = renderer.xr.enabled;
|
|
131
131
|
const currentShadowMapEnabled = renderer.shadowMap.enabled;
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
renderTarget.texture.generateMipmaps = true;
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
try {
|
|
136
136
|
// Disable XR and shadow mapping during our render to avoid interference
|
|
137
137
|
renderer.xr.enabled = false;
|
|
138
138
|
renderer.shadowMap.enabled = false;
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
// Render to our target
|
|
141
141
|
renderer.autoClear = true;
|
|
142
142
|
renderer.setRenderTarget(renderTarget);
|
|
@@ -148,7 +148,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
|
|
|
148
148
|
renderer.autoClear = currentAutoClear;
|
|
149
149
|
renderer.xr.enabled = currentXrEnabled;
|
|
150
150
|
renderer.shadowMap.enabled = currentShadowMapEnabled;
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
// Clean up temporary objects
|
|
153
153
|
geometry.dispose();
|
|
154
154
|
material.dispose();
|
|
@@ -159,7 +159,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
|
|
|
159
159
|
renderTarget.texture.mapping = EquirectangularReflectionMapping;
|
|
160
160
|
|
|
161
161
|
// Log mipmap infos
|
|
162
|
-
if (debug) console.log('
|
|
162
|
+
if (debug) console.log('PMREM to Equirect Render Target:', {
|
|
163
163
|
width: renderTarget.width,
|
|
164
164
|
height: renderTarget.height,
|
|
165
165
|
mipmaps: renderTarget.texture.mipmaps?.length,
|
package/src/utils.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { Context, getParam } from "@needle-tools/engine";
|
|
|
2
2
|
import { Mesh } from "three";
|
|
3
3
|
|
|
4
4
|
export const debug = getParam("debugmaterialx");
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* =====================================
|
|
@@ -30,44 +33,6 @@ const patchWebGL2 = () => {
|
|
|
30
33
|
}
|
|
31
34
|
return uniform4fv.call(this, location, v);
|
|
32
35
|
};
|
|
33
|
-
|
|
34
|
-
const uniform3fv = WebGL2RenderingContext.prototype.uniform3fv;
|
|
35
|
-
WebGL2RenderingContext.prototype.uniform3fv = function (location: WebGLUniformLocation | null, v: Float32Array | number[]) {
|
|
36
|
-
if (location) {
|
|
37
|
-
const uniformName = programAndNameToUniformLocation.get(location);
|
|
38
|
-
if (true) console.log("Calling uniform3fv", { location, v, name: uniformName?.name });
|
|
39
|
-
}
|
|
40
|
-
return uniform3fv.call(this, location, v);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const uniform3iv = WebGL2RenderingContext.prototype.uniform3iv;
|
|
44
|
-
WebGL2RenderingContext.prototype.uniform3iv = function (location: WebGLUniformLocation | null, v: Int32Array | number[]) {
|
|
45
|
-
if (location) {
|
|
46
|
-
const uniformName = programAndNameToUniformLocation.get(location);
|
|
47
|
-
if (true) console.log("Calling uniform3iv", { location, v, name: uniformName?.name });
|
|
48
|
-
}
|
|
49
|
-
return uniform3iv.call(this, location, v);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const uniform3uiv = WebGL2RenderingContext.prototype.uniform3uiv;
|
|
53
|
-
WebGL2RenderingContext.prototype.uniform3uiv = function (location: WebGLUniformLocation | null, v: Uint32Array | number[]) {
|
|
54
|
-
if (location) {
|
|
55
|
-
const uniformName = programAndNameToUniformLocation.get(location);
|
|
56
|
-
if (true) console.log("Calling uniform3uiv", { location, v, name: uniformName?.name });
|
|
57
|
-
}
|
|
58
|
-
return uniform3uiv.call(this, location, v);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const uniform3f = WebGL2RenderingContext.prototype.uniform3f;
|
|
62
|
-
WebGL2RenderingContext.prototype.uniform3f = function (location: WebGLUniformLocation
|
|
63
|
-
| null, x: number, y: number, z: number) {
|
|
64
|
-
if (location) {
|
|
65
|
-
const uniformName = programAndNameToUniformLocation.get(location);
|
|
66
|
-
if (uniformName?.name !== "diffuse")
|
|
67
|
-
if (true) console.log("Calling uniform3f", { location, x, y, z, name: uniformName?.name });
|
|
68
|
-
}
|
|
69
|
-
return uniform3f.call(this, location, x, y, z);
|
|
70
|
-
};
|
|
71
36
|
};
|
|
72
37
|
// patchWebGL2();
|
|
73
38
|
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { Camera, DoubleSide, FrontSide, GLSL3, Matrix3, Matrix4, Mesh, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
|
|
2
|
-
import { debug } from "./utils.js";
|
|
3
|
-
import { MaterialXEnvironment, state } from "./materialx.js";
|
|
4
|
-
import { getUniformValues, Loaders } from "./materialx.helper.js";
|
|
5
|
-
import { Context } from "@needle-tools/engine";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// Add helper matrices for uniform updates (similar to MaterialX example)
|
|
9
|
-
const identityMatrix = new Matrix4();
|
|
10
|
-
const normalMat = new Matrix3();
|
|
11
|
-
const viewProjMat = new Matrix4();
|
|
12
|
-
const worldViewPos = new Vector3();
|
|
13
|
-
|
|
14
|
-
declare type MaterialXMaterialInitParameters = {
|
|
15
|
-
name: string,
|
|
16
|
-
shader: any,
|
|
17
|
-
loaders: Loaders,
|
|
18
|
-
transparent?: boolean,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class MaterialXMaterial extends ShaderMaterial {
|
|
22
|
-
|
|
23
|
-
// copy(source: MaterialXMaterial): this {
|
|
24
|
-
// super.copy(source);
|
|
25
|
-
// this.name = source.name;
|
|
26
|
-
// this.uniforms = { ...source.uniforms }; // Shallow copy of uniforms
|
|
27
|
-
// this.envMapIntensity = source.envMapIntensity;
|
|
28
|
-
// this.envMap = source.envMap;
|
|
29
|
-
// this.updateUniforms = source.updateUniforms; // Copy the update function
|
|
30
|
-
// return this;
|
|
31
|
-
// }
|
|
32
|
-
|
|
33
|
-
constructor(init?: MaterialXMaterialInitParameters) {
|
|
34
|
-
|
|
35
|
-
// TODO: we need to properly copy the uniforms and other properties from the source material
|
|
36
|
-
if (!init) {
|
|
37
|
-
super();
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Get vertex and fragment shader source, and remove #version directive for newer js.
|
|
42
|
-
// It's added by three.js glslVersion.
|
|
43
|
-
let vertexShader = init.shader.getSourceCode("vertex");
|
|
44
|
-
let fragmentShader = init.shader.getSourceCode("pixel");
|
|
45
|
-
|
|
46
|
-
vertexShader = vertexShader.replace(/^#version.*$/gm, '').trim();
|
|
47
|
-
fragmentShader = fragmentShader.replace(/^#version.*$/gm, '').trim();
|
|
48
|
-
|
|
49
|
-
// MaterialX uses different attribute names than js defaults,
|
|
50
|
-
// so we patch the MaterialX shaders to match the js standard names.
|
|
51
|
-
// Otherwise, we'd have to modify the mesh attributes (see original MaterialX for reference).
|
|
52
|
-
|
|
53
|
-
// Patch vertexShader
|
|
54
|
-
vertexShader = vertexShader.replace(/\bi_position\b/g, 'position');
|
|
55
|
-
vertexShader = vertexShader.replace(/\bi_normal\b/g, 'normal');
|
|
56
|
-
vertexShader = vertexShader.replace(/\bi_texcoord_0\b/g, 'uv');
|
|
57
|
-
vertexShader = vertexShader.replace(/\bi_texcoord_1\b/g, 'uv1');
|
|
58
|
-
vertexShader = vertexShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
59
|
-
vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
|
|
60
|
-
|
|
61
|
-
// Patch fragmentShader
|
|
62
|
-
fragmentShader = fragmentShader.replace(/\bi_position\b/g, 'position');
|
|
63
|
-
fragmentShader = fragmentShader.replace(/\bi_normal\b/g, 'normal');
|
|
64
|
-
fragmentShader = fragmentShader.replace(/\bi_texcoord_0\b/g, 'uv');
|
|
65
|
-
fragmentShader = fragmentShader.replace(/\bi_texcoord_1\b/g, 'uv1');
|
|
66
|
-
fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
67
|
-
fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
|
|
68
|
-
|
|
69
|
-
// Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
|
|
70
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
|
|
71
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
|
|
72
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
|
|
73
|
-
vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
|
|
74
|
-
vertexShader = vertexShader.replace(/in\s+vec4\s+tangent;/g, '');
|
|
75
|
-
vertexShader = vertexShader.replace(/in\s+vec4\s+color;/g, '');
|
|
76
|
-
|
|
77
|
-
// Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
|
|
78
|
-
// TODO what if we actually have a 3-component UV? Not sure what three.js does then
|
|
79
|
-
vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
|
|
80
|
-
|
|
81
|
-
// Patch units – seems MaterialX uses different units and we end up with wrong light values?
|
|
82
|
-
// result.direction = light.position - position;
|
|
83
|
-
fragmentShader = fragmentShader.replace(
|
|
84
|
-
/result\.direction\s*=\s*light\.position\s*-\s*position;/g,
|
|
85
|
-
'result.direction = (light.position - position) * 10.0 / 1.0;');
|
|
86
|
-
|
|
87
|
-
// Add tonemapping and colorspace handling
|
|
88
|
-
// Replace `out vec4 out1;` with `out vec4 gl_FragColor;`
|
|
89
|
-
fragmentShader = fragmentShader.replace(
|
|
90
|
-
/out\s+vec4\s+out1;/,
|
|
91
|
-
'layout(location = 0) out vec4 pc_fragColor;\n#define gl_FragColor pc_fragColor');
|
|
92
|
-
|
|
93
|
-
// Replace `out1 = vec4(<CAPTURE>)` with `gl_FragColor = vec4(<CAPTURE>)` and tonemapping/colorspace handling
|
|
94
|
-
fragmentShader = fragmentShader.replace(/^\s*out1\s*=\s*vec4\((.*)\);/gm, `
|
|
95
|
-
gl_FragColor = vec4($1);
|
|
96
|
-
#include <tonemapping_fragment>
|
|
97
|
-
#include <colorspace_fragment>`);
|
|
98
|
-
|
|
99
|
-
const searchPath = ""; // Could be derived from the asset path if needed
|
|
100
|
-
const flipV = false; // Set based on your geometry requirements
|
|
101
|
-
const isTransparent = init.transparent ?? false;
|
|
102
|
-
super({
|
|
103
|
-
name: init.name,
|
|
104
|
-
uniforms: {
|
|
105
|
-
...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath, flipV),
|
|
106
|
-
...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath, flipV),
|
|
107
|
-
},
|
|
108
|
-
vertexShader: vertexShader,
|
|
109
|
-
fragmentShader: fragmentShader,
|
|
110
|
-
glslVersion: GLSL3,
|
|
111
|
-
transparent: isTransparent,
|
|
112
|
-
side: FrontSide,
|
|
113
|
-
depthTest: true,
|
|
114
|
-
depthWrite: !isTransparent,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Object.assign(this.uniforms, {
|
|
120
|
-
u_envMatrix: { value: new Matrix4() },
|
|
121
|
-
u_envRadiance: { value: null, type: 't' },
|
|
122
|
-
u_envRadianceMips: { value: 8, type: 'i' },
|
|
123
|
-
// TODO we need to figure out how we can set a PMREM here... doing many texture samples is prohibitively expensive
|
|
124
|
-
u_envRadianceSamples: { value: 8, type: 'i' },
|
|
125
|
-
u_envIrradiance: { value: null, type: 't' },
|
|
126
|
-
u_refractionEnv: { value: true },
|
|
127
|
-
u_numActiveLightSources: { value: 0 },
|
|
128
|
-
u_lightData: { value: [] }, // Array of light data
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
if (debug) {
|
|
132
|
-
// Get lighting and environment data from MaterialX environment
|
|
133
|
-
console.group("[MaterialX]: ", name);
|
|
134
|
-
console.log("Vertex shader length:", vertexShader.length, vertexShader);
|
|
135
|
-
console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
|
|
136
|
-
console.groupEnd();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
envMapIntensity: number = 1.0; // Default intensity for environment map
|
|
143
|
-
envMap: Texture | null = null; // Environment map texture, can be set externally
|
|
144
|
-
updateUniforms = (context: Context, environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
|
|
145
|
-
|
|
146
|
-
const uniforms = this.uniforms;
|
|
147
|
-
|
|
148
|
-
// TODO remove. Not sure why this is needed, but without it
|
|
149
|
-
// we currently get some "swimming" where matrices are not up to date.
|
|
150
|
-
camera.updateMatrixWorld(true);
|
|
151
|
-
|
|
152
|
-
// Update standard transformation matrices
|
|
153
|
-
if (uniforms.u_worldMatrix) {
|
|
154
|
-
if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
|
|
155
|
-
uniforms.u_worldMatrix.value = object.matrixWorld;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (uniforms.u_viewProjectionMatrix) {
|
|
159
|
-
if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
|
|
160
|
-
uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (uniforms.u_viewPosition) {
|
|
164
|
-
if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
|
|
165
|
-
uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (uniforms.u_worldInverseTransposeMatrix) {
|
|
169
|
-
if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
|
|
170
|
-
uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Update time uniforms
|
|
174
|
-
if (uniforms.u_time) {
|
|
175
|
-
uniforms.u_time.value = context.time.time;
|
|
176
|
-
}
|
|
177
|
-
if (uniforms.u_frame) {
|
|
178
|
-
uniforms.u_frame.value = context.time.frame;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Update light uniforms
|
|
182
|
-
this.updateEnvironmentUniforms(environment);
|
|
183
|
-
|
|
184
|
-
this.uniformsNeedUpdate = true;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private updateEnvironmentUniforms = (environment: MaterialXEnvironment) => {
|
|
188
|
-
|
|
189
|
-
// Get lighting data from environment
|
|
190
|
-
const lightData = environment.lightData || null;
|
|
191
|
-
const lightCount = environment.lightCount || 0;
|
|
192
|
-
const textures = environment.getTextures(this) || null;
|
|
193
|
-
|
|
194
|
-
// Update light count
|
|
195
|
-
if (this.uniforms.u_numActiveLightSources && lightCount >= 0) {
|
|
196
|
-
this.uniforms.u_numActiveLightSources.value = lightCount;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Update light data
|
|
200
|
-
if (lightData) {
|
|
201
|
-
this.uniforms.u_lightData.value = lightData;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Update environment uniforms
|
|
205
|
-
if (this.uniforms.u_envMatrix) {
|
|
206
|
-
this.uniforms.u_envMatrix.value = identityMatrix;
|
|
207
|
-
}
|
|
208
|
-
if (this.uniforms.u_envRadiance) {
|
|
209
|
-
this.uniforms.u_envRadiance.value = textures.radianceTexture || null;
|
|
210
|
-
}
|
|
211
|
-
if (this.uniforms.u_envRadianceMips) {
|
|
212
|
-
this.uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(textures.radianceTexture?.source.data.width ?? 0, textures.radianceTexture?.source.data.height ?? 0))) + 1;
|
|
213
|
-
}
|
|
214
|
-
if (this.uniforms.u_envIrradiance) {
|
|
215
|
-
this.uniforms.u_envIrradiance.value = textures.irradianceTexture;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
this.uniformsNeedUpdate = true;
|
|
219
|
-
}
|
|
220
|
-
}
|