Quaternion
A Quaternion consists of a 3D vector (x, y, z) along with an additional scalar value, w.
Just like a Vector3, it's a struct-based data type.
In other words, it has x, y, z as the axis of rotation, and w as a scalar value representing the amount of rotation.
Scalar: A value with magnitude but no direction
Vector: A value with both magnitude and direction
So why does it need these four components?
Gimbal Lock / Euler Angles
In 3D space, the axes look like this:
Visualized differently:
When rotating around these axes...
...axes can lock together, causing what's known as gimbal lock.
In this situation, the purple and green axes rotate together, causing one or more axis's rotation to be indistinguishable from another's.
Even though a rotation is occurring, it might appear stationary. This type of rotation is called Euler angle rotation.
To solve this problem, Quaternions were introduced with the help of that extra w
component.
How to Rotate?
According to the Unity documentation:
In general, it’s better to use Euler angles in scripts.
In this case, store the angle as a variable and only use the Euler angles to apply rotation.
Ultimately, it should still be stored as a Quaternion.
You can read Euler angles from a Quaternion, but modifying them and converting back can cause issues.
Unity Quaternion
So while using Euler angles in code can be convenient, modifying values extracted from a Quaternion and then converting them back to a Quaternion can lead to unexpected rotation results.
Here are some code examples:
1. Modifying the x
component of a Quaternion directly
[SerializeField] private float rotSpeed;
private void Update()
{
Quaternion rot = transform.rotation;
rot.x += Time.deltaTime * rotSpeed;
transform.rotation = rot;
}
This leads to an incorrect rotation.
The problem arises because the modified value does not represent an actual angle.
2. Reading Euler angles from a Quaternion, modifying, and applying back
[SerializeField] private float rotSpeed = 170;
private void Update()
{
Vector3 angles = transform.rotation.eulerAngles;
angles.x += Time.deltaTime * rotSpeed;
transform.rotation = Quaternion.Euler(angles);
}
This results in gimbal lock.
Euler angles extracted from a Quaternion can be inconsistent due to internal Quaternion calculations.
3. The correct way
As suggested by the official documentation, keep the angle as a class-level variable and apply it directly using Quaternion.Euler
.
[SerializeField] private float rotSpeed;
private float x;
void Update ()
{
x += Time.deltaTime * rotSpeed;
transform.rotation = Quaternion.Euler(x, 0, 0);
}
Store the Euler angles in a class-level variable
Don’t rely on reading and modifying them every frame!
summary
Incorrect Approach | Recommended Approach |
---|---|
Read eulerAngles every frame and modify |
Store Euler angles in a variable and update it |
Re-create a Quaternion every frame | Convert to Quaternion only once per update |
Usage
For example, if you're implementing user movement:
void OnKeyboard()
{
if (Input.GetKey(KeyCode.W))
{
transform.rotation =
Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation(Vector3.forward),
_rotationSpeed);
transform.position += Vector3.forward * (Time.deltaTime * _speed);
}
//...
}
Here, we use Quaternion.Slerp to interpolate between the current rotation and the target rotation (Quaternion.LookRotation) based on _rotationSpeed.
Interpolation :
Filling in the values between A and B at a given ratio.
So, from the current rotation to the new target (Quaternion.LookRotation(Vector3.forward)), the code fills in the rotation by a proportion of _rotationSpeed.
The start and end values are considered to be 1 unit apart, and the third parameter determines the fraction to interpolate between them.
Lerp vs Slerp
Abbreviation
- Lerp: Linear interpolation
- Slerp: Spherical (as in "sphere-shaped") linear interpolation
Difference:
- Lerp: Straight-line interpolation
- Slerp: Curve-based interpolation over a sphere
If _rotationSpeed is 1/3,
- Lerp will evenly divide the straight line between A and B into thirds.
- But when projected onto a spherical surface, it will no longer represent exactly 1/3 of the rotation arc.
Thus:
- Lerp: Linearly fills in based on a straight path
- Slerp: Fills in proportionally along a curve, maintaining constant rotation speed
Top comments (0)