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.
The following example demonstrates a 3-joint, skinned arm, with an animation that rotates the elbow by 90 degrees, over 10 frames:
We will proceed to break this down one piece at a time.
When a model contains skinned data, it must be encapsulated within a UsdSkelRoot primitive. This serves two purposes:
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.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:
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
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
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.
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
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.
Binding An Animation To A Skeleton
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:
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.
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:
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.
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:
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:
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.
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.