Cg Programming/Unity/Rotations

Orientation (or “attitude”) of a plane measured by bank (= Z = φ), elevation (= X = θ), and heading (= Y = ψ). (The axes are labeled differently in the main text.)

This tutorial discusses different representations of local rotations in Unity. It is based on Section “Vertex Transformations”.

Unity's Euler AnglesEdit

If you create a GameObject and select it, the Inspector Window will show three values X, Y, Z under Transform > Rotation. These are three Euler angles measured in degrees. (More precisely spoken, they are one possible set of three Tait-Bryan angles, or nautical angles or Cardan angles, since they describe rotations about three different axes while “proper Euler angles” would describe a set of three angles that describe three rotations where two rotations are about the same axis.)

In C# you can access these three angles as the Vector3 variable Transform.localEulerAngles. The documentation states that the angles represent — in this order — a rotation by Z degrees about the z axis, X degrees about the x axis, and Y degrees about the y axis. More precisely spoken, these are rotations about the fixed(!) axes of the parent object (or the world axes if there is no parent object). Since these rotations use fixed axes, they are also called “extrinsic rotations”.

These three angles can describe any rotation of an object in three dimensions. In the case of Transform.localEulerAngles they actually describe the orientation of an object relative to the parent's coordinate system (or the world's coordinate system if there is no parent). Rotation in the sense of a rotating motion is discussed below.

In aviation, Euler angles are used when the orientation of a plane is specified relatively to the fixed axes of the ground (e.g. a tower of an airport) as illustrated in the figure above. In this case, they are called “bank” (corresponding to Z), “elevation” (corresponding to X), and “heading” (corresponding to Y). The following C# script named "Extrinsic_Rotations" can be attached to an object to set the Euler angles in terms of these names. (In the Project Window select Create > C# Script, rename it to "Extrinsic_Rotations", double-click it to open it, copy & paste the code from below into the script, and drag the script from the Project Window over the game object in the Hierarchy Window, then select the game object and find the public variables of the script in the Inspector Window.)

using UnityEngine;
[ExecuteInEditMode]
public class Extrinsic_Rotations : MonoBehaviour {
void Update () {
transform.localEulerAngles =
}
}


Interact with the three variables in the Inspector to get familiar with their meaning. You could start with all three angles set to 0. Then change bank, elevation, and heading (in this order) and observe how the object rotates about the parent's (or world's) z axis (the blue axis in Unity) when changing bank, about the parent's x axis (red) when changing elevation, and about the parent's y axis (green) when changing heading.

Rotation of the vector (1,0) by angle t.

Computing the Rotation MatrixEdit

In order to establish the connection with the elementary model matrices in Section “Vertex Transformations”, this subsection presents how to compute a rotation matrix from the three angles X, Y, Z (or θ, ψ, and φ).

The 4×4 rotation matrix ${\displaystyle \mathrm {R} _{z}(\varphi )}$  for a rotation by an angle φ (= Z) about the z axis is:

${\displaystyle \mathrm {R} _{z}(\varphi )=\left[{\begin{matrix}\cos \varphi &-\sin \varphi &0&0\\\sin \varphi &\cos \varphi &0&0\\0&0&1&0\\0&0&0&1\end{matrix}}\right]}$

The first two coordinates of the leftmost column can be understood as the rotation of the vector (1,0) by an angle ${\displaystyle \varphi }$ , which results in the rotated vector ${\displaystyle (\cos \varphi ,\sin \varphi )}$ . (The illustration uses the angle t; thus, the result is ${\displaystyle (\cos t,\sin t)}$ .) Similarly, the first two components of the next column can be understood as the rotation of the vector (0,1) by the angle ${\displaystyle \varphi }$  with the result ${\displaystyle (-\sin \varphi ,\cos \varphi )}$ .

Analogously, the 4×4 rotation matrix ${\displaystyle \mathrm {R} _{x}(\theta )}$  for a rotation by an angle θ (= X) about the x axis is:

${\displaystyle \mathrm {R} _{x}(\theta )=\left[{\begin{matrix}1&0&0&0\\0&\cos \theta &-\sin \theta &0\\0&\sin \theta &\cos \theta &0\\0&0&0&1\end{matrix}}\right]}$

And the 4×4 rotation matrix ${\displaystyle \mathrm {R} _{y}(\psi )}$  for a rotation by an angle ψ (= Y) about the y axis is:

${\displaystyle \mathrm {R} _{y}(\psi )=\left[{\begin{matrix}\cos \psi &0&\sin \psi &0\\0&1&0&0\\-\sin \psi &0&\cos \psi &0\\0&0&0&1\end{matrix}}\right]}$

Note that the sign of the sine terms depend on some conventions. Here we use the conventions of Unity.

These three matrices have to be combined in one matrix product to form the total rotation. Since (column) vectors are multiplied from the right to transformation matrices, the first rotation ${\displaystyle \mathrm {R} _{z}(\varphi )}$  has to be in the rightmost place and the last rotation ${\displaystyle \mathrm {R} _{y}(\psi )}$  has to be in the leftmost place. Thus, the correctly ordered matrix product is:

${\displaystyle \mathrm {M} _{\text{rotation}}(\varphi ,\theta ,\psi )=\mathrm {R} _{y}(\psi )\mathrm {R} _{x}(\theta )\mathrm {R} _{z}(\varphi )}$

The matrix ${\displaystyle \mathrm {M} _{\text{rotation}}(\varphi ,\theta ,\psi )}$  can then be multiplied with other elementary transformations matrices to form the model matrix as discussed in Section “Vertex Transformations”.

In Unity, you could compute a 4×4 matrix m for the Euler angles X, Y, Z in this way (quaternions are discussed in more detail below):

Matrix4x4 m = Matrix4x4.TRS(Vector3.zero,
Quaternion.Euler(X, Y, Z), Vector3.one);


An articulated robot: the rotations about z, y, and x are referred to as yaw, pitch, and roll. (In the main text, these axes are labeled y, x, and z.)

Moving Rotation AxesEdit

In some cases, for example robotics, it is more interesting to work with moving rotation axes (i.e. intrinsic rotations) instead of the fixed rotation axes (i.e. extrinsic rotations) discussed so far. Usually, the rotation relative to the base of the robot is referred to as “yaw”, the up-down rotation of the arm as “pitch”, and the next rotation as “roll”. Since the joints are rotated by each rotation, a description of rotations with moving rotation axes is required.

Fortunately, there is a simple equivalence: the extrinsic rotations specified by Z, X, and Y correspond to the following three intrinsic rotations (in this order): a rotation about the y axis by Y (“yaw”), a rotation about the rotated x axis by X (“pitch”), and a rotation about the rotated z axis by Z (“roll”). In other words, the rotation angles for moving rotation axes are the same as for fixed rotation axes if they are applied in reverse order (Y, X, Z instead of Z, X, Y). For most people, this equivalence is not intuitive at all; however, you might gain a better understanding by playing with the following C# script that lets you specify yaw, pitch, and roll.

using UnityEngine;
[ExecuteInEditMode]
public class Intrinsic_Rotations : MonoBehaviour {
public float yawY, pitchX, rollZ;
void Update() {
transform.localEulerAngles =
new Vector3(pitchX, yawY, rollZ);
}
}


Mathematically, the equivalence can be shown by computing the rotation matrix for the intrinsic rotations. We start with the rotation about the y axis, i.e. , the matrix ${\displaystyle \mathrm {R} _{y}(\psi )}$ . If we think of this matrix as a transformation from local to global coordinates, then it is clear that any additional transformation in local coordinates should be multiplied from the right (where vectors are specified in local coordinates), while any additional transformation in global coordinates should be multiplied from the left (where the resulting vectors are in global coordinates). Thus, a rotation ${\displaystyle \mathrm {R} _{x}(\theta )}$  about the local (i.e., rotated) x axis, should be multiplied from the right and the product becomes ${\displaystyle \mathrm {R} _{y}(\psi )\mathrm {R} _{x}(\theta )}$ . With the same logic, the following rotation ${\displaystyle \mathrm {R} _{z}(\varphi )}$  about the local (i.e., rotated) z axis should again be multiplied from the right, and the complete product becomes ${\displaystyle \mathrm {R} _{y}(\psi )\mathrm {R} _{x}(\theta )\mathrm {R} _{z}(\varphi )}$ , which is the same result as for the extrinsic rotations in reverse order, i.e., intrinsic rotations are equivalent to extrinsic rotations in reverse order.

No gimbal lock: the axes of the three gimbals are different.

Gimbal lock: the axes of two gimbals are parallel.

Gimbal LockEdit

One interesting feature of Euler angles (and one reason why Unity and most other graphics applications don't use them internally) is that they can result in a situation that is called “gimbal lock”. In this situation, two of the rotation axes are parallel and, therefore, only two different rotation axes are used. In other words, one degree of freedom is lost. This happens with Unity's Euler angles if the 1st rotation (the elevation about the x-axis) is X = ±90°. In this case, the z axis is rotated onto the y axis; thus, the (rotated) first rotation axis is the same as the (fixed) third rotation axis.

The situation is easily constructed with the script from above if you set the elevation angle to 90° or -90°.

Aircraft principal axes: yaw, pitch, and roll.

Roll, Pitch, and Yaw in AviationEdit

Roll, pitch, and yaw are also used to describe the orientation of vehicles; see the Wikipedia article on “Axes Conventions”. Moreover, the principal axes of an aircraft are also called roll (z axis in Unity), pitch (x axis in Unity), and yaw (y axis in Unity). These axes are always fixed to the aircraft; thus, rolling, pitching, and yawing always refers to rotations about these aircraft principal axes regardless of the orientation of the aircraft. The mathematics of these rotations is therefore different from the Euler angles of the same name. In fact, the rotation about aircraft principal axes is more similar to the rotation about arbitrary axes discussed next.

Rotation about an Axis by an AngleEdit

As mentioned in Section “Vertex Transformations”, the rotation matrix for a rotation about a normalized axis ${\displaystyle (x,y,z)}$  by an angle α is:

${\displaystyle \mathrm {M} _{\text{rotation}}(x,y,z,\alpha )=\left[{\begin{matrix}(1-\cos \alpha )x\,x+\cos \alpha &(1-\cos \alpha )x\,y-z\sin \alpha &(1-\cos \alpha )z\,x+y\sin \alpha &0\\(1-\cos \alpha )x\,y+z\sin \alpha &(1-\cos \alpha )y\,y+\cos \alpha &(1-\cos \alpha )y\,z-x\sin \alpha &0\\(1-\cos \alpha )z\,x-y\sin \alpha &(1-\cos \alpha )y\,z+x\sin \alpha &(1-\cos \alpha )z\,z+\cos \alpha &0\\0&0&0&1\end{matrix}}\right]}$

Fortunately, this matrix is almost never needed when working with Unity. Instead, the object's local “rotation” can be set to a rotation about axis by angle in degrees in this way:

using UnityEngine;
[ExecuteInEditMode]
public class Angle_Axis_Rotations : MonoBehaviour {
public float angle;
public Vector3 axis;
void Update() {
transform.localRotation =
Quaternion.AngleAxis(angle, axis);
}
}


Actually, this sets the orientation of the object relative to its parent (or to the world if there is no parent). In order to rotate about a given axis at a given angular velocity in degrees per second, the function Transform.Rotate can be used:

using UnityEngine;
[ExecuteInEditMode]
public class Angle_Axis_Rotating : MonoBehaviour {
public float degreesPerSecond;
public Vector3 axis;
void Update() {
transform.Rotate(axis,
degreesPerSecond * Time.deltaTime,
Space.Self);
}
}


degreesPerSecond is an angular speed, which specifies how fast the object is rotating. However, Transform.Rotate requires the rotation angle in degrees. To compute this angle, the angular speed has to be multiplied with the time (in seconds) that has passed since the last call to Update, which is Time.deltaTime.

Furthermore, Transform.Rotate takes a third argument that specifies the coordinate system in which the rotation axis is specified: either Space.Self for the object's local coordinate system or Space.World for the world coordinate system. With Space.Self we can easily simulate rolling, pitching, and yawing by specifying the axes (1,0,0) for pitching,(0,1,0) for yawing, and (0,0,1) for rolling.

QuaternionsEdit

As you might have noticed in the code above, Unity is using quaternions (actually normalized quaternions) to represent rotations. For example, the variable Transform.localRotation is of type Quaternion.

In some sense, quaternions are simply four-dimensional vectors with some special functions. Normalized quaternions of length 1 correspond to rotations and are easy to construct when you know the normalized rotation axis (x, y, z) and the rotation angle α. The corresponding quaternion q is just:

${\displaystyle q=(x_{q},y_{q},z_{q},w_{q})=(x\sin(\alpha /2),y\sin(\alpha /2),z\sin(\alpha /2),\cos(\alpha /2))}$

In Unity, however, you can just use the constructor Quaternion.AngleAxis(alpha, new Vector3(x, y, z)) with alpha in degrees to construct the normalized quaternion. (See the previous subsection for an example.)

Conversely, a normalized quaternion q with components ${\displaystyle (x_{q},y_{q},z_{q},w_{q})}$  corresponds to a rotation by the angle ${\displaystyle 2\arccos(w_{q})}$ . The direction of the rotation axis can be determined by normalizing the 3D vector ${\displaystyle (x_{q},y_{q},z_{q})}$ . In Unity, you can use the function Quaternion.ToAngleAxis to compute the corresponding axis and angle.

As normalized quaternions correspond to rotations, they also correspond to rotation matrices. In fact, the product of two normalized quaternions corresponds to the product of the corresponding rotation matrices in the same order. Computing the product of two quaternions is actually a bit tricky but in Unity you can just use the * operator. In the case of products of quaternions (and also inverse quaternions), it is therefore useful to think of a quaternion as “something like a rotation matrix” instead of a four-dimensional vector.

Quaternions and Incremental RotationsEdit

Incremental rotations are very common in real-time graphics. The code for the rotation at a given angular speed about an axis in the object's local coordinate system would require to combine the previous rotation matrix (which specifies the previous orientation) with the new incremental rotation matrix. If the incremental rotation matrix is applied in local coordinates, it should be applied first (when the points are still in local coordinates, i.e., before any other transformation). Since matrix-matrix products should be read from right to left, this means that we have to multiply the previous rotation matrix with the incremental rotation matrix in this order. The quaternion for the previous orientation is transform.localRotation and the new incremental quaternion is called q in this code:

using UnityEngine;
[ExecuteInEditMode]
public class Quaternion_Rotating : MonoBehaviour {
public float degreesPerSecond; // angular speed
public Vector3 axis;
void Update() {
Quaternion q = Quaternion.AngleAxis(
degreesPerSecond * Time.deltaTime, axis);
transform.localRotation = transform.localRotation * q;
}
}


If the quaternion product in the last line is changed to

   transform.localRotation =
q * transform.localRotation;


it corresponds to applying the incremental rotation in the parent's coordinate system (or the world's coordinate system if there is no parent) just like one would expect for applying a rotation matrix after transforming the vertices into the parent's coordinate system. (Always remember that matrix products and quaternion products should be read from right to left because column vectors are multiplied from the right.)

Quaternions and Hierarchical ModelingEdit

Thinking of a quaternion as a rotation matrix and then thinking of the rotation matrix as a (passive) transformation between coordinate systems is useful in many circumstances. For example, the transformation matrix ${\displaystyle \mathrm {M} _{{\text{object}}\to {\text{world}}}}$  from object to world coordinates can be considered a product of the transformation matrix ${\displaystyle \mathrm {M} _{{\text{parent}}\to {\text{world}}}}$  from the parent's coordinate system to world coordinates times the transformation matrix ${\displaystyle \mathrm {M} _{{\text{object}}\to {\text{parent}}}}$  from object coordinates to the parent's coordinate system (remember to read matrix products from right to left), i.e.:

${\displaystyle \mathrm {M} _{{\text{object}}\to {\text{world}}}=\mathrm {M} _{{\text{parent}}\to {\text{world}}}\ \mathrm {M} _{{\text{object}}\to {\text{parent}}}}$

The corresponding quaternions for the global and local rotation of an object and the rotation of its parent are multiplied in the same order:

${\displaystyle q_{\text{global rotation}}=q_{\text{parent's rotation}}\ q_{\text{local rotation}}}$

We can solve these equations for any one of the matrices or quaternions. For example, we might have a global rotation (e.g., from Quaternion.LookRotation) but we need to know the local rotation because some functions require it, e.g., Animator.SetBoneLocalRotation. To this end, we can solve the matrix equation for ${\displaystyle \mathrm {M} _{{\text{object}}\to {\text{parent}}}}$  by multiplying with the inverse of ${\displaystyle \mathrm {M} _{{\text{parent}}\to {\text{world}}}}$  from the left and swapping the left-hand side with the right-hand side:

${\displaystyle \mathrm {M} _{{\text{object}}\to {\text{parent}}}=\mathrm {M} _{{\text{parent}}\to {\text{world}}}^{-1}\ \mathrm {M} _{{\text{object}}\to {\text{world}}}}$

Again, thinking of these matrices as representations of quaternions, we can write an equation for the corresponding quaternions:

${\displaystyle q_{\text{local rotation}}=q_{\text{parent's rotation}}^{-1}\ q_{\text{global rotation}}}$

Thus, if we have a quaternion qGlobal for a global rotation of a game object gameObject then we can compute the local rotation qLocal with this code:

Quaternion qLocal =
Quaternion.Inverse(gameObject.transform.parent.rotation)
* qGlobal;


Quaternions and Calibrated TrackingEdit

In Virtual Reality (VR) applications, it is often necessary to set the orientation of an object according to the orientation of a tracker. For example, one might need to set the orientation of an avatar's virtual foot based on the orientation of a tracker that is attached to a user's foot. Or one might need to set the orientation of a virtual camera based on the orientation of a head-mounted display.

In these situations, it is often unknown and it should not matter how the tracker is initially orientated. Therefore, a calibration is often used, i.e., the user is asked to assume a certain pose (the calibration pose) and the orientation of the tracker for the calibration pose is recorded. After the calibration, the current orientation of the tracker is used to determine the orientation of an object. If the tracker's current orientation is the same as its orientation for the calibration pose, the orientation of the object should correspond to the calibration pose. Otherwise the deviation of the tracker's current orientation from the tracker's orientation for the calibration pose is used to rotate the calibration pose.

The mathematics for this works as follows: We represent the orientation of the tracker when calibrating by the quaternion ${\displaystyle q_{\text{tracker when calibrating}}}$ , and the current orientation of the tracker by the quaternion ${\displaystyle q_{\text{current tracker}}}$ ; both relative to world coordinates. The deviation of the current tracker orientation from the orientation of the tracker when calibrating is given by the product ${\displaystyle q_{\text{current tracker}}q_{\text{tracker when calibrating}}^{-1}}$ . Reading the product from right to left: we undo the recorded rotation of the tracker when calibrating (hence the inverse) and then rotate according to the current tracker orientation. (Since ${\displaystyle q_{\text{current tracker}}}$  is a rotation relative to world coordinates, it has to be multiplied from the left.) In case the two quaternions are equal, then there is no deviation between the two orientations. In this case, the multiplication of the quaternion with its inverse cancels out any rotation and the resulting product represents a rotation by 0 degrees, i.e., no rotation, as it should be when there is no deviation between the two orientations.

For the actual orientation of the object, we need to rotate the calibration pose by this deviation. Since this deviation is given in world coordinates, we just multiply this deviation from the left to the quaternion that represents the calibration pose:

${\displaystyle q_{\text{current tracker}}q_{\text{tracker when calibrating}}^{-1}q_{\text{calibration pose}}}$ .

This product represents the resulting orientation for an object based on a calibrated tracker.

Summary of the Advantages of QuaternionsEdit

At least in computer graphics, (normalized) quaternions are rather harmless and never more complicated than rotation matrices or axis-angle representations (depending on the context). Their specific advantages are that they show no gimbal lock (as opposed to Euler angles), they can be easily combined by multiplication (as opposed to Euler angles and the angle-axis representation of rotations), and they are easy to normalize (as opposed to the orthogonalization of rotation matrices). Therefore, most graphics applications use quaternions internally to represent rotations — even if Euler angles are used in the user interface.

SummaryEdit

In this rather theoretical tutorial, we have looked at:

• Unity's Euler angles for extrinsic rotations (i.e., elevation, heading, and bank).
• Unity's Euler angles for intrinsic rotations (i.e., pitch, yaw, and roll).
• The matrix representation of rotations.
• The axis-angle representation of rotations.
• The quaternion representation of rotations.