Mistakes to Make on a Raytracer

Writ­ing your first clas­si­cal ray tracer was at one point a big deal. Now I don’t see what’s so diffi­cult about the dark side of computer graph­ics. Tray racing, as I call it, is a remark­ably enter­tain­ing exer­cise that will become the future of gaming and 3D visu­al­iza­tion as comput­ing hard­ware catch up with our ambi­tions1.

Simply put, ray tracing’s gener­at­ing computer images through a phys­i­cal simu­la­tion of light, by “shoot­ing” rays from a view­point in 3D space and then “trac­ing” them along their paths for objects to draw on screen. Shoot a lot of them and you will have enough infor­ma­tion for the screen.

This is far supe­rior in qual­ity and correct­ness, though also very much less effi­cient, than our current method of raster­i­za­tion: gath­er­ing up all the objects we put into the virtual space, decid­ing what they should look like given a few arbi­trary para­me­ters, and then draw­ing them on the screen where we think they should go. Ray trac­ers produce such good-look­ing images, in fact, because they are accu­rate.

For an exam­ple, see Wikipedia’s exem­plar spec­i­men of ray traced work:

If you need more convinc­ing, check out a small company in Cali­for­nia called Pixar. I believe they make these moving pictures or some­thing.

“Now Xo,” you ask, “why did you call this wonder­ful ‘tray racing’ tech­nique the ‘dark side’ of computer graph­ics? Surely it would be the ‘light side (no pun intended)?’”

No, it’s not. I learned raster­i­za­tion first, I worked hard at some very point­less but very fast soft­ware raster­iz­ers, and I just like it more. Shoot me (no pun intended).

Anyways, I wrote a simple “clas­si­cal” (prop­erly known as Whit­ted) ray tracer for CS34512 as an assign­ment. To my surprise, it took only eight hours of marathon coding to write, none of them daytime. The work consisted primar­ily of making and fixing simple mistakes that, once I had worked out the math, were phys­i­cally incor­rect.

That was the nice bit of it all; if you had a render­ing error, you can just look at your model of the world and see if you did some­thing in a way that makes no sense in the real world. Once I fixed those mistakes, I had a pretty nice ray tracer, if triv­ial by today’s stan­dards.

These are the tech­ni­cal issues I stum­bled on (and have stum­bled on before; I don’t learn too well from my mistakes3), and ones you prob­a­bly should watch out for:

  • Normal­ize your unit vectors!
    Rays have direc­tions repre­sent­ing their straight line path through space. These are usually repre­sented with unit vectors. If you don’t keep them normal, then the distances you get through ray-object inter­sec­tions will be inac­cu­rate. This will cause issues if you actu­ally depend on distances at some point, like when you do shad­ow­ing or light atten­u­a­tion.
  • Check for nega­tive inter­sec­tions!
    When check­ing for ray-object inter­sec­tions. Make sure you discard inter­sec­tions that are behind the rays origin. Say that you para­me­ter­ize your ray as o + td, where o is the ray’s origin, d is the direc­tion (uuunit veeec­tor!), and t is the scalar para­me­ter indi­cat­ing distance along the path given by o and d. If you get an inter­sec­tion of t < 0, discard the inter­sec­tion.

    Don’t do this:
  • Termi­nate your reflec­tion rays by contri­bu­tion!
    When doing lots of bounces around shiny objects, that is, objects of constant of reflec­tion Krefl > 0, make sure to stop doing more bounces when addi­tional bounces will contribute no visi­ble differ­ence to the image. My measur­ing stick here is by recur­sive descent along reflec­tion rays: with each bounce, I multi­ply the contri­bu­tion factor (which starts out at 1) by the Krefl of the inter­sected object. If the contri­bu­tion of the next recur­sive level is less than 0.5/255, which is half the value of least signif­i­cance for an 8-bit/chan­nel output buffer, then I consider further bounces to be useless and termi­nate further recur­sion. It’s just a perfor­mance thing.
  • Don’t let your reflec­tion rays check against its own origin!
    When bounc­ing reflec­tion rays, be sure to not check for inter­sec­tions against the object you just bounced it off of. It’s a simple way to avoid a render­ing arti­fact known as surface acne4. However, this tech­nique will prevent objects from cast­ing self shad­ows, which are exactly what they sound like. There­fore, omit the reflec­tion inter­sec­tion check for the biggest thing in an object that does not self-shadow, e.g. spheres, trian­gles, planar poly­gons, etc. So if you are reflect­ing off an object composed of many trian­gles, don’t inter­sect the reflec­tion ray against the trian­gle it bounced off of, but do check against the other trian­gles in the object.
  • Inter­sect your shadow rays with geom­e­try and lights!
    OK, this is hard to describe, but it’s impor­tant. You check for shad­ow­ing by shoot­ing a shadow ray towards the light you’re trying to err, check for shad­ow­ing against, right? If there is an inter­sec­tion with another object, then there’s some­thing block­ing the light, right? Non monsieur! What if the light is between the two objects? For a shadow to be cast, the distance to the inter­sec­tion must be less than the distance to the light.

    Exam­ple: There is a light smack dab in between two spheres. It should look like this:

    Except when it doesn’t:

    Just for the heck of it, let’s add more magic:

So there you have it. Hope­fully the tech­ni­cal bits in this post will help some poor uni student out there strug­gling to race his/her first tray, the writ­ing has enter­tained you, and at the very least the pretty pictures got you all excited.

  1. Not mean­ing comput­ers will get fast enough to simu­late light real­is­ti­cally in real time; mean­ing they will make dining trays big enough to fit young adven­tur­ous men like myself []
  2. Computer Graph­ics at Geor­gia Tech []
  3. Unless I write it down with lots of paren­thet­i­cals and foot­notes like this []
  4. See this arti­cle: It’s Really Not a Render­ing Bug, You see… []