Instancing in UsdSkel

Instancing the "Bind State"

A critical component of scalability when attempting to encode a crowd is that a model's Bind State must be instanced across similar models. For example, suppose that a crowd has multiple copies of a particular model: Each of those copies will typically have a different root transform, and different set of joint transforms. Visually, the skinned models may all look entirely unique, but most of the contents of each copy of that model is identical: The topology of each geometric primitive within the model is the same. The primvars within the model are the same. The only properties that are not changed when skinning a copy of the model are points and normals of the skinned geometry.

Scalability when encoding many such copies of characters depends on ensuring that those properties of each character that can be shared, are shared. So what is really meant when talking about instancing in UsdSkel is this notion of instancing the bind state of a skeletally posed model. It is important that that level of instancing can be encoded in USD.

UsdSkel encodes this concept using a combination of inherited properties and scene graph instancing.

Instancing Example

This section contains a simple example of bind state instancing. We will begin with the complete example, before breaking it into parts:

def Xform "Model" (
instanceable = true
)
{
rel skel:skeleton = <Skel>
def Mesh "Mesh" {}
def Skeleton "Skel" {}
}
def SkelAnimation "ModelAnim_1" {}
...
def SkelAnimation "ModelAnim_N" {}
over Model_1 (
add references = </Model>
)
{
rel skel:animationSource = </ModelAnim_1>
}
...
over Model_N (
add references = </Model>
)
{
rel skel:animationSource = </ModelAnim_N>
}

This example begins with the definition of an instanceable model, which contains a Skeleton:

def Xform "Model" (
instanceable = true
)
{
rel skel:skeleton = <Skel>
def Mesh "Mesh" {}
def Skeleton "Skel" {}
}

Typically, the </Model> primitive would be defined in a separate file, and the reference that is added at each model instance would target that file.

Since this model has instanceable to true, when another primitive is created that references </Model>, USD will instance the scene graph beneath the model.

The contents of </Model> consist of a mesh (often many meshes), which has been bound to a Skeleton. This encapsulates the entire bind state of the model as an instanced scene graph location.

def SkelAnimation "ModelAnim_1" {}
...
def SkelAnimation "ModelAnim_N" {}

Here, we encode unique joint animations, so that we can bind them to the copies of </Model> that will follow.

Note that when a scene graphf location is instanced in USD, it is not possible to add additional primitives beneath that scene graph location.

So we cannot add the SkelAnimation primitives as children of each copy of </Model>.

Having defined unique animations, we then proceed to make copies of </Model>:

over Model_1 (
add references = </Model>
)
{
rel skel:animationSource = </ModelAnim_1>
}
...
over Model_N (
add references = </Model>
)
{
rel skel:animationSource = </ModelAnim_N>
}

As discussed in other sections, such as the UsdSkel_SchemaOverview_SkelAnimations "skel animation overview", the skel:animationSource relationship is "inherited" down namespace, onto Skeleton primitives. This inheritance property passes down into instances as well. So each instanced Skeleton beneath our </Model_1>, ... </Model_N> instances adopts the SkelAnimation that has been assigned on the instance itself.

Note that this ability to specify overrides on an instance is not unique to UsdSkel: Primvars in USD work the same way.

The net result of this encoding: The entire bind state of </Model> has been instanced to different scene graph locations, each of which has specified a unique SkelAnimation to apply to the underlying Skeleton(s).