Nested instanced materials?

Hello, I am trying to understand why the following doesnt work :

I have a simple structure like this :

root.usda → Two prims each referencing a different asset, asset_1, and asset_2. Both are flagged instanceable.

asset_1.usda → a cube, with a material assigned. The material is brought in by a reference, which is flagged instanceable.

asset_2.usda → a cube, with a material assigned. The material is brought in by a reference, which is flagged instanceable. The material layer referenced here is the same as in asset_1.

Now - I would have expected the material to end up instanced / reused, even if the assets are different.

However, instead, I end up with two prototypes for the material…each instanced once…?

Is “nested” material instancing like so no expected to work? Am I missing something?

At a high level, i want to reference in different assets, which themselves reference the same material - and I want that material to be instanced in the composed scene.

Here are the layers :

root.usda

#usda 1.0
(
    defaultPrim = "root"
    metersPerUnit = 0.0254
    upAxis = "Z"
)

def Xform "root"
{
    def Xform "box" (
        instanceable = true
        prepend references = @./Asset_1.usda@
    )
    {
        matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
        uniform token[] xformOpOrder = ["xformOp:transform"]
    }

    def Xform "box2" (
        instanceable = true
        prepend references = @./Asset_2.usda@
    )
    {
        matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (10, 0, 0, 1) )
        uniform token[] xformOpOrder = ["xformOp:transform"]
    }
}

Asset_1.usda :

#usda 1.0
(
    defaultPrim = "root"
    metersPerUnit = 0.0254
    upAxis = "Z"
)

def Xform "root"
{
    def Cube "Asset_1" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        rel material:binding = </root/mtl/mysharedmaterial>
    }

    def Scope "mtl"
    {
      def Material "mysharedmaterial"
      (
        prepend references = @./mysharedmaterial.usda@</mysharedmaterial>
        instanceable = true
      )
      {
      }
    }
}

asset_2.usda :

#usda 1.0
(
    defaultPrim = "root"
)

def Xform "root"
{
    def Cube "Asset_2" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        rel material:binding = </root/mtl/mysharedmaterial>
    }

    def Scope "mtl"
    {
      def Material "mysharedmaterial"
      (
        prepend references = @./mysharedmaterial.usda@</mysharedmaterial>
        instanceable = true
      )
      {
      }
    }
}

mysharedmaterial.usda :

#usda 1.0
(
)

def Material "mysharedmaterial"
{
    token outputs:surface.connect = </mysharedmaterial/mysharedmaterial.outputs:surface>

    def Shader "mysharedmaterial"
    {
        uniform token info:id = "UsdPreviewSurface"
        float inputs:clearcoat = 0
        float inputs:clearcoatRoughness = 0.01
        color3f inputs:diffuseColor = (1.0, 0.0, 0.0)
        float inputs:displacement = 0
        color3f inputs:emissiveColor = (0, 0, 0)
        float inputs:ior = 1.5
        float inputs:metallic = 0
        normal3f inputs:normal = (0.047379453, 0.013452399, 0.86981463)
        float inputs:occlusion = 1
        float inputs:opacity = 1
        float inputs:opacityThreshold = 0
        float inputs:roughness = 0.5
        color3f inputs:specularColor = (0, 0, 0)
        int inputs:useSpecularWorkflow = 0
        token outputs:surface
    }
}

I guess I should not instance directly at the material level, with one additional prim in between, and instancing there … seems something gets instanced…at least an instancer gets created?

However, still two prototypes… Seems I still get multiple materials hitting public pxr::HdMaterial…? Why is that? I.e. one HdMaterial gets created for each asset :

  • /__Prototype_1/mtl/sub/mysharedmaterial
  • /__Prototype_2/mtl/sub/mysharedmaterial

Any advice? Feels like i am still doing something wrong…

You’re not doing anything wrong! Material instancing is “fully supported” in the USD scenegraph, and Hydra currently does correctly ingest them. But it currently does so in a naive way that immediately deinstances them, because actually preserving material instancing in an “optimal” way when each material instance has differing interface property values is a challenging task.

It is one we hope to be tackling soon, though. You are correct that today the only way to have a Material be shared is either: two assets bind to the exact same Material prim, or instancing an asset that contains both geometry and material.

Thanks for your answer!

Can you expand on “two assets bind to the exact same Material prim”? Isnt that what i tried to do and that you are saying ultimately gets deinstanced? Or by “exact” do mean mean the same prim “path” in the fully composed stage?

Or do you just mean that usd supports that - but its just hydra not dealing with it right?