Stop using lookAt
!
2020-10-28 Permalink
The most intuitive way of controlling a 3D camera orientation must be by specifying its yaw, pitch and roll. The first two are usually bound to the horizontal and vertical axes of the input device (a mouse, a controller stick, etc...). It can be coupled with back/forth and left/right (strafing) movement to provide what’s the most common navigation method in virtual 3D environments.
Yet, there seems to be a common theme among novices to use the lookAt
function for constructing the view matrix based on the forward direction, while independently calculating left/right vectors for movement. That’s not surprising at all, considering that so many tutorials teach exactly that:[1][2][3][4]
forward = { -sin(yaw)*sin(pitch), cos(yaw)*sin(pitch), -cos(pitch) }; right = { cos(yaw), sin(yaw), 0 }; up = { 0, 0, 1 }; view = lookAt(eye, eye + forward, up);
Direct construction
However, what those tutorial do not mention, is that -forward
,[5] right
and eye
vectors already form three columns of the inverse view matrix. Calling lookAt
afterwards is a rather contrived way of reconstructing the values that were already calculated (!):
B = back = { sin(yaw)*sin(pitch), -cos(yaw)*sin(pitch), cos(pitch) }; R = right = { cos(yaw), sin(yaw), 0 }; U = up = cross(B, R); view = inverse({ Rx, Ux, Bx, eye.x, Ry, Uy, By, eye.y, Rz, Uz, Bz, eye.z, 0, 0, 0, 1 });
Notice that back
, right
and up
are already orthonormal by construction, and accordingly the inverse
can be easily computed by transposition:
view = { Rx, Ry, Rz, -dot(eye, R), Ux, Uy, Uz, -dot(eye, U), Bx, By, Bz, -dot(eye, B), 0, 0, 0, 1 };
What’s even worse—once taught the lookAt
method, novices tend to keep the forward
vector as their camera state, and try to rotate it in response to user input, instead of recalculating it based off yaw
and pitch
.[6] I once witnessed[link lost] somebody using rotation matrices to calculate forward
and right
, just to feed them back into lookAt
, without realizing that they could use their rotation matrix directly as their view matrix.
The easy way
As it turns out, the above view matrix can be easily composed of primitive transformations as follows:
view = rotateX(-pitch) * rotateZ(-yaw) * translate(-eye);
Once the view
matrix is calculated, the movement vectors can be extracted from its rows directly:
right = { view[0][0], view[0][1], view[0][2] }; back = { view[2][0], view[2][1], view[2][2] };
This method is easy to extend to accommodate other parameters. For example camera roll can be added to the equation:
view = rotateZ(-roll) * rotateX(-pitch) * rotateZ(-yaw) * translate(-eye);
Or the camera can orbit around a center
at a given radius
(e.g. for 3rd person view):
view = translate(-radius) * rotateX(-pitch) * rotateZ(-yaw) * translate(-center);
Conclusion
lookAt
shall be used only sparingly, when the camera and the subject move truly independently of each other. For example a helicopter mounted camera tracking an on-road vehicle comes to mind. However even then, a physically simulated camera with its own inertia may result in more pleasant and natural looking animation.
So please, stop using lookAt
!
Footnotes
- Learn OpenGL - Camera by Joey de Vries.
- Tutorial 6 : Keyboard and Mouse on www.opengl-tutorial.org.
- Keyboard Example: Moving the Camera on Lighthouse3d.com Tutorials.
- Moving in a 3D world - Camera by Jérôme Jouvie.
- Following graphics convention for dummies the camera is looking towards the negative Z axis, therefore the 3rd column has to be the
backward
direction. - Rotating a lookAt camera by an angle