Schema Intro By Example

This page provides an overview of the UsdSkel schemas, using example scene description. The examples here only deal with the scene description itself. For coding examples, see the API Introduction page. For a more an more in-depth look at the schemas themselves, see the Schemas In-Depth page.

Skinning an Arm

The following example demonstrates a 3-joint, skinned arm, with an animation that rotates the elbow by 90 degrees, over 10 frames:

#usda 1.0
(
startTimeCode = 1
endTimeCode = 10
)
def SkelRoot "Model" {
def Skeleton "Skel" {
uniform token[] joints = ["Shoulder", "Shoulder/Elbow", "Shoulder/Elbow/Hand"]
uniform matrix4d[] bindTransforms = [
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,2,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,4,1))
]
uniform matrix4d[] restTransforms = [
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,2,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,2,1))
]
def SkelAnimation "Anim" {
uniform token[] joints = ["Shoulder/Elbow"]
float3[] translations = [(0,0,2)]
quatf[] rotations.timeSamples = {
1: [(1,0,0,0)],
10: [(0.7071, 0.7071, 0, 0)]
}
half3[] scales = [(1,1,1)]
}
rel skel:animationSource = <Anim>
}
def Mesh "Arm" {
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [
2, 3, 1, 0,
6, 7, 5, 4,
8, 9, 7, 6,
3, 2, 9, 8,
10, 11, 4, 5,
0, 1, 11, 10,
7, 9, 10, 5,
9, 2, 0, 10,
3, 8, 11, 1,
8, 6, 4, 11
]
point3f[] points = [
(0.5, -0.5, 4), (-0.5, -0.5, 4), (0.5, 0.5, 4), (-0.5, 0.5, 4),
(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (0.5, 0.5, 0),
(-0.5, 0.5, 2), (0.5, 0.5, 2), (0.5, -0.5, 2), (-0.5, -0.5, 2)
]
rel skel:skeleton = </Model/Skel>
int[] primvars:skel:jointIndices = [
2,2,2,2, 0,0,0,0, 1,1,1,1
] (
interpolation = "vertex"
elementSize = 1
)
float[] primvars:skel:jointWeights = [
1,1,1,1, 1,1,1,1, 1,1,1,1
] (
interpolation = "vertex"
elementSize = 1
)
matrix4d primvars:skel:geomBindTransform = ((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1))
}
}

We will proceed to break this down one piece at a time.

Skinning an Arm: The Skel Root

def SkelRoot "Model" {
...
}

When a model contains skinned data, it must be encapsulated within a UsdSkelRoot primitive. This serves two purposes:

  1. Informs applications that are consuming the file that a branch of the scene graph contains skinned primitives, as such data often requires special processing by the consuming application.
  2. Provides a place to encode pre-computed bounding information for the skinned primitives (a UsdSkelRoot is a subclass of UsdGeomBoundable). This allows renderers to determine where in space the result of skinning resides without having to apply skinning first. For example, a renderer is capable of efficiently culling objects that are not visibile in camera without paying the cost of computing skinning.
See also
Skeleton Root Schema

Skinning an Arm: Defining a Skeleton

def Skeleton "Skel" {
uniform token[] joints = ["Shoulder", "Shoulder/Elbow", "Shoulder/Elbow/Hand"]
...
}

A Skeleton encodes a joint hierarchy. The actual joints and their parent<->child relationships are encoded in a compact, vectorized token array. Each token in the array is the token-valued form of a relative path to that joint. The paths follow the same syntax rules are other prim paths and in USD, and may be constructed using the SdfPath API.

The tokens authored for joints may be considered as defining the topology of the Skeleton.

Each joint's parent is identified by path. If an intermediate path is excluded, then the next nearest ancestor path that is included is used instead. For example, if the joint list above did not include Shoulder/Elbow, then the parent of Shoulder/Elbow/Hand would be Shoulder. Consumers of UsdSkel files are encouraged to use the UsdSkelTopology utility class to reason about these relationships.

Note that it is valid to have multiple root joints in a Skeleton. For example:

def Skeleton "Skel" {
uniform token[] joints = ["RootA", "RootA/Child", "RootB"]
...
}

This is allowed because some of the applications that UsdSkel interchanges with also allow it. In a way, the Skeleton primitive itself may be thought of as the true root of the Skeleton.

Bind Transforms

def Skeleton "Skel" {
...
uniform matrix4d[] bindTransforms = [
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,2,0,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,4,0,1))
]
...
}

The bindTransforms property of a Skeleton provides the world space transform of each joint at bind time. A world space encoding has been chosen for bind transforms, since most DCC apps tend to use the same encoding, so using the same encoding tends to simplify IO.

The entries of the bindTransforms array are ordered according to the order of the joints attribute. So for this example, the first entry in the array corresponds to joint Shoulder, the second to Shoulder/Eblow, and so forth.

Rest Transforms

def Skeleton "Skel" {
...
uniform matrix4d[] restTransforms = [
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,2,0,1)),
((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,2,0,1))
]
...
}

The restTransforms property of a Skeleton provides local space rest transforms of each joint, which serve as a fallback values for joints that have not been overridden by an animation. As with bindTransforms, the restTransforms are ordered according to the order of the joints attribute.

The restTransforms may be considered optional, but if left unspecified, the Skeleton must be bound to a complete, non-sparse animation. It is common for the restTransforms to be refer to the same pose as the bindTransforms, only in joint-local space, rather than world space, but this is not requried.

See also
Skeleton Schema

Skinning an Arm: Skel Animations

def SkelAnimation "Anim" {
uniform token[] joints = ["Shoulder/Elbow"]
...
}

Instead of encoding joint animations directly on a Skeleton, the animations are encoded in a UsdSkelAnimation primitive. The animation of a Skeleton is kept separate in this manner both to allow different animation encodings – as different prim types – as well as for the sake of instancing.

An animation must define joints, which determines the set of joints that the animation affects. This order does not need to match the order given on the Skeleton primitive. To emphasize that point, this particular animation has been setup to affect only a single joint (Shoulder/Elbow).

Joint Transforms

def SkelAnimation "Anim" {
...
float3[] translations = [(0,0,2)]
quatf[] rotations.timeSamples = {
1: [(1,0,0,0)],
10: [(0.7071, 0.7071, 0, 0)]
}
half3[] scales = [(1,1,1)]
}

Currently, UsdSkel encodes joint animations as arrays of translation, rotation and scale components. The encoding is vectorized for scalability reasons, and with consideration for IO performance on large-scale crowds. It is broken into these separate components primarily for the sake interpolation, but this also has storage benefits – such as enabling separate run-length encoding for different components. For example, if only the rotations are changing over time, as is often the case, then we need only store animated values for those rotations, and need not store unique scale/translation samples per frame.

These transform components combine to describe the local space transforms of a set of joints. Helper methods UsdSkelDecomposeTransforms() and UsdSkelMakeTransforms() can be used to convert inbetween these components and arrays of matrices.

Note that this encoding implies that joint local transforms must have an orthogonal basis. While non-uniform scales may still be authored, non-uniform scale values should only be used to apply reflections; each component of the scale should otherwise have the same magnitude. It is further recommended that users make an effort to stick to purely orthonormal transforms: Many applications do not support non-uniform joint scaling, so translating non-orthonormal transforms to other packages may be problematic.

See also
Skel Animation Schema

Binding An Animation To A Skeleton

def Skeleton "Skel" {
def SkelAnimation "Anim" { ... }
rel skel:animationSource = <Anim>
}

In order for a SkelAnimation to have any effect, it must be bound to a Skeleton. This is done using the skel:animationSource relationship, as created through the UsdSkelBindingAPI.

The animation source binding is inherited down namespace. For example, suppose we have:

def Scope "Scope" {
rel skel:animationSource = </Anim>
def Skeleton "Skel" {}
}

In that case, the Skeleton at </Scope/Skel> inherits the animation source defined at </Scope>. The primary motivation for this inheritance property is that it is the means by which UsdSkel allows instanced primitives to be driven by different joint animations. The instancing section goes into this in more detail.

See also
Binding API Schema

Skinning an Arm: Binding Skeletons to Prims

def Mesh "Arm" {
...
rel skel:skeleton = </Model/Skel>
...
}

Skeletons are bound to the primitives that they skin by way of the skel:skeleton binding relationship, which can be set through the UsdSkelBindingAPI.

This relationship works in a similar manner to the skel:animationSource binding, in that the binding is inherited. Using this inheritance, it is common on production assets to bind the skeleton at a higher scope, rather than on individual primitives – of which there may be many! For example:

def SkelRoot "ComplexModelWithHundredsOfMeshes" {
rel skel:skeleton = </Skel>
def Mesh "Mesh0" {}
def Mesh "Mesh1" {}
...
def Mesh "Mesh1000" {}
}

Note that it is only valid to bind skeletons either on UsdSkelRoot primitives, or on their descendants.

This example shows the common case of mesh skinning, but UsdSkel's skinning is not restricted to only meshes.

See also
Binding API Schema What Can Be Skinned?

Skinning an Arm: Joint Influences

def Mesh "Arm" {
...
int[] primvars:skel:jointIndices = [2,2,2,2, 0,0,0,0, 1,1,1,1] (
interpolation = "vertex"
elementSize = 1
)
float[] primvars:skel:jointWeights = [1,1,1,1, 1,1,1,1, 1,1,1,1] (
interpolation = "vertex"
elementSize = 1
)
...
}

The jointIndices and jointWeights primvars store the joints and joint weights for each vertex. Both primvars are required to have a matching interpolation and array size, and the interpolation must be either 'constant' or 'vertex'.

Each value from primvars:skel:jointIndices gives the index of a joint, while the corresponding element from primvars:skel:jointWeights provides the weight for that joint.

Without setting any additional properties, the index values stored by the jointIndices primvar refers to joints in the ordering defined by the joints attribute of the bound Skeleton. So, referring back to the definition of the Skeleton:

def Skeleton "Skel" {
uniform token[] joints = ["Shoulder", "Shoulder/Elbow", "Shoulder/Elbow/Hand"]
...
}

Given the set of joints defined here, a value of 0 in jointIndices refers to the Shoulder joint, a value of 1 refers to Shoulder/Elbow, and so forth.

Explicit Joint Orders

Instead of using the joint order declared on the Skeleton, it is also possible to define an explicit ordering directly on a skinned mesh. For example:

def Mesh "Arm" {
...
uniform token[] skel:joints = ["Shoulder/Elbow", "Shoulder"]
int[] primvars:skel:jointIndices = [1,1,1,1, 1,1,1,1, 0,0,0,0] (
interpolation = "vertex"
elementSize = 1
)
float[] primvars:skel:jointWeights = [1,1,1,1, 1,1,1,1, 1,1,1,1] (
interpolation = "vertex"
elementSize = 1
)
...
}

Here, we have an explicit skel:joints ordering. Using that ordering, a value of 0 in jointIndices refers to the Shoulder/Elbow joint, and a value of 1 refers to the Shoulder joint. As with the skel:skeleton and skel:animationSource binding relationships, primvars:skel:joints is an inherited property, and may be set set on ancestor primitives. For that matter, all primvars in USD inherited down namespace.

Skinning an Arm: Geom Bind Transform

def Mesh "Arm" {
...
matrix4d primvars:skel:geomBindTransform = ((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1))
}

The primvars:skel:geomBindTransform primvar provides the world space transform of a skinned primitive at bind-time. As with the case of the Skeleton's bindTransform property, the bind transforms are given in world space, since that is how most DCC apps tend to encode the property.

The points of the skinned primitive are transformed by the geomBindTransform prior to skinning.

If left undefined, the geom bind transform is assumed to be the identity.

It is often the case that a skinned primitive will also have a transform authored using the typical UsdGeomXformable API, in addition to the geomBindTransform. If that is the case, the geomBindTransform is still the only transform used for skinning.