|
Notes for March 11 class -- More matrices
Perspective, part 1
Perspective is a linear transformation,
which uses the bottom row of the 4×4 transformation matrix.
Which makes sense, because in order for a point (x,y,z,w) to "go out to infinity"
or to "come in from infinity", the
matrix needs to change the homogeneous coordinate
of that point either from or to zero.
|
|
|
Perspective, part 2
The figure to the right is a top-down view of what happens when
we apply perspective transformation to z.
In this case the "focal length" is -2.4. That is,
the "point at infinity" (0,0,-1,0), also known, as
the negative z direction,
has been moved to (0,0,-2.4,1).
Practically speaking, if your camera is placed
at the origin (0,0,0,1), and your camera's focal length is
is f (where f is a negative value in z), then
you can just do the following to create
a persective linear transformation equivalent
to what you were doing in ray tracing:
(x,y,z,1) → ( x , y , 1 , z/f )
When we divide by the homogeneous coordinate, this turns into the point:
( fx/z , fy/z , f/z )
|
|
|
Parametric cylinder
You can describe many surfaces parametrically,
using the two parameters
0 ≤ u ≤ 1 and
0 ≤ v ≤ 1
to define values of x, y and z over the surface.
For example,
the open cylindrical section to the right
is described by:
x = sin(θ)
y = 2 * v - 1
z = cos(θ)
where:
θ = 2 π u
|
|
|
Parametric sphere
Similarly,
the longitude / latitude parameterization of a sphere to the right
is described by:
x = cos(φ) * sin(θ)
y = sin(φ)
z = cos(φ) * cos(θ)
where:
θ = 2 π u
φ = π v - π / 2
|
|
|
Parametric superquadric
We can also modify parameterizations procedurally.
For example, the image to the right shows a
superquadric surface,
a surface consisting of points for which
xp +
yp +
zp = 1,
for some positive value of p.
This shape was
formed by modifying the previous example.
In this case, rather than following the constraint
x2 +
y2 +
z2 = 1,
points on the surface follow the constraint
x4 +
y4 +
z4 = 1,
points on the surface follow the constraint.
This shape was made by
scaling each vertex (x,y,z) of the sphere by:
|
|
|
Chaining matrix transformations
Generally speaking, computer graphics scenes contain
many vertices, and each of those vertices may go through
multiple transformations. In practice we only need to transform
any vertex once, because linear transformations are associative.
In other words, the result of (A × B) × C is the same as A × (B × C), if A, B and C are
transformation matrices.
This also means that the two following operations are equivalent:
(1) return A × (B × (C × p)))
|
|
Apply three successive linear transformations to a point
|
|
(2) return M × p
where
M = A × B × C
|
|
Form a single linear transformation, and apply that transformation to the point.
|
|
Matrix multiply
In order to form a composite matrix such as the matrix M
in the above discussion, we need to be able to multiply matrices.
Matrix multiplication A × B between two matrices A and B proceeds
by multiplying each of the four row vectors that constitute A
by each of the four column vectors that constitute B:
C
|
=
|
A0,0 |
A1,0 |
A2,0 |
A3,0 |
A0,1 |
A1,1 |
A2,1 |
A3,1 |
A0,2 |
A1,2 |
A2,2 |
A3,2 |
A0,3 |
A1,3 |
A2,3 |
A3,3 |
|
×
|
B0,0 |
B1,0 |
B2,0 |
B3,0 |
B0,1 |
B1,1 |
B2,1 |
B3,1 |
B0,2 |
B1,2 |
B2,2 |
B3,2 |
B0,3 |
B1,3 |
B2,3 |
B3,3 |
|
So the value of any entry in the result matrix Ccol,row is:
Ccol,row =
A(0,row) B(col,0) +
A(1,row) B(col,1) +
A(2,row) B(col,2) +
A(3,row) B(col,3)
|
Building a matrix by applying successive primitives
In order to animate objects, as in the swinging arm example
we showed in class, you will want to keep an composite matrix M.
At every animation frame, set the value of M to identity.
Then apply successive linear transformations to M,
at each step
multiplying it on the right (locally) by one of your primitives (translate, rotate or scale):
M ← M × T // translate
M ← M × R // rotate
M ← M × S // scale
Then you can use matrix M to transform each of the
vertices of a shape.
After you have transformed any vertex,
you will want to apply a perspective transform
to that transformed vertex,
followed by the viewPort transform.
Then you can draw your edges onto the canvas.
|
Defining a camera matrix
In order to fly a camera around a scene, you need to
do two things:
- Define a camera matrix C
- Replace M by C-1M
In other words, you need to multiply on the left (globally)
by the inverse of the C matrix.
To compute C,
you need to know the location of the camera (its origin) and the
direction it is aiming (its aim vector),
as in the figure to the right.
- The origin will be the translation (the rightmost column of C) of your C matrix.
- Cz, the z axis of C (the third column of C), will be the negative of your aim vector.
- To compute the Cx, the x axis of C (the leftmost column of C),
take the normalized cross product of the global y (upward) direction [0,1,0] and Cz:
Cx = normalize( [0,1,0] × Cz )
- Finally, get Cy (the second column of C) by:
Cz × Cx.
Here is the javascript code for a very simple matrix inversion routine
that you can use to invert your camera matrix, which takes arrays of
length 16 as input and output.
This implementation of matrix inversion doesn't handle every case, but it will handle
the special case where the x,y,z axes of the source matrix are
of the same magnitude and are
perpendicular to each other (which is the case here).
|
|
|
Swinging arm example
In class we showed the example of a swinging arm.
The code we wrote in class is below.
If you already have a matrix object implemented,
and you implement a reasonable drawLine() method,
as well as
methods that apply translate, rotate and scale
cumulatively to the matrix value,
then the below code will
result in a swinging arm like the one we showed in class (right).
var origin = new Vector3(0,0,0);
var shoulder = new Vector3();
var elbow = new Vector3();
var wrist = new Vector3();
var fingertips = new Vector3();
var m = new Matrix();
canvas.update = function(g) {
this.g = g; // so the drawLine method will know where to draw to.
var theta = 3 * time;
g.lineCap = "round";
g.lineJoin = "round";
g.fillStyle = 'rgb(220,250,255)';
g.beginPath();
g.moveTo(0,0);
g.lineTo(this.width,0);
g.lineTo(this.width,this.height);
g.lineTo(0,this.height);
g.fill();
g.lineWidth = 10;
m.identity();
m.translate(.2,.3,0);
m.rotateZ(Math.cos(theta) * .5);
m.transform(origin, shoulder);
m.translate(0,-.2,0);
m.transform(origin, elbow);
m.rotateZ(Math.sin(theta) * .5 + .5);
m.translate(0,-.2,0);
m.transform(origin, wrist);
m.rotateZ(Math.cos(theta) * .25 - .25);
m.translate(0,-.1,0);
m.transform(origin, fingertips);
this.drawLine(shoulder, elbow);
this.drawLine(elbow, wrist);
this.drawLine(wrist, fingertips);
}
|
|
|
Homework, due by start of class on Wednesday March 25
-
Add simple perspective to your scenes (that is, replace (x,y,z) by (fx/z,fy/z,f/z)).
-
Create some interesting parametric surfaces. Try to make them time-varying.
Use interesting time-varying functions.
For example, you can try to animate shapes using
this Javascript implementation of the Noise function.
-
Change the translate, rotateX, rotateY, rotateZ and scale methods from your previous assignment so that
rather than just setting your matrix to be a translation, rotation or scale matrix, they instead
modify an existing matrix value.
For example, your new M.translate(x,y,z) method should modify the value of matrix M
as follows:
- Given translation values (x,y,z), compute the value of a translation matrix T (as in your previous assignment);
- Replace the value of your matrix M by the matrix product M×T.
-
Build an animated scene with multiple moving parts, by alternating successive changes to a matrix M
at each animation frame with commands to draw primitive objects (such as boxes,
cylinders and spheres). You can draw objects as edge-connected vertices,
as in the previous assignment.
Don't forget to reset the value of M by calling M.identity()
at the start of every animation frame.
As always, you get extra points for making something that is fun, exciting, beautiful or original.
|
| |