1961 @brief Convert a MaterialX document to glTF.
1962 @param doc: The MaterialX document to convert.
1963 @param gltfJson: The glTF document to write to.
1964 @param resetMaterials: Whether to clear any existing glTF materials.
1969 addInputsFromNodeDef = self.
_options[
'writeDefaultInputs']
1971 for material
in doc.getMaterialNodes():
1972 shaderNodes = mx.getShaderNodes(material)
1973 for shaderNode
in shaderNodes:
1974 category = shaderNode.getCategory()
1975 path = shaderNode.getNamePath()
1976 if category == MTLX_GLTF_PBR_CATEGORY
and path
not in pbrNodes:
1977 if addInputsFromNodeDef:
1978 hasNormal = shaderNode.getInput(
'normal')
1979 hasTangent = shaderNode.getInput(
'tangent')
1980 shaderNode.addInputsFromNodeDef()
1982 shaderNode.removeChild(
'tangent')
1984 shaderNode.removeChild(
'normal')
1985 pbrNodes[path] = shaderNode
1986 elif category == MTLX_UNLIT_CATEGORY_STRING
and path
not in unlitNodes:
1987 if addInputsFromNodeDef:
1988 hasNormal = shaderNode.getInput(
'normal')
1989 hasTangent = shaderNode.getInput(
'tangent')
1990 shaderNode.addInputsFromNodeDef()
1992 shaderNode.removeChild(
'tangent')
1994 shaderNode.removeChild(
'normal')
1995 unlitNodes[path] = shaderNode
1997 materials_count = len(pbrNodes) + len(unlitNodes)
1998 if materials_count == 0:
2003 if 'materials' in gltfJson
and not resetMaterials:
2004 materials = gltfJson[
'materials']
2006 materials = gltfJson[
'materials'] = list()
2009 if 'textures' in gltfJson:
2010 textures = gltfJson[
'textures']
2012 textures = gltfJson[
'textures'] = list()
2015 if 'images' in gltfJson:
2016 images = gltfJson[
'images']
2018 images = gltfJson[
'images'] = list()
2021 if 'samplers' in gltfJson:
2022 samplers = gltfJson[
'samplers']
2024 samplers = gltfJson[
'samplers'] = list()
2027 extensionsUsed =
None
2028 if not 'extensionsUsed' in gltfJson:
2029 gltfJson[
'extensionsUsed'] = []
2030 extensionsUsed = gltfJson[
'extensionsUsed']
2034 COLOR_SEMANTIC =
'color'
2036 for name
in unlitNodes:
2039 unlitExtension =
'KHR_materials_unlit'
2040 if not unlitExtension
in extensionsUsed:
2041 extensionsUsed.append(unlitExtension)
2042 mat_extensions = material[
'extensions'] = {}
2043 mat_extensions[unlitExtension] = {}
2045 unlitNode = unlitNodes[name]
2046 material[
'name'] = name
2047 roughness = material[
'pbrMetallicRoughness'] = {}
2049 base_color_set =
False
2050 base_color = [ 1.0, 1.0, 1.0, 1.0 ]
2052 imageNode = unlitNode.getConnectedNode(
'emission_color')
2055 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2056 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2057 filename = fileInput.getResolvedValueString()
2059 if len(filename) == 0:
2065 textures.append(texture)
2067 roughness[
'baseColorTexture'] = {}
2068 roughness[
'baseColorTexture'][
'index'] = len(textures) - 1
2073 color = unlitNode.getInputValue(
'emission_color')
2075 base_color[0] = color[0]
2076 base_color[1] = color[1]
2077 base_color[2] = color[2]
2078 base_color_set =
True
2081 color = unlitNode.getInputValue(
'emission_color')
2083 base_color[0] = color[0]
2084 base_color[1] = color[1]
2085 base_color[2] = color[2]
2086 base_color_set =
True
2088 value = unlitNode.getInputValue(
'opacity')
2090 base_color[3] = value
2091 base_color_set =
True
2094 roughness[
'baseColorFactor'] = base_color
2097 roughness[
'baseColorFactor'] = base_color
2099 materials.append(material)
2101 for name
in pbrNodes:
2106 if not 'extensions' in material:
2107 material[
'extensions'] = {}
2108 extensions = material[
'extensions']
2110 pbrNode = pbrNodes[name]
2112 print(
'- Convert MaterialX node to glTF:', pbrNode.getNamePath())
2115 material[
'name'] = name
2119 roughness = material[
'pbrMetallicRoughness'] = {}
2122 base_color = [ 1.0, 1.0, 1.0, 1.0 ]
2123 base_color_set =
False
2126 imageNode = pbrNode.getConnectedNode(
'base_color')
2128 if self.
_options[
'createProceduralTextures']:
2130 if imageNode.getParent().isA(mx.NodeGraph):
2131 imageGraph = imageNode.getParent()
2135 print(
'- Generate KHR procedurals for graph: ' + imageGraph.getName())
2136 self.
log(
'- Generate KHR procedurals for graph: ' + imageGraph.getName())
2141 extensionName =
'KHR_procedurals'
2142 if extensionName
not in extensionsUsed:
2143 extensionsUsed.append(extensionName)
2149 graphOutputs, procDict = self.
graphToJson(imageGraph, gltfJson, materials, textures, images, samplers)
2150 outputs = imageGraph.getOutputs()
2151 if len(outputs) > 0:
2152 connectionName = outputs[0].getNamePath()
2153 inputBaseColor = pbrNode.getInput(
'base_color')
2154 outputSpecifier = inputBaseColor.getAttribute(
'output')
2155 if len(outputSpecifier) > 0:
2156 connectionName = imageGraph.getNamePath() +
'/' + outputSpecifier
2159 baseColorEntry = roughness[
'baseColorTexture'] = {}
2160 baseColorEntry[
'index'] = 0
2161 if 'extensions' not in baseColorEntry:
2162 baseColorEntry[
'extensions'] = {}
2163 extensions = baseColorEntry[
'extensions']
2164 if extensionName
not in extensions:
2165 extensions[extensionName] = {}
2166 procExtensions = extensions[extensionName]
2167 procExtensions[
'index'] = procDict[connectionName]
2171 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2172 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2173 filename = fileInput.getResolvedValueString()
2175 if len(filename) == 0:
2181 textures.append(texture)
2183 roughness[
'baseColorTexture'] = {}
2184 roughness[
'baseColorTexture'][
'index'] = len(textures) - 1
2189 color = pbrNode.getInputValue(
'base_color')
2191 base_color[0] = color[0]
2192 base_color[1] = color[1]
2193 base_color[2] = color[2]
2194 base_color_set =
True
2197 color = pbrNode.getInputValue(
'base_color')
2199 base_color[0] = color[0]
2200 base_color[1] = color[1]
2201 base_color[2] = color[2]
2202 base_color_set =
True
2204 value = pbrNode.getInputValue(
'alpha')
2206 base_color[3] = value
2207 base_color_set =
True
2210 roughness[
'baseColorFactor'] = base_color
2217 metallicFactor = pbrNode.getInputValue(
'metallic')
2225 extractInputs = [
'metallic',
'roughness',
'occlusion' ]
2226 filenames = [ EMPTY_STRING, EMPTY_STRING, EMPTY_STRING ]
2227 imageNamePaths = [ EMPTY_STRING, EMPTY_STRING, EMPTY_STRING ]
2228 roughnessInputs = [
'metallicFactor',
'roughnessFactor',
'' ]
2230 IN_STRING = MTLX_IN_STRING
2233 extractCategory =
'extract'
2234 for e
in range(0, 3):
2235 inputName = extractInputs[e]
2236 pbrInput = pbrNode.getInput(inputName)
2239 connectedNode = pbrNode.getConnectedNode(inputName)
2241 if connectedNode.getCategory() == extractCategory:
2242 imageNode = connectedNode.getConnectedNode(IN_STRING)
2244 imageNode = connectedNode
2247 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2248 filename = EMPTY_STRING
2249 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2250 filename = fileInput.getResolvedValueString()
2251 filenames[e] = filename
2252 imageNamePaths[e] = imageNode.getNamePath()
2256 if len(roughnessInputs[e]):
2257 value = pbrInput.getValue()
2259 roughness[roughnessInputs[e]] = value
2269 metallicFilename = mx.FilePath(filenames[0])
2270 roughnessFilename = mx.FilePath(filenames[1])
2271 occlusionFilename = mx.FilePath(filenames[2])
2272 metallicFilename = self.
_options[
'searchPath'].find(metallicFilename)
2273 roughnessFilename = self.
_options[
'searchPath'].find(roughnessFilename)
2274 occlusionFilename = self.
_options[
'searchPath'].find(occlusionFilename)
2277 if metallicFilename == roughnessFilename:
2278 if roughnessFilename == occlusionFilename:
2280 if not roughnessFilename.isEmpty():
2281 print(
'- Append single ORM texture', roughnessFilename.asString())
2285 textures.append(texture)
2287 roughness[
'metallicRoughnessTexture'] = {}
2288 roughness[
'metallicRoughnessTexture'][
'index'] = len(textures) - 1
2291 if not metallicFilename.isEmpty():
2292 print(
'- Append single metallic-roughness texture', metallicFilename.asString())
2296 textures.append(texture)
2298 roughness[
'metallicRoughnessTexture'] = {}
2299 roughness[
'metallicRoughnessTexture'][
'index'] = len(textures) - 1
2302 if not occlusionFilename.isEmpty():
2303 print(
'- Append single occlusion texture', metallicFilename.asString())
2307 textures.append(texture)
2309 material[
'occlusionTexture'] = {}
2310 material[
'occlusionTexture'][
'index'] = len(textures) - 1
2313 elif not metallicFilename.isEmpty()
or not (roughnessFilename).isEmpty():
2314 loader = mx_render.StbImageLoader.create()
2315 handler = mx_render.ImageHandler.create(loader)
2316 handler.setSearchPath(self.
_options[
'searchPath'])
2318 ormFilename = roughnessFilename
if metallicFilename.isEmpty()
else metallicFilename
2323 roughnessImage = handler.acquireImage(roughnessFilename)
if not roughnessFilename.isEmpty()
else None
2325 imageWidth = max(roughnessImage.getWidth(), imageWidth)
2326 imageHeight = max(roughnessImage.getHeight(), imageHeight)
2328 metallicImage = handler.acquireImage(metallicFilename)
if not metallicFilename.isEmpty()
else None
2330 imageWidth = max(metallicImage.getWidth(), imageWidth)
2331 imageHeight = max(metallicImage.getHeight(), imageHeight)
2334 if (imageWidth * imageHeight) != 0:
2335 color = mx.Color4(0.0)
2336 outputImage = mx_render.createUniformImage(imageWidth, imageHeight,
2337 3, mx_render.BaseType.UINT8, color)
2339 uniformRoughnessColor = 1.0
2340 if 'roughnessFactor' in roughness:
2341 uniformRoughnessColor = roughness[
'roughnessFactor']
2343 roughness[
'roughnessFactor'] = 1.0
2345 uniformMetallicColor = 1.0
2346 if 'metallicFactor' in roughness:
2347 uniformMetallicColor = roughness[
'metallicFactor']
2349 roughness[
'metallicFactor'] = 1.0
2351 for y
in range(0, imageHeight):
2352 for x
in range(0, imageWidth):
2353 finalColor = outputImage.getTexelColor(x, y)
2354 finalColor[1] = roughnessImage.getTexelColor(x, y)[0]
if roughnessImage
else uniformRoughnessColor
2355 finalColor[2] = metallicImage.getTexelColor(x, y)[0]
if metallicImage
else uniformMetallicColor
2356 outputImage.setTexelColor(x, y, finalColor)
2358 ormFilename.removeExtension()
2359 ormfilePath = ormFilename.asString(mx.FormatPosix) +
'_combined.png'
2361 saved = loader.saveImage(ormfilePath, outputImage, flipImage)
2363 uri = mx.FilePath(ormfilePath).getBaseName()
2364 print(
'- Merged metallic-roughness to single texture:', uri,
'Saved: ', saved)
2368 textures.append(texture)
2370 roughness[
'metallicRoughnessTexture'] = {}
2371 roughness[
'metallicRoughnessTexture'][
'index'] = len(textures) - 1
2374 filename = EMPTY_STRING
2375 imageNode = pbrNode.getConnectedNode(
'normal')
2378 if imageNode.getCategory() ==
'normalmap':
2379 imageNode = imageNode.getConnectedNode(IN_STRING)
2381 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2382 filename = EMPTY_STRING
2383 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2384 filename = fileInput.getResolvedValueString()
2385 if len(filename) == 0:
2390 textures.append(texture)
2392 material[
'normalTexture'] = {}
2393 material[
'normalTexture'][
'index'] = len(textures) - 1
2398 outputExtension = {}
2400 'transmissionTexture',
'transmissionFactor', outputExtension, textures, images, samplers)
2401 if len(outputExtension) > 0:
2402 extensionName =
'KHR_materials_transmission'
2403 extensions[extensionName] = outputExtension
2404 if extensionName
not in extensionsUsed:
2405 extensionsUsed.append(extensionName)
2408 imageNode = pbrNode.getConnectedNode(
'specular_color')
2411 if imageNode.getParent().isA(mx.NodeGraph):
2412 imageGraph = imageNode.getParent()
2416 extensionName =
'KHR_procedurals'
2417 if extensionName
not in extensionsUsed:
2418 extensionsUsed.append(extensionName)
2419 if extensionName
not in extensions:
2420 extensions[extensionName] = {}
2421 outputExtension = extensions[extensionName]
2423 graphOutputs, procDict = self.
graphToJson(imageGraph, gltfJson, materials, textures, images, samplers)
2424 outputs = imageGraph.getOutputs()
2425 if len(outputs) > 0:
2426 connectionName = outputs[0].getNamePath()
2427 inputBaseColor = pbrNode.getInput(
'specular_color')
2428 outputSpecifier = inputBaseColor.getAttribute(
'output')
2429 if len(outputSpecifier) > 0:
2430 connectionName = imageGraph.getNamePath() +
'/' + outputSpecifier
2431 outputExtension[
'specularColorTexture'] = procDict[connectionName]
2434 outputExtension = {}
2436 'specularColorTexture',
'specularColorFactor', outputExtension, textures, images, samplers)
2438 'specularTexture',
'specularFactor', outputExtension, textures, images, samplers)
2439 if len(outputExtension) > 0:
2440 extensionName =
'KHR_materials_specular'
2441 if extensionName
not in extensionsUsed:
2442 extensionsUsed.append(extensionName)
2443 extensions[extensionName] = outputExtension
2447 'emissiveTexture',
'emissiveFactor', material, textures, images, samplers)
2450 outputExtension = {}
2452 '',
'emissiveStrength', outputExtension, textures, images, samplers)
2453 if len(outputExtension) > 0:
2454 extensionName =
'KHR_materials_emissive_strength'
2455 if extensionName
not in extensionsUsed:
2456 extensionsUsed.append(extensionName)
2457 extensions[extensionName] = outputExtension
2460 outputExtension = {}
2462 '',
'ior', outputExtension, textures, images, samplers)
2463 if len(outputExtension) > 0:
2464 extensionName =
'KHR_materials_ior'
2465 if extensionName
not in extensionsUsed:
2466 extensionsUsed.append(extensionName)
2467 extensions[extensionName] = outputExtension
2470 outputExtension = {}
2472 'sheenColorTexture',
'sheenColorFactor', outputExtension, textures, images, samplers)
2474 'sheenRoughnessTexture',
'sheenRoughnessFactor', outputExtension, textures, images, samplers)
2475 if len(outputExtension) > 0:
2476 extensionName =
'KHR_materials_sheen'
2477 if extensionName
not in extensionsUsed:
2478 extensionsUsed.append(extensionName)
2479 extensions[extensionName] = outputExtension
2482 outputExtension = {}
2484 'clearcoatTexture',
'clearcoatFactor', outputExtension, textures, images, samplers)
2486 'clearcoatRoughnessTexture',
'clearcoatRoughnessFactor', outputExtension, textures, images, samplers)
2487 if len(outputExtension) > 0:
2488 extensionName =
'KHR_materials_clearcoat'
2489 if extensionName
not in extensionsUsed:
2490 extensionsUsed.append(extensionName)
2491 extensions[extensionName] = outputExtension
2495 outputExtension = {}
2496 thicknessInput = pbrNode.getInput(
'thickness')
2499 thicknessNode = thicknessInput.getConnectedNode()
2500 thicknessFileName = EMPTY_STRING
2502 fileInput = thicknessNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2503 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2504 thicknessFileName = fileInput.getResolvedValueString()
2506 if len(thicknessFileName) > 0:
2509 textures.append(texture)
2511 outputExtension[
'thicknessTexture'] = {}
2512 outputExtension[
'thicknessTexture'][
'index'] = len(textures) - 1
2514 thicknessValue = thicknessInput.getValue()
2516 outputExtension[
'thicknessFactor'] = thicknessValue
2519 attenuationInput = pbrNode.getInput(
'attenuation_color')
2520 if attenuationInput:
2521 attenuationValue = attenuationInput.getValue()
2522 if attenuationValue:
2523 inputType = attenuationInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE)
2524 outputExtension[
'attenuationColor'] = self.
stringToScalar(attenuationInput.getValueString(), inputType)
2525 attenuationInput = pbrNode.getInput(
'attenuation_distance')
2526 if attenuationInput:
2527 attenuationValue = attenuationInput.getValue()
2528 if attenuationValue:
2529 outputExtension[
'attenuationDistance'] = attenuationValue
2531 if len(outputExtension) > 0:
2532 extensionName =
'KHR_materials_volume'
2533 if extensionName
not in extensionsUsed:
2534 extensionsUsed.append(extensionName)
2535 extensions[extensionName] = outputExtension
2538 filename = EMPTY_STRING
2539 imageNode = pbrNode.getConnectedNode(
'clearcoat_normal')
2542 if imageNode.getCategory() ==
'normalmap':
2543 imageNode = imageNode.getConnectedNode(IN_STRING)
2545 fileInput = imageNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2546 filename = EMPTY_STRING
2547 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2548 filename = fileInput.getResolvedValueString()
2549 if filename == EMPTY_STRING:
2555 textures.append(texture)
2557 outputExtension[
'clearcoatNormalTexture'] = {}
2558 outputExtension[
'clearcoatNormalTexture'][
'index'] = len(textures) - 1
2561 alphModeInput = pbrNode.getInput(
'alpha_mode')
2563 value = alphModeInput.getValue()
2566 alphaModeMap[0] =
'OPAQUE'
2567 alphaModeMap[1] =
'MASK'
2568 alphaModeMap[2] =
'BLEND'
2569 self.
writeFloatInput(pbrNode,
'alpha_mode',
'',
'alphaMode', material, textures, images, samplers, alphaModeMap)
2571 self.
writeFloatInput(pbrNode,
'alpha_cutoff',
'',
'alphaCutoff', material, textures, images, samplers)
2575 outputExtension = {}
2577 'iridescenceTexture',
'iridescenceFactor', outputExtension, textures, images, samplers)
2579 'iridescenceTexture',
'iridescenceIor', outputExtension, textures, images, samplers)
2580 if len(outputExtension) > 0:
2581 extensionName =
'KHR_materials_iridescence'
2582 if extensionName
not in extensionsUsed:
2583 extensionsUsed.append(extensionName)
2584 extensions[extensionName] = outputExtension
2591 thicknessInput = pbrNode.getInput(
'iridescence_thickness')
2594 thicknessNode = thicknessInput.getConnectedNode()
2595 thicknessFileName = mx.FilePath()
2597 fileInput = thicknessNode.getInput(mx.Implementation.FILE_ATTRIBUTE)
2598 thicknessFileName = EMPTY_STRING
2599 if fileInput
and fileInput.getAttribute(mx.TypedElement.TYPE_ATTRIBUTE) == mx.FILENAME_TYPE_STRING:
2600 thicknessFileName = fileInput.getResolvedValueString()
2604 textures.append(texture)
2606 outputExtension[
'iridescenceThicknessTexture'] = {}
2607 outputExtension[
'iridescenceThicknessTexture'][
'index'] = len(textures) - 1
2609 thickessInput = thicknessNode.getInput(
'thicknessMin')
2610 thicknessValue = thickessInput.getValue()
if thickessInput
else None
2612 outputExtension[
'iridescenceThicknessMinimum'] = thicknessValue
2613 thickessInput = thicknessNode.getInput(
'thicknessMax')
2614 thicknessValue = thickessInput.getValue()
if thickessInput
else None
2616 outputExtension[
'iridescenceThicknessMaximum'] = thicknessValue
2618 if len(material[
'extensions']) == 0:
2619 del material[
'extensions']
2621 materials.append(material)
2624 if len(gltfJson[
'extensionsUsed']) == 0:
2625 del gltfJson[
'extensionsUsed']
2626 if len(gltfJson[
'samplers']) == 0:
2627 del gltfJson[
'samplers']
2628 if len(gltfJson[
'images']) == 0:
2629 del gltfJson[
'images']
2630 if len(gltfJson[
'textures']) == 0:
2631 del gltfJson[
'textures']