Closed emackey closed 1 year ago
Here's a reduced test case with only 2 objects.
<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:
But when I load the .mtlx file, I see the child's material assigned to both:
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?
"nodes": [
{
"mesh": 0,
"name": "Child",
"translation": [
0,
1.4495644569396973,
0
]
},
{
"children": [
0
],
"mesh": 1,
"name": "Parent"
}
],
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.
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."
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.
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.
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.
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.
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>
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 = }
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 , 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.
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.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.