Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Material assignments to child objects #1109

Closed
emackey opened this issue Oct 19, 2022 · 10 comments · Fixed by #1113
Closed

Material assignments to child objects #1109

emackey opened this issue Oct 19, 2022 · 10 comments · Fixed by #1113

Comments

@emackey
Copy link

emackey commented Oct 19, 2022

Spotted by @pablode in #1098 (comment)

When two objects have a parent/child relationship, <materialassign ... geom="..."> cannot distinguish between them correctly. In the linked PR, there is a chess set pawn object, and it has a "top" child object with a different material.

    <materialassign name="Pawn_Body_B1" geom="/Pawn_Body_B1" material="M_Pawn_Body_B" />

    <materialassign name="Pawn_Top_B1" geom="/Pawn_Body_B1/Pawn_Top_B1" material="M_Pawn_Top_B" />

The first line above should assign the pawn's body material to the base of the pawn, which is the parent object. The next line should assign a separate material to the top of the pawn, which is a child of the base.

In MaterialXView, I see the top material get assigned to both parent and child.

@emackey
Copy link
Author

emackey commented Oct 19, 2022

Here's a reduced test case with only 2 objects.

parent_child_test.zip

  <look name="L_ParentChildTest">
    <!-- Larger parent cube should be blue. -->
    <materialassign name="L_Parent" geom="/Parent" material="M_Parent" />
    <!-- Smaller child cube should be orange. -->
    <materialassign name="L_Child" geom="/Parent/Child" material="M_Child" />
  </look>

The expected behavior is a large blue parent cube, with a smaller orange child:

screenshot of it working

But when I load the .mtlx file, I see the child's material assigned to both:

screenshot of it broken

@kwokcb
Copy link
Contributor

kwokcb commented Oct 20, 2022

Hi @emackey ,
I think this boils down to what is considered to be "supported" assignments. It seems you can have a leaf mesh at any place
in the transform hierarchy within glTF, but I'm not sure if this is generally supported for other formats and authoring tools.

I'm pinging @dbsmythe and @jstone-lucasfilm as I can't find anywhere in the spec which says what the behaviour is supposed to be in this case, and whether it's worth pursuing support for this as USD is supposed to handle "full" assignment logic. Does this import into USD for instance?

  • As a note: since MaterialXView is a demo app only there is no hierarchy and both parent and child are just leave nodes so it could support this assignment.
  • Pulled out the gltf text for this config for easier viewing:
 "nodes": [
    {
      "mesh": 0,
      "name": "Child",
      "translation": [
        0,
        1.4495644569396973,
        0
      ]
    },
    {
      "children": [
        0
      ],
      "mesh": 1,
      "name": "Parent"
    }
  ],

@emackey
Copy link
Author

emackey commented Oct 20, 2022

As a note: since MaterialXView is a demo app only there is no hierarchy and both parent and child are just leave nodes so it could support this assignment.

I tried specifying geom that way (treating the child as not having its parent), but that doesn't seem to work either. I haven't found a way for both child and parent to be assigned different materials.

This sort of hierarchy is fairly common for inorganic or hard-surface models. It's especially notable on robotic arms and other situations with multiple mechanical joints that build on each other. And it's useful on the chess set's pawns too, because the top requires a different material (because of transmission, realtime systems must render its material in a separate pass from the opaque pawn base material). If the base were to move or animate, the top must come with it, and that makes the top a child.

@dbsmythe
Copy link
Contributor

The intention of the assignments is that they would in fact work as emackey believes, with the blue parent cube and orange child cube. The "Geometry Representation" part of the Spec says this (bold emphasis added for this discussion):
"Assignments to non-leaf locations apply hierarchically to all geometries below the specified location, unless they are the target of another assignment. By extension, an assignment to "/" applies to all geometries within the MaterialX setup, unless they are the target of another assignment."

@emackey
Copy link
Author

emackey commented Oct 20, 2022

So it might be understandable for a parent's material to end up on a child, if the child did not express its own material. But surely it's a bug to have the child's material ever end up on the parent, which is what's observed here.

@kwokcb
Copy link
Contributor

kwokcb commented Oct 20, 2022

Thanks for the clarification @dbsmythe and @emackey.
I guess it's a matter of how much of this hierarchical assignment logic will be supported in the sample app as it's not really a "lookdev" app.

@dbsmythe
Copy link
Contributor

Right- as we move forward with separating out the "Geometry Extensions" from the main Specification (in 1.38) into a separate, optional (but still standard) specification document (in 1.39), and applications move toward other mechanisms like USD for doing material/etc assignments to geometries, this will hopefully be less of a problem. I do think though that MaterialXView should support those "Geometry Extensions" (the syntax and mechanics aren't changing at all, just where they are defined in Spec documents), and the "blue vs orange" assignment issue demonstrated above really should be fixed to work as expected.

@kwokcb
Copy link
Contributor

kwokcb commented Oct 20, 2022

Took a look at this, and the support logic is there but it's "reversed" for membership so parents are assigned child materials.

@emackey If you get chance, can you try out this change to see if it works overall for your tests / chess set ? It's a one line change in C++ and a few lines for web if your using that.

kwokcb#31

If it's good feel free to use it.

@jstone-lucasfilm , leaving a ping for when your back as I think either you or I wrote this code from a few years back.

Thanks.

@kwokcb
Copy link
Contributor

kwokcb commented Oct 21, 2022

As a FYI, I asked in the ASWF USD/MaterialX group for USD side support.
(https://academysoftwarefdn.slack.com/archives/C02HJH53RN3/p1666285940551279)

Here is an example I posted for this parent-child relationship as well as child faceset support.

@emackey , as far as I can tell indexing is on a mesh in glTF so if you want to specify a subset to assign a material to you need to create a new mesh. I created them as children so that they are leaves when deriving the path syntax.

If worthwhile can pull a faceset example request issue out. I asked @ashwinbhat if perhaps we could add something like this as a reference for USD/MTLX/glTF equivalence.

  • MateriaLX:
<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709" >

  <!-- Parent Material -->
  <gltf_pbr name="S_Parent" type="surfaceshader">
    <input name="base_color" type="color3" value="0.1 0.4 0.7" />
    <input name="metallic" type="float" value="0" />
    <input name="roughness" type="float" value="0.4" />
  </gltf_pbr>
  <surfacematerial name="M_Parent" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="S_Parent" />
  </surfacematerial>

  <!-- Child Material -->
  <gltf_pbr name="S_Child" type="surfaceshader">
    <input name="base_color" type="color3" value="0.7 0.4 0.1" />
    <input name="metallic" type="float" value="0" />
    <input name="roughness" type="float" value="0.4" />
  </gltf_pbr>
  <surfacematerial name="M_Child" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="S_Child" />
  </surfacematerial>

  <!-- Child Face Set 1 Material -->
  <gltf_pbr name="S_Child_Set1" type="surfaceshader">
    <input name="base_color" type="color3" value="0, 0, 0" />
    <input name="metallic" type="float" value="0" />
    <input name="roughness" type="float" value="0.4" />
  </gltf_pbr>
  <surfacematerial name="M_Child_Set1" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="S_Child_Set1" />
  </surfacematerial>

  <!-- Child Face Set 2 Material -->
  <gltf_pbr name="S_Child_Set2" type="surfaceshader">
    <input name="base_color" type="color3" value="1, 1, 1" />
    <input name="metallic" type="float" value="0" />
    <input name="roughness" type="float" value="0.4" />
  </gltf_pbr>
  <surfacematerial name="M_Child_Set2" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="S_Child_Set2" />
  </surfacematerial>

  <!-- Look -->
  <look name="L_ParentChildTest">
    <!-- Larger parent cube should be blue. -->
    <materialassign name="L_Parent" geom="/Parent" material="M_Parent" />
    
    <!-- Smaller child cube should be orange. -->
    <materialassign name="L_Child" geom="/Parent/Child" material="M_Child" />

    <!-- Child cube faceset 1 should be black . -->
    <materialassign name="L_Child_Set1" geom="/Parent/Child/Child_subset1" material="M_Child_Set1" />
    <!-- Child cube faceset 2 should be white. -->
    <materialassign name="L_Child_Set2" geom="/Parent/Child/Child_subset2" material="M_Child_Set2" />
  </look>
</materialx>
  • USD
def Material "M_Parent"
{
     def Shader "S_Parent"
     { ... }
}

def Material "M_Child"
{
     def Shader "S_Child"
     { ... }
}

def Material "M_Child_Set1"
{
     def Shader "S_Child_Set1"
     { ... }
}

def Material "M_Child_Set2"
{
     def Shader "M_Child_Set2"
     { ... }
}

def Xform "Root"
{
    def Xform "Parent"
    {
        def Mesh "Parent"
        {
            rel material:binding:full = </M_Parent>
        }

        def Xform "Child"
        {
            def Mesh "Child"
            {
                rel material:binding = </M_Child>

                def GeomSubset "Child_subset1"
                {
                    uniform token elementType = "face"
                    int[] indices = [1, 2, 3, 5]
                    rel material:binding = </M_Child_Set1>
                }
                def GeomSubset "Child_subset2"
                {
                    uniform token elementType = "face"
                    int[] indices = [0]
                    rel material:binding = </M_Child_Set2>
                }
            }
        }
    }
}
  • glTF
  "nodes": [
  {
    "mesh": 0,
    "children": [
      1,2
    ],
    "name": "Child",
    "translation": [
      0,
      1.4495644569396973,
      0
    ]
  },
  {
    "mesh": 1,
    "name": "Child_subset1",
  },
  {
    "mesh": 2,
    "name": "Child_subset2",
  },
  {
    "children": [
      0
    ],
    "mesh": 3,
    "name": "Parent"
  }
],

@emackey
Copy link
Author

emackey commented Oct 21, 2022

emackey , as far as I can tell indexing is on a mesh in glTF so if you want to specify a subset to assign a material to you need to create a new mesh.

In glTF, meshes contain an array of "primitives," and each primitive has a different material assigned. A glTF mesh with only one material needs only one glTF primitive. A multi-material mesh will need one glTF primitive per material.

The idea is that a realtime system will draw the whole primitive with a single draw call, and change textures or shader programs before drawing the next primitive. There's no per-index material assignment, because typically GPUs don't do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants