At least in terms of Unity's implementation of Quaternions, the multiplication order described in the question is not correct. This is important because 3D rotation is not commutative.
So, if I want to rotate an object by
rotationChange starting from its
currentOrientation I'd write it like this:
Quaternion newOrientation = rotationChange * currentOrientation;
(ie. Transformations stack up to the left - same as Unity's matrix convention. The rightmost rotation is applied first / at the "most local" end)
And if I wanted to transform a direction or offset vector by a rotation, I'd write it like this:
Vector3 rotatedOffsetVector = rotationChange * currentOffsetVector;
(Unity will generate a compile error if you do the opposite)
For most cases you can get away with Lerping rotations. That's because the angle used "under the hood" in a quaternion is half the angle of rotation, making it substantially closer to the linear approximation of Lerp than something like a Matrix (which in general will not Lerp well!). Check out around 40 minutes into this video for more explanation.
The one case when you really need Slerp is when you need consistent speed over time, like interpolating between keyframes on an animation timeline. For cases where you just care that an output is intermediate between two inputs (like blending layers of an animation) then usually Lerp serves quite well.
The dot product of two unit quaternions gives the cosine of the angle between them, so you can use the dot product as a measure of similarity if you need to compare rotations. This is a little obscure though, so for more legible code I'd often use Quaternion.Angle(a, b) instead, which more clearly expresses that we're comparing angles, in familiar units (degrees).
These types of convenience methods that Unity provides for Quaternions are super useful. In almost every project I use a this one at least a few times:
Quaternion.LookRotation(Vector3 forward, Vector3 up)
This builds a quaternion that:
- rotates the local z+ axis to point exactly along the
forward vector argument
- rotates the local y+ axis to point as close as possible to the
up vector argument, if provided, or to
(0, 1, 0) if omitted
The reason the "up" only gets "as close as possible" is that the system is overdetermined. Facing z+ to
forward uses up two degrees of freedom (ie. yaw and pitch) so we have only one degree of freedom remaining (roll).
I find quite often I want something with the opposite exactness properties: I want local y+ to point exactly along
up, and local z+ to get as close as possible to
forward with the remaining freedom.
This comes up for example when trying to form a camera-relative coordinate frame for movement input: I want my local up direction to stay perpendicular to the floor or inclined surface normal, so my input doesn't try to tunnel the character into the terrain or levitate them off of it.
You might also get this if you want the turret housing of a tank to face a target, without peeling up off of the tank's body when aiming up/down.
We can build our own convenience function to do this, using
LookRotation for the heavy lifting:
Quaternion TurretLookRotation(Vector3 approximateForward, Vector3 exactUp)
Quaternion rotateZToUp = Quaternion.LookRotation(exactUp, -approximateForward);
Quaternion rotateYToZ = Quaternion.Euler(90f, 0f, 0f);
return rotateZToUp * rotateYToZ;
Here we first rotate local y+ to z+, and local z+ to y-.
Then we rotate the new z+ to our up direction (so the net result is local y+ points directly along
exactUp), and the new y+ as close as possible to the negated forward direction (so the net result is local z+ points as close as possible along
Another handy convenience method is
Quaternion.RotateTowards, which I often use like so:
Quaternion newRotation = Quaternion.RotateTowards(
maxDegreesPerSecond * Time.deltaTime
This lets us close in on
targetRotation at a consistent, controllable speed regardless of framerate - important for rotations that affect the outcome/fairness of gameplay mechanics (like turning a character's movement, or having a turret track-in on the player). Naively Lerping/Slerping in this situation can easily lead to cases where the movement gets snappier at high framerates, impacting game balance. (That's not to say these methods are wrong - there's ways to use them correctly without changing fairness, it just requires care.
RotateTowards gives a convenient shortcut that takes care of this for us)