Experience writing a ray tracer in Haskell
I promised I would give a bit more in the way of details on the ray tracer. Here they are.
During the last three weeks of the term we were directed to come up with a “term project” for the functional programming course. [1] We needed to come up with something that was interesting and exercised the features we’d been learning about all term. I’ve wanted to write a ray tracer for a while now, so I picked that. The assignment, as I gave it to myself, was to write a minimal but complete ray tracer and report on the experience of profiling and parallelizing it.
The ray tracer itself took around 30 hours of work. Partially this was me struggling with the language, partially me struggling with the topic, and partially me being picky and/or making and fixing bad design decision. When I turned it in it would only do spheres in flat colors and reflection (or a combination) with specular and diffuse lighting. I’ve been working on transparency and diffraction, but that code isn’t really worth showing off yet. I’ll eventually do some more complicated shapes and may explore more interesting materials.
Writing a ray tracer in Haskell was a … luxurious experience. The heart of a ray tracer is almost entirely math, and Haskell represents that very well. The only real difference between my Haskell code and the math behind it was some additional let bindings I introduced while profiling. I enjoyed being able to avoid unnecessary computations implicitly through laziness, instead of explicitly through conditionals, as I would have needed to do in other languages.
I originally planned to spend a few hours working on parallelization. I started playing around with it for fun while I was waking up with coffee one morning. Half an hour and 53 characters later I had around a 40% speedup on two cores. In this, Haskell kind of ruined the project for me. It was too easy to introduce parallelization into the program and have it just work. A very helpful co-worker ran some benchmarks for me on a few systems. Here’s the performance chart.
These results are rather informal, but you can see the impressive increases from parallel execution. I’m not entirely sure what is going on at the eight core mark, but I think it has something to do with an issue with (GHC only?) multicore support on Linux.
If you’d like to point and laugh at my code, you can find it here. I compile it on ghc with “ghc -O -threaded -o ray –make Main.lhs”. Comments are very welcome.
[1] Taught by Mark Jones (the initial author of Hugs) and Tim Sheard (who is just FP brilliant) amazing class.
7 comments7 Comments so far
Leave a reply
Performance chart
Re. the final core drop off, this thread on Linux v OSX and GHC from April seems relevant — No “last core parallel slowdown” on OS X: http://www.haskell.org/pipermail/glasgow-haskell-users/2009-April/017050.html
Well done.
A couple of small points:
-If you make a cabal package for it you’ll get lots more people playing with it.
-Some form of progress monitor while you render would be good. I have no idea if this render is going to take 2 minutes or 2 days.
I recently bought the PBRT book which is very good (unfortunately very expensive too). One day I’ll get time to write a haskell version of it.
What is the PBRT book?
http://www.pbrt.org/
The book is a literate program for a ray-tracer. Much better than the toy ray-tracers that you get in most books which don’t really do global illumination. eg, lot of explanation of Monte Carlo integration, sampling, photon mapping etc.
The luxrender program is an open source project forked from pbrt, I think the gallery on this page has some impressive renders:
http://www.luxrender.net/
If use data or newtype rather than type you can get a lot more help from the type system.
For instance, rather than:
I’d use:
That way you can’t accidentally use a Shininess value as a Diffusion etc.
@Joe Thornber
I really was just using the types to make the code read better. At one point I did have trouble confusing the different material constants, but switching to record syntax helped with that.
A progress monitor is on my mental list of improvements, but it would involve a bit of work restructuring the code to make it happen. We’ll see how long I keep interest in it (which is also a reason why I’m avoiding putting up a package for now).
I’m already using Mercurial, so I might see how much work it would be to put up a repo for that. Long term I wouldn’t mind setting this up to do photon mapping, but I’m not sure where things will go.
@Eric June
PBRT book: http://www.pbrt.org/