This week we talked briefly about perspective and then we spent most of the lecture on the wonderful world of splines.
Perspective:
To do perspective, let's assume that you've done all the transformations that make your camera appear to be at the origin looking into negative z. Now assuming that you want all things at z=f to retain their original size, while things that are further away should get smaller, and things that are nearer should get larger. Note: Because we are looking into the negative z direction, you'll want f to have a negative value. For example, f = -10.0 is a reasonable value.
Given any point (x,y,z), the perspective transformation of that point is:
As we said in class, this operation corresponds to what you get if you apply the homogeneous matrix transform:
xf | f | 0 | 0 | 0 | x | ||
yf | ← | 0 | f | 0 | 0 | y | |
f | 0 | 0 | 0 | f | z | ||
z | 0 | 0 | 1 | 0 | 1 |
Once you've done the perspective transform, then you can do the final 2D viewport transformation on your perspective x and y, to convert them into integer pixel coordinates in your applet window.
For now you can just ignore the perspective z. Once we start doing shaded rendering, we'll need to use that value to figure out which objects are in front and which are behind.
Splines:
I started by pointing out that you can make arbitrarily complex - but controllable - smooth curves by stringing together parametric cubic spline curves end to end, as long as you make sure that the derivatives of successive spline curves match where they join.
In any given dimension, the value of a parametric cubic spline is given by the vector C of coefficients [a,b,c,d]:
f(t) = a t^{3} + b t^{2} + c t + d
So if you wanted to make a two dimensional parametric cubic spline, to define a path on the (x,y) plane, you would use two of these functions:
x(t) = a_{x} t^{3} + b_{x} t^{2} + c_{x} t + d
y(t) = a_{y} t^{3} + b_{y} t^{2} + c_{y} t + d
Generally we can split the right side of the above equations into two parts, the coefficient vector C = [a,b,c,d] and the cubic parameter vector T = [t^{3},t^{2},t,1], because we're really just doing an inner (dot) product between those two vectors. For example, we can express the last two equations as:
x(t) = C_{x} • T
y(t) = C_{y} • T
In this form it's easy to evaluate splines. For example, let's say you're doing an animation, and you want to animate a value (eg: the angle of a swinging arm) from time time_{1} to time_{2}. If you have the four spline coefficients C = [a,b,c,d] for that segment of the animation, then at any moment in time you just need to evaluate the cubic spline function based on the fraction of time that has elapsed between time_{1} and time_{2}:
t = (time - time_{1}) / (time_{2} - time_{1})
T = [t^{3},t^{2},t,1]
f(t) = C • T
Similarly, if you want to draw a cubic curve, and you have the coefficient vectors C_{x} and C_{y}, then you can do something like this (in pseudocode):
for (t = 0 ; t < 1 ; t += ε) {
T_{0} = [t^{3},t^{2},t,1]
T_{1} = [(t+&epsilon)^{3}, (t+&epsilon)^{2}, t+&epsilon, 1]
drawLine ( C_{x}•T_{0} , C_{y}•T_{0} , C_{x}•T_{1} , C_{y}•T_{1} )
}
Of course, as we said in class, it is not really intuitive for humans to build nice looking cubic curves as weighted sums of the primitive functions t^{3}, t^{2}, t and 1. So we generally want to design parametric cubic spline curves not in the space C, but rather in some more convenient space of coefficients G, which would allow us to use more intuitive curve shapes.
Hermite splines:
For example, if we want to specify a function value at the beginning and at the end (of the spline curve, as well as a function derivative at the beginning and the end of the spline curve, then we are working with Hermite splines. By convention, we refer to the beginning and end points as P1 and P4, respectively, and we refer to their derivatives as as R1 and R4, respectively.
This allows us to work with the four intuitive curve primitives:
To do our inner loop computations, we need to convert this geometry description G = [P1,P4,R1,R4] to a cubic coefficients description C = [a,b,c,d].
In other words, we need to transform function coordinate systems:
We want cubic functions | We have geometric functions | |
---|---|---|
To do this, we just use the Hermite matrix:
a | 2 | -2 | 1 | 1 | P1 | ||
b | ← | -3 | 3 | -2 | -1 | P4 | |
c | 0 | 0 | 1 | 0 | R1 | ||
d | 1 | 0 | 0 | 0 | R4 |
Note that the columns of the Hermite matrix are just the coefficients of the geometric functions (you can see this from the parts highlighted in red above).
Bezier splines:
Another popular interpolating spline is the Bezier spline. Here we have our two end points P_{1} and P_{4}, which the curve goes through (which is why it's called an interpolating spline) but also two other "in-between" points P_{2} and P_{3}, that guide the direction of the curve.
As we said in class, the Bezier spline is made from successively nesting linear interpolations.
The coefficients that you get when you do successive nested linear transformations are called the Bernstein polynomials, named for the mathematician who discovered them. Bezier cubic spline functions are simply the third order Bernstein polynomials.
Let's go over the math: We can implement linear interpolation by:
lerp(t, P_{1}, P_{2}) = (1-t) P_{1} + t P_{2}We can define a parabolic (ie: 2nd order) parametric curve, given three points P_{1}, P_{2}, P_{3}, by nested linear interpolations:
lerp(t, lerp(t, P_{1}, P_{2}), lerp(t, P_{2}, P_{3})) =Multiplying this out gives: (1-t)^{2} P_{1} + 2(1-t) t P_{2} + t^{2} P_{3}(1-t) ((1-t) P_{1} + t P_{2}) + t ((1-t) P_{2} + t P_{3})
Similarly, we can define a cubic (ie: 3rd order) parametric curve given four points P_{1}, P_{2}, P_{3}, P_{4}, by nested linear interpolations:
lerp(t, lerp(t, lerp(t, P_{1}, P_{2}), lerp(t, P_{2}, P_{3})), lerp(t, lerp(t, P_{2}, P_{3}), lerp(t, P_{3}, P_{4}))) =(1-t)^{3} P_{1} + 3(1-t)^{2} t P_{2} + 3(1-t) t^{2} P_{3} + t^{3} P_{4}
Conveniently, the above equation contains four polynomials, each of which multiplies by only one of P_{1}, P_{2}, P_{3} or P_{4}. These are in fact the third-order Bernstein polynomials. I've colored each polynomial differently so you can track them more easily in the following discussion.
These third-order Bernstein polynomials describe the cubic curves modulated by the geometric coefficients P_{1},P_{2},P_{3},P_{4}, respectively, in terms of t^{3},t^{2},t and 1.
Now we just need to convert from one function basis to another:
We want cubic functions | We have geometric functions | |
---|---|---|
The coefficients of the functions on the right give us the columns of the Bezier matrix:
a | -1 | 3 | -3 | 1 | P_{1} | ||
b | ← | 3 | -6 | 3 | 0 | P_{2} | |
c | ← | -3 | 3 | 0 | 0 | P_{3} | |
d | ← | 1 | 0 | 0 | 0 | P_{4} |
Homework due Oct 16:
Your homework is two-fold:
Also, for next week, make sure that you have hierarchical movement in your scenes (ie: parts moving relative to other parts, like wheels on a car or arms on a body, etc.).
For extra credit, try using cubic splines to make drawings with smooth curves.