MaterialXMaterials 0.0.1
Utilities for retrieving materials from remote servers
Loading...
Searching...
No Matches
JsMaterialXPhysicallyBased.js
1let MTLX_NODE_NAME_ATTRIBUTE = 'nodename';
2
13 url = '';
18 headers = {};
23 materials = null;
38 mx = null;
43 doc = null;
48 stdlib = null;
54
60 constructor(mtlx_module = null, mtlx_stdlib = null)
61 {
62 this.url = 'https://api.physicallybased.info/materials';
63 this.headers = { 'Accept': 'application/json' };
64
65 this.materials = null;
66 this.materialNames = [];
67
68 this.mxMaterialNames = [];
69 this.mx = null;
70 if (mtlx_module) {
71 this.mx = mtlx_module;
72 }
73 this.stdlib = null;
74 if (mtlx_stdlib) {
75 this.stdlib = mtlx_stdlib;
76 }
77 this.doc = null;
78
80 }
81
87 {
88 return this.materials
89 }
90
95 {
96 return this.materialNames
97 }
98
103 {
104 return this.doc;
105 }
106
112 {
113 if (this.doc) {
114 let errors = {}
115 let errorString = ''
116 var valid = this.doc.validate(errors);
117 if (!valid) {
118 errorString = errors.message;
119 }
120 return [valid, errorString]
121 }
122 return [false, 'No MaterialX document'];
123 }
124
130 getInputRemapping(shadingModel)
131 {
132 if (shadingModel in this.remapMap) {
133 return this.remapMap[shadingModel];
134 }
135 return {};
136 }
137
143 {
144 // Remap keys for Autodesk Standard Surface shader. How to verify this?
145 const standard_surface_remapKeys = {
146 'color': 'base_color',
147 'specularColor': 'specular_color',
148 'roughness': 'specular_roughness',
149 //'metalness': 'metalness',
150 'ior': 'specular_IOR',
151 //'transmission': 'transmission',
152 'transmission_color': 'transmission_color',
153 'thinFilmIor': 'thin_film_IOR',
154 'thinFilmThickness': 'thin_film_thickness',
155 'transmissionDispersion': 'transmission_dispersion',
156 }
157 // Remap keys for OpenPBR shading model.
158 const openpbr_remapKeys = {
159 'color': 'base_color',
160 'specularColor': 'specular_color',
161 'roughness': 'specular_roughness', // 'base_diffuse_roughness',
162 'metalness': 'base_metalness',
163 'ior': 'specular_ior',
164 'transmission': 'transmission_weight',
165 'transmission_color': 'transmission_color',
166 'subsurfaceRadius': 'subsurface_radius',
167 'thinFilmIor': 'thin_film_ior',
168 'thinFilmThickness': 'thin_film_thickness',
169 'transmissionDispersion': 'transmission_dispersion_scale',
170 }
171 // Remap keys for Khronos glTF shading model.
172 const gltf_remapKeys = {
173 'color': 'base_color',
174 'specularColor': 'specular_color',
175 'roughness': 'roughness',
176 'metalness': 'metallic',
177 'transmission_color': 'attenuation_color',
178 //'ior': 'ior',
179 //'transmission': 'transmission',
180 }
181
182 this.remapMap = {}
183 this.remapMap['standard_surface'] = standard_surface_remapKeys;
184 this.remapMap['gltf_pbr'] = gltf_remapKeys;
185 this.remapMap['open_pbr_surface'] = openpbr_remapKeys;
186 }
187
193 {
194 return new Promise((resolve, reject) => {
195 MaterialX().then((mtlx) => {
196 this.mx = mtlx;
197 resolve();
198 }).catch((error) => {
199 reject(error);
200 });
201 });
202 }
203
209 {
210 try {
211 this.materials = null
212 this.materialNames = [];
213
214 const response = await fetch(this.url, {
215 method: 'GET',
216 headers: this.headers
217 });
218
219 if (!response.ok) {
220 throw new Error('Network response was not ok ' + response.statusText);
221 }
222
223 this.materials = await response.json();
224 for (let i = 0; i < this.materials.length; i++) {
225 this.materialNames.push(this.materials[i]['name']);
226 }
227 return this.materials;
228 } catch (error) {
229 console.error('There has been a problem with your fetch operation:', error);
230 }
231
232 return null;
233 }
234
240 {
241 if (!this.mx) {
242 // Call the asynchronous function and then perform additional logic
243 this.loadMaterialX().then(() => {
244
245 this.esslgenerator = new this.mx.EsslShaderGenerator();
246 this.esslgenContext = new this.mx.GenContext(this.esslgenerator);
247 this.stdlib = this.mx.loadStandardLibraries(this.esslgenContext);
248 let children = this.stdlib.getChildren();
249 for (let i = 0; i < children.length; i++) {
250 let child = children[i];
251 child.setSourceUri('STDLIB_ELEMENT');
252 }
253
254 console.log("MaterialX is loaded");
255 }).catch((error) => {
256 console.error("Error loading MaterialX:", error);
257 });
258 }
259 }
260
267 {
268 return !elem.hasSourceUri()
269 }
270
276 {
277 if (!this.doc) {
278 console.error('No MaterialX document to convert');
279 return '';
280 }
281
282 // Create write options
283 const writeOptions = new this.mx.XmlWriteOptions();
284 writeOptions.writeXIncludeEnable = false;
285 //writeOptions.writeXIncludes = false;
286 writeOptions.elementPredicate = this.skipLibraryElement;
287
288 // Convert the MaterialX document to a string
289 const mtlx = this.mx.writeToXmlString(this.doc, writeOptions);
290 return mtlx;
291 }
292
298 addComment(doc, commentString)
299 {
300 let comment = doc.addChildOfCategory('comment')
301 comment.setDocString(commentString)
302 }
303
304
314 convertToMaterialX(shaderCategory, addAllInputs = false, materialNames = [], remapKeys = {}, shaderPreFix = '')
315 {
316 if (!this.mx) {
317 console.error('MaterialX module is not loaded');
318 return false;
319 }
320
321 if (!this.materials) {
322 console.warn('No Physically Based Materials to convert');
323 return false;
324 }
325
326 if (remapKeys.length == 0) {
327 remapKeys = this.getInputRemapping(shaderCategory);
328 }
329
330 // Create a dummy doc with the surface shader with all inputs
331 // as reference
332 let refDoc = this.mx.createDocument();
333 refDoc.importLibrary(this.stdlib);
334 const refNode = refDoc.addNode(shaderCategory, 'refShader', this.mx.SURFACE_SHADER_TYPE_STRING);
335 //refNode.addInputsFromNodeDef() -- This is missing from the JS API.
336 this.doc = this.mx.createDocument();
337
338 // Add header comments
339 this.addComment(this.doc, 'Physically Based Materials from https://api.physicallybased.info ');
340 this.addComment(this.doc, ' Processed via API and converted to MaterialX ');
341 this.addComment(this.doc, ' Target Shading Model: ' + shaderCategory);
342 this.addComment(this.doc, ' Utility Author: Bernard Kwok. kwokcb@gmail.com ');
343
344 // Add properties to the material
345 for (let i = 0; i < this.materials.length; i++) {
346 const mat = this.materials[i];
347 let matName = mat['name'];
348
349 // Filter by material name(s)
350 if (materialNames.length > 0 && !materialNames.includes(matName)) {
351 // Skip material
352 console.log('Skipping material:', matName);
353 continue;
354 }
355
356
357 if (shaderPreFix.length > 0) {
358 matName = shaderPreFix + '_' + matName;
359 }
360
361 const shaderName = this.doc.createValidChildName('SPB_' + matName);
362 this.addComment(this.doc, ' Generated shader: ' + shaderName + ' ');
363 const shaderNode = this.doc.addNode(shaderCategory, shaderName, this.mx.SURFACE_SHADER_TYPE_STRING);
364 let docString = mat['description'];
365 const refString = mat['reference'];
366 if (refString.length > 0) {
367 if (docString.length > 0) {
368 docString += '. ';
369 }
370 docString += 'Reference: ' + refString[0];
371 }
372 if (docString.length > 0) {
373 shaderNode.setDocString(docString);
374 }
375
376 // Create a new material
377 const materialName = this.doc.createValidChildName('MPB_' + matName);
378 this.addComment(this.doc, ' Generated material: ' + materialName + ' ');
379 const materialNode = this.doc.addNode(this.mx.SURFACE_MATERIAL_NODE_STRING, materialName, this.mx.MATERIAL_TYPE_STRING);
380 const shaderInput = materialNode.addInput(this.mx.SURFACE_SHADER_TYPE_STRING, this.mx.SURFACE_SHADER_TYPE_STRING);
381 shaderInput.setAttribute(MTLX_NODE_NAME_ATTRIBUTE, shaderNode.getName());
382
383 // Warning this is a bit bespoke for remapping keys
384 // to Autodesk Standard Surface shader inputs
385 const skipKeys = ['name', "density", "category", "description", "sources", "tags", "reference"];
386
387 let metallness = null;
388 let roughness = null;
389 let transmission_color = null;
390 let transmission = null;
391 Object.entries(mat).forEach(([key, value]) => {
392
393 if (!skipKeys.includes(key)) {
394
395 if (key == 'metalness') {
396 metallness = value;
397 //console.log('Metalness:', metallness);
398 }
399 if (key == 'roughness') {
400 roughness = value;
401 //console.log('Roughness:', roughness);
402 }
403 if (key == 'transmission') {
404 transmission = value;
405 //console.log('Transmission:', transmission);
406 }
407 if (key == 'color') {
408 transmission_color = value;
409 //console.log('Color:', color);
410 }
411
412 if (remapKeys[key]) {
413 key = remapKeys[key];
414 }
415
416 let refInput = refNode.getInput(key);
417 if (!refInput)
418 refInput = refNode.addInputFromNodeDef(key);
419 if (refInput) {
420 const input = shaderNode.addInput(key);
421 input.copyContentFrom(refInput);
422 if (input) {
423 // Convert number vector to string
424 if (Array.isArray(value)) {
425 value = value.join(',');
426 }
427 // Convert number to string
428 else if (typeof value === 'number') {
429 value = value.toString();
430 }
431 // Note: This API has side-effects as the
432 // type is set to "string" when the value is set. Thus
433 // we must explicitly set the type here.
434 input.setValueString(value, refInput.getType());
435 }
436 }
437 else {
438 //console.log('>>> Cannot create input:', key)
439 }
440 }
441 });
442
443 if (transmission !== null && metallness !== null && roughness !== null && transmission_color !== null)
444 {
445 if (metallness == 0 && roughness == 0)
446 {
447 if (remapKeys['transmission_color']) {
448 let inputName = remapKeys['transmission_color'];
449 let input = shaderNode.addInput(inputName);
450 if (input) {
451 let value = transmission_color.join(',');
452 console.log(`Add "${inputName}": "${value}"`);
453 input.setValueString(value, 'color3');
454 }
455 }
456 }
457 };
458 }
459 return true;
460 }
461
462}
Javascript class for querying materials from the Physically Based database and creating MaterialX mat...
skipLibraryElement(element)
Predicate to skip library elements.
getJSONMaterialNames()
Get list of the Physically Based Material names.
initializeInputRemapping()
Initialize the input remapping for different shading models.
getInputRemapping(shadingModel)
Get the remapping keys for a given shading model.
getJSON()
Get the Physically Based Materials as JSON.
getMaterialXDocument()
Get the MaterialX document.
remapMap
Remap keys for input values for different shading models.
materials
List of Physically Based Materials.
loadStandardLibraries()
Load the MaterialX standard libraries.
constructor(mtlx_module=null, mtlx_stdlib=null)
Constructor for the PhysicallyBasedMaterialLoader.
getMaterialXString()
Get the MaterialX document as a string.
async getPhysicallyBasedMaterials()
Get the Physically Based Materials from the API.
materialNames
List of Physically Based Material names.
mxMaterialNames
List of MaterialX Material names.
headers
Headers for the fetch operation.
url
URL to fetch the Physically Based Materials.
convertToMaterialX(shaderCategory, addAllInputs=false, materialNames=[], remapKeys={}, shaderPreFix='')
Convert the Physically Based Materials to MaterialX.
addComment(doc, commentString)
Add a comment to the MaterialX document.
validateDocument()
Validate the MaterialX document.