Friday, September 24, 2010

Raytracing in Javascript

So I've been pretty quiet lately, but not for lack of activity on my part. I'm in one of those funny stages right now where I've got about a thousand ideas I want to try and things I want to learn and not enough time to pull any of them off properly. So I experiment, whip up several dozen cool little proof-of-concept programs, and move on to the next idea. This kind of "brain dump into code" mode is pretty fun, but has the severe downside that none of your projects reach a point where their worth actually showing to anyone else. Recognizing that my blog has been sorely neglected as of late I determined that I would bring at least ONE of my playthings to a semi-showable state and post it, and it so happens that my raytracer was the closest to being presentable. :)

Yes, it's a browser demo (but not a WebGL one, sorry!) It's a Monte Carlo tracer, which means that the image starts out really ugly and gets better as more passes are applied. (Let it sit longer and it becomes prettier.) The image above is the result of letting it sit for 400 passes, and even then you can see a fair bit of noise. It's a very slow way of going about it, but it can produce spectacular results under the right conditions (and, you know, when you're not relying on Javascript to do it...)

Okay, so admittedly this is probably the least impressive demo I've done so far. It's your standard old boring Cornell box, and not even a really cool one at that because it lacks a refractive (glass) sphere. (More on that in a bit). There's a whole bunch of things "wrong" with it, and I may be tempted to come back and update it at some point, but the point of the thing was that I wanted to learn more about raytracing, and this little guy has served that purpose admirably.

A few fun "techy" notes: This started life as a Python port of smallpt, which I eventually gave up on when I realized that the GIL makes threading for this type of thing pretty useless. So I ported my port to Javascript (wait, aren't we moving backwards here?) mostly so I could use Web Workers, but it has a nice side effect of letting me demo it online. :)

The demo uses 4 web workers (though it's super easy to scale to more or less), each of which renders their own jittered version of the full scene. All passes are then send back to the main thread and composited into the final image. I'm actually passing the image data back and forth in an ImageData object, which means that some browsers may not support this yet. It also means that there's the potential for some pretty severe precision loss, since all of the color values are compressed into a 0-255 range, sent to the main thread, and blended with the main image, which does another 0-255 compression. It would be far better to store and composite all of the color components as floats (or doubles, but Javascript doesn't give you the option. It falls somewhere in between), and copy those values over to the image after compositing. I tried this at one point, but the Web Workers kept dying and spitting out a strange "Not enough arguments" message. This is apparently a Chrome bug, but I have no idea how to get around it.

I also eventually dropped the idea of having a refractive sphere, simply because after many MANY iterations I still end up with either a black ball or a clear ball with an ugly black ring around the edges. You can still see the material class for it in the worker script, and if anyone wants to point out where I'm being stupid be my guest!

Anyway, as unimpressive as it is I did have a lot of fun putting this together, and hopefully someone out there at least finds it mildly interesting! For now, though I'm moving on to my next project, which may or may not be any one of the following: (haven't decided yet)

  • A WebGL accelerated version of this demo, to compare speeds
  • A SketchUp to JSON exporter for quick prototyping of WebGL scenes
  • An OpenGL ES 2.0 demo on Android (very tempting)
  • A WebGL Material system
  • Or possible something completely different! I have a bit of an itch to do something non-graphical for a bit, just to shake things up.
I've also got a couple of blog entries I want to do about some subjects I've found interesting lately, so hopefully there won't be as long of a gap before my next post.