Instancing prims from sublayer

I am looking at instancing workflows and have a few questions about embedding references into prototype prims.

My prototype has a breakdown sublayer where assets are placed for layout to arrange/build a more complex assembly from. This is all good it seems. My question is about references being pathed (we don’t have an asset resolver at the moment) vs just embedding a path to prim that points to the reference on disk.

breakdown.usda

def Xform "root"
{
    def Xform "prop"
    {
        def Xform "keyboardA01" (
            instanceable = true
            prepend references = @/asset/job/prop/keyboardA/components/model/usdCache/default/v001/entry.usda@
        )
        {
        }

        def Xform "mouseA01" (
            instanceable = true
            prepend references = @/asset/job/prop/mouseA/components/model/usdCache/default/v001/entry.usda@
        )
        {
        }
    }
}

What I’ve seen is that most approaches will encode the full path in the prototype prim but does it have to be this way? I swaped the absolute paths to the prim path that is fed from the breakdown sublayer and it works fine. Is this bad practice? Is there reasons not to do this?

layout.usda

#usda 1.0
(
    endTimeCode = 1
    framesPerSecond = 24
    metersPerUnit = 1
    startTimeCode = 1
    timeCodesPerSecond = 24
    upAxis = "Y"
)

def Xform "root" (
    kind = "group"
)
{
    def Xform "layout" (
        kind = "group"
    )
    {
        def Scope "Prototypes" (
            kind = "group"
        )
        {
            token visibility = "invisible"

            def "root"
            {
                def "prop"
                {
                    def Xform "mouseA01" (
                        instanceable = true
                        prepend references = @/asset/job/prop/mouseA/components/model/usdCache/default/v001/entry.usda@
                    )
                    {
                    }

                    def Xform "keyboardA01" (
                        instanceable = true
                        prepend references = @/asset/job/prop/keyboardA/components/model/usdCache/default/v001/entry.usda@
                    )
                    {
                    }
                }
            }
        }

        def "instance0" (
            instanceable = true
            prepend references = </root/layout/Prototypes/root/character/horseA01>
        )
        {
            matrix4d xformOp:transform:instancer1 = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-500, 0, -500, 1) )
            uniform token[] xformOpOrder = ["xformOp:transform:instancer1"]
        }

        def "instance1" (
            instanceable = true
            prepend references = </root/layout/Prototypes/root/character/maleA01>
        )
        {
            matrix4d xformOp:transform:instancer1 = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-479.5918273925781, 0, -500, 1) )
            uniform token[] xformOpOrder = ["xformOp:transform:instancer1"]
        }
    }
}

becomes (compare: /root/layout/Prototypes)

#usda 1.0
(
    endTimeCode = 1
    framesPerSecond = 24
    metersPerUnit = 1
    startTimeCode = 1
    timeCodesPerSecond = 24
    upAxis = "Y"
)

def Xform "root" (
    kind = "group"
)
{
    def Xform "layout" (
        kind = "group"
    )
    {
        def Scope "Prototypes" (
            kind = "group"
        )
        {
            token visibility = "invisible"

            def "root"
            {
                def "prop"
                {
                    def Xform "mouseA01" (
                        instanceable = true
                        *prepend references = </root/prop/mouseA01/>
                    )
                    {
                    }

                    def Xform "keyboardA01" (
                        instanceable = true
                        prepend references = </root/prop/keyboardA01/>
                    )
                    {
                    }
                }
            }
        }

        def "instance0" (
            instanceable = true
            prepend references = </root/layout/Prototypes/root/character/horseA01>
        )
        {
            matrix4d xformOp:transform:instancer1 = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-500, 0, -500, 1) )
            uniform token[] xformOpOrder = ["xformOp:transform:instancer1"]
        }

        def "instance1" (
            instanceable = true
            prepend references = </root/layout/Prototypes/root/character/maleA01>
        )
        {
            matrix4d xformOp:transform:instancer1 = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-479.5918273925781, 0, -500, 1) )
            uniform token[] xformOpOrder = ["xformOp:transform:instancer1"]
        }
    }
}

Is this a dumb approach? We have a mechanism for building our sublayer stack on the fly and some other stuff but if the answer is “asset resolver” then that is good to know!

Composition doesn’t seem to have a problem with method but I am no expert on the nuance of USD and I am curious what pitfalls we will run into?

Thanks I hope this wasn’t a dumb question!

This is actually a great question, @patricknagle , on a topic for which there is no “right answer”… like most things USD, there are alot of approaches and tools that provide different tradeoffs… some of which (as in this case), aren’t immediately obvious, so it’s great that you asked!

In the possibly more common pattern you came across, which Pixar uses predominantly in our pipeline, where each instance references “an asset” that doesn’t otherwise exist on the stage, you get the benefits of minimal setup cost, and maximal scalability, since each prototype gets composed only once (in a somewhat “hidden”, but discoverable) part of the stage. A disadvantage of this approach is that if you want your users to be able to edit “all instances at once”, you need to set up inherited classes, either in the referenced assets (which is what we do), or dynamically as you reference each instance; the latter technique is flexible but definitely ups the complexity. If you want to apply edits just to particular VariantSet variations of an instanced prototype, you can do so by getting fancy with EditTargets inside the classes that you edit, but I don’t think any DCC’s really support this advanced style of editing, yet (please correct me if I’m wrong about that, vendor friends!). There are a couple of other subtleties about the effect of editing asset classes that people here debate about, but I won’t go into that now.

The approach you’re taking, which AFAICT is similar to what Weta is building, gives you the ability to carefully assemble and craft your pool of prototypes on a set-by-set or shot-by-shot basis, and you can even specifically lay out different variations of each prototype that you intend to use. The big draw of going this way (in addition to it being the same pattern you need to use with PointInstancers) is that it gives you a concrete, easily accessible, place on the stage to “edit the prototypes” such that all instances are affected, in a way that will work pretty easily in all DCC’s if you go the straightforward way, here.

So, the downsides of this approach include:

  • Those “prototypes” you are laying out are not the actual USD prototypes: the Stage is still going to create the “hidden” prototypes that it needs; yours are just the editable “prototype sources” - for several reasons the internal USD prototypes are not editable. But this means you are paying double the stage composition and (potentially) imaging costs for each prototype, which if you have many, can be significant.
  • If you do apply this technique at both the set-level and the shot level (or really, even just at the set level, and you reference more than one set into your scene), then you are going to “split” your prototypes, even if you and the users believe they should be the same, because each set (and the shot) will be placing “prototypes sources” at different places in namespace, which means they will become different prototypes. When you just reference assets, all instances just naturally share the same prototypes regardless of where the instances were added.

In your example, you’ve tried to mitigate the extra imaging costs by making the “Prototypes” scope invisible. Unfortunately, Storm, and possibly other Hydra renderers, currently still do alot of work for invisible geometry, in service of making it fast to interactively vis/invis things. A more reliable way to “protect” your prototypes from imaging is to use the class specifier on the “Prototypes” prim, and this is roughly what Weta does, I believe. I hope that most DCC’s still make it straightforward to edit class prims, but for sure, Hydra will ignore them completely!

A neat fact about how sub-root referencing works allows you to alternately deactivate the “Prototypes” prim without needing to change how you reference the prototypes into your instances (because the prototypes are “beneath” the deactivated prim, that deactivation is ignored across the reference). This eliminates all the extra stage composition costs as well as the imaging costs, but of course, since descendants of deactivated prims are not present on the stage (that’s the primary reason we deactivate), it also takes away one of the main reasons people go this way… you can no longer easily edit the prototype sources in any DCC or even with the OpenUSD API’s.

The one other thing I’ll mention is that there’s a small, possibly unimportant-in-your-pipeline fragility is your setup in that your layout.usda layer implicitly depends on the presence of the breakdown.usda layer in the same layerStack, or else it will break. If you have the ability in your toolset to nest subLayers, you could remove the fragility by having layout.usda itself subLayer breakdown.usda. But if these layers are never intended to be used in any other context, then it’s probably fine for the dependency to be resolved in the parent layer, as you have it.

Hope that’s helpful, and cheers!

1 Like

Thank you! This is a very helpful reply and gives us a lot to think about! Very grateful!