The reason there's a picture of light passing through clouds (above) is because light travels in a straight line (unless you're close to a black hole), which you can kind of see in the picture. And one of our main tasks in computer graphics is to model how light interacts with the virtual scenes we set up. So, to model light in our scenes, we need to talk about lines. More generally, we need to talk about linear algebra.
Why? Really, it comes down to the compactness of the notation used to write our equations. There's nothing stopping us from writing out the equations for each component, but it will be messy and redundant if we do so. Using vector and matrix notation also helps abstract away the details of the operations.
As a motivating example, what if you wanted to calculate the total area of the low-poly heart below? We can certainly add up the area of each triangle using the
Our first building block is a point, which we'll start by denoting as
These coordinates represent the distance along the coordinate axes (
A vector is described very similarly to a point, except that it's not measured with respect to a fixed origin. All we care about when describing vectors is the direction and length (along that direction). It might help to think about an arrow that can float around in space: the arrow points in a particular direction (measured from the arrow tail to head) and we can measure its length. In this case, you can think about the tail of the vector as the origin, which can change as we translate the vector around.
Similar to points, vectors are represented using components. We will generally delimit the components using square brackets
where the subscripts denote a particular direction for the component (along the x-, y- or z-axis). Note that we have written this as a column.
Adding two vectors
Subtraction follows the same idea:
Scaling a vector
The vector from a point
The length of a 3d vector
A vector is a unit vector if it's length is 1. Any vector other than the zero-vector (a vector with all zero components,
This is scalar division by
Many vectors in our equations will have an assumption of being a unit vector, so normalization is important!
If we line up two vectors so that their tails coincide, we can label the angle
Imagine we want to compute a new vector that represents "how much of
To derive the relationship between the dot product and the angle between vectors, we'll start with the law of cosines:
Expanding the left-hand-side (LHS) for a 3d vector gives:
which means
The
which is the definition of the dot product. So the dot product is a measure of how much a vector "goes" in the direction of another vector. When the dot product between two vectors is
We will very (very) often need a vector that is perpendicular to a surface, particularly when we are shading. This vector is often called a normal vector. For simple analytic surfaces, we could figure out an analytic expression for this normal vector, but most geometries in computer graphics are represented using a triangle mesh (a bunch of triangles glued together to represent a surface). For a point on a surface, assume we know which triangle this point is in. Then the normal at this point is the triangle's normal vector.
How do we find this normal vector, which is perpendicular to the triangle? We can use the cross product.
We won't cover the derivation of the cross product and we'll just jump to the definition. The thing we really care about is the fact that the cross product between two vectors produces a vector perpendicular to both vectors. This is what we want in order to shade surfaces.
For 3d vectors
where
As an exercise, show that
The cross product can be used to calculate the area of the triangle bounded by vectors along two of the triangle's edges
The best way to visualize the result of the cross product is to use your RIGHT hand. (1) Start by aligning your index finger with the first vector (
It's import to move your fingers towards your palm in Step (2), otherwise you'll end up with the reversed orientation.
When we talk about ray tracing (soon), we'll represent rays as lines that emanate from a point into a particular direction. Thus, the following represents the set of all points along a line:
where
Planes can be represented very similarly to lines: we'll use two vectors
We require that
where
The representations we had for lines and planes extends to higher-dimensional spaces - we just need more vectors and parameters. A line is one-dimensional so we just need one vector
These basis vectors are not unique. For example, consider a line parametrized by
For a plane, we can even rotate the basis vectors around in the plane, or even change the angle between them (as long as we don't make them parallel). If all the vectors are orthogonal (each vector makes an angle of 90 degrees with every other vector), then we say that the basis is orthogonal. If all these vectors further have the property of being unit vectors, then the basis is orthonormal. In general, it's a good idea to use an orthogonal or orthonormal basis, and it will make our life a bit easier when transforming between spaces. More on this when we talk about matrices next class.
glMatrix
.Instead of writing our own functions to perform the operations above, we'll use a popular library called glMatrix
. Functions to become familiar with include:
vec3.add
vec3.create
vec3.cross
vec3.distance
vec3.dot
vec3.fromValues
vec3.length
vec3.normalize
vec3.scale
vec3.scaleAndAdd
vec3.subtract
The trickiest part of getting used to glMatrix
is: when calling a function that produces a vector, the output vector is both the return
value AND the first argument to the function. For example:
let u = vec3.fromValues(1, 2, 3);
let v = vec3.fromValues(4, 5, 6);
let w = vec3.cross(vec3.create(), u, v);
Notice the use of vec3.create()
in the first argument passed to vec3.cross
. I'm not entirely sure why the API was designed in this way. My best guess is that it allows you to pass any type that supports array-style access (with some index via []
).
This is a very common source of bugs, so when your graphics programs are not working, the first thing you should check is whether you are calling glMatrix
functions as expected.
Use the editor on the left (below) to make your own vectors and points to plot. Any vec2
's included in the points
array will be plotted as black dots, and any vec2
's included in the vectors
array will be drawn as blue arrows. By default, the vectors will be drawn with the tail at the origin, but you can specify a custom tail for each vector in order to draw it at a different location (set to undefined
if you want to use the origin).
Any errors will be alerted in the webpage. If you want to investigate the contents of a point or vector, you can also use alert
or console.log
(right-click on the webpage and click Inspect to view the console).
|
Try the following operations, starting with the vectors u
and v
and point x0
already defined in the editor:
console.log
to print the result, and verify the result (on paper).console.log
to print the result.vec2.scaleAndAdd
..dot
to verify the vector is indeed perpendicular to w1
(use console.log
to print the result).Finally, calculate the area of an individual triangle in the heart at the top of this page. Hover over a point to retrieve the coordinates of the triangle vertices. You can do this on paper, but you can also calculate it in the editor above and assert
or console.log
the result.
const origin = vec2.fromValues(0, 0);
const u = vec2.fromValues(100, 25);
const v = vec2.fromValues(-50, 40);
const x0 = vec2.add(vec2.create(), origin, u);
const w1 = vec2.add(vec2.create(), u, v);
const x1 = vec2.add(vec2.create(), x0, v);
const mv = vec2.negate(vec2.create(), v);
const w2 = vec2.subtract(vec2.create(), u, v);
const l = vec2.length(w2);
console.log(l);
const w3 = vec2.normalize(vec2.create(), w2);
console.log(w3);
console.log(vec2.scale(vec2.create(), w2, 1.0/l));
const x2 = vec2.scaleAndAdd(vec2.create(), origin, w3, l);
const n = vec3.cross(vec3.create(), vec3.fromValues(0, 0, 1), vec3.fromValues(w1[0], w1[1], 0));
console.log(vec2.dot(n, w1));
// array of points to plot as dots
let points = [origin, x0, x1, x2];
// array of vectors to plot
let vectors = [u, v, w1, mv, w2, n];
// array of vector tails (optional)
// set an entry to 'undefined' to use the origin
let tails = [undefined, points[1], undefined, x0];