@needle-tools/materialx 1.1.0-next.bc1b608 → 1.1.1-next.044ea68
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 +3 -0
- package/README.md +17 -3
- package/index.ts +1 -1
- package/needle.ts +1 -1
- package/package.json +2 -2
- package/src/index.ts +10 -0
- package/src/loader/loader.needle.ts +9 -7
- package/src/loader/loader.three.ts +236 -201
- package/src/materialx.helper.ts +80 -28
- package/src/materialx.material.ts +81 -35
- package/src/materialx.ts +37 -31
- package/src/utils.ts +19 -65
- /package/src/{textureHelper.ts → utils.texture.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,9 @@ All notable changes to this package will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.1.0] - 2025-07-15
|
|
8
|
+
- Add: `useNeedleMaterialX` hooks for vanilla three.js and Needle Engine
|
|
9
|
+
|
|
7
10
|
## [1.0.6] - 2025-07-15
|
|
8
11
|
- Fix: texture/environment sampling on some Android devices
|
|
9
12
|
|
package/README.md
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
# Needle MaterialX
|
|
2
2
|
|
|
3
|
-
Web runtime support to load and display MaterialX materials in Needle Engine
|
|
3
|
+
Web runtime support to load and display MaterialX materials in Needle Engine and three.js
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
`npm i @needle-tools/materialx`
|
|
7
7
|
|
|
8
|
+
## Examples
|
|
9
|
+
- [three.js Example on Stackblitz](https://stackblitz.com/edit/needle-materialx-example?file=main.js,package.json,index.html)
|
|
10
|
+
|
|
8
11
|
## How to use
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
### Use with Needle Engine
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { useNeedleMaterialX } from "@needle-tools/materialx/needle";
|
|
17
|
+
// Simply call this function in global scope as soon as possible
|
|
18
|
+
useNeedleMaterialX();
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Use with three.js
|
|
11
22
|
|
|
12
23
|
```ts
|
|
13
|
-
import "@needle-tools/materialx"
|
|
24
|
+
import { useNeedleMaterialX } from "@needle-tools/materialx";
|
|
25
|
+
// Call the function with your GLTFLoader instance
|
|
26
|
+
useNeedleMaterialX(<yourGltfLoaderInstance>);
|
|
14
27
|
```
|
|
15
28
|
|
|
29
|
+
|
|
16
30
|
<br />
|
|
17
31
|
|
|
18
32
|
# Contact ✒️
|
package/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./src/index.js";
|
|
2
|
-
export {
|
|
2
|
+
export { useNeedleMaterialX } from "./src/loader/loader.three.js";
|
package/needle.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./src/index.js";
|
|
2
|
-
export {
|
|
2
|
+
export { useNeedleMaterialX } from "./src/loader/loader.needle.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/materialx",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1-next.044ea68",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@needle-tools/engine": "4.x || ^4.6.0-0",
|
|
23
|
-
"three": "
|
|
23
|
+
"three": ">=0.169.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@needle-tools/engine": "4.x",
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
export { ready, type MaterialXContext } from "./materialx.js";
|
|
2
2
|
export { MaterialXMaterial } from "./materialx.material.js";
|
|
3
3
|
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import { createMaterialXMaterial } from "./loader/loader.three.js";
|
|
7
|
+
|
|
8
|
+
const Experimental_API = {
|
|
9
|
+
createMaterialXMaterial
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export { Experimental_API }
|
|
@@ -2,8 +2,9 @@ import { addCustomExtensionPlugin } from "@needle-tools/engine";
|
|
|
2
2
|
import { Context, GLTF, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
|
|
3
3
|
import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
4
|
import type { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
5
|
-
import {
|
|
5
|
+
import { useNeedleMaterialX as _useNeedleMaterialX, MaterialXLoader } from "./loader.three.js";
|
|
6
6
|
import { debug } from "../utils.js";
|
|
7
|
+
import { MaterialParameters } from "three";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
@@ -13,13 +14,14 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
13
14
|
|
|
14
15
|
onImport = (loader: GLTFLoader, url: string, context: Context) => {
|
|
15
16
|
if (debug) console.log("MaterialXLoaderPlugin: Registering MaterialX extension for", url);
|
|
16
|
-
|
|
17
|
+
_useNeedleMaterialX(loader, {
|
|
18
|
+
cacheKey: url,
|
|
19
|
+
parameters: {
|
|
20
|
+
precision: context.renderer.capabilities.getMaxPrecision("highp") as MaterialParameters["precision"],
|
|
21
|
+
}
|
|
22
|
+
}, {
|
|
17
23
|
getTime: () => context.time.time,
|
|
18
24
|
getFrame: () => context.time.frame,
|
|
19
|
-
getScene: () => context.scene,
|
|
20
|
-
getRenderer: () => context.renderer,
|
|
21
|
-
}, {
|
|
22
|
-
cacheKey: url
|
|
23
25
|
});
|
|
24
26
|
};
|
|
25
27
|
|
|
@@ -36,6 +38,6 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
|
36
38
|
/**
|
|
37
39
|
* Add the MaterialXLoaderPlugin to the Needle Engine.
|
|
38
40
|
*/
|
|
39
|
-
export async function
|
|
41
|
+
export async function useNeedleMaterialX() {
|
|
40
42
|
addCustomExtensionPlugin(new MaterialXLoaderPlugin());
|
|
41
43
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Material, MeshStandardMaterial, DoubleSide, FrontSide } from "three";
|
|
1
|
+
import { Material, MeshStandardMaterial, DoubleSide, FrontSide, MaterialParameters } from "three";
|
|
2
2
|
import { GLTFLoader, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
3
|
import { ready, state, MaterialXContext } from "../materialx.js";
|
|
4
4
|
import { debug } from "../utils.js";
|
|
5
5
|
import { MaterialXMaterial } from "../materialx.material.js";
|
|
6
|
+
import { Callbacks as callbacks } from "../materialx.helper.js";
|
|
6
7
|
|
|
7
8
|
// TypeScript interfaces matching the C# data structures
|
|
8
9
|
export type MaterialX_root_extension = {
|
|
@@ -14,10 +15,19 @@ export type MaterialX_root_extension = {
|
|
|
14
15
|
mtlx: string;
|
|
15
16
|
/** MaterialX texture pointers */
|
|
16
17
|
textures: Array<{ name: string, pointer: string }>;
|
|
18
|
+
shaders?: Array<{
|
|
19
|
+
/** The materialx node name */
|
|
20
|
+
name: string,
|
|
21
|
+
/** The original name of the shader */
|
|
22
|
+
originalName: string,
|
|
23
|
+
}>,
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export type MaterialX_material_extension = {
|
|
20
|
-
|
|
27
|
+
/** The MaterialX material name */
|
|
28
|
+
name: string;
|
|
29
|
+
/** The index of the shader in the shaders array of the root extension. */
|
|
30
|
+
shader?: number;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
type MaterialDefinition = {
|
|
@@ -28,6 +38,14 @@ type MaterialDefinition = {
|
|
|
28
38
|
},
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
// init.context.getRenderer().capabilities.getMaxPrecision("highp")
|
|
42
|
+
export type MaterialXLoaderOptions = {
|
|
43
|
+
/** The URL of the GLTF file being loaded */
|
|
44
|
+
cacheKey?: string;
|
|
45
|
+
/** Parameters for the MaterialX loader */
|
|
46
|
+
parameters?: Pick<MaterialParameters, "precision">;
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
// MaterialX loader extension for js GLTFLoader
|
|
32
50
|
export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
33
51
|
readonly name = "NEEDLE_materials_mtlx";
|
|
@@ -51,7 +69,11 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
51
69
|
* @param cacheKey The URL of the GLTF file
|
|
52
70
|
* @param context The context for the GLTF loading process
|
|
53
71
|
*/
|
|
54
|
-
constructor(
|
|
72
|
+
constructor(
|
|
73
|
+
private parser: GLTFParser,
|
|
74
|
+
private options: MaterialXLoaderOptions,
|
|
75
|
+
private context: MaterialXContext
|
|
76
|
+
) {
|
|
55
77
|
if (debug) console.log("MaterialXLoader created for parser");
|
|
56
78
|
// Start loading of MaterialX environment if the root extension exists
|
|
57
79
|
if (this.materialX_root_data) {
|
|
@@ -69,36 +91,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
69
91
|
return this._loadMaterialAsync(materialIndex);
|
|
70
92
|
}
|
|
71
93
|
|
|
72
|
-
// Parse the MaterialX document once and cache it
|
|
73
|
-
private async _materialXDocumentReady(): Promise<any> {
|
|
74
|
-
if (this._documentReadyPromise) {
|
|
75
|
-
return this._documentReadyPromise;
|
|
76
|
-
}
|
|
77
|
-
return this._documentReadyPromise = (async () => {
|
|
78
|
-
if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
|
|
79
|
-
|
|
80
|
-
// Ensure MaterialX is initialized
|
|
81
|
-
await ready();
|
|
82
|
-
|
|
83
|
-
if (!state.materialXModule) {
|
|
84
|
-
throw new Error("[MaterialX] module failed to initialize");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Create MaterialX document and parse ALL the XML data from root
|
|
88
|
-
const doc = state.materialXModule.createDocument();
|
|
89
|
-
doc.setDataLibrary(state.materialXStdLib);
|
|
90
|
-
|
|
91
|
-
// Parse all MaterialX XML strings from the root data
|
|
92
|
-
const root = this.materialX_root_data
|
|
93
|
-
if (root) {
|
|
94
|
-
if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
|
|
95
|
-
await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
99
|
-
return doc;
|
|
100
|
-
})();
|
|
101
|
-
}
|
|
102
94
|
|
|
103
95
|
private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
|
|
104
96
|
|
|
@@ -108,8 +100,61 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
108
100
|
// Handle different types of MaterialX data
|
|
109
101
|
const ext = materialDef.extensions?.[this.name] as MaterialX_material_extension;
|
|
110
102
|
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
const mtlx = this.materialX_root_data?.mtlx;
|
|
104
|
+
|
|
105
|
+
if (ext && mtlx) {
|
|
106
|
+
|
|
107
|
+
const materialOptions: MaterialXMaterialOptions = {
|
|
108
|
+
...this.options,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!materialOptions.parameters) materialOptions.parameters = {};
|
|
112
|
+
|
|
113
|
+
if (materialOptions.parameters?.side === undefined && materialDef.doubleSided !== undefined) {
|
|
114
|
+
materialOptions.parameters.side = materialDef.doubleSided ? DoubleSide : FrontSide;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return createMaterialXMaterial(mtlx, ext.name, {
|
|
118
|
+
cacheKey: this.options.cacheKey || "",
|
|
119
|
+
getTexture: async url => {
|
|
120
|
+
// Find the index of the texture in the parser
|
|
121
|
+
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
122
|
+
|
|
123
|
+
// Resolve the texture from the MaterialX root extension
|
|
124
|
+
if (this.materialX_root_data) {
|
|
125
|
+
const textures = this.materialX_root_data.textures || [];
|
|
126
|
+
let index = -1;
|
|
127
|
+
for (const texture of textures) {
|
|
128
|
+
// Find the texture by name and use the pointer string to get the index
|
|
129
|
+
if (texture.name === filenameWithoutExt) {
|
|
130
|
+
const ptr = texture.pointer;
|
|
131
|
+
const indexStr = ptr.substring("/textures/".length);
|
|
132
|
+
index = parseInt(indexStr);
|
|
133
|
+
|
|
134
|
+
if (isNaN(index) || index < 0) {
|
|
135
|
+
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (index < 0) {
|
|
145
|
+
console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
return this.parser.getDependency("texture", index);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}, materialOptions, this.context)
|
|
153
|
+
// Cache and return the generated material
|
|
154
|
+
.then(mat => {
|
|
155
|
+
if (mat instanceof MaterialXMaterial) this._generatedMaterials.push(mat);
|
|
156
|
+
return mat;
|
|
157
|
+
})
|
|
113
158
|
}
|
|
114
159
|
|
|
115
160
|
// Return fallback material instead of null
|
|
@@ -118,189 +163,179 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
|
|
|
118
163
|
return fallbackMaterial;
|
|
119
164
|
}
|
|
120
165
|
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
|
|
166
|
+
}
|
|
124
167
|
|
|
125
|
-
const doc = await this._materialXDocumentReady();
|
|
126
168
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
fallbackMaterial.userData.materialX = material_extension;
|
|
131
|
-
fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
|
|
132
|
-
return fallbackMaterial;
|
|
133
|
-
}
|
|
169
|
+
type MaterialXMaterialOptions = {
|
|
170
|
+
parameters?: MaterialParameters;
|
|
171
|
+
}
|
|
134
172
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Add the MaterialXLoader to the GLTFLoader instance.
|
|
175
|
+
*/
|
|
176
|
+
export function useNeedleMaterialX(loader: GLTFLoader, options?: MaterialXLoaderOptions, context?: MaterialXContext) {
|
|
177
|
+
loader.register(p => {
|
|
178
|
+
const loader = new MaterialXLoader(p, options || {}, context || {});
|
|
179
|
+
return loader;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// Parse the MaterialX document once and cache it
|
|
187
|
+
async function load(mtlx: string): Promise<any> {
|
|
188
|
+
// Ensure MaterialX is initialized
|
|
189
|
+
await ready();
|
|
190
|
+
if (!state.materialXModule) {
|
|
191
|
+
throw new Error("[MaterialX] module failed to initialize");
|
|
192
|
+
}
|
|
193
|
+
// Create MaterialX document and parse ALL the XML data from root
|
|
194
|
+
const doc = state.materialXModule.createDocument();
|
|
195
|
+
doc.setDataLibrary(state.materialXStdLib);
|
|
196
|
+
// Parse all MaterialX XML strings from the root data
|
|
197
|
+
await state.materialXModule.readFromXmlString(doc, mtlx, "");
|
|
198
|
+
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
199
|
+
return doc;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function createMaterialXMaterial(mtlx: string, materialNodeName: string, loaders: callbacks, options?: MaterialXLoaderOptions, context?: MaterialXContext): Promise<Material> {
|
|
203
|
+
try {
|
|
204
|
+
if (debug) console.log(`Creating MaterialX material: ${materialNodeName}`);
|
|
205
|
+
|
|
206
|
+
const doc = await load(mtlx);
|
|
207
|
+
|
|
208
|
+
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
209
|
+
console.warn("[MaterialX] WASM module not ready, returning fallback material");
|
|
210
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
211
|
+
fallbackMaterial.name = `MaterialX_Fallback_${materialNodeName}`;
|
|
212
|
+
return fallbackMaterial;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Find the renderable element following MaterialX example pattern exactly
|
|
216
|
+
let renderableElement: any = null;
|
|
217
|
+
let foundRenderable = false;
|
|
218
|
+
|
|
219
|
+
if (debug) console.log("[MaterialX] document", doc);
|
|
220
|
+
|
|
221
|
+
// Search for material nodes first (following the reference pattern)
|
|
222
|
+
const materialNodes = doc.getMaterialNodes();
|
|
223
|
+
if (debug) console.log(`[MaterialX] Found ${materialNodes.length} material nodes in document`, materialNodes);
|
|
224
|
+
|
|
225
|
+
// Handle both array and vector-like APIs
|
|
226
|
+
for (let i = 0; i < materialNodes.length; ++i) {
|
|
227
|
+
const materialNode = materialNodes[i];
|
|
228
|
+
if (materialNode) {
|
|
229
|
+
const name = materialNode.getNamePath();
|
|
230
|
+
if (debug) console.log(`[MaterialX] Scan material[${i}]: ${name}`);
|
|
231
|
+
|
|
232
|
+
// Find the matching material
|
|
233
|
+
if (materialNodes.length === 1 || name == materialNodeName) {
|
|
234
|
+
materialNodeName = name;
|
|
235
|
+
renderableElement = materialNode;
|
|
236
|
+
foundRenderable = true;
|
|
237
|
+
if (debug) console.log(`[MaterialX] Use material node: '${name}'`);
|
|
238
|
+
break;
|
|
160
239
|
}
|
|
161
240
|
}
|
|
241
|
+
}
|
|
162
242
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
243
|
+
/*
|
|
244
|
+
// If no material nodes found, search nodeGraphs
|
|
245
|
+
if (!foundRenderable) {
|
|
246
|
+
const nodeGraphs = doc.getNodeGraphs();
|
|
247
|
+
console.log(`Found ${nodeGraphs.length} node graphs in document`);
|
|
248
|
+
const nodeGraphsLength = nodeGraphs.length;
|
|
249
|
+
for (let i = 0; i < nodeGraphsLength; ++i) {
|
|
250
|
+
const nodeGraph = nodeGraphs[i];
|
|
251
|
+
if (nodeGraph) {
|
|
252
|
+
// Skip any nodegraph that has nodedef or sourceUri
|
|
253
|
+
if ((nodeGraph as any).hasAttribute('nodedef') || (nodeGraph as any).hasSourceUri()) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
// Skip any nodegraph that is connected to something downstream
|
|
257
|
+
if ((nodeGraph as any).getDownstreamPorts().length > 0) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const outputs = (nodeGraph as any).getOutputs();
|
|
261
|
+
for (let j = 0; j < outputs.length; ++j) {
|
|
262
|
+
const output = outputs[j];
|
|
263
|
+
if (output && !foundRenderable) {
|
|
264
|
+
renderableElement = output;
|
|
265
|
+
foundRenderable = true;
|
|
266
|
+
break;
|
|
188
267
|
}
|
|
189
|
-
if (foundRenderable) break;
|
|
190
268
|
}
|
|
269
|
+
if (foundRenderable) break;
|
|
191
270
|
}
|
|
192
271
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// If still no element found, search document outputs
|
|
275
|
+
if (!foundRenderable) {
|
|
276
|
+
const outputs = doc.getOutputs();
|
|
277
|
+
console.log(`Found ${outputs.length} output nodes in document`);
|
|
278
|
+
const outputsLength = outputs.length;
|
|
279
|
+
for (let i = 0; i < outputsLength; ++i) {
|
|
280
|
+
const output = outputs[i];
|
|
281
|
+
if (output && !foundRenderable) {
|
|
282
|
+
renderableElement = output;
|
|
283
|
+
foundRenderable = true;
|
|
284
|
+
break;
|
|
206
285
|
}
|
|
207
286
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!renderableElement) {
|
|
211
|
-
console.warn(`[MaterialX] No renderable element found in MaterialX document (${material_extension.name})`);
|
|
212
|
-
const fallbackMaterial = new MeshStandardMaterial();
|
|
213
|
-
fallbackMaterial.color.set(0xff00ff);
|
|
214
|
-
fallbackMaterial.userData.materialX = material_extension;
|
|
215
|
-
fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
|
|
216
|
-
return fallbackMaterial;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
220
|
-
|
|
221
|
-
// Check transparency and set context options like the reference
|
|
222
|
-
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
223
|
-
state.materialXGenContext.getOptions().hwTransparency = isTransparent;
|
|
224
|
-
|
|
225
|
-
// Generate shaders using the element's name path
|
|
226
|
-
if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
|
|
227
|
-
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
228
|
-
|
|
229
|
-
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
230
|
-
const shaderMaterial = new MaterialXMaterial({
|
|
231
|
-
name: material_extension.name,
|
|
232
|
-
shader,
|
|
233
|
-
transparent: isTransparent,
|
|
234
|
-
side: material_def.doubleSided ? DoubleSide : FrontSide,
|
|
235
|
-
context: this.context,
|
|
236
|
-
loaders: {
|
|
237
|
-
cacheKey: this.cacheKey,
|
|
238
|
-
getTexture: async url => {
|
|
239
|
-
// Find the index of the texture in the parser
|
|
240
|
-
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
241
|
-
|
|
242
|
-
// Resolve the texture from the MaterialX root extension
|
|
243
|
-
const ext = this.materialX_root_data;
|
|
244
|
-
if (ext) {
|
|
245
|
-
const textures = ext.textures || [];
|
|
246
|
-
let index = -1;
|
|
247
|
-
for (const texture of textures) {
|
|
248
|
-
// Find the texture by name and use the pointer string to get the index
|
|
249
|
-
if (texture.name === filenameWithoutExt) {
|
|
250
|
-
const ptr = texture.pointer;
|
|
251
|
-
const indexStr = ptr.substring("/textures/".length);
|
|
252
|
-
index = parseInt(indexStr);
|
|
253
|
-
|
|
254
|
-
if (isNaN(index) || index < 0) {
|
|
255
|
-
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
287
|
+
}
|
|
288
|
+
*/
|
|
263
289
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
return this.parser.getDependency("texture", index).then(tex => {
|
|
269
|
-
if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
|
|
270
|
-
return tex;
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
// Track this material for later lighting updates
|
|
278
|
-
this._generatedMaterials.push(shaderMaterial);
|
|
279
|
-
|
|
280
|
-
// Add debugging to see if the material compiles correctly
|
|
281
|
-
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
282
|
-
return shaderMaterial;
|
|
283
|
-
|
|
284
|
-
} catch (error) {
|
|
285
|
-
// This is a wasm error (an int) that we need to resolve
|
|
286
|
-
console.error(`[MaterialX] Error creating MaterialX material (${material_extension.name}):`, error);
|
|
287
|
-
// Return a fallback material with stored MaterialX data
|
|
290
|
+
if (!renderableElement) {
|
|
291
|
+
console.warn(`[MaterialX] No renderable element found in MaterialX document (${name})`);
|
|
288
292
|
const fallbackMaterial = new MeshStandardMaterial();
|
|
289
293
|
fallbackMaterial.color.set(0xff00ff);
|
|
290
|
-
fallbackMaterial.
|
|
291
|
-
fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
|
|
294
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${name}`;
|
|
292
295
|
return fallbackMaterial;
|
|
293
296
|
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
297
|
|
|
298
|
+
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
297
299
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
// Check transparency and set context options like the reference
|
|
301
|
+
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
302
|
+
state.materialXGenContext.getOptions().hwTransparency = isTransparent;
|
|
303
|
+
|
|
304
|
+
// Generate shaders using the element's name path
|
|
305
|
+
if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
|
|
306
|
+
const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
|
|
307
|
+
|
|
308
|
+
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
309
|
+
|
|
310
|
+
// const rootExtension = this.materialX_root_data;
|
|
311
|
+
|
|
312
|
+
// const shaderInfo = rootExtension && material_extension.shader !== undefined && material_extension.shader >= 0
|
|
313
|
+
// ? rootExtension.shaders?.[material_extension.shader]
|
|
314
|
+
// : null;
|
|
315
|
+
|
|
316
|
+
const shaderMaterial = new MaterialXMaterial({
|
|
317
|
+
name: materialNodeName,
|
|
318
|
+
shaderName: null, //shaderInfo?.originalName || shaderInfo?.name || null,
|
|
319
|
+
shader,
|
|
320
|
+
context: context || {},
|
|
321
|
+
parameters: {
|
|
322
|
+
transparent: isTransparent,
|
|
323
|
+
...options?.parameters,
|
|
324
|
+
},
|
|
325
|
+
loaders: loaders,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Add debugging to see if the material compiles correctly
|
|
329
|
+
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
330
|
+
return shaderMaterial;
|
|
331
|
+
|
|
332
|
+
} catch (error) {
|
|
333
|
+
// This is a wasm error (an int) that we need to resolve
|
|
334
|
+
console.error(`[MaterialX] Error creating MaterialX material (${name}):`, error);
|
|
335
|
+
// Return a fallback material with stored MaterialX data
|
|
336
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
337
|
+
fallbackMaterial.color.set(0xff00ff);
|
|
338
|
+
fallbackMaterial.name = `MaterialX_Error_${name}`;
|
|
339
|
+
return fallbackMaterial;
|
|
340
|
+
}
|
|
306
341
|
}
|
package/src/materialx.helper.ts
CHANGED
|
@@ -70,17 +70,17 @@ function fromMatrix(matrix: MaterialX.Matrix, dimension: MaterialX.Matrix["size"
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
export type
|
|
73
|
+
export type Callbacks = {
|
|
74
74
|
/**
|
|
75
75
|
* Cache key for the loaders, used to identify and reuse textures
|
|
76
76
|
*/
|
|
77
|
-
readonly cacheKey
|
|
77
|
+
readonly cacheKey?: string;
|
|
78
78
|
/**
|
|
79
79
|
* Get a texture by path
|
|
80
80
|
* @param {string} path - The path to the texture
|
|
81
81
|
* @return {Promise<THREE.Texture>} - A promise that resolves to the texture
|
|
82
82
|
*/
|
|
83
|
-
readonly getTexture: (path: string) => Promise<THREE.Texture>;
|
|
83
|
+
readonly getTexture: (path: string) => Promise<THREE.Texture | null | void>;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const defaultTexture = new THREE.Texture();
|
|
@@ -119,7 +119,7 @@ function addToCache(key: string, value: any): void {
|
|
|
119
119
|
/**
|
|
120
120
|
* Get Three uniform from MaterialX value
|
|
121
121
|
*/
|
|
122
|
-
function toThreeUniform(uniforms: any, type: string, value: any, name: string, loaders:
|
|
122
|
+
function toThreeUniform(uniforms: any, type: string, value: any, name: string, loaders: Callbacks, searchPath: string): THREE.Uniform {
|
|
123
123
|
|
|
124
124
|
const uniform = new THREE.Uniform<any>(null);
|
|
125
125
|
|
|
@@ -165,7 +165,7 @@ function toThreeUniform(uniforms: any, type: string, value: any, name: string, l
|
|
|
165
165
|
checkCache = true;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
const cacheKey = `${loaders.cacheKey}-${texturePath}
|
|
168
|
+
const cacheKey = loaders.cacheKey?.length ? `${loaders.cacheKey}-${texturePath}` : texturePath;
|
|
169
169
|
const cacheValue = checkCache ? tryGetFromCache(cacheKey) : null;
|
|
170
170
|
if (cacheValue) {
|
|
171
171
|
if (debug) console.log('[MaterialX] Use cached texture: ', cacheKey, cacheValue);
|
|
@@ -180,27 +180,29 @@ function toThreeUniform(uniforms: any, type: string, value: any, name: string, l
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
else {
|
|
183
|
-
if (name.toLowerCase().includes("normal")) {
|
|
184
|
-
uniform.value = defaultNormalTexture;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
uniform.value = defaultTexture;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
183
|
if (debug) console.log('[MaterialX] Load texture:', texturePath);
|
|
184
|
+
|
|
185
|
+
if (name.toLowerCase().includes("normal")) uniform.value = defaultNormalTexture;
|
|
186
|
+
else uniform.value = defaultTexture;
|
|
187
|
+
const defaultValue = uniform.value;
|
|
191
188
|
// Save the loading promise in the cache
|
|
192
|
-
const promise = loaders.getTexture(texturePath)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
189
|
+
const promise = loaders.getTexture(texturePath)
|
|
190
|
+
?.then(res => {
|
|
191
|
+
if (res) {
|
|
192
|
+
res = res.clone(); // we need to clone the texture once to avoid colorSpace issues with other materials
|
|
193
|
+
res.colorSpace = THREE.LinearSRGBColorSpace;
|
|
194
|
+
setTextureParameters(res, name, uniforms);
|
|
195
|
+
}
|
|
196
|
+
return res;
|
|
197
|
+
})
|
|
198
|
+
.catch(err => {
|
|
199
|
+
console.error(`[MaterialX] Failed to load texture ${name} '${texturePath}'`, err);
|
|
200
|
+
return defaultValue;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (checkCache) addToCache(cacheKey, promise);
|
|
204
|
+
|
|
205
|
+
promise?.then(res => {
|
|
204
206
|
if (res) uniform.value = res;
|
|
205
207
|
else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
|
|
206
208
|
});
|
|
@@ -454,7 +456,7 @@ export function getLightData(lights: Array<THREE.Light>, genContext: any): { lig
|
|
|
454
456
|
/**
|
|
455
457
|
* Get uniform values for a shader
|
|
456
458
|
*/
|
|
457
|
-
export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders:
|
|
459
|
+
export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Callbacks, searchPath: string) {
|
|
458
460
|
const threeUniforms = {};
|
|
459
461
|
|
|
460
462
|
const uniformBlocks = shaderStage.getUniformBlocks()
|
|
@@ -466,12 +468,62 @@ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Lo
|
|
|
466
468
|
for (let i = 0; i < uniforms.size(); ++i) {
|
|
467
469
|
const variable = uniforms.get(i);
|
|
468
470
|
const value = variable.getValue()?.getData();
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
threeUniforms[
|
|
471
|
+
const uniformName = variable.getVariable();
|
|
472
|
+
const type = variable.getType().getName();
|
|
473
|
+
threeUniforms[uniformName] = toThreeUniform(uniforms, type, value, uniformName, loaders, searchPath);
|
|
474
|
+
if (debug) console.log("Adding uniform", { path: variable.getPath(), type: type, name: uniformName, value: threeUniforms[uniformName], },);
|
|
472
475
|
}
|
|
473
476
|
}
|
|
474
477
|
}
|
|
475
478
|
|
|
476
479
|
return threeUniforms;
|
|
477
480
|
}
|
|
481
|
+
|
|
482
|
+
export function generateMaterialPropertiesForUniforms(material: THREE.ShaderMaterial, shaderStage: MaterialX.ShaderStage) {
|
|
483
|
+
|
|
484
|
+
const uniformBlocks = shaderStage.getUniformBlocks()
|
|
485
|
+
for (const [blockName, uniforms] of Object.entries(uniformBlocks)) {
|
|
486
|
+
// Seems struct uniforms (like in LightData) end up here as well, we should filter those out.
|
|
487
|
+
if (blockName === "LightData") continue;
|
|
488
|
+
|
|
489
|
+
if (!uniforms.empty()) {
|
|
490
|
+
for (let i = 0; i < uniforms.size(); ++i) {
|
|
491
|
+
const variable = uniforms.get(i);
|
|
492
|
+
const uniformName = variable.getVariable();
|
|
493
|
+
let key = variable.getPath().split('/').pop();
|
|
494
|
+
switch (key) {
|
|
495
|
+
case "_Color":
|
|
496
|
+
key = "color";
|
|
497
|
+
break;
|
|
498
|
+
case "_Roughness":
|
|
499
|
+
key = "roughness";
|
|
500
|
+
break;
|
|
501
|
+
case "_Metallic":
|
|
502
|
+
key = "metalness";
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
if (key) {
|
|
506
|
+
if (material.hasOwnProperty(key)) {
|
|
507
|
+
if (debug) console.warn(`[MaterialX] Uniform ${uniformName} already exists in material as property ${key}, skipping.`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
Object.defineProperty(material, key, {
|
|
511
|
+
get: function () {
|
|
512
|
+
return this.uniforms?.[uniformName].value
|
|
513
|
+
},
|
|
514
|
+
set: function (v) {
|
|
515
|
+
const uniforms = this.uniforms;
|
|
516
|
+
if (!uniforms || !uniforms[uniformName]) {
|
|
517
|
+
console.warn(`[MaterialX] Uniform ${uniformName} not found in ${this.name} uniforms`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
this.uniforms[uniformName].value = v;
|
|
521
|
+
this.uniformsNeedUpdate = true;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { BufferGeometry, Camera, FrontSide, GLSL3, Group, IUniform, MaterialParameters, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
2
|
-
import { debug } from "./utils.js";
|
|
2
|
+
import { debug, getFrame, getTime } from "./utils.js";
|
|
3
3
|
import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
|
|
4
|
-
import { getUniformValues,
|
|
4
|
+
import { generateMaterialPropertiesForUniforms, getUniformValues, Callbacks } from "./materialx.helper.js";
|
|
5
|
+
import { cloneUniforms, cloneUniformsGroups } from "three/src/renderers/shaders/UniformsUtils.js";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
// Add helper matrices for uniform updates (similar to MaterialX example)
|
|
8
|
-
const identityMatrix = new Matrix4();
|
|
9
9
|
const normalMat = new Matrix3();
|
|
10
|
-
const viewProjMat = new Matrix4();
|
|
11
10
|
const worldViewPos = new Vector3();
|
|
12
11
|
|
|
13
12
|
declare type MaterialXMaterialInitParameters = {
|
|
14
13
|
name: string,
|
|
14
|
+
shaderName?: string | null, // Optional name of the shader
|
|
15
15
|
shader: any,
|
|
16
|
-
loaders:
|
|
17
|
-
transparent?: boolean,
|
|
18
|
-
side?: MaterialParameters['side'],
|
|
16
|
+
loaders: Callbacks,
|
|
19
17
|
context: MaterialXContext,
|
|
18
|
+
// Optional parameters
|
|
19
|
+
parameters?: MaterialParameters,
|
|
20
|
+
debug?: boolean,
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
|
|
@@ -24,13 +25,26 @@ type Precision = "highp" | "mediump" | "lowp";
|
|
|
24
25
|
|
|
25
26
|
export class MaterialXMaterial extends ShaderMaterial {
|
|
26
27
|
|
|
28
|
+
/** The original name of the shader */
|
|
29
|
+
readonly shaderName: string | null = null;
|
|
30
|
+
|
|
27
31
|
copy(source: MaterialXMaterial): this {
|
|
28
32
|
super.copy(source);
|
|
29
33
|
this._context = source._context;
|
|
34
|
+
this._shader = source._shader;
|
|
35
|
+
this.uniforms = cloneUniforms(source.uniforms) as Uniforms;
|
|
36
|
+
this.uniformsGroups = cloneUniformsGroups(source.uniformsGroups);
|
|
37
|
+
this.envMapIntensity = source.envMapIntensity;
|
|
38
|
+
this.envMap = source.envMap;
|
|
39
|
+
generateMaterialPropertiesForUniforms(this, this._shader.getStage('pixel'));
|
|
40
|
+
generateMaterialPropertiesForUniforms(this, this._shader.getStage('vertex'));
|
|
41
|
+
this.needsUpdate = true;
|
|
30
42
|
return this;
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
private _context: MaterialXContext | null = null;
|
|
46
|
+
private _shader: any;
|
|
47
|
+
private _needsTangents: boolean = false;
|
|
34
48
|
|
|
35
49
|
constructor(init?: MaterialXMaterialInitParameters) {
|
|
36
50
|
|
|
@@ -63,7 +77,8 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
63
77
|
vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
|
|
64
78
|
|
|
65
79
|
// Patch fragmentShader
|
|
66
|
-
const precision = init.
|
|
80
|
+
const precision: Precision = init.parameters?.precision || "highp" as Precision;
|
|
81
|
+
vertexShader = vertexShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
|
|
67
82
|
fragmentShader = fragmentShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
|
|
68
83
|
fragmentShader = fragmentShader.replace(/#define M_FLOAT_EPS 1e-8/g, precision === "highp" ? `#define M_FLOAT_EPS 1e-8` : `#define M_FLOAT_EPS 1e-3`);
|
|
69
84
|
|
|
@@ -76,9 +91,16 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
76
91
|
fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
|
|
77
92
|
fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
|
|
78
93
|
|
|
94
|
+
// Capture some vertex shader properties
|
|
95
|
+
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
|
|
96
|
+
const uv1_is_vec2 = vertexShader.includes('in vec2 uv1;');
|
|
97
|
+
const uv2_is_vec2 = vertexShader.includes('in vec2 uv2;');
|
|
98
|
+
const uv3_is_vec2 = vertexShader.includes('in vec2 uv3;');
|
|
99
|
+
|
|
79
100
|
// Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
|
|
80
101
|
vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
|
|
81
102
|
vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
|
|
103
|
+
vertexShader = vertexShader.replace(/in\s+vec2\s+uv;/g, '');
|
|
82
104
|
vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
|
|
83
105
|
var hasUv1 = vertexShader.includes('in vec3 uv1;');
|
|
84
106
|
vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
|
|
@@ -93,10 +115,10 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
93
115
|
|
|
94
116
|
// Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
|
|
95
117
|
// TODO what if we actually have a 3-component UV? Not sure what three.js does then
|
|
96
|
-
vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
|
|
97
|
-
vertexShader = vertexShader.replace(/texcoord_1 = uv1;/g, 'texcoord_1 = vec3(uv1, 0.0);');
|
|
98
|
-
vertexShader = vertexShader.replace(/texcoord_2 = uv2;/g, 'texcoord_2 = vec3(uv2, 0.0);');
|
|
99
|
-
vertexShader = vertexShader.replace(/texcoord_3 = uv3;/g, 'texcoord_3 = vec3(uv3, 0.0);');
|
|
118
|
+
if (!uv_is_vec2) vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
|
|
119
|
+
if (!uv1_is_vec2) vertexShader = vertexShader.replace(/texcoord_1 = uv1;/g, 'texcoord_1 = vec3(uv1, 0.0);');
|
|
120
|
+
if (!uv2_is_vec2) vertexShader = vertexShader.replace(/texcoord_2 = uv2;/g, 'texcoord_2 = vec3(uv2, 0.0);');
|
|
121
|
+
if (!uv3_is_vec2) vertexShader = vertexShader.replace(/texcoord_3 = uv3;/g, 'texcoord_3 = vec3(uv3, 0.0);');
|
|
100
122
|
|
|
101
123
|
// Patch units – seems MaterialX uses different units and we end up with wrong light values?
|
|
102
124
|
// result.direction = light.position - position;
|
|
@@ -124,19 +146,22 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
124
146
|
if (hasColor) defines['USE_COLOR'] = '';
|
|
125
147
|
|
|
126
148
|
const searchPath = ""; // Could be derived from the asset path if needed
|
|
127
|
-
const isTransparent = init.transparent ?? false;
|
|
149
|
+
const isTransparent = init.parameters?.transparent ?? false;
|
|
128
150
|
super({
|
|
129
151
|
name: init.name,
|
|
130
152
|
uniforms: {},
|
|
131
153
|
vertexShader: vertexShader,
|
|
132
154
|
fragmentShader: fragmentShader,
|
|
133
155
|
glslVersion: GLSL3,
|
|
134
|
-
transparent: isTransparent,
|
|
135
|
-
side: init.side ? init.side : FrontSide,
|
|
136
156
|
depthTest: true,
|
|
137
157
|
depthWrite: !isTransparent,
|
|
138
158
|
defines: defines,
|
|
159
|
+
...init.parameters, // Spread any additional parameters passed to the material
|
|
139
160
|
});
|
|
161
|
+
this.shaderName = init.shaderName || null;
|
|
162
|
+
this._context = init.context;
|
|
163
|
+
this._shader = init.shader;
|
|
164
|
+
this._needsTangents = vertexShader.includes('in vec4 tangent;') || vertexShader.includes('in vec3 tangent;');
|
|
140
165
|
|
|
141
166
|
Object.assign(this.uniforms, {
|
|
142
167
|
...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
|
|
@@ -147,6 +172,9 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
147
172
|
u_viewPosition: { value: new Vector3() },
|
|
148
173
|
u_worldInverseTransposeMatrix: { value: new Matrix4() },
|
|
149
174
|
|
|
175
|
+
// u_shadowMatrix: { value: new Matrix4() },
|
|
176
|
+
// u_shadowMap: { value: null, type: 't' }, // Shadow map
|
|
177
|
+
|
|
150
178
|
u_envMatrix: { value: new Matrix4() },
|
|
151
179
|
u_envRadiance: { value: null, type: 't' },
|
|
152
180
|
u_envRadianceMips: { value: 8, type: 'i' },
|
|
@@ -158,9 +186,11 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
158
186
|
u_lightData: { value: [], needsUpdate: false }, // Array of light data. We need to set needsUpdate to false until we actually update it
|
|
159
187
|
});
|
|
160
188
|
|
|
161
|
-
this
|
|
189
|
+
generateMaterialPropertiesForUniforms(this, init.shader.getStage('pixel'));
|
|
190
|
+
generateMaterialPropertiesForUniforms(this, init.shader.getStage('vertex'));
|
|
162
191
|
|
|
163
|
-
|
|
192
|
+
|
|
193
|
+
if (debug || init.debug) {
|
|
164
194
|
// Get lighting and environment data from MaterialX environment
|
|
165
195
|
console.group("[MaterialX]: ", this.name);
|
|
166
196
|
console.log(`Vertex shader length: ${vertexShader.length}\n`, vertexShader);
|
|
@@ -169,20 +199,28 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
169
199
|
}
|
|
170
200
|
}
|
|
171
201
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
202
|
+
private _missingTangentsWarned: boolean = false;
|
|
203
|
+
onBeforeRender(renderer: WebGLRenderer, _scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D, _group: Group): void {
|
|
204
|
+
if (this._needsTangents && !geometry.attributes.tangent) {
|
|
205
|
+
if (!this._missingTangentsWarned) {
|
|
206
|
+
this._missingTangentsWarned = true;
|
|
207
|
+
console.warn(`[MaterialX] Tangents are required for this material (${this.name}) but not present in the geometry.`);
|
|
208
|
+
// TODO: can we compute tangents here?
|
|
178
209
|
}
|
|
179
210
|
}
|
|
211
|
+
const time = this._context?.getTime?.() || getTime();
|
|
212
|
+
const frame = this._context?.getFrame?.() || getFrame();
|
|
213
|
+
const env = MaterialXEnvironment.get(_scene);
|
|
214
|
+
if (env) {
|
|
215
|
+
env.update(frame, _scene, renderer);
|
|
216
|
+
this.updateUniforms(env, renderer, object, camera, time, frame);
|
|
217
|
+
}
|
|
180
218
|
}
|
|
181
219
|
|
|
182
220
|
|
|
183
221
|
envMapIntensity: number = 1.0; // Default intensity for environment map
|
|
184
222
|
envMap: Texture | null = null; // Environment map texture, can be set externally
|
|
185
|
-
updateUniforms = (environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
|
|
223
|
+
updateUniforms = (environment: MaterialXEnvironment, renderer: WebGLRenderer, object: Object3D, camera: Camera, time?: number, frame?: number) => {
|
|
186
224
|
|
|
187
225
|
const uniforms = this.uniforms as Uniforms;
|
|
188
226
|
|
|
@@ -197,24 +235,32 @@ export class MaterialXMaterial extends ShaderMaterial {
|
|
|
197
235
|
uniforms.u_viewProjectionMatrix.needsUpdate = true;
|
|
198
236
|
}
|
|
199
237
|
|
|
238
|
+
if (uniforms.u_worldInverseTransposeMatrix) {
|
|
239
|
+
uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
|
|
240
|
+
uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
200
243
|
if (uniforms.u_viewPosition) {
|
|
201
244
|
uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
|
|
202
245
|
uniforms.u_viewPosition.needsUpdate = true;
|
|
203
246
|
}
|
|
204
247
|
|
|
205
|
-
if (uniforms.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
248
|
+
// if (uniforms.u_shadowMap) {
|
|
249
|
+
// const light = environment.lights?.[2] || null;
|
|
250
|
+
// uniforms.u_shadowMatrix.value = light?.shadow?.matrix.clone().premultiply(object.matrixWorld.clone()).invert();
|
|
251
|
+
// uniforms.u_shadowMap.value = light.shadow?.map || null;
|
|
252
|
+
// uniforms.u_shadowMap.needsUpdate = true;
|
|
253
|
+
// console.log("[MaterialX] Renderer shadow map updated", light);
|
|
254
|
+
// }
|
|
209
255
|
|
|
210
256
|
// Update time uniforms
|
|
211
|
-
if (
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
257
|
+
if (uniforms.u_time) {
|
|
258
|
+
if (time === undefined) time = getTime();
|
|
259
|
+
uniforms.u_time.value = time;
|
|
260
|
+
}
|
|
261
|
+
if (uniforms.u_frame) {
|
|
262
|
+
if (frame === undefined) frame = getFrame();
|
|
263
|
+
uniforms.u_frame.value = frame;
|
|
218
264
|
}
|
|
219
265
|
|
|
220
266
|
// Update light uniforms
|
package/src/materialx.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type { MaterialX as MX } from "./materialx.types.js";
|
|
2
2
|
import MaterialX from "../bin/JsMaterialXGenShader.js";
|
|
3
3
|
import { debug } from "./utils.js";
|
|
4
|
-
import { renderPMREMToEquirect } from "./
|
|
4
|
+
import { renderPMREMToEquirect } from "./utils.texture.js";
|
|
5
5
|
import { Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
|
|
6
6
|
import { registerLights, getLightData, LightData } from "./materialx.helper.js";
|
|
7
7
|
import type { MaterialXMaterial } from "./materialx.material.js";
|
|
8
8
|
|
|
9
9
|
export type MaterialXContext = {
|
|
10
|
-
getTime(): number,
|
|
11
|
-
getFrame(): number,
|
|
12
|
-
getScene(): Scene,
|
|
13
|
-
getRenderer(): WebGLRenderer,
|
|
10
|
+
getTime?(): number,
|
|
11
|
+
getFrame?(): number,
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
|
|
@@ -97,6 +95,8 @@ export async function ready(): Promise<void> {
|
|
|
97
95
|
// Set a reasonable default for max active lights
|
|
98
96
|
state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
|
|
99
97
|
|
|
98
|
+
// state.materialXGenContext.getOptions().hwShadowMap = true;
|
|
99
|
+
|
|
100
100
|
// This prewarms the shader generation context to have all light types
|
|
101
101
|
await registerLights(state.materialXModule, state.materialXGenContext);
|
|
102
102
|
|
|
@@ -114,23 +114,21 @@ type EnvironmentTextureSet = {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
type EnvironmentContext = Pick<MaterialXContext, "getRenderer" | "getScene">;
|
|
119
117
|
/**
|
|
120
118
|
* MaterialXEnvironment manages the environment settings for MaterialX materials.
|
|
121
119
|
*/
|
|
122
120
|
export class MaterialXEnvironment {
|
|
123
121
|
|
|
124
|
-
static get(
|
|
125
|
-
return this.getEnvironment(
|
|
122
|
+
static get(scene: Scene): MaterialXEnvironment | null {
|
|
123
|
+
return this.getEnvironment(scene);
|
|
126
124
|
}
|
|
127
|
-
private static _environments: WeakMap<
|
|
128
|
-
private static getEnvironment(
|
|
129
|
-
if (this._environments.has(
|
|
130
|
-
return this._environments.get(
|
|
125
|
+
private static _environments: WeakMap<Scene, MaterialXEnvironment> = new Map();
|
|
126
|
+
private static getEnvironment(scene: Scene): MaterialXEnvironment {
|
|
127
|
+
if (this._environments.has(scene)) {
|
|
128
|
+
return this._environments.get(scene)!;
|
|
131
129
|
}
|
|
132
|
-
const env = new MaterialXEnvironment(
|
|
133
|
-
this._environments.set(
|
|
130
|
+
const env = new MaterialXEnvironment(scene);
|
|
131
|
+
this._environments.set(scene, env);
|
|
134
132
|
return env;
|
|
135
133
|
}
|
|
136
134
|
|
|
@@ -143,22 +141,22 @@ export class MaterialXEnvironment {
|
|
|
143
141
|
private _isInitialized: boolean = false;
|
|
144
142
|
private _lastUpdateFrame: number = -1;
|
|
145
143
|
|
|
146
|
-
constructor(private
|
|
144
|
+
constructor(private _scene: Scene) {
|
|
147
145
|
if (debug) console.log("[MaterialX] Environment created");
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
// Initialize with Needle Engine context
|
|
151
|
-
async initialize(): Promise<boolean> {
|
|
149
|
+
async initialize(renderer: WebGLRenderer): Promise<boolean> {
|
|
152
150
|
if (this._initializePromise) {
|
|
153
151
|
return this._initializePromise;
|
|
154
152
|
}
|
|
155
|
-
this._initializePromise = this._initialize();
|
|
153
|
+
this._initializePromise = this._initialize(renderer);
|
|
156
154
|
return this._initializePromise;
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
update(frame: number, scene: Scene) {
|
|
157
|
+
update(frame: number, scene: Scene, renderer: WebGLRenderer): void {
|
|
160
158
|
if (!this._initializePromise) {
|
|
161
|
-
this.initialize();
|
|
159
|
+
this.initialize(renderer);
|
|
162
160
|
return;
|
|
163
161
|
}
|
|
164
162
|
if (!this._isInitialized) {
|
|
@@ -183,13 +181,15 @@ export class MaterialXEnvironment {
|
|
|
183
181
|
const radianceCube = new Mesh(planeGeometry, radianceMat);
|
|
184
182
|
const irradianceMat = unlitMat.clone();
|
|
185
183
|
irradianceMat.map = textures.irradianceTexture;
|
|
186
|
-
const irradianceCube = new Mesh(planeGeometry,
|
|
184
|
+
const irradianceCube = new Mesh(planeGeometry, irradianceMat);
|
|
187
185
|
scene.add(radianceCube);
|
|
188
186
|
scene.add(irradianceCube);
|
|
189
187
|
radianceCube.name = "MaterialXRadianceCube";
|
|
190
|
-
radianceCube.position.set(.
|
|
188
|
+
radianceCube.position.set(.8, 1, .01);
|
|
189
|
+
radianceCube.scale.set(1.5, 1, 1);
|
|
191
190
|
irradianceCube.name = "MaterialXIrradianceCube";
|
|
192
|
-
irradianceCube.position.set(-.
|
|
191
|
+
irradianceCube.position.set(-.8, 1, -.01);
|
|
192
|
+
irradianceCube.scale.set(1.5, 0.98, 1);
|
|
193
193
|
console.log("[MaterialX] environment initialized from Needle context", { textures, radianceCube, irradianceCube });
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -205,6 +205,7 @@ export class MaterialXEnvironment {
|
|
|
205
205
|
this._lightCount = 0;
|
|
206
206
|
this._pmremGenerator?.dispose();
|
|
207
207
|
this._pmremGenerator = null;
|
|
208
|
+
this._renderer = null;
|
|
208
209
|
for (const textureSet of this._texturesCache.values()) {
|
|
209
210
|
textureSet.radianceTexture?.dispose();
|
|
210
211
|
textureSet.irradianceTexture?.dispose();
|
|
@@ -212,6 +213,9 @@ export class MaterialXEnvironment {
|
|
|
212
213
|
this._texturesCache.clear();
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
get lights() {
|
|
217
|
+
return this._lights;
|
|
218
|
+
}
|
|
215
219
|
get lightData() {
|
|
216
220
|
return this._lightData;
|
|
217
221
|
}
|
|
@@ -221,15 +225,17 @@ export class MaterialXEnvironment {
|
|
|
221
225
|
// If the material has its own envMap, we don't use the irradiance texture
|
|
222
226
|
return this._getTextures(material.envMap);
|
|
223
227
|
}
|
|
224
|
-
return this._getTextures(this.
|
|
228
|
+
return this._getTextures(this._scene.environment);
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
private _pmremGenerator: PMREMGenerator | null = null;
|
|
232
|
+
private _renderer: WebGLRenderer | null = null;
|
|
228
233
|
private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
|
|
229
234
|
|
|
230
|
-
private async _initialize(): Promise<boolean> {
|
|
235
|
+
private async _initialize(renderer: WebGLRenderer): Promise<boolean> {
|
|
231
236
|
this._isInitialized = false;
|
|
232
|
-
this._pmremGenerator = new PMREMGenerator(
|
|
237
|
+
this._pmremGenerator = new PMREMGenerator(renderer);
|
|
238
|
+
this._renderer = renderer;
|
|
233
239
|
this.updateLighting(true);
|
|
234
240
|
this._isInitialized = true;
|
|
235
241
|
return true;
|
|
@@ -244,11 +250,11 @@ export class MaterialXEnvironment {
|
|
|
244
250
|
return res;
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
if (this.
|
|
253
|
+
if (this._scene && this._pmremGenerator && this._renderer && texture) {
|
|
248
254
|
if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
|
|
249
255
|
const target = this._pmremGenerator.fromEquirectangular(texture);
|
|
250
|
-
const radianceRenderTarget = renderPMREMToEquirect(this.
|
|
251
|
-
const irradianceRenderTarget = renderPMREMToEquirect(this.
|
|
256
|
+
const radianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 0.0, 1024, 512, target.height);
|
|
257
|
+
const irradianceRenderTarget = renderPMREMToEquirect(this._renderer, target.texture, 1.0, 32, 16, target.height);
|
|
252
258
|
target.dispose();
|
|
253
259
|
res = {
|
|
254
260
|
radianceTexture: radianceRenderTarget.texture,
|
|
@@ -266,11 +272,11 @@ export class MaterialXEnvironment {
|
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
private updateLighting = (collectLights: boolean = false) => {
|
|
269
|
-
if (!this.
|
|
275
|
+
if (!this._scene) return;
|
|
270
276
|
// Find lights in scene
|
|
271
277
|
if (collectLights) {
|
|
272
278
|
const lights = new Array<Light>();
|
|
273
|
-
this.
|
|
279
|
+
this._scene.traverse((object: Object3D) => {
|
|
274
280
|
if ((object as Light).isLight && object.visible)
|
|
275
281
|
lights.push(object as Light);
|
|
276
282
|
});
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// import { BufferGeometry } from 'three';
|
|
2
|
+
// import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
export function getParam(name: string) {
|
|
2
6
|
const urlParams = new URLSearchParams(window.location.search);
|
|
3
7
|
const param = urlParams.get(name);
|
|
@@ -10,70 +14,20 @@ export const debug = getParam("debugmaterialx");
|
|
|
10
14
|
export const debugUpdate = debug === "update";
|
|
11
15
|
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// const getUniformLocation = WebGL2RenderingContext.prototype.getUniformLocation;
|
|
22
|
-
// const programAndNameToUniformLocation = new WeakMap<WebGLUniformLocation, { program: WebGLProgram, name: string }>();
|
|
23
|
-
// WebGL2RenderingContext.prototype.getUniformLocation = function (program: WebGLProgram, name: string) {
|
|
24
|
-
// const location = getUniformLocation.call(this, program, name);
|
|
25
|
-
// if (location) {
|
|
26
|
-
// programAndNameToUniformLocation.set(location, { program, name });
|
|
27
|
-
// }
|
|
28
|
-
// return location;
|
|
29
|
-
// };
|
|
30
|
-
|
|
31
|
-
// const uniform4fv = WebGL2RenderingContext.prototype.uniform4fv;
|
|
32
|
-
// WebGL2RenderingContext.prototype.uniform4fv = function (location: WebGLUniformLocation | null, v: Float32Array | number[]) {
|
|
33
|
-
// if (location) {
|
|
34
|
-
// const uniformName = programAndNameToUniformLocation.get(location);
|
|
35
|
-
// if (true) console.log("Calling uniform4fv", { location, v, name: uniformName?.name });
|
|
36
|
-
// }
|
|
37
|
-
// return uniform4fv.call(this, location, v);
|
|
38
|
-
// };
|
|
39
|
-
|
|
40
|
-
// const uniform3fv = WebGL2RenderingContext.prototype.uniform3fv;
|
|
41
|
-
// WebGL2RenderingContext.prototype.uniform3fv = function (location: WebGLUniformLocation | null, v: Float32Array | number[]) {
|
|
42
|
-
// if (location) {
|
|
43
|
-
// const uniformName = programAndNameToUniformLocation.get(location);
|
|
44
|
-
// if (true) console.log("Calling uniform3fv", { location, v, name: uniformName?.name });
|
|
45
|
-
// }
|
|
46
|
-
// return uniform3fv.call(this, location, v);
|
|
47
|
-
// };
|
|
48
|
-
|
|
49
|
-
// const uniform3iv = WebGL2RenderingContext.prototype.uniform3iv;
|
|
50
|
-
// WebGL2RenderingContext.prototype.uniform3iv = function (location: WebGLUniformLocation | null, v: Int32Array | number[]) {
|
|
51
|
-
// if (location) {
|
|
52
|
-
// const uniformName = programAndNameToUniformLocation.get(location);
|
|
53
|
-
// if (true) console.log("Calling uniform3iv", { location, v, name: uniformName?.name });
|
|
54
|
-
// }
|
|
55
|
-
// return uniform3iv.call(this, location, v);
|
|
56
|
-
// };
|
|
57
|
-
|
|
58
|
-
// const uniform3uiv = WebGL2RenderingContext.prototype.uniform3uiv;
|
|
59
|
-
// WebGL2RenderingContext.prototype.uniform3uiv = function (location: WebGLUniformLocation | null, v: Uint32Array | number[]) {
|
|
60
|
-
// if (location) {
|
|
61
|
-
// const uniformName = programAndNameToUniformLocation.get(location);
|
|
62
|
-
// if (true) console.log("Calling uniform3uiv", { location, v, name: uniformName?.name });
|
|
63
|
-
// }
|
|
64
|
-
// return uniform3uiv.call(this, location, v);
|
|
65
|
-
// };
|
|
17
|
+
let time = 0;
|
|
18
|
+
export function getTime() {
|
|
19
|
+
return time;
|
|
20
|
+
}
|
|
21
|
+
let frame = 0;
|
|
22
|
+
export function getFrame() {
|
|
23
|
+
return frame;
|
|
24
|
+
}
|
|
66
25
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// }
|
|
75
|
-
// return uniform3f.call(this, location, x, y, z);
|
|
76
|
-
// };
|
|
77
|
-
// };
|
|
78
|
-
// // patchWebGL2();
|
|
26
|
+
const performance = window.performance || (window as any).webkitPerformance || (window as any).mozPerformance;
|
|
27
|
+
function updateTime() {
|
|
28
|
+
time = performance.now() / 1000; // Convert to seconds
|
|
29
|
+
frame++;
|
|
30
|
+
window.requestAnimationFrame(updateTime);
|
|
31
|
+
}
|
|
32
|
+
window.requestAnimationFrame(updateTime);
|
|
79
33
|
|
|
File without changes
|