This is the second ray tracing assignment. In this assignment I'm going to ask you to do as many of the following as you can:
Implementing shadows is really are not that hard to do with ray tracing. The trick is, when you're at a single surface point and looping over light sources, fire a ray from the surface point into the direction of each light source. If the ray toward a light source hits any objects, then don't illuminate that surface point by that light source (ie: don't add in any Diffuse or Specular contributions for that light source).
Reflecting a ray off a surface:
To do reflections you will want to be able to bounce a ray (v + t w) off a surface to create a reflecting ray (v' + t w'). As we discussed in class, given surface normal vector n you can compute w' by:
w' = w - 2 (w n) n
and then the reflecting ray's origin can be given by moving just a little off of the surface point S:
v' = S + ε w'
where ε is an appropriately small number such as 0.001.
Refracting a ray into a surface:
Refraction is implemented by Snell's Law:
n1 sin(θ1) = n2 sin(θ2)
where n1 and n2 are the respective indices of refraction of the medium that the ray comes from and the medium that the ray passes into.
To compute the direction of the exit ray w2, take the dot product of entering ray direction w1 with the surface normal n. This will let you decompose w1 into two components s1 and c1 which are, respectively, perpendicular to and parallel to the surface.
The vector s1 = (w1 n) n is the component of w1 perpendicular to the surface. This component has length sin(θ1).
The vector c1 = w1 - s1. is the component of w1 parallel to the surface. This component has length cos(θ1).
You can then just adjust the lengths of these two component vectors to sin(θ2) and cos(θ2), respectively. When you sum the length-adjusted component vectors, you will have w2.
Ray tracing to polyhedra:
As we said in class, to implement ray tracing to polyhedra:
For example, the six linear inequalities for a unit cube are:
a b c d x ≥ -1 → -x-1 ≤ 0 → -1 0 0 -1 x ≤ +1 → +x-1 ≤ 0 → +1 0 0 -1 y ≥ -1 → -y-1 ≤ 0 → 0 -1 0 -1 y ≤ +1 → +y-1 ≤ 0 → 0 +1 0 -1 z ≥ -1 → -z-1 ≤ 0 → 0 0 -1 -1 z ≤ +1 → +z-1 ≤ 0 → 0 0 +1 -1
If the planar face is facing toward v (ie: if v is on the positive-valued outside of the face), then t is at a point where the ray enters the polyhedron.
Conversely, if the planar face is facing away from v (ie: if v is on the negative-valued inside of the face), then t is at a point where the ray exits the polyhedron.
Using pre-exisitng polyhedral models:
Although we didn't cover this in class, you may already have a convex polyhedron described as vertices and faces (eg: a pyramid or rectangular prism or icosohedron), and wish to convert this shape into an intersection of half spaces so you can ray trace to it. You would do this by taking each of your shape's polygonal faces in turn, and computing the half space for that face. You can figure out the linear inequality (a,b,c,d) that goes through the face of any polygon, by considering any three of the polygon's vertices P,Q,R. Remember that we have been following the convention that the points P,Q,R go around counterclockwise, as seen from the outside (ie: from the positive-valued half-space) of the shape. The method is as follows:
Transforming polyhedral shapes:
To transform, by some matrix M, a polyhedral shape to which we are ray tracing, we need to transform each of the polyhedron's component half-spaces (a b c d).
In the discussion that follows, I'm going use transpose notation (x y z 1)T when referring to the homogeneous decription of a point in space, to make it clear that I am describing the point as a column vector.
When transforming a half-space (a b c d) by matrix M, the key thing to observe is that if you transform each point
(x y z 1)T → M(x y z 1)T,
then your corresponding transformation (a b c d) → (a' b' c' d') has to ensure that the transformed point still lies on the same side of the transformed plane:
(a' b' c' d') (M(x y z 1)T) = (a b c d) (x y z 1)T.
In other words, if we ask the question "is this point inside the original half-space?", we need to get the same answer after we transform both the point and the half-space.
As we have discussed in class, the way to do this is to post-multiply by the matrix inverse, so that:
(a b c d) → ( (a b c d) M-1 ).
This works because linear transformations are associative:
( (a b c d) M-1 ) ( M(x y z 1)T ) =
(a b c d) ( M-1 M ) (x y z 1)T =
(a b c d) (x y z 1)T
Ray tracing to second order polynomial shapes:
A second order polynomial shape is defined by a second order polynomial. That's a polynomial where the sum of the powers of any term is not greater than two. Examples are x2 + y2 - 1 and xy + 3.
In contrast, a polynomial like x2z + y2 - 1 is not second order, because the sum of the powers in the first term x2z is too high: 2 + 1 = 3.
The most general possible second order polynomial has ten terms:
ax2 + by2 + cz2 + dxy + eyz + fzx + gx + hy + iz + jWe can define a solid shape as all the places where such a polynomial evaluates to less than or equal to zero. Points on the surface of the shape give a zero value, and points on the outsize of the shape give a positive value.
For points at the surface, you can compute the x,y,z partial derivatives of this polynomial to get the gradient vector (a vector which points perpendicularly out of the surface). If you normalize this gradient vector to unit length, you get the surface normal.
As you probably know from your freshman calculus class, you can get the x,y,z components of this gradient vector by taking the derivative of the polynomial with respect to x,y and z, respectively:
( 2ax+dy+fz+g , 2by+dx+ez+h , 2cz+ey+fx+i )
If you want to transform a second order polynomial by a linear transformation matrix, it is useful to describe the evaluation of the polynomial at any point (x y z 1)T by using the notation of linear transformations. As we discussed in class, the following notation, devised by Jim Blinn, does the trick, by forming the polynomial coefficients into a 4×4 matrix:
The above expression multiplies out to exactly ax2 + by2 + cz2 + dxy + eyz + fzx + gx + hy + iz + j.
Now that we have the equation in this form, we can transform the original point and see what happens. First we transform (x y z 1)T to (M (x y z 1)T). We also need to transform the point in the other place it appears - on the left side of the coefficients matrix. When you transpose vectors and matrices, you end up reversing the order that they multiply together, so (x y z 1) is transformed to ((x y z 1) MT).
Following the same logic that we used for transforming half-spaces, we need to insert a matrix both to the left and to the right of the coefficients matrix, so that the result of the transformed equation doesn't change:
As we discussed in class, we do this as follows:
Of the three terms above, the middle term gives the transformed coefficients matrix.
Given any convex shapes (such as spheres, cylinders, or boxes), it is very easy to do boolean intersection via raytracing. When you trace a ray to any convex shape, either the ray misses the shape, or else the ray intersects the shape along some interval [tin , tout] along the ray.
Given a collection of convex shapes, if the ray misses any one of them, then the ray misses their intersection. If the ray hits all of them, then the intersection is given by the interval [max(tin) , min(tout)], where max(tin) is the maximum over all the tin, and where min(tin) is the minimum over all the tout.
For example, you can create a unit length cylinder by intersecting the infinite cylinder x2 + y2 - 1 = 0 with the cube that goes from -1 to +1 in x, y and z. Then you can apply a matrix to the component shapes in order to transform this cylinder to any other finite length cylinder.
Boolean unions and differences (making non-convex shapes):
Non-convex shapes are more difficult to deal with than are convex shapes, because the same ray can enter and leave a shape multiple times, to it is not sufficient just to speak of a single interval [tin , tout]. Instead, we need to describe the places where the ray is inside the shape by a sequence of t values: [ t0 t1 t2 ... tn-1 ], where the even numbered elements represent places where the ray enters the shape, and the odd numbered elements represent places where the ray exits from the shape. For example, [0.3 1.4 2.7 3.5] represents two successive segments where the ray is inside the shape: a first segment between t=0.3 and t=1.4, followed by a second segment between t=2.7 and t=3.5.
This notation makes it possible to describe non-convex things
like the result of boolean difference.
For example, suppose shape A
contains a hollow cavity with shape B, and suppose that
a particular ray intersects shape A
[t(A)in , t(A)out],
and that the same ray intersects shape B at
[t(B)in , t(B)out].
If t(A)in < t(B)in < t(B)out < t(A)in, then the boolean difference A - B is described along this ray by:
[ t(A)in t(B)in t(B)out t(A)out ].
In general you can implement boolean difference by replacing:
[ t0 t2 t3 ... tn-1 ] → [ 0 t0 t1 t2 ... tn-1 ∞ ]
Notice that if t0 was already zero, then the first interval becomes null, and you can just drop the first two elements of the resulting sequence. Similarly, if if tn-1 was already ∞ then you can just drop the last two elements of the resulting sequence.
In order to implement boolean union, you only need to implement boolean intersection and boolean difference, since de Morgan's law gives us:
A ˇ B = ¬ ( (A ˆ B) ˆ (A ˆ B) )
The hardest part is implementing intersection for non-convex shapes. To intersect two non-convex shapes along a ray you need to merge together their two sequences. This is done by progressively moving along the two sequences, looking for values of t where the ray is inside both shapes, and using those values of t to construct a new sequence. Here is a sketch of the algorithm:
Create an empty destination sequence. Starting from the beginning of both sequences, Repeat until at the end of both sequences: Choose the smallest remaining t value, from whichever of the two input sequences you find it. If this was an entering (even numbered) t: If this t value is inside the other shape: then append it to the destination sequence. If this was an exiting (odd numbered) t: If this t value is outside the other shape: then append it to the destination sequence.
Your job for this assignment is to use as many different ray tracing techniques as you feel comfortable implementing, and come up with an interesting collection of reflecting, refracting and shaded shapes, rendered by ray tracing, to produce cool and compelling pictures or animations. Try to use a variety of shapes, and make sure to create something fun!