Placing A Camera: The Lookat Function
Placing A Camera: The Lookat Function
Placing A Camera: The Lookat Function
0
Sign in
What's interesting is that from this pair of points, we can create a camera 4x4 matrix as we will
demonstrate in this lesson.
The camera is aligned along the negative z-axis. Does it mean I need to rotate the camera by 180 degrees along
the y-axis or scale it up by -1 along the z-axis? Not at all. Transforming a camera is no different from
transforming any other object in a scene. Keep in mind that in ray-tracing, we build the primary rays as if the
camera was located in its default position. This is explained in the lesson Ray Tracing: Generating Camera
Rays. This is when we actually reverse the direction of the rays. In other words, the z-coordinates of the rays'
directions at this point are always negative: the camera in its default position, looks down along the negative z-
axis. Those primary rays are then transformed by the camera-to-world matrix. Therefore, there is no need to
account for the default orientation of the camera when the 4x4 camera-to-world matrix is built.
The Method
Remember that a 4x4 matrix encodes the 3-axis of a Cartesian coordinate system. Again if this is
not obvious to you, please read the lesson on Geometry. Remember that there are two
conventions you need to pay attention to when you deal with matrices and coordinate systems.
For matrices you need to choose between the row-major and column-major representation. At
Scratchapixel, we use a row-major notation. As for coordinate system, you need to choose
between a right-hand and left-hand coordinate system. We use a right-hand coordinate system.
The fourth row of the 4x4 matrix (in a row-major matrix) encodes the translation values.
RightxUpxForwardxTxRightyUpyForwardyTyRightzUpzForwardzTz0001RightxRightyRig
htz0UpxUpyUpz0ForwardxForwardyForwardz0TxTyTz1
How you name the axis of a Cartesian coordinate system depends on your preference, you can
call them x, y and z but in this lesson for clarity, we will name them right(for x-axis), up (for the
y-axis) and forward for the (z-axis). This is illustrated in figure 1. The method from building a
4x4 matrix from the from-to pair of points can be broken down in four steps:
Step 1: compute the forward axis. In figure 1 and 2 it is quite easy to see that the
forward axis of the camera local coordinate system is aligned along the line segment defined by
the points from and to. A little bit of geometry suffices to compute this vector. You just need to
normalize the vector To−FromTo−From (mind the direction of this vector: it
is To−FromTo−From not From−ToFrom−To). This can be done with the following code
snippet:
001
Vec3f forward = Normalize(from - to);
Step 2: compute the right vector. Recall from the lesson on Geometry that Cartesian
coordinates are defined by three unit vectors that are perpendicular to each other. We also know
that if we take two vectors AA and BB, they can be seen as lying into a plane, and that the cross
product of these two vectors create a third vector CC perpendicular to that plane and thus also
obviously perpendicular to AAand BB. We can use this property to create our right vector. This
idea here is to use some arbitrary vector and compute the cross vector between the forward
vector and this arbitrary vector. The result is a vector that is necessarily perpendicular to the
forward vector and that can be used in the construction of our Cartesian coordinate system as the
right vector. The code for computing this vector is simple since it only implies a cross product
between the forward vector and this arbitrary vector:
001
Figure 3: the vector (0,1,0) is in the plane defined by the forward and up vector. The vector perpendicular to this plane is thus
the right vector.
The question is now, how do we choose this arbitrary vector? Well this vector can't really be
totally arbitrary which is the reason why we wrote the word in italic. Think about this: if the
forward vector is (0,0,1), then the right vector ought to be (1,0,0). This can only be done if we
choose as our arbitrary vector, the vector (0,1,0). Indeed: (0,1,0) x (0,0,1) =
(1,0,0) where the sign x here accounts for the cross product. Remember that the equations to
compute the cross-product are:
cx=ay∗bz−az∗by,cy=az∗bx−ax∗bz,cz=ax∗by−ay∗bxcx=ay∗bz−az∗by,cy=az∗bx−ax∗bz,cz=a
x∗by−ay∗bx
where aa and bb are two vectors and cc is the result of the cross product of aa and bb. When you
look at figure 3, you can also notice that regardless of the forward vector's direction, the vector
perpendicular to the plane defined by the forward vector and the vector (0,1,0) is always the right
vector of the camera's Cartesian coordinate system. That is because the up vector of that
coordinate system lies within that same plane as showed in figure 4. That's great because the
vector (0,1,0) can clearly be used in place of what we called earlier our arbitrary vector.
Note also from that observation that the right vector always lie within the xz-plane. How come you may ask? If
the cameras has a roll wouldn't the right vector be in a different plane? That's actually true, but applying a roll
to the camera is not something you can do directly with the look-at method. To add a camera roll, you would
first need to create a matrix to roll the camera (rotate the camera around the z-axis) and then multiply this
matrix by the camera-to-world matrix built with the look-at method.
001
002
Note that we normalize the arbitrary vector just in case you would actually be using a vector that
is different from (0,1,0). So in order to be on the safe side, we will normalise it. Also pay
attention to the order of the vectors in the cross product. Keep in mind that the cross product is
not commutative (it is actually anti-commutative, check the lesson on Geometry for more
details). The best mnemonic way of remembering the right order, is to think of the cross product
of the forward vector (0,0,1) with the up vector (0,1,0) we know it should give (1,0,0) and not (-
1,0,0). If you know the equations of the cross-product, you should easily find out that the order
is up×forwardup×forward and not the other way around. Great we have the forward and right
vector. How do now find the up vector?
Step 4: compute the up vector. Well this is very simple, we have two orthogonal
vectors, the forward and right vector, so computing the cross product between this two vectors
will just give us the missing third vector, the up vector. Note that if the forward and right vector
are normalized, then the resulting up vector computed from the cross product will be normalized
too:
001
Here again, you need to be careful about the order of the vectors involved in the cross product.
Great we now have the three vectors defining the camera coordinate system. Let's now build our
final 4x4 camera-to-world matrix.
Step 4: set the 4x4 matrix using the right, up and forward vector as from point.All
there is to do to complete the process is to build the camera-to-world matrix itself. For that, we
just replace each row of the matrix with the right data:
o Row 1: replace the first three coefficients of the row with the coordinates of
the right vector,
o Row 2: replace the first three coefficients of the row with the coordinates of
the up vector,
o Row 3: replace the first three coefficients of the row with the coordinates of
the forward vector,
o Row 4: replace the first three coefficients of the row with the coordinates of
the from point.
Again, if you are unsure about why we do that, check the lesson on Geometry. Finally here is the
source code of the complete function. It computes and return a camera-to-world matrix from two
arguments, the from and to points. Note that the function third parameter (called tmp in the
following code) is the arbitrary vector used in the computation of the right vector. It is set with
the default value of (0,1,0) but it can be changed if desired (hence the need to normalize it when
used).
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
Matrix44f lookAt(const Vec3f& from, const Vec3f& to, const Vec3f& tmp =
Vec3f(0, 1, 0))
{
Vec3f forward = normalize(from - to);
Vec3f right = crossProduct(normalize(tmp), forward);
Vec3f up = crossProduct(forward, right);
Matrix44f camToWorld;
camToWorld[0][0] = right.x;
camToWorld[0][1] = right.y;
camToWorld[0][2] = right.z;
camToWorld[1][0] = up.x;
camToWorld[1][1] = up.y;
camToWorld[1][2] = up.z;
camToWorld[2][0] = forward.x;
camToWorld[2][1] = forward.y;
camToWorld[2][2] = forward.z;
camToWorld[3][0] = from.x;
camToWorld[3][1] = from.y;
camToWorld[3][2] = from.z;
return camToWorld;
}