Graphics Reference
In-Depth Information
that question in the first place. That is, in Chapter 37, we will introduce data struc-
tures that quickly and conservatively eliminate whole sets of triangles that the ray
could not possibly intersect, without ever performing the ray-triangle intersection.
So optimizing this routine now would only complicate it without affecting our
long-term performance profile.
Our renderer only processes triangles. We could easily extend it to render
scenes containing any kind of primitive for which we can provide a ray intersec-
tion solution. Surfaces defined by low-order equations, like the plane, rectangle,
sphere, and cylinder, have explicit solutions. For others, such as bicubic patches,
we can use root-finding methods.
15.4.4 Debugging
We now verify that the intersection code code is correct. (The code we've given
you is correct, but if you invoked it with the wrong parameters, or introduced an
error when porting to a different language or support code base, then you need to
learn how to find that error.) This is a good opportunity for learning some addi-
tional graphics debugging tricks, all of which demonstrate the Visual Debugging
principle.
It would be impractical to manually examine every intersection result in a
debugger or printout. That is because the rayTrace function invokes intersect
thousands of times. So instead of examining individual results, we visualize the
barycentric coordinates by setting the radiance at a pixel to be proportional to the
barycentric coordinates following the Visual Debugging principle. Figure 15.6
shows the correct resultant image. If your program produces a comparable result,
then your program is probably nearly correct.
Figure 15.6: The single triangle
scene visualized with color equal
to barycentric weight for debug-
ging the intersection code.
What should you do if your result looks different? You can't examine every
result, and if you place a breakpoint in intersect , then you will have to step
through hundreds of ray casts that miss the triangle before coming to the interest-
ing intersection tests.
This is why we structured rayTrace to trace within a caller-specified rectan-
gle, rather than the whole image. We can invoke the ray tracer on a single pixel
from main() , or better yet, create a debugging interface where clicking on a pixel
with the mouse invokes the single-pixel trace on the selected pixel. By setting
breakpoints or printing intermediate results under this setup, we can investigate
why an artifact appears at a specific pixel. For one pixel, the math is simple enough
that we can also compute the desired results by hand and compare them to those
produced by the program.
In general, even simple graphics programs tend to have large amounts of data.
This may be many triangles, many pixels, or many frames of animation. The
processing for these may also be running on many threads, or on a GPU. Tra-
ditional debugging methods can be hard to apply in the face of such numerous
data and massive parallelism. Furthermore, the graphics development environment
may preclude traditional techniques such as printing output or setting breakpoints.
For example, under a hardware rendering API, your program is executing on an
embedded processor that frequently has no access to the console and is inaccessi-
ble to your debugger.
Fortunately, three strategies tend to work well for graphics debugging.
1. Use assertions liberally. These cost you nothing in the optimized version of
the program, pass silently in the debug version when the program operates
 
 
Search WWH ::




Custom Search