@needle-tools/materialx 1.0.0 → 1.0.1-next.19d0723
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 +8 -0
- package/README.md +24 -0
- package/codegen/register_types.ts +4 -0
- package/index.ts +1 -1
- package/package.json +26 -4
- package/src/index.ts +2 -2
- package/src/loader/loader.needle.ts +27 -40
- package/src/loader/loader.three.ts +152 -395
- package/src/{helper.js → materialx.helper.ts} +162 -190
- package/src/materialx.material.ts +220 -0
- package/src/materialx.ts +189 -148
- package/src/materialx.types.d.ts +50 -0
- package/src/textureHelper.ts +6 -6
- package/src/utils.ts +39 -4
- package/bin/README.md +0 -5
- /package/bin/{JsMaterialXGenShader.data → JsMaterialXGenShader.data.txt} +0 -0
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
//
|
|
5
5
|
|
|
6
|
-
import { getParam } from '@needle-tools/engine';
|
|
6
|
+
import { getParam, getWorldDirection } from '@needle-tools/engine';
|
|
7
7
|
import * as THREE from 'three';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { debug, debugUpdate } from './utils';
|
|
9
|
+
import { MaterialX } from './materialx.types';
|
|
10
10
|
|
|
11
11
|
const IMAGE_PROPERTY_SEPARATOR = "_";
|
|
12
12
|
const UADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "uaddressmode";
|
|
@@ -20,8 +20,7 @@ const IMAGE_PATH_SEPARATOR = "/";
|
|
|
20
20
|
* @param {Object} capabilities
|
|
21
21
|
* @returns {THREE.Texture}
|
|
22
22
|
*/
|
|
23
|
-
export function prepareEnvTexture(texture, capabilities)
|
|
24
|
-
{
|
|
23
|
+
export function prepareEnvTexture(texture, capabilities) {
|
|
25
24
|
let newTexture = new THREE.DataTexture(texture.image.data, texture.image.width, texture.image.height, texture.format, texture.type);
|
|
26
25
|
newTexture.wrapS = THREE.RepeatWrapping;
|
|
27
26
|
newTexture.anisotropy = capabilities.getMaxAnisotropy();
|
|
@@ -39,15 +38,12 @@ export function prepareEnvTexture(texture, capabilities)
|
|
|
39
38
|
* @param {any} dimension
|
|
40
39
|
* @returns {THREE.Uniform}
|
|
41
40
|
*/
|
|
42
|
-
function fromVector(value, dimension)
|
|
43
|
-
{
|
|
41
|
+
function fromVector(value, dimension) {
|
|
44
42
|
let outValue;
|
|
45
|
-
if (value)
|
|
46
|
-
{
|
|
43
|
+
if (value) {
|
|
47
44
|
outValue = [...value.data()];
|
|
48
45
|
}
|
|
49
|
-
else
|
|
50
|
-
{
|
|
46
|
+
else {
|
|
51
47
|
outValue = [];
|
|
52
48
|
for (let i = 0; i < dimension; ++i)
|
|
53
49
|
outValue.push(0.0);
|
|
@@ -58,23 +54,16 @@ function fromVector(value, dimension)
|
|
|
58
54
|
|
|
59
55
|
/**
|
|
60
56
|
* Get Three uniform from MaterialX matrix
|
|
61
|
-
* @param {mx.matrix} matrix
|
|
62
|
-
* @param {mx.matrix.size} dimension
|
|
63
57
|
*/
|
|
64
|
-
function fromMatrix(matrix, dimension)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for (let i = 0; i < matrix.numRows(); ++i)
|
|
70
|
-
{
|
|
71
|
-
for (let k = 0; k < matrix.numColumns(); ++k)
|
|
72
|
-
{
|
|
58
|
+
function fromMatrix(matrix: MaterialX.Matrix, dimension: MaterialX.Matrix["size"]) {
|
|
59
|
+
const vec = new Array(dimension);
|
|
60
|
+
if (matrix) {
|
|
61
|
+
for (let i = 0; i < matrix.numRows(); ++i) {
|
|
62
|
+
for (let k = 0; k < matrix.numColumns(); ++k) {
|
|
73
63
|
vec.push(matrix.getItem(i, k));
|
|
74
64
|
}
|
|
75
65
|
}
|
|
76
|
-
} else
|
|
77
|
-
{
|
|
66
|
+
} else {
|
|
78
67
|
for (let i = 0; i < dimension; ++i)
|
|
79
68
|
vec.push(0.0);
|
|
80
69
|
}
|
|
@@ -82,125 +71,99 @@ function fromMatrix(matrix, dimension)
|
|
|
82
71
|
return vec;
|
|
83
72
|
}
|
|
84
73
|
|
|
74
|
+
|
|
75
|
+
export type Loaders = {
|
|
76
|
+
getTexture: (path: string) => THREE.Texture;
|
|
77
|
+
}
|
|
78
|
+
|
|
85
79
|
/**
|
|
86
80
|
* Get Three uniform from MaterialX value
|
|
87
81
|
* @param {mx.Uniform.type} type
|
|
88
82
|
* @param {mx.Uniform.value} value
|
|
89
83
|
* @param {mx.Uniform.name} name
|
|
90
84
|
* @param {mx.Uniforms} uniforms
|
|
91
|
-
* @param {
|
|
85
|
+
* @param {Loaders} loaders
|
|
92
86
|
* @param {string} searchPath
|
|
93
87
|
* @param {boolean} flipY
|
|
94
88
|
*/
|
|
95
|
-
function toThreeUniform(type, value, name, uniforms,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
switch (type)
|
|
99
|
-
{
|
|
89
|
+
function toThreeUniform(type: string, value: any, name: string, uniforms: any, loaders: Loaders, searchPath, flipY: boolean) {
|
|
90
|
+
|
|
91
|
+
switch (type) {
|
|
100
92
|
case 'float':
|
|
101
93
|
case 'integer':
|
|
102
94
|
case 'boolean':
|
|
103
|
-
|
|
95
|
+
return value;
|
|
104
96
|
break;
|
|
105
97
|
case 'vector2':
|
|
106
|
-
|
|
98
|
+
return fromVector(value, 2);
|
|
107
99
|
break;
|
|
108
100
|
case 'vector3':
|
|
109
101
|
case 'color3':
|
|
110
|
-
|
|
111
|
-
break;
|
|
102
|
+
return fromVector(value, 3);
|
|
112
103
|
case 'vector4':
|
|
113
104
|
case 'color4':
|
|
114
|
-
|
|
115
|
-
break;
|
|
105
|
+
return fromVector(value, 4);
|
|
116
106
|
case 'matrix33':
|
|
117
|
-
|
|
118
|
-
break;
|
|
107
|
+
return fromMatrix(value, 9);
|
|
119
108
|
case 'matrix44':
|
|
120
|
-
|
|
121
|
-
break;
|
|
109
|
+
return fromMatrix(value, 16);
|
|
122
110
|
case 'filename':
|
|
123
|
-
if (value)
|
|
124
|
-
{
|
|
111
|
+
if (value) {
|
|
125
112
|
// Cache / reuse texture to avoid reload overhead.
|
|
126
113
|
// Note: that data blobs and embedded data textures are not cached as they are transient data.
|
|
127
114
|
let checkCache = false;
|
|
128
115
|
let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value;
|
|
129
|
-
if (value.startsWith('blob:'))
|
|
130
|
-
{
|
|
116
|
+
if (value.startsWith('blob:')) {
|
|
131
117
|
texturePath = value;
|
|
132
118
|
if (debug) console.log('Load blob URL:', texturePath);
|
|
133
119
|
checkCache = false;
|
|
134
120
|
}
|
|
135
|
-
else if (value.startsWith('http'))
|
|
136
|
-
{
|
|
121
|
+
else if (value.startsWith('http')) {
|
|
137
122
|
texturePath = value;
|
|
138
123
|
if (debug) console.log('Load HTTP URL:', texturePath);
|
|
139
124
|
}
|
|
140
|
-
else if (value.startsWith('data:'))
|
|
141
|
-
{
|
|
125
|
+
else if (value.startsWith('data:')) {
|
|
142
126
|
texturePath = value;
|
|
143
127
|
checkCache = false;
|
|
144
128
|
if (debug) console.log('Load data URL:', texturePath);
|
|
145
129
|
}
|
|
146
130
|
const cachedTexture = checkCache && THREE.Cache.get(texturePath);
|
|
147
|
-
if (cachedTexture)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
outValue = cachedTexture;
|
|
151
|
-
if (debug) console.log('Use cached texture: ', texturePath, outValue);
|
|
131
|
+
if (cachedTexture) {
|
|
132
|
+
if (debug) console.log('Use cached texture: ', texturePath, cachedTexture);
|
|
133
|
+
return cachedTexture;
|
|
152
134
|
}
|
|
153
|
-
else
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
texturePath,
|
|
157
|
-
function (texture) {
|
|
158
|
-
if (debug) console.log('Load new texture: ' + texturePath, texture);
|
|
159
|
-
outValue = texture;
|
|
160
|
-
|
|
161
|
-
// Add texture to ThreeJS cache
|
|
162
|
-
if (checkCache)
|
|
163
|
-
THREE.Cache.add(texturePath, texture);
|
|
164
|
-
},
|
|
165
|
-
undefined,
|
|
166
|
-
function (error) {
|
|
167
|
-
console.error('Error loading texture: ', error);
|
|
168
|
-
});
|
|
169
|
-
|
|
135
|
+
else {
|
|
136
|
+
const texture = loaders.getTexture(texturePath);
|
|
137
|
+
if (checkCache) THREE.Cache.add(texturePath, texture);
|
|
170
138
|
// Set address & filtering mode
|
|
171
|
-
if (
|
|
172
|
-
|
|
139
|
+
if (texture) setTextureParameters(texture, name, uniforms, flipY);
|
|
140
|
+
return texture;
|
|
173
141
|
}
|
|
174
142
|
}
|
|
175
143
|
break;
|
|
176
144
|
case 'samplerCube':
|
|
177
145
|
case 'string':
|
|
178
|
-
|
|
146
|
+
return null;
|
|
179
147
|
default:
|
|
180
148
|
const key = type + ':' + name;
|
|
181
|
-
if (!valueTypeWarningMap.has(key))
|
|
182
|
-
{
|
|
149
|
+
if (!valueTypeWarningMap.has(key)) {
|
|
183
150
|
valueTypeWarningMap.set(key, true);
|
|
184
151
|
console.warn('MaterialX: Unsupported uniform type: ' + type + ' for uniform: ' + name, value);
|
|
185
152
|
}
|
|
186
|
-
|
|
153
|
+
return null;
|
|
187
154
|
}
|
|
188
|
-
|
|
189
|
-
return outValue;
|
|
190
155
|
}
|
|
191
156
|
|
|
192
|
-
const valueTypeWarningMap = new Map();
|
|
157
|
+
const valueTypeWarningMap = new Map<string, boolean>();
|
|
193
158
|
|
|
194
159
|
/**
|
|
195
160
|
* Get Three wrapping mode
|
|
196
161
|
* @param {mx.TextureFilter.wrap} mode
|
|
197
162
|
* @returns {THREE.Wrapping}
|
|
198
163
|
*/
|
|
199
|
-
function getWrapping(mode)
|
|
200
|
-
{
|
|
164
|
+
function getWrapping(mode) {
|
|
201
165
|
let wrap;
|
|
202
|
-
switch (mode)
|
|
203
|
-
{
|
|
166
|
+
switch (mode) {
|
|
204
167
|
case 1:
|
|
205
168
|
wrap = THREE.ClampToEdgeWrapping;
|
|
206
169
|
break;
|
|
@@ -222,12 +185,9 @@ function getWrapping(mode)
|
|
|
222
185
|
* @param {mx.TextureFilter.minFilter} type
|
|
223
186
|
* @param {mx.TextureFilter.generateMipmaps} generateMipmaps
|
|
224
187
|
*/
|
|
225
|
-
function getMinFilter(type, generateMipmaps)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
let filterType = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
|
|
229
|
-
if (type === 0)
|
|
230
|
-
{
|
|
188
|
+
function getMinFilter(type, generateMipmaps) {
|
|
189
|
+
let filterType: THREE.TextureFilter = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
|
|
190
|
+
if (type === 0) {
|
|
231
191
|
filterType = generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter;
|
|
232
192
|
}
|
|
233
193
|
return filterType;
|
|
@@ -240,25 +200,22 @@ function getMinFilter(type, generateMipmaps)
|
|
|
240
200
|
* @param {mx.Uniforms} uniforms
|
|
241
201
|
* @param {mx.TextureFilter.generateMipmaps} generateMipmaps
|
|
242
202
|
*/
|
|
243
|
-
function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true)
|
|
244
|
-
{
|
|
203
|
+
function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true) {
|
|
245
204
|
const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
|
|
246
205
|
const base = name.substring(0, idx) || name;
|
|
247
206
|
|
|
248
|
-
texture.generateMipmaps = generateMipmaps;
|
|
249
|
-
texture.wrapS = THREE.RepeatWrapping;
|
|
250
|
-
texture.wrapT = THREE.RepeatWrapping;
|
|
251
|
-
texture.magFilter = THREE.LinearFilter;
|
|
252
|
-
texture.flipY = flipY;
|
|
207
|
+
// texture.generateMipmaps = generateMipmaps;
|
|
208
|
+
// texture.wrapS = THREE.RepeatWrapping;
|
|
209
|
+
// texture.wrapT = THREE.RepeatWrapping;
|
|
210
|
+
// texture.magFilter = THREE.LinearFilter;
|
|
211
|
+
// texture.flipY = flipY;
|
|
253
212
|
|
|
254
|
-
if (uniforms.find(base + UADDRESS_MODE_SUFFIX))
|
|
255
|
-
{
|
|
213
|
+
if (uniforms.find(base + UADDRESS_MODE_SUFFIX)) {
|
|
256
214
|
const uaddressmode = uniforms.find(base + UADDRESS_MODE_SUFFIX).getValue().getData();
|
|
257
215
|
texture.wrapS = getWrapping(uaddressmode);
|
|
258
216
|
}
|
|
259
217
|
|
|
260
|
-
if (uniforms.find(base + VADDRESS_MODE_SUFFIX))
|
|
261
|
-
{
|
|
218
|
+
if (uniforms.find(base + VADDRESS_MODE_SUFFIX)) {
|
|
262
219
|
const vaddressmode = uniforms.find(base + VADDRESS_MODE_SUFFIX).getValue().getData();
|
|
263
220
|
texture.wrapT = getWrapping(vaddressmode);
|
|
264
221
|
}
|
|
@@ -270,8 +227,7 @@ function setTextureParameters(texture, name, uniforms, flipY = true, generateMip
|
|
|
270
227
|
/**
|
|
271
228
|
* Return the global light rotation matrix
|
|
272
229
|
*/
|
|
273
|
-
export function getLightRotation()
|
|
274
|
-
{
|
|
230
|
+
export function getLightRotation() {
|
|
275
231
|
return new THREE.Matrix4().makeRotationY(Math.PI / 2);
|
|
276
232
|
}
|
|
277
233
|
|
|
@@ -280,32 +236,27 @@ export function getLightRotation()
|
|
|
280
236
|
* @param {mx.Document} doc
|
|
281
237
|
* @returns {Array.<mx.Node>}
|
|
282
238
|
*/
|
|
283
|
-
export function findLights(doc)
|
|
284
|
-
|
|
285
|
-
let
|
|
286
|
-
for (let node of doc.getNodes())
|
|
287
|
-
{
|
|
239
|
+
export function findLights(doc: MaterialX.Document) {
|
|
240
|
+
let lights = new Array<any>;
|
|
241
|
+
for (let node of doc.getNodes()) {
|
|
288
242
|
if (node.getType() === "lightshader")
|
|
289
243
|
lights.push(node);
|
|
290
244
|
}
|
|
291
245
|
return lights;
|
|
292
246
|
}
|
|
293
247
|
|
|
248
|
+
let lightTypesBound = {};
|
|
249
|
+
|
|
294
250
|
/**
|
|
295
251
|
* Register lights in shader generation context
|
|
296
|
-
* @param {
|
|
297
|
-
* @param {Array.<mx.Node>} lights Light nodes
|
|
252
|
+
* @param {MaterialX.MODULE} mx MaterialX Module
|
|
298
253
|
* @param {mx.GenContext} genContext Shader generation context
|
|
299
|
-
* @returns {Array.<mx.Node>}
|
|
300
254
|
*/
|
|
301
|
-
export async function registerLights(mx
|
|
302
|
-
{
|
|
255
|
+
export async function registerLights(mx: MaterialX.MODULE, genContext: any): Promise<void> {
|
|
256
|
+
lightTypesBound = {};
|
|
257
|
+
const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
|
|
303
258
|
mx.HwShaderGenerator.unbindLightShaders(genContext);
|
|
304
|
-
|
|
305
|
-
const lightTypesBound = {};
|
|
306
|
-
const lightData = [];
|
|
307
259
|
let lightId = 1;
|
|
308
|
-
|
|
309
260
|
// All light types so that we have NodeDefs for them
|
|
310
261
|
const defaultLightRigXml = `<?xml version="1.0"?>
|
|
311
262
|
<materialx version="1.39">
|
|
@@ -323,7 +274,7 @@ export async function registerLights(mx, lights, genContext)
|
|
|
323
274
|
|
|
324
275
|
// Load default light rig XML to ensure we have all light types available
|
|
325
276
|
const lightRigDoc = mx.createDocument();
|
|
326
|
-
await mx.readFromXmlString(lightRigDoc, defaultLightRigXml);
|
|
277
|
+
await mx.readFromXmlString(lightRigDoc, defaultLightRigXml, "");
|
|
327
278
|
const document = mx.createDocument();
|
|
328
279
|
const stdlib = mx.loadStandardLibraries(genContext);
|
|
329
280
|
document.setDataLibrary(stdlib);
|
|
@@ -331,18 +282,19 @@ export async function registerLights(mx, lights, genContext)
|
|
|
331
282
|
const defaultLights = findLights(document);
|
|
332
283
|
if (debug) console.log("Default lights in MaterialX document", defaultLights);
|
|
333
284
|
|
|
285
|
+
// Loading a document seems to reset this option for some reason, so we set it again
|
|
286
|
+
genContext.getOptions().hwMaxActiveLightSources = maxLightCount;
|
|
287
|
+
|
|
334
288
|
// Register types only – we get these from the default light rig XML above
|
|
335
289
|
// This is needed to ensure that the light shaders are bound for each light type
|
|
336
|
-
for (let light of defaultLights)
|
|
337
|
-
{
|
|
290
|
+
for (let light of defaultLights) {
|
|
338
291
|
const lightDef = light.getNodeDef();
|
|
339
292
|
if (debug) console.log("Default light node definition", lightDef);
|
|
340
293
|
if (!lightDef) continue;
|
|
341
294
|
|
|
342
295
|
const lightName = lightDef.getName();
|
|
343
296
|
if (debug) console.log("Registering default light", { lightName, lightDef });
|
|
344
|
-
if (!lightTypesBound[lightName])
|
|
345
|
-
{
|
|
297
|
+
if (!lightTypesBound[lightName]) {
|
|
346
298
|
// TODO check if we need to bind light shader for each three.js light instead of once per type
|
|
347
299
|
if (debug) console.log("Bind light shader for node", { lightName, lightId, lightDef });
|
|
348
300
|
lightTypesBound[lightName] = lightId;
|
|
@@ -351,52 +303,40 @@ export async function registerLights(mx, lights, genContext)
|
|
|
351
303
|
}
|
|
352
304
|
|
|
353
305
|
if (debug) console.log("Light types bound in MaterialX context", lightTypesBound);
|
|
306
|
+
}
|
|
354
307
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
{
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const lightDirection = light.getValueElement("direction").getValue().getData().data();
|
|
371
|
-
const lightColor = light.getValueElement("color").getValue().getData().data();
|
|
372
|
-
const lightIntensity = light.getValueElement("intensity").getValue().getData();
|
|
373
|
-
|
|
374
|
-
let rotatedLightDirection = new THREE.Vector3(...lightDirection)
|
|
375
|
-
rotatedLightDirection.transformDirection(getLightRotation())
|
|
376
|
-
|
|
377
|
-
lightData.push({
|
|
378
|
-
type: lightTypesBound[nodeName],
|
|
379
|
-
direction: rotatedLightDirection,
|
|
380
|
-
color: new THREE.Vector3(...lightColor),
|
|
381
|
-
intensity: lightIntensity,
|
|
382
|
-
});
|
|
308
|
+
// Converts Three.js light type to MaterialX node name
|
|
309
|
+
function threeLightTypeToMaterialXNodeName(threeLightType) {
|
|
310
|
+
switch (threeLightType) {
|
|
311
|
+
case 'PointLight':
|
|
312
|
+
return 'ND_point_light';
|
|
313
|
+
case 'DirectionalLight':
|
|
314
|
+
return 'ND_directional_light';
|
|
315
|
+
case 'SpotLight':
|
|
316
|
+
return 'ND_spot_light';
|
|
317
|
+
default:
|
|
318
|
+
console.warn('MaterialX: Unsupported light type: ' + threeLightType);
|
|
319
|
+
return 'ND_point_light'; // Default to point light
|
|
383
320
|
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
type LightData = {
|
|
324
|
+
type: number, // Light type ID
|
|
325
|
+
position: THREE.Vector3, // Position in world space
|
|
326
|
+
direction: THREE.Vector3, // Direction in world space
|
|
327
|
+
color: THREE.Color, // Color of the light
|
|
328
|
+
intensity: number, // Intensity of the light
|
|
329
|
+
decay_rate: number, // Decay rate for point and spot lights
|
|
330
|
+
inner_angle: number, // Inner angle for spot lights
|
|
331
|
+
outer_angle: number, // Outer angle for spot lights
|
|
332
|
+
}
|
|
384
333
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
case 'SpotLight':
|
|
392
|
-
return 'ND_spot_light';
|
|
393
|
-
default:
|
|
394
|
-
console.warn('MaterialX: Unsupported light type: ' + threeLightType);
|
|
395
|
-
return 'ND_point_light'; // Default to point light
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
if (debug) console.log("Registering lights in MaterialX context", lights, lightData);
|
|
334
|
+
/**
|
|
335
|
+
* Update light data for shader uniforms
|
|
336
|
+
*/
|
|
337
|
+
export function getLightData(lights: any, genContext: any): { lightData: LightData[], lightCount: number } {
|
|
338
|
+
const lightData = new Array();
|
|
339
|
+
const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
|
|
400
340
|
|
|
401
341
|
// Three.js lights
|
|
402
342
|
for (let light of lights) {
|
|
@@ -408,50 +348,82 @@ export async function registerLights(mx, lights, genContext)
|
|
|
408
348
|
const lightDefinitionName = threeLightTypeToMaterialXNodeName(light.type);
|
|
409
349
|
|
|
410
350
|
if (!lightTypesBound[lightDefinitionName])
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
351
|
+
console.error("MaterialX: Light type not registered in context. Make sure to register light types before using them.", lightDefinitionName);
|
|
352
|
+
|
|
353
|
+
const wp = light.getWorldPosition(new THREE.Vector3());
|
|
354
|
+
const wd = getWorldDirection(light, new THREE.Vector3(0, 0, -1));
|
|
355
|
+
|
|
356
|
+
// Shader math from the generated MaterialX shader:
|
|
357
|
+
// float low = min(light.inner_angle, light.outer_angle);
|
|
358
|
+
// float high = light.inner_angle;
|
|
359
|
+
// float cosDir = dot(result.direction, -light.direction);
|
|
360
|
+
// float spotAttenuation = smoothstep(low, high, cosDir);
|
|
361
|
+
|
|
362
|
+
const outerAngleRad = light.angle;
|
|
363
|
+
const innerAngleRad = outerAngleRad * (1 - light.penumbra);
|
|
364
|
+
const inner_angle = Math.cos(innerAngleRad);
|
|
365
|
+
const outer_angle = Math.cos(outerAngleRad);
|
|
416
366
|
|
|
417
367
|
lightData.push({
|
|
418
368
|
type: lightTypesBound[lightDefinitionName],
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
369
|
+
position: wp.clone(),
|
|
370
|
+
direction: wd.clone(),
|
|
371
|
+
color: new THREE.Color().fromArray(light.color.toArray()),
|
|
372
|
+
// Luminous efficacy for converting radiant power in watts (W) to luminous flux in lumens (lm) at a wavelength of 555 nm.
|
|
373
|
+
// Also, three.js lights don't have PI scale baked in, but MaterialX does, so we need to divide by PI for point and spot lights.
|
|
374
|
+
intensity: light.intensity * (light.isPointLight ? 683.0 / 3.1415 : light.isSpotLight ? 683.0 / 3.1415 : 1.0),
|
|
375
|
+
decay_rate: 2.0,
|
|
376
|
+
// Approximations for testing – the relevant light has 61.57986...129.4445 as inner/outer spot angle
|
|
377
|
+
inner_angle: inner_angle,
|
|
378
|
+
outer_angle: outer_angle,
|
|
422
379
|
});
|
|
423
380
|
}
|
|
424
381
|
|
|
425
|
-
//
|
|
426
|
-
|
|
382
|
+
// Count the number of lights that are not empty
|
|
383
|
+
const lightCount = lightData.length;
|
|
384
|
+
|
|
385
|
+
// If we don't have enough entries in lightData, fill with empty lights
|
|
386
|
+
while (lightData.length < maxLightCount) {
|
|
387
|
+
const emptyLight = {
|
|
388
|
+
type: 0, // Default light type
|
|
389
|
+
position: new THREE.Vector3(0, 0, 0),
|
|
390
|
+
direction: new THREE.Vector3(0, 0, -1),
|
|
391
|
+
color: new THREE.Color(0, 0, 0),
|
|
392
|
+
intensity: 0.0,
|
|
393
|
+
decay_rate: 2.0,
|
|
394
|
+
inner_angle: 0.0,
|
|
395
|
+
outer_angle: 0.0,
|
|
396
|
+
};
|
|
397
|
+
lightData.push(emptyLight);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (debugUpdate) console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
|
|
427
401
|
|
|
428
|
-
return lightData;
|
|
402
|
+
return { lightData, lightCount };
|
|
429
403
|
}
|
|
430
404
|
|
|
431
405
|
/**
|
|
432
406
|
* Get uniform values for a shader
|
|
433
|
-
* @param {mx.shaderStage} shaderStage
|
|
434
|
-
* @param {THREE.TextureLoader} textureLoader
|
|
435
407
|
*/
|
|
436
|
-
export function getUniformValues(shaderStage,
|
|
437
|
-
{
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
{
|
|
445
|
-
for (let i = 0; i < uniforms.size(); ++i)
|
|
446
|
-
{
|
|
408
|
+
export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Loaders, searchPath: string, flipY: boolean) {
|
|
409
|
+
const threeUniforms = {};
|
|
410
|
+
|
|
411
|
+
const uniformBlocks = shaderStage.getUniformBlocks()
|
|
412
|
+
for (const [blockName, uniforms] of Object.entries(uniformBlocks)) {
|
|
413
|
+
// Seems struct uniforms (like in LightData) end up here as well, we should filter those out.
|
|
414
|
+
if (blockName === "LightData") continue;
|
|
415
|
+
|
|
416
|
+
if (!uniforms.empty()) {
|
|
417
|
+
for (let i = 0; i < uniforms.size(); ++i) {
|
|
447
418
|
const variable = uniforms.get(i);
|
|
448
419
|
const value = variable.getValue()?.getData();
|
|
449
420
|
const name = variable.getVariable();
|
|
421
|
+
if (debug) console.log("Adding uniform", { path: variable.getPath(), name, value, type: variable.getType().getName() });
|
|
450
422
|
threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
|
|
451
|
-
|
|
423
|
+
loaders, searchPath, flipY));
|
|
452
424
|
}
|
|
453
425
|
}
|
|
454
|
-
}
|
|
426
|
+
}
|
|
455
427
|
|
|
456
428
|
return threeUniforms;
|
|
457
429
|
}
|