In every tutorial for implementing a camera in OpenGL, the front vector is calculated with something like this :

front.x = cos(pitch)*cos(yaw);
front.y = sin(pitch);
front.z = cos(pitch)*sin(yaw);

What I don't understand is why the pitch affects the x component? Shouldn't it just be :

front.x = cos(yaw);

Also, for the z component, why do we multiply cos(pitch) with sin(yaw). I understand that the pitch and the yaw both affect the z component, but why multiply? Why not add or something else?


"Why does pitch affect the x component?"

Short answer: because you could be looking left or right.

Now, you may say if yaw is zero (or ±π), then front.x = cos(pitch) * cos(yaw) is cos(pitch) * 1, so pitch is affecting the x component even with no yaw at all!

This is a bit of an artifact of the yaw convention they've chosen to use. OpenGL examples I've found seem to base this off of a version of Euler/Tait-Byran angles, where yaw is measured as the angle between forward and the positive x-axis:

Diagram showing measurement of yaw in the article linked above.

This threw me for a loop at first too - I'm used to 0 yaw meaning we have applied zero rotation about the vertical axis (Unity uses this convention), but these articles take 0 yaw to mean "facing along the positive x axis."

Once we adapt to this slightly-turned way of looking at things, the formulas make sense again. When yaw is zero or ±π, our camera is looking along the x-axis, and so naturally pitching up or down should foreshorten the x component of the forward vector.

Diagram showing how a vector's projection on the horizontal axis shrinks as it's rotated up/down

To see where these particular formulas come from, we can work it through as a pair of transformation matrices.

Starting with a matrix that, when pitch is zero gives the identity matrix, and as we increase pitch the y+ and z+ axes spin through the unit circle:

(Here I'm using a right-handed coordinate system where positive pitch tips "upward," and assuming vectors will be multiplied in from the left side)

              ┌   1       0           0       ┐
PitchMatrix = |   0   cos(pitch)  sin(pitch)  |
              └   0  -sin(pitch)  cos(pitch)  ┘

We can see, again according to your intuition, the x axis (first row) is unaffected by `pitch alone.

Then the yaw matrix is similar, just shuffled and with cos exchanged with -sin to satisfy the rule that "0 yaw = local z- rotates to face x+"

              ┌ -sin(yaw)  0   cos(yaw)  ┐
  YawMatrix = |     0      1       0     |
              └ -cos(yaw)  0  -sin(yaw)  ┘

Now, to get the combined result of both these matrices on a vector, we can multiply the vector by each one in turn:

TransformedVector = LocalVector * PitchMatrix * YawMatrix

(Note that contrary to Bálint's remark, I'm actually applying the YawMatrix to the vector after the PitchMatrix has been applied. This ensures we pitch about the local x, and yaw about the global y axis, which is typically how we want to use this. For example, a common Euler/Tait-Bryan angle multiplication order is z first, then x, and y last)

Subbing this in with localVector = (0, 0, -1)...

                                 ┌ 1      0          0      ┐┌ -sin(yaw) 0   cos(yaw)┐
TransformedForward = [ 0  0  -1 ]| 0  cos(pitch) sin(pitch) ||     0     1      0    |
                                 └ 0 -sin(pitch) cos(pitch) ┘└ -cos(yaw) 0  -sin(yaw)┘

Starting from the right, the first multiplication effectively selects the bottom row:

                                ┌ -sin(yaw) 0  cos(yaw) ┐
= [ 0  sin(pitch)  -cos(pitch) ]|     0     1     0     |
                                └ -cos(yaw) 0 -sin(yaw) ┘

Again we can see this satisfies our intuition - the x coordinate was untouched and is still zero. Following through with the yaw rotation...

= [ cos(pitch) * cos(yaw)    sin(pitch)    cos(pitch) * sin(yaw) ]

Now we have the formulas we started with:

front.x = cos(pitch)*cos(yaw);
front.y = sin(pitch);
front.z = cos(pitch)*sin(yaw);

You don't strictly have to use these rotation conventions. There's a million ways to form a coordinate system, and in all honesty the mess above is not my favourite way - so just use the conventions that make the most sense to you.

  • \$\begingroup\$ "because you could be looking left or right." Does that mean that you apply yaw first, then pitch, and that pitch is applied in local space? I don't understand what is the order of the rotations (yaw and pitch). Changing the order changes the final result but with the three equations it doesn't seem to be an order. Thanks \$\endgroup\$ Jun 12 '17 at 15:11
  • 1
    \$\begingroup\$ This is partly a matter of how you think about rotations. If you stack each rotation on top of the current orientation, rotating around the corresponding global axis, then yaw happens after pitch - the way I've shown it here. The reason the yaw affects the result of the pitch can be seen in the steps above: After we apply the pitch, our forward vector is still in the yz plane. Applying the yaw then rotates that plane, so it can have a component facing along the x direction. So yaw doesn't have to come "first" to have an effect on the result of the pitch. \$\endgroup\$
    – DMGregory
    Jun 12 '17 at 15:17
  • 1
    \$\begingroup\$ You can equivalently think of this as rotating about the local yaw axis first, then about the local pitch axis that's been transformed by that yaw. Comments on this answer go into more detail about this perspective & terminology choice - some engines & docs aren't consistent about which convention they use. Personally, I find it clearer to think about the order in which the matrices get multiplied with the vector, which is why I described it as "pitch first, then yaw" above. \$\endgroup\$
    – DMGregory
    Jun 12 '17 at 15:22

Yaw is applied first, then pitch, you can see this on the following gif (I first rotate the pitch, then I do the same but I change the yaw too): enter image description here


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .