MaterialX介绍

Other2257 字

Houdini终于从19.0版开始支持MaterialX(1.38版):
在:https://www.sidefx.com/docs/houdini/solaris/materialx
所以我想写一些关于MaterialX的东西。

目前的Houdini虽然支持导入MaterialX,不支持导出。也许它将来会支持出口。否则无法使用。
看H19.0.440的更新

MaterialX vops 有一个 RMB > Save > MaterialX... 菜单项,用于将着色器网络保存到 .mtlx 文件。

您现在可以导出到 MaterialX。

此外,大约有 148 个由 MaterialX 准备的标准节点(在官方 MaterialX 中内联或 OSL 中编写)作为 VOP 实现。

当我尝试放置它时...... 看起来 MaterialX 中作为标准提供的大多数节点都是用 VOP 实现的,除了 Lama 节点。 可用的节点不多。有吸引力的节点也很少。 但是你要注意什么? 此 MaterialX 标准曲面的规格在https://autodesk.github.io/standard-surface/中有详细说明。 Karma 的 MaterialX 标准曲面: Arnold 的 aiStandardSurace 参数: 参数完全相同。 换句话说,Karma 和 Arnold 现在可以使用通用的 Uber 着色器。


standard_surface

此外,AMD 的 GPUOpen 站点 ( https://matlib.gpuopen.com/main/materials/all ) 发布了大量的 MaterialX 文件。

什么是 MaterialX?

什么是 MaterialX?我认为有些人会成为这样的人。
这里有一些我想介绍的网站。

MaterialX 很难解释,因为您必须多次阅读规范才能理解它,但这里是我发布的翻译介绍的一部分。

1.1. 简介

许多 CG 制作工作室为每个制作流程使用涉及多个软件工具的工作流程。此外,由于需要在多个设施之间共享和外包大量工作,因此其他部门和工作室需要使用不同的软件包和渲染系统的完全外观开发模型。必须通过。此外,工作室渲染管道过去使用由经验丰富的程序员开发并由技术总监修改的整体着色器。纹理和着色器之间的预硬化连接和硬编码纹理颜色校正选项现在可用于通过图像处理树和混合运算符将输入纹理图像和程序纹理生成器转换为各种着色器。灵活的基于节点图的着色器网络,可连接和构建任意输入。

至少需要四种不同的相关数据关系来指定 CG 对象的完整“外观”:

根据图像处理网络是如何使用源、运算符、连接和参数构建的,输出空间可变数据流的数量会有所不同。 不同的贴图类型取决于与纹理文件名或 ID 关联的几何特定信息。 根据空间可变数据流、统一值和 BxDF 着色器输入之间的关系,您可以定义不同数量的材质。 根据材质与特定几何图形的关联方式,您创建的外观数量会有所不同。

据我们所知,没有通用的开放标准来传输上述所有数据关系。每个应用程序都有自己的文件格式来存储这些信息,但这些格式是私有的、专有的,并且没有充分指定和实施以完全打开和重现应用程序。不是... 因此,要指定由着色器网络构造的 CG 对象的“外观”,可以将这种外观(或外观的子组件)从一个软件包传递到另一个软件包或在安装之间传递。我们需要一个定义明确的标准,独立于平台。

1.2. 提案

我们提出了一个新的 Material Content Schema、MaterialX 和一个基于 XML 的文件,用于读取和写入该 MaterialX 内容。MaterialX 模式定义了一些主要元素类型和一些互补的子元素类型。主要元素类型有:

  • 定义数据处理图的标准节点集
  • 使用 BxDF 着色器和自定义处理运算符扩展标准节点集。
  • 定义绑定到特定统一参数值和空间变化输入数据流的着色器实例。
  • 定义可以从节点图中引用的几何属性。
  • 定义附加到几何体的材料和属性的特定组合。
    MTLX 文件是表示 MaterialX 文档的标准 XML 文件。此处使用的 XML 元素和属性对应于 MaterialX 元素和属性。一个 MTLX 文件可以是一个完全独立的文件,也可以拆分为多个文件以共享和重用组件。

粗略地说,它是一种使用 XML 格式文件在不同 DCC 之间交换数据的机制,用于着色器图形定义和材质分配。

例如,假设您在 Maya 中为角色建模,在 Arnold 中构建着色图,然后将该材质分配给模型。
Maya 场景可以使用 Maya 进行渲染,但是如果您想像使用另一个 DCC(Houdini 或 Katana)进行外观开发的情况,您可以理解为什么需要 MaterialX。
当您通过 FBX 或 Alembic 将 Maya 场景传递到另一个 DCC 时会发生什么?几何会通过,但着色器图不会通过,对吧?
好吧,在另一个 DCC 中重建着色器图并重新分配材质是一项非常低效的任务。
此外,代理(Arnold 术语中的 Stand-In,Maya 术语中的 GPU 缓存)无法直接访问几何体。
如果渲染器和Arnold统一,可以不使用MaterialX,使用Arnold标准格式ass或者USD来解决这个问题,但是如果渲染器不同呢?
不同的渲染器提供不同的着色器。
官方的 MaterialX 提供了基于 OSL 的标准节点,因此如果您创建一个仅包含这些标准节点的着色器图,您可以使用支持 MaterialX 的渲染器按原样渲染。
Houdini 的 Karma 在 VOP 中使用其标准节点,而不是在其 OSL 中。

使用MaterialXViewer

有一个查看器可以查看 MaterialX 着色器图并将着色器从 MaterialX 转换为 OSL/MDL/GLSL,所以让我们使用这个查看器来玩一下 MaterialX。1.首先,根据自己机器的环境,从https://github.com/materialx/MaterialX/releases
下载MaterialXViewer 。 2.zip解压到任意位置时,内容如下。执行文件夹中的MaterialXView.exe 启动MaterialXViewer。 3. MaterialXViewer的使用方法请参考以下文档。https://github.com/materialx/MaterialX/blob/main/documents/DeveloperGuide/Viewer.md 4.点击Load Material按钮,选择下面的一个MaterialX示例文件,我们来看看。在查看器中按一个键会将OSL 文件转换为相同的 MaterialX 文件位置,将键转换为 GLSL 文件,将键转换为 MDL 文件。

MaterialXViewer安装目录
/resources/Materials/Examples/StandardSurface

MaterialXViewer 可以读取 MaterialX 文件并将其转换为 OSL/GLSL/MDL 文件(最初它是一个名为 ShaderX 的项目,但在我知道之前它已与 MaterialX 项目合并)。
此 MaterialXViewer 无法读取任何 MaterialX 文件,并且由于它在内部通过 MaterialXGenGLSL 显示 GLSL,因此无法显示具有光线跟踪处理节点的 MaterialX 文件,例如 Ambient Occlusion。此外,无法显示 MaterialX 标准节点以外的节点,例如带有 Arnold 节点的 MaterialX。作为一个例外,可以显示 aiStandardSurface。这是因为它对应于 MaterialX 标准曲面。

在Solaris上导入MaterialX文件

要加载 MaterialX 文件,只需在参考 LOP 中指定该文件。
现在材质已添加到场景图树中,您可以在“指定材质 LOP”中将其指定给目标 prim 并进行渲染。

导出MaterialX文件

我想自己导出 MaterialX,而不是标准的 Houdini 导出器。
如果在Python shell 中键入,import MaterialXMaterialX 模块可以作为标准使用。 这允许您创建自己的导出器。 由于 MaterialX 文件是 XML 格式的,所以我认为那些处理过 XML 解析器的人可以直观地控制它们。 这次我想编写一个 Python 脚本,将在 Houdini 的 mat 上下文中构建的 MaterialX 节点图导出到 MaterialX 文件中。

将Houdini内置的MaterialX节点图导出到MaterialX文件

我已经构建了一个 MaterialX 节点图,如下所示。
构建 MaterialX 节点图时要记住的一件事是输入和输出类型必须匹配。一种常见的做法是将vector3的输出连接到color3的输入。请注意,这往往与其他节点图一起使用。
我想用我自己的 Python 脚本将它导出到 MaterialX 文件中。 以 MaterialX 格式查看,这是一个必需的表面材质元素,其节点是通过该方法添加的。通过该方法添加 单个节点的 Node 元素。 MaterialX 具有与 Houdini 中的子网相对应的 NodeGraph 元素。 所以通过方法添加子网节点。

MtlX Surface MaterialaddMaterial
addNode

addNodeGraph

要将Houdini中创建的MaterialX节点图导出到MaterialX,首先需要从MaterialX VOP节点检查MaterialX节点类型和输出类型。我这次做的脚本是根据MaterialX VOP的节点类型名称和签名参数判断创建的。
这是我根据以上内容整理的 Python 脚本。
/stage/一个 mtlx 文件会自动生成在与直接在 context 下materialx命名的 Material Network 中的MtlX Surface Material每个节点的 hip 文件相同的位置。

import hou
import MaterialX as mx
import re

signatureLabeltoValueMap ={
    "Boolean (Float)":"boolean_float",
    "Boolean":"boolean",
    "BSDF (C)":"bsdfC",
    "BSDF (F)":"bsdfF",
    "BSDF":"bsdf",
    "Color (B)":"color3B",
    "Color (Color 4)":"color3_color4",
    "Color (Color Array)":"color3_color3array",
    "Color (Color)":"color3_color3",
    "Color (FA)":"color3FA",
    "Color (Float)":"color3_float",
    "Color (I)":"color3I",
    "Color (Vector 2)":"color3_vector2",
    "Color (Vector 3)":"color3_vector3",
    "Color (Vector 4)":"color3_vector4",
    "Color 4 (B)":"color4B",
    "Color 4 (CF)":"color4CF",
    "Color 4 (Color 4 Array)":"color4_color4array",
    "Color 4 (Color 4)":"color4_color4",
    "Color 4 (Color)":"color4_color3",
    "Color 4 (FA)":"color4FA",
    "Color 4 (Float)":"color4_float",
    "Color 4 (I)":"color4I",
    "Color 4 (Vector 2)":"color4_vector2",
    "Color 4 (Vector 3)":"color4_vector3",
    "Color 4 (Vector 4)":"color4_vector4",
    "Color 4 Array (Color 4 Array)":"color4array_color4array",
    "Color 4":"color4",
    "Color Array (Color Array)":"color3array_color3array",
    "Color":"color3",
    "Displacement Shader (F)":"displacementshaderF",
    "Displacement Shader (V)":"displacementshaderV",
    "Displacement Shader":"displacementshader",
    "EDF (C)":"edfC",
    "EDF (F)":"edfF",
    "EDF":"edf",
    "filename":"filename",
    "Float (B)":"floatB",
    "Float (Color 4)":"float_color4",
    "Float (Color)":"float_color3",
    "Float (Float Array)":"float_floatarray",
    "Float (I)":"floatI",
    "Float (Vector 2)":"float_vector2",
    "Float (Vector 3)":"float_vector3",
    "Float (Vector 4)":"float_vector4",
    "Float Array (Float Array)":"floatarray_floatarray",
    "Float":"float",
    "Integer (Float)":"integer_float",
    "Integer (Integer Array)":"integer_integerarray",
    "Integer Array (Integer Array)":"integerarray_integerarray",
    "Integer":"integer",
    "Light Shader":"lightshader",
    "Matrix 3x3 (FA)":"matrix33FA",
    "Matrix 3x3":"matrix33",
    "Matrix 4x4 (FA)":"matrix44FA",
    "Matrix 4x4":"matrix44",
    "String (String Array)":"string_stringarray",
    "String Array (String Array)":"stringarray_stringarray",
    "String":"string",
    "Surface Shader (C)":"surfaceshaderC",
    "Surface Shader (F)":"surfaceshaderF",
    "Surface Shader":"surfaceshader",
    "VDF (C)":"vdfC",
    "VDF (F)":"vdfF",
    "VDF":"vdf",
    "Vector 2 (B)":"vector2B",
    "Vector 2 (Color 4)":"vector2_color4",
    "Vector 2 (Color)":"vector2_color3",
    "Vector 2 (FA)":"vector2FA",
    "Vector 2 (Float)":"vector2_float",
    "Vector 2 (I)":"vector2I",
    "Vector 2 (M3)":"vector2M3",
    "Vector 2 (Vector 2 Array)":"vector2_vector2array",
    "Vector 2 (Vector 2)":"vector2_vector2",
    "Vector 2 (Vector 3)":"vector2_vector3",
    "Vector 2 (Vector 4)":"vector2_vector4",
    "Vector 2 Array (Vector 2 Array)":"vector2array_vector2array",
    "Vector 2":"vector2",
    "Vector 3 (B)":"vector3B",
    "Vector 3 (Color 4)":"vector3_color4",
    "Vector 3 (Color)":"vector3_color3",
    "Vector 3 (FA)":"vector3FA",
    "Vector 3 (Float)":"vector3_float",
    "Vector 3 (I)":"vector3I",
    "Vector 3 (M4)":"vector3M4",
    "Vector 3 (Vector 2)":"vector3_vector2",
    "Vector 3 (Vector 3 Array)":"vector3_vector3array",
    "Vector 3 (Vector 3)":"vector3_vector3",
    "Vector 3 (Vector 4)":"vector3_vector4",
    "Vector 3 Array (Vector 3 Array)":"vector3array_vector3array",
    "Vector 3":"vector3",
    "Vector 4 (B)":"vector4B",
    "Vector 4 (Color 4)":"vector4_color4",
    "Vector 4 (Color)":"vector4_color3",
    "Vector 4 (FA)":"vector4FA",
    "Vector 4 (Float)":"vector4_float",
    "Vector 4 (I)":"vector4I",
    "Vector 4 (Vector 2)":"vector4_vector2",
    "Vector 4 (Vector 3)":"vector4_vector3",
    "Vector 4 (Vector 4 Array)":"vector4_vector4array",
    "Vector 4 (Vector 4)":"vector4_vector4",
    "Vector 4 (VF)":"vector4VF",
    "Vector 4 (VV)":"vector4VV",
    "Vector 4 Array (Vector 4 Array)":"vector4array_vector4array",
    "Vector 4":"vector4",
    "Volume Shader (C)":"volumeshaderC",
    "Volume Shader (F)":"volumeshaderF",
    "Volume Shader":"volumeshader",
}

nosignatureOutputMap={
    "surface":"surfaceshader",
    "dispacement":"displacementshader",
    "vector2":"vector2",
    "float":"float",
    "color":"color3",
    "vector4":"vector4",
    "vdf":"vdf",
    "vector":"vector3",
    "bsdf":"bsdf",
    "edf":"edf",
}

def defineNodeAndNodeGraph(outputNode,doc,ref):
    for eachVop in outputNode.inputAncestors():
        nodeName = eachVop.name()
        nodeTypeName = eachVop.type().name()
        #ノードグラフを追加する
        if nodeTypeName == "subnet":
            subnetOutput = None
            for eachChild in eachVop.children():
                if eachChild.type().name()=="suboutput":
                    subnetOutput = eachChild
                    break
            nodeGraph = doc.addNodeGraph(nodeName)
            defineNodeAndNodeGraph(subnetOutput,nodeGraph,ref)
        #ノードを追加する
        else:
            if nodeTypeName.startswith("mtlx"):
                nodeTypeName = nodeTypeName.split("mtlx")[-1]
            else:
                continue
            #Separate系のノードにsignatureパラメータがないのでVOPノードタイプ名からMaterialXノードタイプを判断する
            if nodeTypeName.endswith(("3c","4c","3v","4v")):
                nodeTypeName = nodeTypeName[:-1]

            signatureValue = ""
            signature = eachVop.parm("signature")
            if signature != None:
                signatureLabel = signature.menuLabels()[signature.menuItems().index(signature.eval())]
                if signatureLabel in signatureLabeltoValueMap:
                    signatureValue = signatureLabeltoValueMap[signatureLabel]

            refNodeDef = None
            if signatureValue != "":
                refNodeDef = ref.getNodeDef("ND_"+nodeTypeName+"_"+signatureValue)
            else:
                refNodeDef = ref.getNodeDef("ND_"+nodeTypeName)
            if refNodeDef == None:
                for outputType in eachVop.outputDataTypes():
                    if outputType in nosignatureOutputMap:
                        refNodeDef = ref.getNodeDef("ND_"+nodeTypeName+"_"+nosignatureOutputMap[outputType])
                        if refNodeDef != None:
                            break
            if refNodeDef == None:
                continue

            nodeElement = doc.addNode(refNodeDef.getNodeString(),nodeName)
            nodeElement.setType(refNodeDef.getType())

            for eachInput in refNodeDef.getInputs():
                parmType = eachInput.getType()
                value = eachInput.getValueString()
                eachParmName = eachInput.getName()

                if parmType in ["color2","color3","color4","matrix33","matrix44","vector2","vector3","vector4"]:
                    tupleValues = 0
                    if value != "":
                        tupleValues = tuple([float(i.strip()) for i in value.split(",")])
                    vopParmName = eachParmName+"_"+signatureValue
                    parmCheck = eachVop.parmTuple(vopParmName)
                    if parmCheck == None:
                        vopParmName = eachParmName
                    evaluatedValues = eachVop.parmTuple(vopParmName).eval()
                    if len(evaluatedValues)==1:
                        evaluatedValues = evaluatedValues*int(re.sub(r"\D","",parmType))
                    if evaluatedValues != tupleValues:
                        nodeElement.setInputValue(eachParmName, ",".join([str(i) for i in evaluatedValues]), parmType)
                elif parmType == "integer":
                    vopParmName = eachParmName+"_"+signatureValue
                    parmCheck = eachVop.parm(vopParmName)
                    if parmCheck == None:
                        vopParmName = eachParmName
                    evaluatedValue = eachVop.parm(vopParmName).eval()
                    if evaluatedValue != int(value):
                        nodeElement.setInputValue(eachParmName, str(evaluatedValue), parmType)
                elif parmType == "float":
                    vopParmName = eachParmName+"_"+signatureValue
                    parmCheck = eachVop.parm(vopParmName)
                    if parmCheck == None:
                        vopParmName = eachParmName
                    evaluatedValue = eachVop.parm(vopParmName).eval()
                    if evaluatedValue != float(value):
                        nodeElement.setInputValue(eachParmName, str(evaluatedValue), parmType)
                elif parmType in ["string","filename"]:
                    vopParmName = eachParmName+"_"+signatureValue
                    parmCheck = eachVop.parm(vopParmName)
                    if parmCheck == None:
                        vopParmName = eachParmName
                    evaluatedValue = eachVop.parm(vopParmName).eval()
                    if evaluatedValue != value:
                        nodeElement.setInputValue(eachParmName, evaluatedValue, parmType)
                elif parmType == "boolean":
                    if value == "true":
                        value = True
                    else:
                        value = False
                    vopParmName = eachParmName+"_"+signatureValue
                    parmCheck = eachVop.parm(vopParmName)
                    if parmCheck == None:
                        vopParmName = eachParmName
                    evaluatedValue = eachVop.parm(vopParmName).eval()
                    if evaluatedValue != value:
                        nodeElement.setInputValue(eachParmName, str(evaluatedValue).lower(), parmType)
    return

def connectNodeAndNodeGraph(outputNode,doc):
    for eachVop in outputNode.inputAncestors():
        if eachVop.type().name() == "subnet":
            eachNodeElement = doc.getNodeGraph(eachVop.name())
            suboutputNode = None
            for i in eachVop.children():
                if i.type().name()=="suboutput":
                    suboutputNode = i
                    break
            connectNodeAndNodeGraph(suboutputNode,eachNodeElement)
        else:
            eachNodeElement = doc.getNode(eachVop.name())

        for eachConnect in eachVop.inputConnections():
            inputNode = eachConnect.inputNode()
            inputNodeTypeName = inputNode.type().name()
            connectedParm = eachConnect.outputName()
            targetParm = eachConnect.inputName()

            if inputNodeTypeName == "subnet":
                targetNodeElement = doc.getNodeGraph(eachConnect.inputNode().name())
                targetOutput = targetNodeElement.getOutput(targetParm)
                if not targetOutput:
                    targetOutput = targetNodeElement.addOutput(targetParm)
                subnetOutputType = eachConnect.outputDataType()
                if subnetOutputType == "color":
                    targetOutput.setType("color3")
                elif subnetOutputType == "color2":
                    targetOutput.setType("color2")
                elif subnetOutputType == "color4":
                    targetOutput.setType("color4")
                elif subnetOutputType == "float":
                    targetOutput.setType("float")
                elif subnetOutputType == "vector":
                    targetOutput.setType("vector3")
                elif subnetOutputType == "vector2":
                    targetOutput.setType("vector2")
                elif subnetOutputType == "vector4":
                    targetOutput.setType("vector4")
                elif subnetOutputType == "int":
                    targetOutput.setType("integer")
                elif subnetOutputType == "bool":
                    targetOutput.setType("boolean")
                elif subnetOutputType == "surface":
                    targetOutput.setType("surfaceshader")
                elif subnetOutputType == "displacement":
                    targetOutput.setType("displacementshader")

                subnetOutputNode = inputNode.subnetTerminalChild(targetParm)[0]
                for i in subnetOutputNode.inputConnections():
                    if i.outputName() == targetParm:
                        outputNodeName = i.inputNode().name()
                        break
                targetOutput.setConnectedNode(targetNodeElement.getNode(outputNodeName))

                eachNodeElement.setConnectedOutput(connectedParm,targetOutput)

            else:
                targetNodeElement = doc.getNode(eachConnect.inputNode().name())
                eachNodeElement.setConnectedNode(connectedParm,targetNodeElement)
    return

searchPath = hou.text.expandString("$HH/materialx")

ref = mx.createDocument()
mx.loadLibraries(["libraries"],searchPath,ref)

materialxNetwork = hou.node("/stage/materialx")

if materialxNetwork != None and materialxNetwork.type().name() == "matnet":
    for eachVop in materialxNetwork.recursiveGlob("*",filter=hou.nodeTypeFilter.Vop):
        if eachVop.type().name()!="mtlxsurfacematerial":
            continue
        outputNodeName = eachVop.name()
        surfaceshaderName = ""
        displacementshaderName = ""
        for eachConnect in eachVop.inputConnections():
            if eachConnect.outputName() == "surfaceshader":
                surfaceshaderName = eachConnect.inputNode().name()
            elif eachConnect.outputName() == "displacementshader":
                displacementshaderName = eachConnect.inputNode().name()
        if surfaceshaderName == "":
            continue
        doc = mx.createDocument()
        defineNodeAndNodeGraph(eachVop,doc,ref)
        connectNodeAndNodeGraph(eachVop,doc)

        material = doc.addMaterial(outputNodeName)
        material.setConnectedNode("surfaceshader", doc.getNode(surfaceshaderName))
        if displacementshaderName != "":
            material.setConnectedNode("displacementshader", doc.getNode(displacementshaderName))

        filePath = hou.text.expandString("$HIP") + "/" + hou.text.expandString("$HIPNAME") + "_" +outputNodeName + ".mtlx"
        mx.writeToXmlFile(doc, filePath)

使用导出的 MaterialX 文件

如果在 Maya 的 Arnold 环境中使用 aiMaterialX 读取 MaterialX 文件(Arnold7.0 对应 MaterialX1.38)或使用 MaterialXViewer 读取,则可以像这样轻松传递着色器图。

hip文件和python脚本都在这里:
https://drive.google.com/file/d/1frXCG4R2Zv2MiFZl0cn2k2RvUgkoEmfc/view?usp=sharing

admin
博学之,审问之,慎思之,明辨之,笃行之。
OωO
开启隐私评论,您的评论仅作者和评论双方可见