1962 @brief Convert a MaterialX document to glTF.
1963 @param doc: The MaterialX document to convert.
1964 @param gltfJson: The glTF document to write to.
1965 @param resetMaterials: Whether to clear any existing glTF materials.
1970 addInputsFromNodeDef = self.
_options[
'writeDefaultInputs']
1972 for material
in doc.getMaterialNodes():
1973 shaderNodes = mx.getShaderNodes(material)
1974 for shaderNode
in shaderNodes:
1975 category = shaderNode.getCategory()
1976 path = shaderNode.getNamePath()
1977 if category == MTLX_GLTF_PBR_CATEGORY
and path
not in pbrNodes:
1978 if addInputsFromNodeDef:
1979 hasNormal = shaderNode.getInput(
'normal')
1980 hasTangent = shaderNode.getInput(
'tangent')
1981 shaderNode.addInputsFromNodeDef()
1983 shaderNode.removeChild(
'tangent')
1985 shaderNode.removeChild(
'normal')
1986 pbrNodes[path] = shaderNode
1987 elif category == MTLX_UNLIT_CATEGORY_STRING
and path
not in unlitNodes:
1988 if addInputsFromNodeDef:
1989 hasNormal = shaderNode.getInput(
'normal')
1990 hasTangent = shaderNode.getInput(
'tangent')
1991 shaderNode.addInputsFromNodeDef()
1993 shaderNode.removeChild(
'tangent')
1995 shaderNode.removeChild(
'normal')
1996 unlitNodes[path] = shaderNode
1998 materials_count = len(pbrNodes) + len(unlitNodes)
1999 if materials_count == 0:
2004 if 'materials' in gltfJson
and not resetMaterials:
2005 materials = gltfJson[
'materials']
2007 materials = gltfJson[
'materials'] = list()
2010 if 'textures' in gltfJson:
2011 textures = gltfJson[
'textures']
2013 textures = gltfJson[
'textures'] = list()
2016 if 'images' in gltfJson:
2017 images = gltfJson[
'images']
2019 images = gltfJson[
'images'] = list()
2022 if 'samplers' in gltfJson:
2023 samplers = gltfJson[
'samplers']
2025 samplers = gltfJson[
'samplers'] = list()
2028 extensionsUsed =
None
2029 if not 'extensionsUsed' in gltfJson:
2030 gltfJson[
'extensionsUsed'] = []
2031 extensionsUsed = gltfJson[
'extensionsUsed']
2035 COLOR_SEMANTIC =
'color'
2037 for name
in unlitNodes:
2040 unlitExtension =
'KHR_materials_unlit'
2041 if not unlitExtension
in extensionsUsed:
2042 extensionsUsed.append(unlitExtension)
2043 mat_extensions = material[
'extensions'] = {}
2044 mat_extensions[unlitExtension] = {}
2046 unlitNode = unlitNodes[name]
2047 material[
'name'] = name
2048 roughness = material[
'pbrMetallicRoughness'] = {}
2050 base_color_set =
False
2051 base_color = [ 1.0, 1.0, 1.0, 1.0 ]
2053 imageNode = unlitNode.getConnectedNode(
'emission_color')
2056 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2057 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2058 filename = fileInput.getResolvedValueString()
2060 if len(filename) == 0:
2066 textures.append(texture)
2068 roughness[
'baseColorTexture'] = {}
2069 roughness[
'baseColorTexture'][
'index'] = len(textures) - 1
2074 color = unlitNode.getInputValue(
'emission_color')
2076 base_color[0] = color[0]
2077 base_color[1] = color[1]
2078 base_color[2] = color[2]
2079 base_color_set =
True
2082 color = unlitNode.getInputValue(
'emission_color')
2084 base_color[0] = color[0]
2085 base_color[1] = color[1]
2086 base_color[2] = color[2]
2087 base_color_set =
True
2089 value = unlitNode.getInputValue(
'opacity')
2091 base_color[3] = value
2092 base_color_set =
True
2095 roughness[
'baseColorFactor'] = base_color
2098 roughness[
'baseColorFactor'] = base_color
2100 materials.append(material)
2102 for name
in pbrNodes:
2107 if not 'extensions' in material:
2108 material[
'extensions'] = {}
2109 extensions = material[
'extensions']
2111 pbrNode = pbrNodes[name]
2113 print(
'- Convert MaterialX node to glTF:', pbrNode.getNamePath())
2116 material[
'name'] = name
2120 roughness = material[
'pbrMetallicRoughness'] = {}
2123 base_color = [ 1.0, 1.0, 1.0, 1.0 ]
2124 base_color_set =
False
2127 imageNode = pbrNode.getConnectedNode(
'base_color')
2129 if self.
_options[
'createProceduralTextures']:
2131 if imageNode.getParent().isA(mx.NodeGraph):
2132 imageGraph = imageNode.getParent()
2136 print(
'- Generate KHR procedurals for graph: ' + imageGraph.getName())
2137 self.
log(
'- Generate KHR procedurals for graph: ' + imageGraph.getName())
2142 extensionName =
'KHR_procedurals'
2143 if extensionName
not in extensionsUsed:
2144 extensionsUsed.append(extensionName)
2150 graphOutputs, procDict = self.
graphToJson(imageGraph, gltfJson, materials, textures, images, samplers)
2151 outputs = imageGraph.getOutputs()
2152 if len(outputs) > 0:
2153 connectionName = outputs[0].getNamePath()
2154 inputBaseColor = pbrNode.getInput(
'base_color')
2155 outputSpecifier = inputBaseColor.getAttribute(
'output')
2156 if len(outputSpecifier) > 0:
2157 connectionName = imageGraph.getNamePath() +
'/' + outputSpecifier
2160 baseColorEntry = roughness[
'baseColorTexture'] = {}
2161 baseColorEntry[
'index'] = 0
2162 if 'extensions' not in baseColorEntry:
2163 baseColorEntry[
'extensions'] = {}
2164 extensions = baseColorEntry[
'extensions']
2165 if extensionName
not in extensions:
2166 extensions[extensionName] = {}
2167 procExtensions = extensions[extensionName]
2168 procExtensions[
'index'] = procDict[connectionName]
2172 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2173 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2174 filename = fileInput.getResolvedValueString()
2176 if len(filename) == 0:
2182 textures.append(texture)
2184 roughness[
'baseColorTexture'] = {}
2185 roughness[
'baseColorTexture'][
'index'] = len(textures) - 1
2190 color = pbrNode.getInputValue(
'base_color')
2192 base_color[0] = color[0]
2193 base_color[1] = color[1]
2194 base_color[2] = color[2]
2195 base_color_set =
True
2198 color = pbrNode.getInputValue(
'base_color')
2200 base_color[0] = color[0]
2201 base_color[1] = color[1]
2202 base_color[2] = color[2]
2203 base_color_set =
True
2205 value = pbrNode.getInputValue(
'alpha')
2207 base_color[3] = value
2208 base_color_set =
True
2211 roughness[
'baseColorFactor'] = base_color
2218 metallicFactor = pbrNode.getInputValue(
'metallic')
2226 extractInputs = [
'metallic',
'roughness',
'occlusion' ]
2227 filenames = [ EMPTY_STRING, EMPTY_STRING, EMPTY_STRING ]
2228 imageNamePaths = [ EMPTY_STRING, EMPTY_STRING, EMPTY_STRING ]
2229 roughnessInputs = [
'metallicFactor',
'roughnessFactor',
'' ]
2231 IN_STRING = MTLX_IN_STRING
2234 extractCategory =
'extract'
2235 for e
in range(0, 3):
2236 inputName = extractInputs[e]
2237 pbrInput = pbrNode.getInput(inputName)
2240 connectedNode = pbrNode.getConnectedNode(inputName)
2242 if connectedNode.getCategory() == extractCategory:
2243 imageNode = connectedNode.getConnectedNode(IN_STRING)
2245 imageNode = connectedNode
2248 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2249 filename = EMPTY_STRING
2250 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2251 filename = fileInput.getResolvedValueString()
2252 filenames[e] = filename
2253 imageNamePaths[e] = imageNode.getNamePath()
2257 if len(roughnessInputs[e]):
2258 value = pbrInput.getValue()
2260 roughness[roughnessInputs[e]] = value
2270 metallicFilename = mx.FilePath(filenames[0])
2271 roughnessFilename = mx.FilePath(filenames[1])
2272 occlusionFilename = mx.FilePath(filenames[2])
2273 metallicFilename = self.
_options[
'searchPath'].find(metallicFilename)
2274 roughnessFilename = self.
_options[
'searchPath'].find(roughnessFilename)
2275 occlusionFilename = self.
_options[
'searchPath'].find(occlusionFilename)
2278 if metallicFilename == roughnessFilename:
2279 if roughnessFilename == occlusionFilename:
2281 if not roughnessFilename.isEmpty():
2282 print(
'- Append single ORM texture', roughnessFilename.asString())
2286 textures.append(texture)
2288 roughness[
'metallicRoughnessTexture'] = {}
2289 roughness[
'metallicRoughnessTexture'][
'index'] = len(textures) - 1
2292 if not metallicFilename.isEmpty():
2293 print(
'- Append single metallic-roughness texture', metallicFilename.asString())
2297 textures.append(texture)
2299 roughness[
'metallicRoughnessTexture'] = {}
2300 roughness[
'metallicRoughnessTexture'][
'index'] = len(textures) - 1
2303 if not occlusionFilename.isEmpty():
2304 print(
'- Append single occlusion texture', metallicFilename.asString())
2308 textures.append(texture)
2310 material[
'occlusionTexture'] = {}
2311 material[
'occlusionTexture'][
'index'] = len(textures) - 1
2314 elif not metallicFilename.isEmpty()
or not (roughnessFilename).isEmpty():
2315 loader = mx_render.StbImageLoader.create()
2316 handler = mx_render.ImageHandler.create(loader)
2317 handler.setSearchPath(self.
_options[
'searchPath'])
2319 ormFilename = roughnessFilename
if metallicFilename.isEmpty()
else metallicFilename
2324 roughnessImage = handler.acquireImage(roughnessFilename)
if not roughnessFilename.isEmpty()
else None
2326 imageWidth = max(roughnessImage.getWidth(), imageWidth)
2327 imageHeight = max(roughnessImage.getHeight(), imageHeight)
2329 metallicImage = handler.acquireImage(metallicFilename)
if not metallicFilename.isEmpty()
else None
2331 imageWidth = max(metallicImage.getWidth(), imageWidth)
2332 imageHeight = max(metallicImage.getHeight(), imageHeight)
2335 if (imageWidth * imageHeight) != 0:
2336 color = mx.Color4(0.0)
2337 outputImage = mx_render.createUniformImage(imageWidth, imageHeight,
2338 3, mx_render.BaseType.UINT8, color)
2340 uniformRoughnessColor = 1.0
2341 if 'roughnessFactor' in roughness:
2342 uniformRoughnessColor = roughness[
'roughnessFactor']
2344 roughness[
'roughnessFactor'] = 1.0
2346 uniformMetallicColor = 1.0
2347 if 'metallicFactor' in roughness:
2348 uniformMetallicColor = roughness[
'metallicFactor']
2350 roughness[
'metallicFactor'] = 1.0
2352 for y
in range(0, imageHeight):
2353 for x
in range(0, imageWidth):
2354 finalColor = outputImage.getTexelColor(x, y)
2355 finalColor[1] = roughnessImage.getTexelColor(x, y)[0]
if roughnessImage
else uniformRoughnessColor
2356 finalColor[2] = metallicImage.getTexelColor(x, y)[0]
if metallicImage
else uniformMetallicColor
2357 outputImage.setTexelColor(x, y, finalColor)
2359 ormFilename.removeExtension()
2360 ormfilePath = ormFilename.asString(mx.FormatPosix) +
'_combined.png'
2362 saved = loader.saveImage(ormfilePath, outputImage, flipImage)
2364 uri = mx.FilePath(ormfilePath).getBaseName()
2365 print(
'- Merged metallic-roughness to single texture:', uri,
'Saved: ', saved)
2369 textures.append(texture)
2371 roughness[
'metallicRoughnessTexture'] = {}
2372 roughness[
'metallicRoughnessTexture'][
'index'] = len(textures) - 1
2375 filename = EMPTY_STRING
2376 imageNode = pbrNode.getConnectedNode(
'normal')
2379 if imageNode.getCategory() ==
'normalmap':
2380 imageNode = imageNode.getConnectedNode(IN_STRING)
2382 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2383 filename = EMPTY_STRING
2384 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2385 filename = fileInput.getResolvedValueString()
2386 if len(filename) == 0:
2391 textures.append(texture)
2393 material[
'normalTexture'] = {}
2394 material[
'normalTexture'][
'index'] = len(textures) - 1
2399 outputExtension = {}
2401 'transmissionTexture',
'transmissionFactor', outputExtension, textures, images, samplers)
2402 if len(outputExtension) > 0:
2403 extensionName =
'KHR_materials_transmission'
2404 extensions[extensionName] = outputExtension
2405 if extensionName
not in extensionsUsed:
2406 extensionsUsed.append(extensionName)
2409 imageNode = pbrNode.getConnectedNode(
'specular_color')
2412 if imageNode.getParent().isA(mx.NodeGraph):
2413 imageGraph = imageNode.getParent()
2417 extensionName =
'KHR_procedurals'
2418 if extensionName
not in extensionsUsed:
2419 extensionsUsed.append(extensionName)
2420 if extensionName
not in extensions:
2421 extensions[extensionName] = {}
2422 outputExtension = extensions[extensionName]
2424 graphOutputs, procDict = self.
graphToJson(imageGraph, gltfJson, materials, textures, images, samplers)
2425 outputs = imageGraph.getOutputs()
2426 if len(outputs) > 0:
2427 connectionName = outputs[0].getNamePath()
2428 inputBaseColor = pbrNode.getInput(
'specular_color')
2429 outputSpecifier = inputBaseColor.getAttribute(
'output')
2430 if len(outputSpecifier) > 0:
2431 connectionName = imageGraph.getNamePath() +
'/' + outputSpecifier
2432 outputExtension[
'specularColorTexture'] = procDict[connectionName]
2435 outputExtension = {}
2437 'specularColorTexture',
'specularColorFactor', outputExtension, textures, images, samplers)
2439 'specularTexture',
'specularFactor', outputExtension, textures, images, samplers)
2440 if len(outputExtension) > 0:
2441 extensionName =
'KHR_materials_specular'
2442 if extensionName
not in extensionsUsed:
2443 extensionsUsed.append(extensionName)
2444 extensions[extensionName] = outputExtension
2448 'emissiveTexture',
'emissiveFactor', material, textures, images, samplers)
2451 outputExtension = {}
2453 '',
'emissiveStrength', outputExtension, textures, images, samplers)
2454 if len(outputExtension) > 0:
2455 extensionName =
'KHR_materials_emissive_strength'
2456 if extensionName
not in extensionsUsed:
2457 extensionsUsed.append(extensionName)
2458 extensions[extensionName] = outputExtension
2461 outputExtension = {}
2463 '',
'ior', outputExtension, textures, images, samplers)
2464 if len(outputExtension) > 0:
2465 extensionName =
'KHR_materials_ior'
2466 if extensionName
not in extensionsUsed:
2467 extensionsUsed.append(extensionName)
2468 extensions[extensionName] = outputExtension
2471 outputExtension = {}
2473 'sheenColorTexture',
'sheenColorFactor', outputExtension, textures, images, samplers)
2475 'sheenRoughnessTexture',
'sheenRoughnessFactor', outputExtension, textures, images, samplers)
2476 if len(outputExtension) > 0:
2477 extensionName =
'KHR_materials_sheen'
2478 if extensionName
not in extensionsUsed:
2479 extensionsUsed.append(extensionName)
2480 extensions[extensionName] = outputExtension
2483 outputExtension = {}
2485 'clearcoatTexture',
'clearcoatFactor', outputExtension, textures, images, samplers)
2487 'clearcoatRoughnessTexture',
'clearcoatRoughnessFactor', outputExtension, textures, images, samplers)
2488 if len(outputExtension) > 0:
2489 extensionName =
'KHR_materials_clearcoat'
2490 if extensionName
not in extensionsUsed:
2491 extensionsUsed.append(extensionName)
2492 extensions[extensionName] = outputExtension
2496 outputExtension = {}
2497 thicknessInput = pbrNode.getInput(
'thickness')
2500 thicknessNode = thicknessInput.getConnectedNode()
2501 thicknessFileName = EMPTY_STRING
2503 fileInput = thicknessNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2504 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2505 thicknessFileName = fileInput.getResolvedValueString()
2507 if len(thicknessFileName) > 0:
2510 textures.append(texture)
2512 outputExtension[
'thicknessTexture'] = {}
2513 outputExtension[
'thicknessTexture'][
'index'] = len(textures) - 1
2515 thicknessValue = thicknessInput.getValue()
2517 outputExtension[
'thicknessFactor'] = thicknessValue
2520 attenuationInput = pbrNode.getInput(
'attenuation_color')
2521 if attenuationInput:
2522 attenuationValue = attenuationInput.getValue()
2523 if attenuationValue:
2524 inputType = attenuationInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE)
2525 outputExtension[
'attenuationColor'] = self.
stringToScalar(attenuationInput.getValueString(), inputType)
2526 attenuationInput = pbrNode.getInput(
'attenuation_distance')
2527 if attenuationInput:
2528 attenuationValue = attenuationInput.getValue()
2529 if attenuationValue:
2530 outputExtension[
'attenuationDistance'] = attenuationValue
2532 if len(outputExtension) > 0:
2533 extensionName =
'KHR_materials_volume'
2534 if extensionName
not in extensionsUsed:
2535 extensionsUsed.append(extensionName)
2536 extensions[extensionName] = outputExtension
2539 filename = EMPTY_STRING
2540 imageNode = pbrNode.getConnectedNode(
'clearcoat_normal')
2543 if imageNode.getCategory() ==
'normalmap':
2544 imageNode = imageNode.getConnectedNode(IN_STRING)
2546 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2547 filename = EMPTY_STRING
2548 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2549 filename = fileInput.getResolvedValueString()
2550 if filename == EMPTY_STRING:
2556 textures.append(texture)
2558 outputExtension[
'clearcoatNormalTexture'] = {}
2559 outputExtension[
'clearcoatNormalTexture'][
'index'] = len(textures) - 1
2562 alphModeInput = pbrNode.getInput(
'alpha_mode')
2564 value = alphModeInput.getValue()
2567 alphaModeMap[0] =
'OPAQUE'
2568 alphaModeMap[1] =
'MASK'
2569 alphaModeMap[2] =
'BLEND'
2570 self.
writeFloatInput(pbrNode,
'alpha_mode',
'',
'alphaMode', material, textures, images, samplers, alphaModeMap)
2572 self.
writeFloatInput(pbrNode,
'alpha_cutoff',
'',
'alphaCutoff', material, textures, images, samplers)
2576 outputExtension = {}
2578 'iridescenceTexture',
'iridescenceFactor', outputExtension, textures, images, samplers)
2580 'iridescenceTexture',
'iridescenceIor', outputExtension, textures, images, samplers)
2581 if len(outputExtension) > 0:
2582 extensionName =
'KHR_materials_iridescence'
2583 if extensionName
not in extensionsUsed:
2584 extensionsUsed.append(extensionName)
2585 extensions[extensionName] = outputExtension
2592 thicknessInput = pbrNode.getInput(
'iridescence_thickness')
2595 thicknessNode = thicknessInput.getConnectedNode()
2596 thicknessFileName = mx.FilePath()
2598 fileInput = thicknessNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2599 thicknessFileName = EMPTY_STRING
2600 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2601 thicknessFileName = fileInput.getResolvedValueString()
2605 textures.append(texture)
2607 outputExtension[
'iridescenceThicknessTexture'] = {}
2608 outputExtension[
'iridescenceThicknessTexture'][
'index'] = len(textures) - 1
2610 thickessInput = thicknessNode.getInput(
'thicknessMin')
2611 thicknessValue = thickessInput.getValue()
if thickessInput
else None
2613 outputExtension[
'iridescenceThicknessMinimum'] = thicknessValue
2614 thickessInput = thicknessNode.getInput(
'thicknessMax')
2615 thicknessValue = thickessInput.getValue()
if thickessInput
else None
2617 outputExtension[
'iridescenceThicknessMaximum'] = thicknessValue
2619 if len(material[
'extensions']) == 0:
2620 del material[
'extensions']
2622 materials.append(material)
2625 if len(gltfJson[
'extensionsUsed']) == 0:
2626 del gltfJson[
'extensionsUsed']
2627 if len(gltfJson[
'samplers']) == 0:
2628 del gltfJson[
'samplers']
2629 if len(gltfJson[
'images']) == 0:
2630 del gltfJson[
'images']
2631 if len(gltfJson[
'textures']) == 0:
2632 del gltfJson[
'textures']