AcademySoftwareFoundation / MaterialX

MaterialX is an open standard for the exchange of rich material and look-development content across applications and renderers.
http://www.materialx.org/
Apache License 2.0
1.83k stars 335 forks source link

Material assignments to child objects #1109

Closed emackey closed 1 year ago

emackey commented 1 year ago

Spotted by @pablode in https://github.com/AcademySoftwareFoundation/MaterialX/pull/1098#issuecomment-1282935116

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 commented 1 year ago

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 commented 1 year ago

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?

emackey commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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.

https://github.com/kwokcb/MaterialX/pull/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 commented 1 year ago

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.

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 = }

    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
```JSON
  "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 commented 1 year ago

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.