MaterialX icon indicating copy to clipboard operation
MaterialX copied to clipboard

Proposal : Support JSON serialization

Open kwokcb opened this issue 2 years ago • 5 comments

Proposal

This is an initial "strawman" proposal for including JSON serialization support as part of the core of MaterialX. A suitable library would probably be MaterialXFormat where XML serialization exists.

Reasons for JSON vs XML

  • Allows Integration with environments / ecosystem not using XML (glTF / Web)
  • Compact. It can be smaller than the equivalent XML data.
  • Ability to Stream for transmission (Web)
  • Improved Security
  • Does not require a custom XML parser. e.g. Can use built in Javascript parsers (Web)

Implementation Notes

  • The size of this support should be small. Based on prototyping, the code required for this is a bit smaller than that required for XML. That is can be implemented in a single file. (See here, for a prototype example written in Python)
  • The syntax of decorations in JSON is more compact which can make smaller.
  • Uses key / value pairs so must have unique key'd children (unlike XML)

Base Requirements

  • No extraneous data structure information should be added.
  • JSON can represent all of MateriaLX without loss.
  • The core representation is still MaterialX with JSON optional. This includes the "standard" libraries
  • The representation Is not biased to suite only 1 workflow. E.g. Does not embed glTF specifics.
  • The format has the option to compact or make more human "readable".
  • Should be able to support XML include files (can add in special { key, value } for this).

Examples:

This are some examples with possible format (based on prototyping)

Marble Example (XML)
<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">
  <nodegraph name="NG_marble1">
    <input name="base_color_1" type="color3" value="0.8, 0.8, 0.8" uiname="Color 1" uifolder="Marble Color" />
    <input name="base_color_2" type="color3" value="0.1, 0.1, 0.3" uiname="Color 2" uifolder="Marble Color" />
    <input name="noise_scale_1" type="float" value="6.0" uisoftmin="1.0" uisoftmax="10.0" uiname="Scale 1" uifolder="Marble Noise" />
    <input name="noise_scale_2" type="float" value="4.0" uisoftmin="1.0" uisoftmax="10.0" uiname="Scale 2" uifolder="Marble Noise" />
    <input name="noise_power" type="float" value="3.0" uisoftmin="1.0" uisoftmax="10.0" uiname="Power" uifolder="Marble Noise" />
    <input name="noise_octaves" type="integer" value="3" uisoftmin="1" uisoftmax="8" uiname="Octaves" uifolder="Marble Noise" />
    <position name="obj_pos" type="vector3" />
    <dotproduct name="add_xyz" type="float">
      <input name="in1" type="vector3" nodename="obj_pos" />
      <input name="in2" type="vector3" value="1, 1, 1" />
    </dotproduct>
    <multiply name="scale_xyz" type="float">
      <input name="in1" type="float" nodename="add_xyz" />
      <input name="in2" type="float" interfacename="noise_scale_1" />
    </multiply>
    <multiply name="scale_pos" type="vector3">
      <input name="in1" type="vector3" nodename="obj_pos" />
      <input name="in2" type="float" interfacename="noise_scale_2" />
    </multiply>
    <fractal3d name="noise" type="float">
      <input name="octaves" type="integer" interfacename="noise_octaves" />
      <input name="position" type="vector3" nodename="scale_pos" />
    </fractal3d>
    <multiply name="scale_noise" type="float">
      <input name="in1" type="float" nodename="noise" />
      <input name="in2" type="float" value="3.0" />
    </multiply>
    <add name="sum" type="float">
      <input name="in1" type="float" nodename="scale_xyz" />
      <input name="in2" type="float" nodename="scale_noise" />
    </add>
    <sin name="sin" type="float">
      <input name="in" type="float" nodename="sum" />
    </sin>
    <multiply name="scale" type="float">
      <input name="in1" type="float" nodename="sin" />
      <input name="in2" type="float" value="0.5" />
    </multiply>
    <add name="bias" type="float">
      <input name="in1" type="float" nodename="scale" />
      <input name="in2" type="float" value="0.5" />
    </add>
    <power name="power" type="float">
      <input name="in1" type="float" nodename="bias" />
      <input name="in2" type="float" interfacename="noise_power" />
    </power>
    <mix name="color_mix" type="color3">
      <input name="bg" type="color3" interfacename="base_color_1" />
      <input name="fg" type="color3" interfacename="base_color_2" />
      <input name="mix" type="float" nodename="power" />
    </mix>
    <output name="out" type="color3" nodename="color_mix" />
  </nodegraph>
  <standard_surface name="SR_marble1" type="surfaceshader" xpos="13.768116" ypos="-0.672414">
    <input name="base" type="float" value="1" />
    <input name="base_color" type="color3" nodegraph="NG_marble1" output="out" />
    <input name="specular_roughness" type="float" value="0.1" />
    <input name="subsurface" type="float" value="0.4" />
    <input name="subsurface_color" type="color3" nodegraph="NG_marble1" output="out" />
  </standard_surface>
  <surfacematerial name="Marble_3D" type="material" xpos="17.391304" ypos="0.000000">
    <input name="surfaceshader" type="surfaceshader" nodename="SR_marble1" />
  </surfacematerial>
</materialx>
Marble Example (JSON)
{
  "materialx": {
    "colorspace": "lin_rec709",
    "nodegraph:NG_marble1": {
      "add:bias": {
        "input:in1": {
          "nodename": "scale",
          "type": "float"
        },
        "input:in2": {
          "type": "float",
          "value": "0.5"
        },
        "type": "float"
      },
      "add:sum": {
        "input:in1": {
          "nodename": "scale_xyz",
          "type": "float"
        },
        "input:in2": {
          "nodename": "scale_noise",
          "type": "float"
        },
        "type": "float"
      },
      "dotproduct:add_xyz": {
        "input:in1": {
          "nodename": "obj_pos",
          "type": "vector3"
        },
        "input:in2": {
          "type": "vector3",
          "value": "1, 1, 1"
        },
        "type": "float"
      },
      "fractal3d:noise": {
        "input:octaves": {
          "interfacename": "noise_octaves",
          "type": "integer"
        },
        "input:position": {
          "nodename": "scale_pos",
          "type": "vector3"
        },
        "type": "float"
      },
      "input:base_color_1": {
        "type": "color3",
        "uifolder": "Marble Color",
        "uiname": "Color 1",
        "value": "0.8, 0.8, 0.8"
      },
      "input:base_color_2": {
        "type": "color3",
        "uifolder": "Marble Color",
        "uiname": "Color 2",
        "value": "0.1, 0.1, 0.3"
      },
      "input:noise_octaves": {
        "type": "integer",
        "uifolder": "Marble Noise",
        "uiname": "Octaves",
        "uisoftmax": "8",
        "uisoftmin": "1",
        "value": "3"
      },
      "input:noise_power": {
        "type": "float",
        "uifolder": "Marble Noise",
        "uiname": "Power",
        "uisoftmax": "10.0",
        "uisoftmin": "1.0",
        "value": "3.0"
      },
      "input:noise_scale_1": {
        "type": "float",
        "uifolder": "Marble Noise",
        "uiname": "Scale 1",
        "uisoftmax": "10.0",
        "uisoftmin": "1.0",
        "value": "6.0"
      },
      "input:noise_scale_2": {
        "type": "float",
        "uifolder": "Marble Noise",
        "uiname": "Scale 2",
        "uisoftmax": "10.0",
        "uisoftmin": "1.0",
        "value": "4.0"
      },
      "mix:color_mix": {
        "input:bg": {
          "interfacename": "base_color_1",
          "type": "color3"
        },
        "input:fg": {
          "interfacename": "base_color_2",
          "type": "color3"
        },
        "input:mix": {
          "nodename": "power",
          "type": "float"
        },
        "type": "color3"
      },
      "multiply:scale": {
        "input:in1": {
          "nodename": "sin",
          "type": "float"
        },
        "input:in2": {
          "type": "float",
          "value": "0.5"
        },
        "type": "float"
      },
      "multiply:scale_noise": {
        "input:in1": {
          "nodename": "noise",
          "type": "float"
        },
        "input:in2": {
          "type": "float",
          "value": "3.0"
        },
        "type": "float"
      },
      "multiply:scale_pos": {
        "input:in1": {
          "nodename": "obj_pos",
          "type": "vector3"
        },
        "input:in2": {
          "interfacename": "noise_scale_2",
          "type": "float"
        },
        "type": "vector3"
      },
      "multiply:scale_xyz": {
        "input:in1": {
          "nodename": "add_xyz",
          "type": "float"
        },
        "input:in2": {
          "interfacename": "noise_scale_1",
          "type": "float"
        },
        "type": "float"
      },
      "output:out": {
        "nodename": "color_mix",
        "type": "color3"
      },
      "position:obj_pos": {
        "type": "vector3"
      },
      "power:power": {
        "input:in1": {
          "nodename": "bias",
          "type": "float"
        },
        "input:in2": {
          "interfacename": "noise_power",
          "type": "float"
        },
        "type": "float"
      },
      "sin:sin": {
        "input:in": {
          "nodename": "sum",
          "type": "float"
        },
        "type": "float"
      }
    },
    "standard_surface:SR_marble1": {
      "input:base": {
        "type": "float",
        "value": "1"
      },
      "input:base_color": {
        "nodegraph": "NG_marble1",
        "output": "out",
        "type": "color3"
      },
      "input:specular_roughness": {
        "type": "float",
        "value": "0.1"
      },
      "input:subsurface": {
        "type": "float",
        "value": "0.4"
      },
      "input:subsurface_color": {
        "nodegraph": "NG_marble1",
        "output": "out",
        "type": "color3"
      },
      "type": "surfaceshader",
      "xpos": "13.768116",
      "ypos": "-0.672414"
    },
    "surfacematerial:Marble_3D": {
      "input:surfaceshader": {
        "nodename": "SR_marble1",
        "type": "surfaceshader"
      },
      "type": "material",
      "xpos": "17.391304",
      "ypos": "0.000000"
    },
    "version": "1.38"
  },
  "mimetype": "application/mtlx+json"
}
glTF "Olives" Sample Library (XML)
<?xml version="1.0"?>
<materialx version="1.38">
  <gltf_pbr name="SHD_glassDish" type="surfaceshader" xpos="13.768116" ypos="-5.250000">
    <input name="occlusion" type="float" nodename="image_occlusion" />
    <input name="base_color" type="color3" value="1, 1, 1" />
    <input name="alpha" type="float" value="1" />
    <input name="metallic" type="float" value="0" />
    <input name="roughness" type="float" value="0.07" />
    <input name="iridescence" type="float" nodename="image_iridescence" />
    <input name="iridescence_ior" type="float" value="1.3" />
    <input name="iridescence_thickness" type="float" nodename="image_iridescence_thickness" />
    <input name="transmission" type="float" value="1" />
    <input name="emissive" type="color3" value="0, 0, 0" />
    <input name="thickness" type="float" value="0.01" />
    <input name="attenuation_color" type="color3" value="1, 1, 1" />
    <input name="attenuation_distance" type="float" value="3.40282e+38" />
  </gltf_pbr>
  <surfacematerial name="MAT_glassDish" type="material" xpos="17.391304" ypos="0.000000">
    <input name="surfaceshader" type="surfaceshader" nodename="SHD_glassDish" />
  </surfacematerial>
  <gltf_image name="image_occlusion" type="float" xpos="10.144928" ypos="-5.732759">
    <input name="file" type="filename" value="goldleaf_orm.png" />
  </gltf_image>
  <gltf_image name="image_iridescence" type="float" xpos="10.144928" ypos="-4.396552">
    <input name="file" type="filename" value="glassdish_irid.png" />
  </gltf_image>
  <gltf_iridescence_thickness name="image_iridescence_thickness" type="float" xpos="10.144928" ypos="-3.060345">
    <input name="file" type="filename" value="glassdish_irid.png" />
    <input name="thicknessMin" type="float" value="500" />
    <input name="thicknessMax" type="float" value="550" />
  </gltf_iridescence_thickness>
  <gltf_pbr name="SHD_olives" type="surfaceshader" xpos="13.768116" ypos="-0.379310">
    <input name="base_color" type="color3" nodename="image_basecolor" />
    <input name="metallic" type="float" value="1" nodename="image_orm" channels="z" />
    <input name="roughness" type="float" value="1" nodename="image_orm" channels="y" />
    <input name="occlusion" type="float" value="1" nodename="image_orm" channels="x" />
    <input name="normal" type="vector3" nodename="image_normal" />
    <input name="emissive" type="color3" value="0, 0, 0" />
  </gltf_pbr>
  <surfacematerial name="MAT_olives" type="material" xpos="17.391304" ypos="1.336207">
    <input name="surfaceshader" type="surfaceshader" nodename="SHD_olives" />
  </surfacematerial>
  <gltf_image name="image_basecolor" type="color3" xpos="10.144928" ypos="-1.137931">
    <input name="file" type="filename" value="olives_col.png" colorspace="srgb_texture" />
  </gltf_image>
  <gltf_image name="image_orm" type="vector3" xpos="10.144928" ypos="0.198276">
    <input name="file" type="filename" value="olives_orm.png" />
  </gltf_image>
  <gltf_normalmap name="image_normal" type="vector3" xpos="10.144928" ypos="1.534483">
    <input name="file" type="filename" value="olives_nrm.png" />
  </gltf_normalmap>
  <gltf_pbr name="SHD_glassCover" type="surfaceshader" xpos="13.768116" ypos="2.396552">
    <input name="base_color" type="color3" value="1, 1, 1" />
    <input name="alpha" type="float" value="1" />
    <input name="metallic" type="float" value="1" nodename="image_orm2" channels="z" />
    <input name="roughness" type="float" value="1" nodename="image_orm2" channels="y" />
    <input name="occlusion" type="float" value="1" nodename="image_orm2" channels="x" />
    <input name="normal" type="vector3" nodename="image_normal2" />
    <input name="iridescence" type="float" nodename="image_iridescence2" />
    <input name="iridescence_ior" type="float" value="1.3" />
    <input name="iridescence_thickness" type="float" nodename="image_iridescence_thickness2" />
    <input name="transmission" type="float" value="1" />
    <input name="ior" type="float" value="1.5" />
    <input name="emissive" type="color3" value="0, 0, 0" />
    <input name="thickness" type="float" nodename="thickness" />
    <input name="attenuation_color" type="color3" value="1, 1, 1" />
    <input name="attenuation_distance" type="float" value="3.40282e+38" />
  </gltf_pbr>
  <surfacematerial name="MAT_glassCover" type="material" xpos="17.391304" ypos="2.672414">
    <input name="surfaceshader" type="surfaceshader" nodename="SHD_glassCover" />
  </surfacematerial>
  <gltf_image name="image_orm2" type="vector3" xpos="10.144928" ypos="2.870690">
    <input name="file" type="filename" value="glasscover_orm.png" />
  </gltf_image>
  <gltf_normalmap name="image_normal2" type="vector3" xpos="10.144928" ypos="4.206897">
    <input name="file" type="filename" value="glasscover_nrm.png" />
  </gltf_normalmap>
  <gltf_image name="image_iridescence2" type="float" xpos="10.144928" ypos="5.543103">
    <input name="file" type="filename" value="glasscover_irid.png" />
  </gltf_image>
  <gltf_iridescence_thickness name="image_iridescence_thickness2" type="float" xpos="10.144928" ypos="6.879310">
    <input name="file" type="filename" value="glasscover_irid.png" />
    <input name="thicknessMin" type="float" value="500" />
    <input name="thicknessMax" type="float" value="550" />
  </gltf_iridescence_thickness>
  <gltf_image name="thickness" type="float" xpos="10.144928" ypos="8.801724">
    <input name="file" type="filename" value="glasscover_thick.png" />
  </gltf_image>
  <gltf_pbr name="SHD_goldLeaf" type="surfaceshader" xpos="13.768116" ypos="7.836207">
    <input name="base_color" type="color3" nodename="image_basecolor2" />
    <input name="metallic" type="float" value="1" nodename="image_orm3" channels="z" />
    <input name="roughness" type="float" value="1" nodename="image_orm3" channels="y" />
    <input name="occlusion" type="float" value="1" nodename="image_orm3" channels="x" />
    <input name="alpha_mode" type="integer" value="1" />
    <input name="normal" type="vector3" nodename="image_normal3" />
    <input name="emissive" type="color3" value="0, 0, 0" />
  </gltf_pbr>
  <surfacematerial name="MAT_goldLeaf" type="material" xpos="17.391304" ypos="4.008621">
    <input name="surfaceshader" type="surfaceshader" nodename="SHD_goldLeaf" />
  </surfacematerial>
  <gltf_image name="image_basecolor2" type="color3" xpos="10.144928" ypos="10.137931">
    <input name="file" type="filename" value="goldleaf_col.png" colorspace="srgb_texture" />
  </gltf_image>
  <gltf_image name="image_orm3" type="vector3" xpos="10.144928" ypos="11.474138">
    <input name="file" type="filename" value="goldleaf_orm.png" />
  </gltf_image>
  <gltf_normalmap name="image_normal3" type="vector3" xpos="10.144928" ypos="12.810345">
    <input name="file" type="filename" value="goldleaf_nrm.png" />
  </gltf_normalmap>
</materialx>

This shows a potential target for integration which would be inclusion as part of a GLTF document

glTF "Olives" Sample Library (GLTF)
{
  "asset": {
    "copyright": "Copyright 2022-2023: Bernard Kwok",
    "generator": "glTF_MaterialX generator: MaterialX 1.38.8 to glTF 2.0 .",
    "version": "2.0"
  },
  "images": [
    {
      "name": "image_basecolor2",
      "uri": "goldleaf_col.png"
    },
    {
      "name": "image_orm3",
      "uri": "goldleaf_orm.png"
    },
    {
      "name": "image_normal3",
      "uri": "goldleaf_nrm.png"
    },
    {
      "name": "image_basecolor",
      "uri": "olives_col.png"
    },
    {
      "name": "image_orm",
      "uri": "olives_orm.png"
    },
    {
      "name": "image_normal",
      "uri": "olives_nrm.png"
    },
    {
      "name": "image_occlusion",
      "uri": "goldleaf_orm.png"
    },
    {
      "name": "image_iridescence",
      "uri": "glassdish_irid.png"
    },
    {
      "name": "image_iridescence_thickness",
      "uri": "glassdish_irid.png"
    },
    {
      "name": "image_orm2",
      "uri": "glasscover_orm.png"
    },
    {
      "name": "image_normal2",
      "uri": "glasscover_nrm.png"
    },
    {
      "name": "image_iridescence2",
      "uri": "glasscover_irid.png"
    },
    {
      "name": "image_iridescence_thickness2",
      "uri": "glasscover_irid.png"
    }
  ],
  "materials": [
    {
      "name": "SHD_goldLeaf",
      "pbrMetallicRoughness": {
        "baseColorTexture": {
          "index": 0
        },
        "metallicRoughnessTexture": {
          "index": 1
        }
      },
      "extensions": {
        "KHR_materials_clearcoat": {
        },
        "KHR_materials_ior": {
        },
        "KHR_materials_specular": {
        },
        "KHR_materials_transmission": {
        },
        "KHR_materials_sheen": {
        },
        "KHR_materials_emissive_strength": {
        },
        "KHR_materials_iridescence": {
        }
      },
      "occlusionTexture": {
        "index": 1,
        "scale": 0
      },
      "alphaMode": "MASK"
    },
    {
      "name": "SHD_olives",
      "pbrMetallicRoughness": {
        "baseColorTexture": {
          "index": 3
        },
        "metallicRoughnessTexture": {
          "index": 4
        }
      },
      "extensions": {
        "KHR_materials_clearcoat": {
        },
        "KHR_materials_ior": {
        },
        "KHR_materials_specular": {
        },
        "KHR_materials_transmission": {
        },
        "KHR_materials_sheen": {
        },
        "KHR_materials_emissive_strength": {
        },
        "KHR_materials_iridescence": {
        }
      },
      "occlusionTexture": {
        "index": 4,
        "scale": 0
      }
    },
    {
      "name": "SHD_glassDish",
      "pbrMetallicRoughness": {
        "metallicFactor": 0,
        "roughnessFactor": 0.0700000003
      },
      "extensions": {
        "KHR_materials_clearcoat": {
        },
        "KHR_materials_ior": {
        },
        "KHR_materials_specular": {
        },
        "KHR_materials_transmission": {
          "transmissionFactor": 1
        },
        "KHR_materials_sheen": {
        },
        "KHR_materials_emissive_strength": {
        },
        "KHR_materials_iridescence": {
          "iridescenceFactor": 1,
          "iridescenceTexture": {
            "index": 7,
            "scale": 0
          },
          "iridescenceThicknessMinimum": 500,
          "iridescenceThicknessMaximum": 550,
          "iridescenceThicknessTexture": {
            "index": 8
          }
        }
      },
      "occlusionTexture": {
        "index": 6,
        "scale": 0
      }
    },
    {
      "name": "SHD_glassCover",
      "pbrMetallicRoughness": {
        "metallicRoughnessTexture": {
          "index": 9
        }
      },
      "extensions": {
        "KHR_materials_clearcoat": {
        },
        "KHR_materials_ior": {
        },
        "KHR_materials_specular": {
        },
        "KHR_materials_transmission": {
          "transmissionFactor": 1
        },
        "KHR_materials_sheen": {
        },
        "KHR_materials_emissive_strength": {
        },
        "KHR_materials_iridescence": {
          "iridescenceFactor": 1,
          "iridescenceTexture": {
            "index": 11,
            "scale": 0
          },
          "iridescenceThicknessMinimum": 500,
          "iridescenceThicknessMaximum": 550,
          "iridescenceThicknessTexture": {
            "index": 12
          }
        }
      },
      "occlusionTexture": {
        "index": 9,
        "scale": 0
      }
    }
  ],
  "textures": [
    {
      "name": "image_basecolor2",
      "source": 0
    },
    {
      "name": "image_orm3",
      "source": 1
    },
    {
      "name": "image_normal3",
      "source": 2
    },
    {
      "name": "image_basecolor",
      "source": 3
    },
    {
      "name": "image_orm",
      "source": 4
    },
    {
      "name": "image_normal",
      "source": 5
    },
    {
      "name": "image_occlusion",
      "source": 6
    },
    {
      "name": "image_iridescence",
      "source": 7
    },
    {
      "name": "image_iridescence_thickness",
      "source": 8
    },
    {
      "name": "image_orm2",
      "source": 9
    },
    {
      "name": "image_normal2",
      "source": 10
    },
    {
      "name": "image_iridescence2",
      "source": 11
    },
    {
      "name": "image_iridescence_thickness2",
      "source": 12
    }
  ],
  "extensionsUsed": [
    "KHR_materials_clearcoat",
    "KHR_materials_ior",
    "KHR_materials_specular",
    "KHR_materials_transmission",
    "KHR_materials_sheen",
    "KHR_materials_emissive_strength",
    "KHR_materials_iridescence"
  ]
}
glTF "Olives" Sample Library (JSON)
{
  "materialx": {
    "colorspace": "lin_rec709",
    "nodegraph:NG_marble1": {
      "add:bias": {
        "input:in1": {
          "nodename": "scale",
          "type": "float"
        },
        "input:in2": {
          "type": "float",
          "value": "0.5"
        },
        "type": "float"
      },
      "add:sum": {
        "input:in1": {
          "nodename": "scale_xyz",
          "type": "float"
        },
        "input:in2": {
          "nodename": "scale_noise",
          "type": "float"
        },
        "type": "float"
      },
      "dotproduct:add_xyz": {
        "input:in1": {
          "nodename": "obj_pos",
          "type": "vector3"
        },
        "input:in2": {
          "type": "vector3",
          "value": "1, 1, 1"
        },
        "type": "float"
      },
      "fractal3d:noise": {
        "input:octaves": {
          "interfacename": "noise_octaves",
          "type": "integer"
        },
        "input:position": {
          "nodename": "scale_pos",
          "type": "vector3"
        },
        "type": "float"
      },
      "input:base_color_1": {
        "type": "color3",
        "uifolder": "Marble Color",
        "uiname": "Color 1",
        "value": "0.8, 0.8, 0.8"
      },
      "input:base_color_2": {
        "type": "color3",
        "uifolder": "Marble Color",
        "uiname": "Color 2",
        "value": "0.1, 0.1, 0.3"
      },
      "input:noise_octaves": {
        "type": "integer",
        "uifolder": "Marble Noise",
        "uiname": "Octaves",
        "uisoftmax": "8",
        "uisoftmin": "1",
        "value": "3"
      },
      "input:noise_power": {
        "type": "float",
        "uifolder": "Marble Noise",
        "uiname": "Power",
        "uisoftmax": "10.0",
        "uisoftmin": "1.0",
        "value": "3.0"
      },
      "input:noise_scale_1": {
        "type": "float",
        "uifolder": "Marble Noise",
        "uiname": "Scale 1",
        "uisoftmax": "10.0",
        "uisoftmin": "1.0",
        "value": "6.0"
      },
      "input:noise_scale_2": {
        "type": "float",
        "uifolder": "Marble Noise",
        "uiname": "Scale 2",
        "uisoftmax": "10.0",
        "uisoftmin": "1.0",
        "value": "4.0"
      },
      "mix:color_mix": {
        "input:bg": {
          "interfacename": "base_color_1",
          "type": "color3"
        },
        "input:fg": {
          "interfacename": "base_color_2",
          "type": "color3"
        },
        "input:mix": {
          "nodename": "power",
          "type": "float"
        },
        "type": "color3"
      },
      "multiply:scale": {
        "input:in1": {
          "nodename": "sin",
          "type": "float"
        },
        "input:in2": {
          "type": "float",
          "value": "0.5"
        },
        "type": "float"
      },
      "multiply:scale_noise": {
        "input:in1": {
          "nodename": "noise",
          "type": "float"
        },
        "input:in2": {
          "type": "float",
          "value": "3.0"
        },
        "type": "float"
      },
      "multiply:scale_pos": {
        "input:in1": {
          "nodename": "obj_pos",
          "type": "vector3"
        },
        "input:in2": {
          "interfacename": "noise_scale_2",
          "type": "float"
        },
        "type": "vector3"
      },
      "multiply:scale_xyz": {
        "input:in1": {
          "nodename": "add_xyz",
          "type": "float"
        },
        "input:in2": {
          "interfacename": "noise_scale_1",
          "type": "float"
        },
        "type": "float"
      },
      "output:out": {
        "nodename": "color_mix",
        "type": "color3"
      },
      "position:obj_pos": {
        "type": "vector3"
      },
      "power:power": {
        "input:in1": {
          "nodename": "bias",
          "type": "float"
        },
        "input:in2": {
          "interfacename": "noise_power",
          "type": "float"
        },
        "type": "float"
      },
      "sin:sin": {
        "input:in": {
          "nodename": "sum",
          "type": "float"
        },
        "type": "float"
      }
    },
    "standard_surface:SR_marble1": {
      "input:base": {
        "type": "float",
        "value": "1"
      },
      "input:base_color": {
        "nodegraph": "NG_marble1",
        "output": "out",
        "type": "color3"
      },
      "input:specular_roughness": {
        "type": "float",
        "value": "0.1"
      },
      "input:subsurface": {
        "type": "float",
        "value": "0.4"
      },
      "input:subsurface_color": {
        "nodegraph": "NG_marble1",
        "output": "out",
        "type": "color3"
      },
      "type": "surfaceshader",
      "xpos": "13.768116",
      "ypos": "-0.672414"
    },
    "surfacematerial:Marble_3D": {
      "input:surfaceshader": {
        "nodename": "SR_marble1",
        "type": "surfaceshader"
      },
      "type": "material",
      "xpos": "17.391304",
      "ypos": "0.000000"
    },
    "version": "1.38"
  },
  "mimetype": "application/mtlx+json"
}

kwokcb avatar Aug 29 '23 19:08 kwokcb

This is a great topic for discussion, @kwokcb, and thanks for getting it started!

It may be user error on my own part, but when I compare the character count of the two representations of your example in Sublime Text I get 3581 characters for XML and 5062 characters for JSON, which seems comparable to the results we've seen in earlier tests.

Am I miscounting or misunderstanding what I should expect?

jstone-lucasfilm avatar Aug 29 '23 22:08 jstone-lucasfilm

Hi @jstone-lucasfilm, The examples are the default formatting so yes they are larger. It's all the whitespace padding added. There are a few possible options available:

  1. Instead of whitespace use tabs. This is still slightly a but larger. This is a built in option for the lib is used.
  2. Put all attributes on the same line w/o white space inbetween. This is about the same size.
  3. Don't add whitespace. This appears smaller in general but hard to read.

Or a custom string serializer which handles each JSON element. I haven't tried this but would mean you don't really need to use a external library to produce JSON (nlohmann json was tested).

kwokcb avatar Aug 30 '23 17:08 kwokcb

@kwokcb I think you're right to use default formatting here, and I'm not implying that we should additionally compress either XML or JSON files by removing whitespace or switching to tabs, but it's worth noting that the JSON files are larger than their XML equivalents. This isn't a deal breaker for the idea of supporting JSON as an alternative format, but it matches the results that we saw in earlier tests of this idea.

jstone-lucasfilm avatar Aug 30 '23 17:08 jstone-lucasfilm

I think even for readability it would be good to make it more compact (as with XML) so you JSON you get something like:

  "add:sum": {
      "input:in1": { "nodename": "scale_xyz", "type": "float" },
      "input:in2": { "nodename": "scale_noise", "type": "float" }, 
      "type": "float"
    }
    <add name="sum" type="float">
      <input name="in1" type="float" nodename="scale_xyz" />
      <input name="in2" type="float" nodename="scale_noise" />
    </add>

kwokcb avatar Aug 30 '23 18:08 kwokcb

Looking at the three.js prototype implementation which consumes XML currently:

  1. Some glsl code is copied from MTLX to create a new shadernodes which are not already defined.
  2. Higher level nodes are written in javascript using MTLX and non-MTLX nodes.
  3. The custom MaterialX loader (MaterialXLoader) parses MTLX files using an XML serializer, and creates a node dictionary (for lookup to perform links for instance).
  4. The parser performs a recursive top down traversal on parent child nodes / attributes. Pretty well the same as what's done for the MTLX XML parser, and what is done in the prototype JSON parser
  5. It will map a std surface node roughly to gltf pbr node.
  6. Connections are formed by parsing a string link (in MTLX) to a binary link in Javascript. (it's not adding args or anything like that as is done in the NVIDIA proposal). Multi-outputs are supported but I think it's just access the output data member.
  7. String file names are directly supported to create transforms and texture lookups. It also handles color space.
  • Given this, basic compatibility for loading seems "trivial" based on the manner of parsing in 4.
  • There could be room to discuss parsing the std library for definitions and implementations, but this is probably an orthogonal item to allow for flattening before export which is generic to any format (XML or JSON).

kwokcb avatar Sep 08 '23 14:09 kwokcb

From the discussion above, it sounds like we should continue to focus on MTLX files within the MaterialX project, with the expectation that external projects will make different choices for serialization and deserialization of MaterialX content (e.g. USDA, JSON).

I'll go ahead and close out this issue for now, but feel free to bring this discussion back if needed in the future.

jstone-lucasfilm avatar May 04 '24 16:05 jstone-lucasfilm

For reference this was implemented in https://github.com/kwokcb/materialxjson

fire avatar May 04 '24 16:05 fire