DEV Community

Cover image for Quaternion in Unity
JaeHonity
JaeHonity

Posted on

Quaternion in Unity

Quaternion

A Quaternion consists of a 3D vector (x, y, z) along with an additional scalar value, w.

Vector3 from unity

quaternion

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:

vector3

Visualized differently:

vector3_1

When rotating around these axes...

Gimbal_Lock

...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.

Rotate and Direction In Unity

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;  
}
Enter fullscreen mode Exit fullscreen mode

This leads to an incorrect rotation.

The problem arises because the modified value does not represent an actual angle.

quaternion1

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);  
}
Enter fullscreen mode Exit fullscreen mode

This results in gimbal lock.

Euler angles extracted from a Quaternion can be inconsistent due to internal Quaternion calculations.

quaternion2

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);  
}
Enter fullscreen mode Exit fullscreen mode

quaternion3

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);  
    }  
    //...
}
Enter fullscreen mode Exit fullscreen mode

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

Lerp VS Slerp

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)