CS 314 More Ray-Object Intersections by sdaferv


									                       CS 314: More Ray-Object Intersections

                                              Robert Bridson

                                              October 9, 2008

1   Using Intersections

Last time we saw how to determine if a ray intersects a plane, and even better found that we got the
parameter value t of the intersection with it—this tells us how far along the ray the intersection occurs.

       This is important because it’s not enough just to know which objects intersect a ray: for ray casting
we want to figure out the nearest or “first” intersection. The t values let us determine this really easily:
find the intersection with the smallest (non-negative) t.

2   The Ray-Sphere Test

Let’s move on to slightly more interesting geometry than planes. Our first example will be spheres. These
are actually non-trivial to directly rasterize, as is any curved shape, but are simple for ray casting: this is
one of the strengths of ray casting.

       Just as we did with planes, we’ll come up with an implicit description of a sphere, and match it
with the parametric description of the ray, and find the values of t which solve the resulting equation. (In
fact, any implicitly defined surface can be given to ray casting; it always boils down to finding roots of an
equation in t, which may or may not be easy.)

       A sphere with centre point p and radius r consists of the points x at distance r from p, i.e. where
 x − p = r. We can square this (to get rid of the square root implied in the vector norm) and are left with
an equivalent equation, which can be rearranged to:
                                               x−p        − r2 = 0

The function F (x) = x − p − r2 can be identified as the implicit surface function for this case.

       A point on the ray is given by x(t) = o+td; an intersection with the sphere occurs when F (x(t)) = 0,
giving the following equation in t:
                                                    o + td − p        − r2 = 0

We can rewrite this, replacing the norm squared with a dot-product and swapping the order of the terms:

                                             (o − p + td) · (o − p + td) − r2 = 0

We can then expand the dot-product out:

                         (o − p) · (o − p) + (o − p) · (td) + (td) · (o − p) + (td) · (td) − r2 = 0

and gathering terms and pulling out the factors of t reduce it to:

                                     d       t2 + 2(o − p) · d t +       o−p     2
                                                                                     − r2 = 0

This is just a simple quadratic equation in t!

       The first step in solving it is to find the discriminant:

                                                        D = b2 − 4ac

where a = d     2   (usually equal to 1 since we usually normalize ray direction vectors), b = 2(o − p) · d
and c = o − p       2   − r2 . If the discriminant is negative, the roots are imaginary—and since we only care
about real intersections, we can conclude the line containing the ray doesn’t intersect the sphere at all.
Otherwise there could be roots.

       The simplest formulas—not necessarily the most accurate in floating-point arithmetic, though they
should work well enough in most ray casting situations—for the roots when D ≥ 0 are:
                                                  −b − D
                                          t0 =
                                                  −b + D
                                          t1 =
Since a > 0 in this context, these two roots satisfy t0 ≤ t1 . (We get a double root t0 = t1 when D = 0, which
corresponds geometrically to the line being tangent to the sphere, instead of piercing it in two separate

       Our final test is to check the sign of these roots. The line enters the sphere at t0 , and exits at t1 . If
t0 ≥ 0 this is part of the ray, and is the first intersection with the sphere; otherwise if t1 ≥ 0 then the ray
began inside the sphere and this provides the first and only intersection. If both roots are negative, finally,
the sphere is behind the ray and there are no intersections.

3   The Ray-Triangle Test

We finally turn to triangles, which are the easiest case for rasterization but turn out to be rather more
tricky for ray casting. The main problem is that triangles have no simple implicit form, which is what we
have used so far in testing for intersections.

       To make life tractable, we’ll split the test into two stages. In the first stage we test if the ray intersects
the infinite plane containing the triangle; if it does, we test in the second stage if the plane intersection
point is inside the triangle or not.

       We already know how to intersect rays with planes: to do the first stage we just need to determine
a point p in the plane of the triangle, and a vector n orthogonal to the plane. Say the triangle’s vertices are
x0 , x1 , and x2 . We can obviously choose any of these points for p; let’s arbitrarily choose p = x0 . To get a
vector orthogonal to the triangle, we can use the cross-product of two of its edges, remembering that it’s
not critical this vector be unit-length:

                                           n = (x1 − x0 ) × (x2 − x0 )

Using these to describe the plane, we can check the ray for intersection; if there is one, we save the
parameter value t of the intersection point and proceed to stage two.

       The traditional approach to stage two (which we’ll take, since it’s also conceptually the simplest)
begins by calculating the coordinates of the intersection point, x = o + td. We’ve figured out in the past
how to test if a 2D point is inside a 2D triangle; here we have a 3D point versus a 3D triangle which isn’t
quite the same, but we can reduce it to the 2D case by projecting the point and triangles along one axis.
For example, we could just throw out the z coordinates, projecting everything onto the xy plane. Then
we evaluate edge functions as before, and check if they all have the same sign or not; as an added bonus
this can give us the barycentric coordinates for just a little extra work.

       Picking the axis to project along (i.e. the coordinate to throw out to get to 2D) can’t be arbitrary.
For example, if the triangle is parallel to the z axis, then its projection onto the xz plane is a degenerate
line segment. Drawing this out, you should be able to see immediately that even if the intersection point
x projects onto this segment, we don’t have enough information to determine if it’s in the 3D triangle or
not: the missing z coordinate is critical in deciding that.

       To make this robust, we instead choose the axis that’s “most” orthogonal to the triangle, avoiding
the degeneracy of the parallel case. We can interpret this as finding the largest magnitude entry in n, the
vector orthogonal to the triangle.

3.1    Floating Point Issues

Unfortunately the test we have described is vulnerable to rounding errors. The prime culprit is the calcu-
lation of the intermediate point x, the intersection of the ray with the plane. It will be rounded; if the ray
intersects on or near an edge of the triangle, the eventual 2D test might be checking an erroneous point
that’s on the wrong side. The most disturbing visual result of these errors are cracks, as before, where a
ray aimed at the common edge of two triangles might be computed to miss both, leaving a spurious hole
in the rendering there.

        We can’t fix this simply by making sure the 2D test uses less-than-or-equals and greater-than-or-
equals tests: the intermediate rounded calculation can result in a negative value that should be positive
or vice versa.

        One approach to fixing the problem is to use a more sophisticated computational geometry ap-
proach to stage two, which can in fact be faster: come talk to me if you’re curious.

        The more typical fix is to stick in an admittedly unelegant “error tolerance” in the 2D test: instead
of checking signs of the edge functions, use code like this:

    • If f01>-epsilon and f12>-epsilon and f20>-epsilon:
       • Return true (ray intersects triangle)
    • Else if f01<epsilon and f12<epsilon and f20<epsilon:
       • Return true (ray intersects triangle)
    • Else return false (no intersection)

The tolerance epsilon essentially grows the triangle a little in each direction to include points which
have been perturbed a little by floating point error. It must be large enough to bound errors we might see
in the calculations, but not so large that the enlargement of the triangle is visible in the rendering. The full
derivation of what a good tolerance is is nontrivial; most codes use a default value that is experimentally
determined to work well enough (and may, if you’re lucky, provide a way to tune it for scenes where it
doesn’t work). For scenes with numbers in a typical range of, say, 10−1 to 102 , a tolerance on the order of
10−4 for single-precision floating point is a reasonable first guess.

4     Acceleration

So far we have a major inefficiency, in that we have to test every ray against every object in the scene. This
is as bad as rasterizing every triangle against every pixel for Z-buffer algorithms, and must be fixed.

       As a first stab at speeding things up, consider computing a bounding box that contains all the
geometry in the scene: if a ray doesn’t intersect this bounding box, then we don’t have to check any of the
geometry. This could be a huge speed-up, at least for some rays. For the other rays, it’s not going to help,
of course; next time we’ll improve this situation.


To top