Mistakes to Make on a Raytracer

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

Sim­ply put, ray tracing’s gen­er­at­ing com­put­er images through a phys­i­cal sim­u­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­ri­or in qual­i­ty and cor­rect­ness, though also very much less effi­cient, than our cur­rent method of ras­ter­i­za­tion: gath­er­ing up all the objects we put into the vir­tu­al 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 pro­duce 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 con­vinc­ing, check out a small com­pa­ny in Cal­i­for­nia called Pixar. I believe they make the­se mov­ing pic­tures or some­thing.

“Now Xo,” you ask, “why did you call this won­der­ful ‘tray rac­ing’ tech­nique the ‘dark side’ of com­put­er graph­ics? Sure­ly it would be the ‘light side (no pun intend­ed)?’”

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

Any­ways, I wrote a sim­ple “clas­si­cal” (prop­er­ly known as Whit­ted) ray trac­er for CS34512 as an assign­ment. To my sur­prise, it took only eight hours of marathon cod­ing to write, none of them day­time. The work con­sist­ed pri­mar­i­ly of mak­ing and fix­ing sim­ple mis­takes that, once I had worked out the math, were phys­i­cal­ly incor­rect.

That was the nice bit of it all; if you had a ren­der­ing error, you can just look at your mod­el 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 mis­takes, I had a pret­ty nice ray trac­er, if triv­ial by today’s stan­dards.

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

  • Nor­mal­ize your unit vec­tors!
    Rays have direc­tions rep­re­sent­ing their straight line path through space. The­se are usu­al­ly rep­re­sent­ed with unit vec­tors. If you don’t keep them nor­mal, then the dis­tances you get through ray-object inter­sec­tions will be inac­cu­rate. This will cause issues if you actu­al­ly depend on dis­tances at some point, like when you do shad­ow­ing or light atten­u­a­tion.
  • Check for neg­a­tive inter­sec­tions!
    When check­ing for ray-object inter­sec­tions. Make sure you dis­card inter­sec­tions that are behind the rays orig­in. Say that you para­me­ter­ize your ray as o + td, where o is the ray’s orig­in, d is the direc­tion (uuu­nit veeec­tor!), and t is the scalar para­me­ter indi­cat­ing dis­tance along the path given by o and d. If you get an inter­sec­tion of t < 0, dis­card the inter­sec­tion.

    Don’t do this:
  • Ter­mi­nate your reflec­tion rays by con­tri­bu­tion!
    When doing lots of bounces around shiny objects, that is, objects of con­stant of reflec­tion Krefl > 0, make sure to stop doing more bounces when addi­tion­al bounces will con­tribute no vis­i­ble dif­fer­ence to the image. My mea­sur­ing stick here is by recur­sive descent along reflec­tion rays: with each bounce, I mul­ti­ply the con­tri­bu­tion fac­tor (which starts out at 1) by the Krefl of the inter­sect­ed object. If the con­tri­bu­tion of the next recur­sive lev­el is less than 0.5/255, which is half the val­ue of least sig­nif­i­cance for an 8-bit/channel out­put buffer, then I con­sid­er fur­ther bounces to be use­less and ter­mi­nate fur­ther recur­sion. It’s just a per­for­mance thing.
  • Don’t let your reflec­tion rays check again­st its own orig­in!
    When bounc­ing reflec­tion rays, be sure to not check for inter­sec­tions again­st the object you just bounced it off of. It’s a sim­ple way to avoid a ren­der­ing arti­fact known as sur­face acne4. How­ev­er, this tech­nique will pre­vent objects from cast­ing self shad­ows, which are exact­ly 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-shad­ow, e.g. spheres, tri­an­gles, pla­nar poly­gons, etc. So if you are reflect­ing off an object com­posed of many tri­an­gles, don’t inter­sect the reflec­tion ray again­st the tri­an­gle it bounced off of, but do check again­st the oth­er tri­an­gles in the object.
  • Inter­sect your shad­ow 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 shad­ow ray towards the light you’re try­ing to err, check for shad­ow­ing again­st, right? If there is an inter­sec­tion with anoth­er object, then there’s some­thing block­ing the light, right? Non mon­sieur! What if the light is between the two objects? For a shad­ow to be cast, the dis­tance to the inter­sec­tion must be less than the dis­tance 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 mag­ic:

So there you have it. Hope­ful­ly the tech­ni­cal bits in this post will help some poor uni stu­dent out there strug­gling to race his/her first tray, the writ­ing has enter­tained you, and at the very least the pret­ty pic­tures got you all excit­ed.

  1. Not mean­ing com­put­ers will get fast enough to sim­u­late light real­is­ti­cal­ly in real time; mean­ing they will make din­ing trays big enough to fit young adven­tur­ous men like myself []
  2. Com­put­er Graph­ics at Geor­gia Tech []
  3. Unless I write it down with lots of par­en­thet­i­cals and foot­notes like this []
  4. See this arti­cle: It’s Real­ly Not a Ren­der­ing Bug, You see… []