HardlyDifficult.com/Tutorials/Quaternions
Goal: This tutorial introduces working with rotations, with a focus on Quaternions. Some math that goes into Quaternions is included; it may help to explain what these numbers represent, but it's not necessary to know when working with a game engine. By the end, you should feel comfortable using Quaternions in Unity.
- 1.) Euler Rotations
- 1.1) About Euler
- 1.2) Gimbal lock
- 1.3) Working with Euler
- 2.) Axis-Angle Rotations
- 2.1) About Axis-Angle
- 2.2) Working with Axis-Angle
- 3.) Quaternion Rotations
- 3.1) About Quaternions
- 3.2) Properties of a Quaternion
- 3.3) Creating Quaternions
- 3.3.1) Quaternion Constructors
- 3.3.2) Quaternion.LookRotation
- 3.3.3) Quaternion.FromToRotation
- 3.3.4) Math for Creating Quaternions
- 3.4) Interpolating Rotations
- 3.4.2) Quaternion.Slerp
- 3.4.1) Quaternion.Lerp
- 3.4.3) Quaternion.RotateTowards
- 3.4.4) Math for Quaternion Lerp
- 3.5) Combining Rotations
- 3.6) Inverse Rotation
- 3.6.1) Quaternion.Inverse
- 3.6.2) Math for Quaternion Inverse
- 3.7) Rotating Vectors
- 3.8) Comparing Rotations
- 3.8.1) Dot Product / Quaternion.Dot
- 3.8.2) Quaternion.Angle
- 3.8.3) Quaternion == Quaternion
- 3.8.4) Math for Quaternion Dot
When we think of rotations, we typically think in terms of 'Euler' (pronounced oi-ler). Euler rotations are degrees of rotation around each axis; e.g., (0, 0, 30) means "rotate the object by 30 degrees around the Z axis."
In the Inspector, modifying a Transform's rotation is done in Euler. In code, you can either work with Quaternions directly, or use Euler (or other representation) and then convert it back to Quaternion for storage.
The main reason that Euler is not the primary way of storing and manipulating rotations in a game is because of issues which arise from "Gimbal lock".
Gimbal lock is a situation in which 2 of the rotation axes collapse, effectively representing the same movement. This means instead of the usual 3 degrees of freedom (x, y, and z), you only have two.
Here is an example. Once an object reaches 90 degrees on the X axis, the Y and Z axes collapse, and modifying either produces the same results (where a change to Y is the same as a negative change to Z).
Gimbal lock is not an all-or-nothing situation. As you approach certain angles, the impact of changing axes may not offer the full range of motion that you might expect.
Note that Euler can represent any possible rotation. Gimbal lock is only a concern when modifying or combining rotations.
For a lot more detail - see Wikipedia's article on Gimbal Lock or GuerrillaCG's video on Gimbal Lock.
Given a Quaternion, you can calculate the Euler value like so:
Quaternion myRotationQuat = transform.rotation;
Vector3 eulerRotation = myRotationQuat.eulerAngles;
Euler rotations are stored as a Vector3. You can perform any of the operations you might use on a position Vector3 such as +, *, and Lerp. Then, given an Euler value, you can calculate the Quaternion:
Quaternion z30Degrees = Quaternion.Euler(eulerRotation);
Another way of representing rotations is Axis-Angle. This approach defines both an axis and the angle defining how much to rotate around that axis.
Here is a simple example where we are rotating around the X axis only. When the axis is one of the world axes like this, the angle is equivalent to an Euler angle.
View source for this example and the one below.
The following example shows a more complex rotation where the axis is not aligned with a world axis.
- It's hard to see with this render, but in the perspective on the right the red axis line is not just straight up and down, but also angled from front to back.
- The bottom two perspectives show the same rotation ,but with a straight-on view of the axis itself.
Axis-Angle and other rotation approaches, including Quaternions and Matrices, are not impacted by Gimbal Lock.
Given a Quaternion, you can calculate the Axis-Angle value like so:
float angle;
Vector3 axis;
transform.rotation.ToAngleAxis(out angle, out axis);
You could modify the angle or even the axis itself. Then given an Axis-Angle value, you can calculate the Quaternion:
Quaternion rotation = Quaternion.AngleAxis(angle, axis);
A Quaternion is an axis-angle representation scaled in a way which optimizes common calculations, such as combining multiple rotations and interpolating between different rotation values.
The default rotation for an object known as 'identity' is (0, 0, 0) in Euler and (0, 0, 0, 1) in Quaternion. If you multiply a rotation by identity, the rotation does not change.
Quaternions are composed of 4 floats, like an Axis-Angle. The first three (x, y, z) are logically grouped into a vector component of the Quaternion and the last value (w) is a scalar component. Some of the math below shows how these parts may be considered separately.
Quaternion rotations must be normalized, meaning:
x * x + y * y + z * z + w * w == 1;
Knowing the Quaternion rotations are normalized simplifies some of the math for using and manipulating Quaternions.
The performance Quaternions offer comes with a small cost in terms of storage. A rotation technically has 3 degrees of freedom, which means that it may be represented with 3 floats (like an Euler); however, a Quaternion requires 4 floats. This tradeoff has been deemed worthwhile by the industry for the performance when a game is running. If size matters, such as for network communication, quaternions may be compressed as well as an Euler could be.
In Unity, rotations are stored as Quaternions. You can construct a Quaternion from the calculated components.
Quaternion identity = new Quaternion(0, 0, 0, 1);
Generally, you would not use the Quaternion constructor. Selecting the values for x, y, z, w to create the rotation you are looking for is difficult for people to do.
Often, rotations are created as Euler and then converted to Quaternion. Then, Quaternions are used to modify other Quaternions using the techniques covered later in this tutorial. See the Euler and Axis-Angle sections above for examples on how-to convert rotation formats.
For the 'identity' rotation, instead of using the Quaternion constructor, you should use the Quaternion.identity variable:
Quaternion rotation = Quaternion.identity;
Note that the 'default' Quaternion is not a valid rotation, and may not be used with any rotation method:
Quaternion invalidQuaternion = default(Quaternion);
// invalidQuaternion == new Quaternion(0, 0, 0, 0)
// This is not normalized, therefore not a valid quaternion
LookRotation creates a rotation which will orient an object so that its forward will face the target forward direction. Its up will face the target up direction as best it can while maintaining the target forward. The up direction defaults to the world's positive Y direction, but you could change this; for example, making it the negative Y direction to rotate an object upside down.
In the following example (code followed by gif), an object is rotated so that it's always facing away from the camera (since the camera defaults to a negative Z position in the world, it is behind objects by default).
Vector3 directionToCamera
= Camera.main.transform.position - transform.position;
transform.rotation
= Quaternion.LookRotation(-directionToCamera, Vector3.up);
Note that the input directions do not need to be normalized.
FromToRotation creates a rotation which would modify a Vector's direction so that after the rotation the Vector is facing the given target direction. In the following example, we rotate an object so that its 'back' direction faces the camera (creating the same effect as the example above).
Vector3 directionToCamera
= Camera.main.transform.position - transform.position;
transform.rotation = Quaternion.FromToRotation(
Vector3.back, directionToCamera);
Note that the input directions do not need to be normalized.
Here is the formula for Quaternion, given an axis-angle rotation. You don't need to know this when working in Unity.
// Given an Axis-Angle rotation
Vector3 axis;
float angle;
transform.rotation.ToAngleAxis(out angle, out axis);
angle *= Mathf.Deg2Rad;
// Calculated the Quaternion components
Vector3 vectorComponent = axis * Mathf.Sin(angle / 2);
float scalarComponent = Mathf.Cos(angle / 2);
// Construct the result
Quaternion rotation = new Quaternion(
vectorComponent.x,
vectorComponent.y,
vectorComponent.z,
scalarComponent);
Slerp, or spherical linear interpolation, is a fancy term for a simple concept. If you were to smoothly/evenly rotate from rotation A to B, Slerp is the formula that calculates the interim rotation given a percent progress from 0 to 1, named 't'. For example:
transform.rotation = Quaternion.Slerp(
originalRotation,
targetRotation,
percentComplete);
Another way of leveraging the Slerp method is by using it in an update loop and providing the same constant for 't' each frame instead of using a percent complete. Each frame it will close a percent of the remaining gap, this will create a motion that slows the closer it is to the target rotation.
transform.rotation = Quaternion.Slerp(
transform.rotation,
Quaternion.identity,
speed * Time.deltaTime);
The following is an example of the two different ways of leveraging 't' in Slerp:
View source for this example and the Lerp example below.
The performance of Slerp is almost on-par with Lerp. We tested running Slerp or Lerp 10k times per frame in Unity and there was no measurable difference between them.
Lerp, or linear interpolation, for rotations is very similar to Slerp. It follows a straight line between rotations instead of curving to ensure a constant angular velocity like Slerp does.
You can use Lerp the exact same way you use Slerp. For example:
transform.rotation = Quaternion.Lerp(
originalRotation,
targetRotation,
percentComplete);
The following example shows two objects, one which is rotating with Lerp (blue) and the other with Slerp (red). Note that they exactly the same at the start, middle, and end; and there is very little different in between.
See also Higeneko's Slerp vs Lerp visualization.
RotateTowards is an alternative to Slerp/Lerp for selecting a rotation between two other rotations. RotateTowards uses a fixed rotation speed instead of rotating by percent (like Slerp and Lerp).
You can use RotateTowards like you use Slerp and Lerp; however, instead of specifying 't' you are providing a speed which is equal to the max degrees the object may rotate this frame.
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
speed);
To help clarify some use case differences between each of these interpolation options:
- Use RotateTowards when you want to rotate with a fixed angular velocity.
- Use Slerp with t = percentComplete when you want the rotation to complete in a fixed amount of time.
- Use Slerp with t = constant when you want the rotation to start fast and slow down as it approaches the target rotation.
- Consider using Lerp over Slerp when you need some acceleration and deceleration at the start/end to smooth the experience Slerp offers.
In Unity, you should use the method above. However, for the interested, below is how the Lerp may be calculated.
// Define terms
Quaternion a = transform.rotation;
Quaternion b = targetRotation;
// Split the Quaternion components
Vector3 aVector = new Vector3(a.x, a.y, a.z);
float aScalar = a.w;
Vector3 bVector = new Vector3(b.x, b.y, b.z);
float bScalar = b.w;
// Calculate target quaternion values
Vector3 targetVector = (1 - percentComplete) * aVector + percentComplete * bVector;
float targetScalar = (1 - percentComplete) * aScalar + percentComplete * bScalar;
// Normalize results
float factor = Mathf.Sqrt(targetVector.sqrMagnitude + targetScalar * targetScalar);
targetVector /= factor;
targetScalar /= factor;
// Update the rotation to the Lerped value
transform.rotation = new Quaternion(
targetVector.x, targetVector.y, targetVector.z, targetScalar);
When a Lerp calculation is performed, the values need to be normalized so that the resulting Quaternion is normalized.
Often you need to combine rotations. With Quaternions this is done with multiplication.
Quaternion rotation = parentRotation * childRotation;
You can use multiplication to combine any number of rotations (e.g., grandparent * parent * child).
When combining rotations, a parent GameObject may rotate the parent and a child, and then the child could add an additional rotation of its own. With Quaternions, you write the multiplication such that the parent comes before the child. Order matters, as shown in this example:
View source for this example and the next.
In Unity, you should use the method above. However, for the interested, below is how multiplication may be calculated.
// Split the Quaternion components
Vector3 parentVector = new Vector3(
parentRotation.x, parentRotation.y, parentRotation.z);
float parentScalar = parentRotation.w;
Vector3 childVector = new Vector3(
childRotation.x, childRotation.y, childRotation.z);
float childScalar = childRotation.w;
// Calculate parentRotation * childRotation
Vector3 targetVector = parentScalar * childVector
+ childScalar * parentVector
+ Vector3.Cross(parentVector, childVector);
float targetScalar = parentScalar * childScalar
- Vector3.Dot(parentVector, childVector);
// Store result
Quaternion targetRotation = new Quaternion(
targetVector.x, targetVector.y, targetVector.z, targetScalar);
The inverse of a rotation is the opposite rotation; if you apply a rotation and then apply the inverse of that rotation, it results in no change.
Quaternion inverseRotation = Quaternion.Inverse(rotation);
In Unity, you should use the method above. However, for the interested, below is how the inverse may be calculated.
// Split the Quaternion components
Vector3 vector = new Vector3(
rotation.x, rotation.y, rotation.z);
float scalar = rotation.w;
// Calculate inverse
vector = -vector;
// Store results
Quaternion inverseRotation = new Quaternion(
vector.x, vector.y, vector.z, scalar);
Given a vector, you can calculate its position after a rotation has been applied. For example, given an offset from the center, you can rotate to orbit around that center point.
In Unity, you can simply use the multiplication symbol, for example:
GameObject center = ...;
Quaternion rotation = ...;
Vector3 offsetPosition = ...;
transform.position
= center.transform.position + rotation * offsetPosition;
You must have the Quaternion before the Vector for multiplication (i.e., offsetPosition * rotation does not work).
In Unity, you should use the method above. However, for the interested, below is how multiplication may be calculated.
// Prep for calculations
Quaternion positionQuaternion = new Quaternion(
position.x, position.y, position.z, 0);
Quaternion inverseRotation = Quaternion.Inverse(rotation);
// Calculate new position
Quaternion newPositionQuat
= rotation * positionQuaternion * inverseRotation;
// Store result
Vector3 newPosition = new Vector3(
newPositionQuat.x, newPositionQuat.y, newPositionQuat.z);
The approach above creates a Quaternion for the position simply to enable the multiplication operations required. It's possible to implement this algorithm without reusing the Quaternion data structure in this way.
Dot product is a fast operation which informs you how well-aligned two rotations are to each other. A dot product of 1 means the two rotations are identical, and -1 means they are oriented in opposite directions.
The dot product does not include direction. For example, a value of .9 tells you that you are nearly facing the same direction, but does not give you enough information to rotate closer to 1.
float dot = Quaternion.Dot(a, b);
Angle returns the difference between two rotations in degrees. This is very similar to the information you get from the Dot product, but returned in degrees, which may be useful for some scenarios.
float angle = Quaternion.Angle(a, b);
The equals operator (operator==) uses the dot product to test if two rotations are nearly identical.
if(transform.rotation == Quaternion.identity)
...
Note that in general, using "==" is not recommended when floats are involved as tiny rounding issues may result in differences which have no impact on the game. Unity has addressed this concern in a custom operator== method for Quaternions, so that "==" is safe to use.
In Unity, you should use the method above. However, for the interested, below is how the dot product may be calculated.
float dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
That's all for now. Questions, issues, or suggestions? Please use the YouTube comments.
Support on Patreon, with Paypal, or by subscribing on Twitch (free with Amazon Prime).
License. Created live at twitch.tv/HardlyDifficult August 2017.