VIEWS: 4 PAGES: 5 POSTED ON: 3/30/2010 Public Domain
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 ﬁgure out the nearest or “ﬁrst” intersection. The t values let us determine this really easily: ﬁnd 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 ﬁrst 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 ﬁnd the values of t which solve the resulting equation. (In fact, any implicitly deﬁned surface can be given to ray casting; it always boils down to ﬁnding 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: 2 x−p − r2 = 0 The function F (x) = x − p − r2 can be identiﬁed as the implicit surface function for this case. 1 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: 2 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: 2 d t2 + 2(o − p) · d t + o−p 2 − r2 = 0 This is just a simple quadratic equation in t! The ﬁrst step in solving it is to ﬁnd 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 ﬂoating-point arithmetic, though they should work well enough in most ray casting situations—for the roots when D ≥ 0 are: √ −b − D t0 = 2a√ −b + D t1 = 2a 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 locations.) Our ﬁnal 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 ﬁrst intersection with the sphere; otherwise if t1 ≥ 0 then the ray began inside the sphere and this provides the ﬁrst and only intersection. If both roots are negative, ﬁnally, the sphere is behind the ray and there are no intersections. 2 3 The Ray-Triangle Test We ﬁnally 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 ﬁrst stage we test if the ray intersects the inﬁnite 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 ﬁrst 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 ﬁgured 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 ﬁnding the largest magnitude entry in n, the vector orthogonal to the triangle. 3 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 ﬁx 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 ﬁxing 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 ﬁx 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 ﬂoating 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 ﬂoating point is a reasonable ﬁrst guess. 4 Acceleration So far we have a major inefﬁciency, 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 ﬁxed. 4 As a ﬁrst 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. 5