Nothing Special   »   [go: up one dir, main page]

Placing A Camera: The Lookat Function

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 8

Scratchapixel 2.

0
Sign in

Placing a Camera: the LookAt Function


Contents
Framing: The LookAt Function
Keywords: Matrix, LookAt, camera, cross product, transformation matrix, transform, camera-
to-world, look-at, Gimbal lock.
In this short lesson we will study a simple but useful method for moving 3D cameras around. You won't
understand this lesson easily if you are not familiar with the concept of transformation matrix and cross
product between vectors. Hopefully if that's not already the case, we recommend you to read the lesson
called Geometry (entirely).

Moving the Camera


Being able to move the camera in a 3D scene is really essential. However in most of the lessons
from Scratchapixel we usually set the camera position and rotation in space (remember that
cameras shouldn't be scaled), using a 4x4 matrix which is often labelled camToWorld.
Remember that the camera in its default position is assumed to be centred at the origin and
aligned along the negative z-axis. This is explained in detail in the lesson Ray Tracing:
Generating Camera Rays. However, using a 4x4 matrix to set the camera position in the scene, is
just not very friendly, unless we can access a 3D animation system such as for example Maya or
Blender to set the camera and export its transformation matrix.
Hopefully, we can use another method which is better than setting up the matrix directly and
doesn't require an editor (though this is of course always better). This technique doesn't really
have a name but programmers usually refer to it to it as the Look-Atmethod. The idea of the
method is simple. In order to set a camera position and orientation, all you really need is a point
to set the camera position in space which we will refer to as the from point, and a point that
defines what the camera is looking at. We will refer to this point as the to point.

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

Figure 1: the local coordinate system of the camera aimed at a point.


Figure 2: computing the forward vector from the position of the camera and target point.

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);

We found one vector. Two left!

 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

Vec3f right = crossProduct(randomVec, forward);

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.

Finally, here is the code to compute the right vector:

001
002

Vec3f tmp(0, 1, 0);


Vec3f right = crossProduct(Normalize(tmp), forward);

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

Vec3f up = crossProduct(forward, right);

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;
}

The Look-At Method Limitation


The method is very simple and works generally well. Though it has an Achilles heels (a
weakness). When the camera is vertical looking straight down or straight up, the forward axis
gets very close to the arbitrary axis used to compute the right axis. The extreme case is of course
when the froward axis and this arbitrary axis are perfectly parallel e.g. when the forward vector
is either (0,1,0) or (0,-1,0). Unfortunately in this particular case, the cross product fails producing
a result for the right vector. There is actually no real solution to this problem. You can either
detect this case, and choose to set the vectors by hand (since you know what the configuration of
the vectors should be anyway). A more elegant solution can be developed using quaternion
interpolation.
Chapter 1 of 1

You might also like