Oct 27, 2021.Knowledge
Control Rig Dynamic Hierarchy
Next stop: Changing the hierarchy at Runtime
In this article we will discuss changes to the Control Rig hierarchy in Unreal 5.0. We are presenting a new and novel way to manipulate the static hierarchy by removing certain limitations seen in other hierarchy implementations.
We will discuss previous limitations, our implementation, and use cases of the Dynamic Hierarchy. This article will be of interest to Character Riggers, Animators, and Programmers regarding Control Rig and Runtime Animation inside Unreal 5.0.
There is a series of assumptions that widely used DCCs (Digital Content Creation Tools) make concerning character rigging:
- The hierarchy is fixed and defined by the rigger before animation starts.
- Graphs that solve the pose are fixed thus all features for the rig have to be defined before animation starts.
- Multiple purposes / tasks for rigs require multiple rigs.
These assumptions / technical limitations drive the design of character rigs all the way to animation pipelines at film and games studios alike. This results in:
- Character rigs can be tremendously complex since they need to solve all potential animation challenges the character might encounter as well as animator specific asks.
- Animation pipelines from Motion Capture to final performance can be fragmented and require many rigs and processing steps.
- Studios have to invest in custom development to solve animation challenges that are in conflict with the assumptions above. Studios tend to develop large and complex animation tools or rely on a series of plugins and patchwork on top of existing pipelines.
Dependency graph based architectures have a series of disadvantages for character rigging and animation:
- Cycles can be problematic. A directed graph solve model doesn’t deal well / at all with cyclic behaviors, such as symmetry or dependencies which change in a cycle over time
- Control structures being combined with the solve portion of the rig create challenges around changing the behavior of controls for certain animation tasks.
Over the years many proprietary solutions have surfaced through conventions and presentations which showcase novel approaches. For example, the approach of ephemeral rigging as presented by Raf Anzovin suggests a different approach - and can provide food for thought.
Raf Anzovin. 2019. Fast, interpolationless character animation through “ephemeral” rigging. In ACM SIGGRAPH 2019 Talks (SIGGRAPH '19). Association for Computing Machinery, New York, NY, USA, Article 54, 1–2. DOI:https://doi.org/10.1145/3306307.3328165
Let’s dive into it by defining a series of assumptions before looking at needs / asks:
- The hierarchy can be changed at any time.
- Rigs can be switched while maintaining the animation.
- Adding / removing elements from the hierarchy at runtime.
- Changing relationships between elements in the hierarchy at runtime.
- Parts of the (or the whole) hierarchy can be created procedurally.
- Hierarchies can be represented with multiple LODs (level of detail variations).
- Cyclic scenarios are possible (left hand connected to right hand, later the right hand is connected to the left hand for ex.).
To answer to the assumptions and requirements above we’ve designed the Dynamic Hierarchy inside of Control Rig to combine flexibility as well as performance. The Dynamic Hierarchy will be familiar to our previous implementations on the top level, making it an opt-in feature for users along with being backwards and forwards compatible as of this writing. Previous implementations (as of 4.26 / 4.27) use a simple flat data container which focused solely on performance and data compactness, but didn’t provide the needed flexibility.
With 5.0 we separate the hierarchy infrastructure into two layers: The URigHierarchy (data model) and the URigHierarchyController (mutating layer). The hierarchy as well as the controller support a notification scheme to wire it up to UI or other services for propagating changes. In short we introduce a soft MVVM design separation. The hierarchy however can perform any changes itself which are relevant for animation without the controller - while the controller is needed for topological changes. A topological change describes an edit which causes a memory layout change.
For example when you set the transform of an element, you only need to access URigHierarchy::SetTransform, but when adding a parent to an element you need to access URigHierarchyController::AddParent.
We’ve identified that there are edge-case scenarios for accessing the hierarchy only in local space (when integrating with the Animation Blueprints, for example) as well as accessing the hierarchy only in global space. Additionally the regular-case scenario is accessing some transforms in local as well as some in global space. To support this we’ve implemented a lazy evaluation scheme in which the local can be computed from global and vice versa - while caching intermediary results. To stay on top of the cache validity complexity we’ve also introduced a cache validity checking mechanism which can be enabled via a C++ define.
To identify topological changes there is a notification scheme via delegates available as well as a topology version - with which you can identify if elements have been added / removed or if relationships have changed since you’ve accessed the hierarchy last.
The hierarchy can store a list of elements, where each element can have relationships to one or more additional elements. As of 5.0 the only relationship that exists is the Parent-Child relationship. Certain elements can only have one parent (FRigSingleParentElement), while others can have multiple (FRigMultiParentElement). We’ve deliberately chosen to offer the multi parenting feature as an option - so that most of the hierarchy can benefit from the slightly better performing single-parent-element.
As an element is related to more parents over time - the weight of each relationship can be modulated / animated. By moving all of the weight from one parent to another the element can switch its space, since the local transform is now expressed in another parent. Combining this with the lazy evaluation scheme the global transform can be compensated easily - so that the element doesn’t move or “pop” in world space. You can mark the local transform as dirty (to be recomputed) and change the parent relationships accordingly.
One of the big differences between classic DCCs and our implementation is that parent relationships can be completely temporal, meaning they can come to existence and disappear completely after some time again. This allows for flexible choice of space and relationships during animation authoring without having to worry about cycles.
In 5.0 Sequencer utilizes these capabilities to offer the Space Switching user-facing featureset.
Specify the space for a given Control.
The hand controls change spaces as the timeline is scrubbed.
- Fast and lazy evaluation of local / global transforms.
- Flexibility for topological edits within the hierarchy data structure.
- Large and fast procedural edits (like inverting a chain, breaking off a chain, etc).
- Notification scheme for integrating arbitrary views / user interface.
- Larger memory footprint (~ 1.6x) for dynamic structure vs flat packed version in 4.26 / 4.27.
- Code complexity and risk of cache validity bugs.
- The flexible topology of the hierarchy opens up the door to solving larger hierarchies than just the per-character scope. Merging multiple hierarchies into a larger one may allow to establish relationships across the smaller hierarchies.