Quaternion interpolation in Blender is an area that many people would like to see improved. This thread is meant to discuss low-level, behind-the-scenes improvements to Blender.
Recently there was a discussion on the topic per email between Brecht van Lommel (@brecht), Stefan Werner (@StefanW) and Mark Theriault from Tangent Animation, and me. I’ll try and summarize the discussion, so that it can be continued here with a wider audience.
I have marked certain phrases in bold. I’m not yelling or saying that these are more important than anything else. It’s just to aid scannability of the text.
The problem is that Blender only performs component-wise interpolation (T45473), even when interpolating between quaternions. This causes interpolation artefacts. Even when you key every frame, interpolation is still necessary to properly render motion blur.
The underlying technical issue is that animation evaluation assumes that each FCurve can be interpolated individually.
The solution is to make Blender use spherical interpolation for quaternions. This category can be split up into linear A-to-B interpolation (SLERP) and quadratic A-via-B-to-C interpolation (SQUAD).
There is an old patch (D1929, 2016) that adds SLERP/SQUAD as new rotation modes (so you have the choices Quaternion, Quaternion SLERP, and Quaternion SQUAD). However, Josua Leung points out that the patch
- only works for FCurves in the current Action of a datablock, and not for any NLA strips that could provide other animation or modify the existing animation,
- doesn’t take FCurve Modifiers into account,
- only works for bones,
- has problems with drivers,
- doesn’t allow animators control over the speed at which rotations are approached, and
- it is unknown how it works with action constraints, motion paths, or manual tweaks to the curves.
I think these are all valid concerns for any solution.
In our email discussion, @brecht writes (emphasis mine):
What needs to be done is refactoring the code to remove the assumption that you can simply call
evaluate_fcurve(fcurve, time)and get a float value for an individual curve.
The simplest solution would be to find the other f-curve for the quaternion in
fcurve_eval_keyframes, and then use them for SLERP. You could quickly hack this in by just following the next/prev pointers in the f-curve, the quaternion components should be consecutive in the list in practically all cases (but I’m not entirely sure that’s something that is guaranteed, it could be enforced if so).
Of course that means performance will not be ideal, the same curves will be evaluated 4x. The most performance-critical case is animation playback where we evaluate all f-curves in a single function, so avoiding it there should not be hard with some refactoring. For other random tools and Python API access, performance may either not be critical, or there could be a local cache (passed to
evaluate_fcurveas an argument) knowing that quaternion components are likely to be evaluated consecutively.
and after I voiced concern about drawing the curves in the Graph Editor:
There is a code path that uses the bezier curves directly, and one that calls
evaluate_fcurvefor every time step, which is slower but needed for modifiers. The quaternion case could be made to use the slower call. The proper abstraction here would be to have a version of
evaluate_fcurvethat can evaluate multiple samples in a time range with whatever optimizations are possible depending on the type of f-curve.
Concerns about Invalid Quaternions
@StefanW voiced his concerns over the fact that having control over the FCurves for individual quaternion components can produce mathematically invalid quaternions. I had the same concerns: it is even possible to have different interpolation options (constant, linear, bouncy etc.) for each component. Although this is true, there is also a strong desire from animators to keep per-component control, at least until there is something that works better. For now we’ll just have to normalise the quaternions before interpolation.
I think that Brecht’s suggestion is a good one to try and implement first.
- Existing animation should not be broken. This means that animators must have a choice over Blender’s current behaviour vs. spherical interpolation, for example via an FCurve setting. This setting could be synced between the four FCurves of a single quaternion.
fcurve_eval_keyframesis altered to do SLERP. Open for discussion is how to handle the case when not all w/x/y/z channels are keyed simultaneously.
- Force Actions to have their FCurves sorted, at least such that curves on the same RNA path are consecutive and ordered by array index.
- Improve animation evaluation to only SLERP once per quaternion, and not once per FCurve.