Transformations, Time-sampled Animation, and Layer Offsets
VERIFIED ON USD VERSION 21.05
This tutorial builds an example scene of a spinning top toy that illustrates the following topics:
We create the scene by starting with a USD file with static geometry, referencing it into another USD file, overlaying animation, and then using a third USD file to reference and re-time that animation.
To fully illustrate these concepts, we walk through a Python script that performs these steps using the Python USD API, as well as showing the resulting text USDA outputs.
The scripts and data files exist in the USD distribution under
USD/extras/usd/tutorials/animatedTop. Run generate_examples.py in that directory to generate all of the snippets for each step shown below.
The toy geometry was modeled in Houdini as a revolved curve, followed by per-face color assignments as Houindi vertex attributes. The model was exported to the file
USD/extras/usd/tutorials/animatedTop/top.geom.usd using the Usd Export ROP. (See the Houdini USD Plugins for details.)
The USD distribution ships with usdview, a lightweight tool for inspecting USD files. Running
usdview on the file, we can see that the geometry looks like this:
Choosing an "Up" axis
Computer graphics pipelines almost always pick an axis to represent the "up" direction. Common choices are +Y and +Z.
As a pipeline interchange format, USD provides ways to configure a site default, as well as to store an explicit choice per-file. See Encoding Stage UpAxis for details.
In the examples here, we use +Z as the up axis, and write this choice out in every file so that they will work regardless of your site configuration.
Moving Objects with Animated Transformations: Spinning the Top
Let's make the top spin. In a typical production pipeline, rigging & animation would be set up in a dedicated package and the results exported to USD. Here, we will create the USD files by hand to illustrate the underlying concepts.
USD represents animation as time-sampled attribute values. To establish the time range over which values are animated, the root layer can specify a start and end time in its layer metadata. We will use USD's cinema default of 24 frames per second, and design the spinning motion to repeat after 192 frames, or 8 seconds. Setting this range up in
You can run
usdview on this file, but the viewport will be mostly empty since we haven't added any geometry.
Let's add a reference to the static top model, using the UsdReferences API:
This adds a "Top" prim to our scene, with the details referenced in from the geometry file.
You may have noticed the 'prepend' operation in the reference statement above. 'Prepend' means that, when this layer is composed with others to populate the stage, the reference will be inserted before any references that might exist in weaker sublayers. This ensures that the contents of the reference will contribute stronger opinions than any reference arcs that might exist in other, weaker layers.
In other words, 'prepend' gives the inuitive result you'd expect when you apply one layer on top of another. This is what the UsdReferences API will create by default. You can specify other options with the
position parameter, but this should rarely be necessary.
Next, let's add animation overrides to make the top spin.
Spatial transformations, such as rotations or translation, are a standard concept in computer graphics. USD represents transformations (also known as "xforms" for short) using the UsdGeomXformable schema. It can encode these kinds of operations as a set of related attributes. Here, we will add a spin rotation on the Z axis, and use time-samples to vary the rotation amount, in degrees:
UsdGeomXformable uses the name of the attribute to encode its meaning. Here, the
rotateZ part specifies a rotation on the Z axis, and
spin is a descriptive suffix. There are similar names for related transformation operations; see the
UsdGeomXformable schema for details. Even though there is only a single transform at the moment, to make it take effect we must express the desired order as the
USD applies a linear interpolation filter to reconstruct the attribute value from the time samples. Rotations are expressed in degrees, so this provides 4 revolutions over the course of the 192-frame animation.
We can use
usdview to play back the animation:
Chaining Multiple Transformations with xformOpOrder
In a real spinning top, gravity causes the top to tilt. Let's add a "tilt" rotation to represent this. Notice that we add the tilt before the spin:
Here is the result. The spin axis is now tilted:
To illustrate the importance of transformation order, here is a variation that shows what we get if we flip the order of rotations, by swapping the two entries in the xformOpOrder:
One way to think about xformOpOrder is that it describes a series of transformations to apply relative to the local coordinate frame, in order.
|First example||Tilt the top, then spin it on its tilted axis.||
xformOpOrder = ["xformOp:rotateX:tilt", "xformOp:rotateZ:spin"]
|Second example||Spin the top, then tilt it in that coordinate frame||
xformOpOrder = ["xformOp:rotateZ:spin", "xformOp:rotateX:tilt"]
This suggests one more detail we'd like to add: precession. Assuming the top is sitting on a surface, torque from gravity will introduce precession, swinging the primary axis of spin around. Friction at the contact point will also cause the top to move slightly on the surface. Restoring the original xformOpOrder, we can model these effects with two more operations:
Here is the result:
To summarize: we used time-samples to animate the motion, and careful ordering of the transformations to express a spinning motion with precession and translation in a relatively simple way. USD uses linear interpolation to reconstruct the intermediate values of the operations and then computes the combined transformation.
Re-timing animation with Layer Offsets
In the scenes above we referenced the static geometry before adding animation, but it is also possible to reference a file containing its own animation.
While referencing animation, a layer offset can be used to apply a simple scale & offset operation to the timeline.
Here we reference the above animation in 3 times, adding a different +X translation to each. The first top has no layer offset; the second has a +96 frame offset (causing it to be 180 degrees out of phase), and the third top has a scale of 0.25 (causing its animation to play back at 4x the rate).
Perhaps it is a surprise that this GIF does not loop cleanly – let's look at the reasons. There are several things to observe here that illustrate how time samples and layer offsets work:
- A layer offset (represented as SdfLayerOffset) expresses both a scale and offset that is applied to the times of the samples when bringing the data from the layer to the stage. For a given sample,
stageTime = layerTime * scale + offset.
- The middle top, with the offset of +96, does not begin rotating until 96 frames after the left top (which has no offset). The reason is that USD does not extrapolate time-samples. Outside the time range covered by samples, the nearest sample value is held. In this case, it takes 96 frames until we hit the first sample and rotation begins.
- The right top spins quickly (4x the rate) and then stops. The 0.25 scale on its reference has "shrunk down" its timeline, so it quickly plays through. After the first 48 frames (== 192 frames * 0.25), there are no further time samples, so again the values are held and rotation stops.
- To lay out the tops in a row, we used a parent Xform prim on each, and set the translate there. If we had referenced the Top in directly and added the translate to xformOpOrder, we would be at risk of replacing (or otherwise falling out of sync with) the underlying xformOpOrder and losing its animation. By using a separate pivot we avoid this. It's worth noting, however, that in a production pipeline most assets do not contain animated root transforms, and so this is not necessary.
- When adding the references, we had to specify the primPath. This is because we did not set the defaultPrim metadata when creating
Step5.usda; if we had done that, the primPath would not be required.
To summarize, layer offsets are intended to support simple cases of retiming animation. For more elaborate scenarios, such as looping animations, USD supports the more powerful (and correspondingly more complex) concept of Value Clips.
We conclude this tutorial with a path-traced render of the above scene, which illustrates how the varying rates of rotation yield corresponding degrees of motion blur.