@needle-tools/materialx 1.0.1-next.2ca9014 → 1.0.1-next.64f3b67
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 +3 -1
- package/codegen/register_types.ts +4 -0
- package/index.ts +1 -1
- package/package.json +6 -2
- package/src/index.ts +2 -2
- package/src/loader/loader.needle.ts +27 -40
- package/src/loader/loader.three.ts +139 -409
- package/src/{helper.js → materialx.helper.ts} +144 -205
- package/src/materialx.material.ts +227 -0
- package/src/materialx.ts +143 -114
- package/src/materialx.types.d.ts +50 -0
- package/src/textureHelper.ts +6 -6
- package/src/utils.ts +39 -4
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
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,36 +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
|
-
// TODO Remove, not sure why we need that – something resets the value inbetween calls to registerLights
|
|
305
|
-
genContext.getOptions().hwMaxActiveLightSources = 4;
|
|
306
|
-
|
|
307
|
-
const lightTypesBound = {};
|
|
308
|
-
const lightData = [];
|
|
309
259
|
let lightId = 1;
|
|
310
|
-
let lightCount = 0;
|
|
311
|
-
const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
|
|
312
|
-
|
|
313
260
|
// All light types so that we have NodeDefs for them
|
|
314
261
|
const defaultLightRigXml = `<?xml version="1.0"?>
|
|
315
262
|
<materialx version="1.39">
|
|
@@ -327,7 +274,7 @@ export async function registerLights(mx, lights, genContext)
|
|
|
327
274
|
|
|
328
275
|
// Load default light rig XML to ensure we have all light types available
|
|
329
276
|
const lightRigDoc = mx.createDocument();
|
|
330
|
-
await mx.readFromXmlString(lightRigDoc, defaultLightRigXml);
|
|
277
|
+
await mx.readFromXmlString(lightRigDoc, defaultLightRigXml, "");
|
|
331
278
|
const document = mx.createDocument();
|
|
332
279
|
const stdlib = mx.loadStandardLibraries(genContext);
|
|
333
280
|
document.setDataLibrary(stdlib);
|
|
@@ -340,16 +287,14 @@ export async function registerLights(mx, lights, genContext)
|
|
|
340
287
|
|
|
341
288
|
// Register types only – we get these from the default light rig XML above
|
|
342
289
|
// This is needed to ensure that the light shaders are bound for each light type
|
|
343
|
-
for (let light of defaultLights)
|
|
344
|
-
{
|
|
290
|
+
for (let light of defaultLights) {
|
|
345
291
|
const lightDef = light.getNodeDef();
|
|
346
292
|
if (debug) console.log("Default light node definition", lightDef);
|
|
347
293
|
if (!lightDef) continue;
|
|
348
294
|
|
|
349
295
|
const lightName = lightDef.getName();
|
|
350
296
|
if (debug) console.log("Registering default light", { lightName, lightDef });
|
|
351
|
-
if (!lightTypesBound[lightName])
|
|
352
|
-
{
|
|
297
|
+
if (!lightTypesBound[lightName]) {
|
|
353
298
|
// TODO check if we need to bind light shader for each three.js light instead of once per type
|
|
354
299
|
if (debug) console.log("Bind light shader for node", { lightName, lightId, lightDef });
|
|
355
300
|
lightTypesBound[lightName] = lightId;
|
|
@@ -358,52 +303,40 @@ export async function registerLights(mx, lights, genContext)
|
|
|
358
303
|
}
|
|
359
304
|
|
|
360
305
|
if (debug) console.log("Light types bound in MaterialX context", lightTypesBound);
|
|
306
|
+
}
|
|
361
307
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
{
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const lightDirection = light.getValueElement("direction").getValue().getData().data();
|
|
378
|
-
const lightColor = light.getValueElement("color").getValue().getData().data();
|
|
379
|
-
const lightIntensity = light.getValueElement("intensity").getValue().getData();
|
|
380
|
-
|
|
381
|
-
let rotatedLightDirection = new THREE.Vector3(...lightDirection)
|
|
382
|
-
rotatedLightDirection.transformDirection(getLightRotation())
|
|
383
|
-
|
|
384
|
-
lightData.push({
|
|
385
|
-
type: lightTypesBound[nodeName],
|
|
386
|
-
direction: rotatedLightDirection,
|
|
387
|
-
color: new THREE.Vector3(...lightColor),
|
|
388
|
-
intensity: lightIntensity,
|
|
389
|
-
});
|
|
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
|
|
390
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
|
+
}
|
|
391
333
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
case 'SpotLight':
|
|
399
|
-
return 'ND_spot_light';
|
|
400
|
-
default:
|
|
401
|
-
console.warn('MaterialX: Unsupported light type: ' + threeLightType);
|
|
402
|
-
return 'ND_point_light'; // Default to point light
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
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;
|
|
407
340
|
|
|
408
341
|
// Three.js lights
|
|
409
342
|
for (let light of lights) {
|
|
@@ -415,76 +348,82 @@ export async function registerLights(mx, lights, genContext)
|
|
|
415
348
|
const lightDefinitionName = threeLightTypeToMaterialXNodeName(light.type);
|
|
416
349
|
|
|
417
350
|
if (!lightTypesBound[lightDefinitionName])
|
|
418
|
-
|
|
419
|
-
lightTypesBound[lightDefinitionName] = lightId;
|
|
420
|
-
const nodeDef = null;
|
|
421
|
-
mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
|
|
422
|
-
}
|
|
351
|
+
console.error("MaterialX: Light type not registered in context. Make sure to register light types before using them.", lightDefinitionName);
|
|
423
352
|
|
|
424
353
|
const wp = light.getWorldPosition(new THREE.Vector3());
|
|
425
|
-
const wd = getWorldDirection(light, new THREE.Vector3(0,0
|
|
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);
|
|
366
|
+
|
|
426
367
|
lightData.push({
|
|
427
368
|
type: lightTypesBound[lightDefinitionName],
|
|
428
369
|
position: wp.clone(),
|
|
429
370
|
direction: wd.clone(),
|
|
430
|
-
color: new THREE.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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,
|
|
435
379
|
});
|
|
436
380
|
}
|
|
437
381
|
|
|
438
382
|
// Count the number of lights that are not empty
|
|
439
|
-
lightCount = lightData.length;
|
|
383
|
+
const lightCount = lightData.length;
|
|
440
384
|
|
|
441
385
|
// If we don't have enough entries in lightData, fill with empty lights
|
|
442
|
-
|
|
443
|
-
{
|
|
386
|
+
while (lightData.length < maxLightCount) {
|
|
444
387
|
const emptyLight = {
|
|
445
388
|
type: 0, // Default light type
|
|
446
389
|
position: new THREE.Vector3(0, 0, 0),
|
|
447
390
|
direction: new THREE.Vector3(0, 0, -1),
|
|
448
|
-
color: new THREE.
|
|
449
|
-
intensity: 0,
|
|
450
|
-
decay_rate: 2,
|
|
451
|
-
inner_angle: 0,
|
|
452
|
-
outer_angle: 0,
|
|
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,
|
|
453
396
|
};
|
|
454
|
-
|
|
455
|
-
lightData.push(emptyLight);
|
|
456
|
-
}
|
|
397
|
+
lightData.push(emptyLight);
|
|
457
398
|
}
|
|
458
399
|
|
|
459
|
-
if (
|
|
400
|
+
if (debugUpdate) console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
|
|
460
401
|
|
|
461
402
|
return { lightData, lightCount };
|
|
462
403
|
}
|
|
463
404
|
|
|
464
405
|
/**
|
|
465
406
|
* Get uniform values for a shader
|
|
466
|
-
* @param {mx.shaderStage} shaderStage
|
|
467
|
-
* @param {THREE.TextureLoader} textureLoader
|
|
468
407
|
*/
|
|
469
|
-
export function getUniformValues(shaderStage,
|
|
470
|
-
{
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
{
|
|
478
|
-
for (let i = 0; i < uniforms.size(); ++i)
|
|
479
|
-
{
|
|
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) {
|
|
480
418
|
const variable = uniforms.get(i);
|
|
481
419
|
const value = variable.getValue()?.getData();
|
|
482
420
|
const name = variable.getVariable();
|
|
421
|
+
if (debug) console.log("Adding uniform", { path: variable.getPath(), name, value, type: variable.getType().getName() });
|
|
483
422
|
threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
|
|
484
|
-
|
|
423
|
+
loaders, searchPath, flipY));
|
|
485
424
|
}
|
|
486
425
|
}
|
|
487
|
-
}
|
|
426
|
+
}
|
|
488
427
|
|
|
489
428
|
return threeUniforms;
|
|
490
429
|
}
|