Tuesday, 31 March 2009

Implementing Minilight in Clojure (1)

Minilight is a global illumination renderer, designed to act as a minimal example of how to achieve certain rendering techniques. Minilight includes:

  • Monte-carlo path-tracing transport
  • Emitter sampling
  • Progressive refinement
  • RGB light
  • Diffuse materials
  • Triangle modelling primitives
  • Octree spatial index
  • Pin-hole ‘lens’
  • Ward linear tone-mapping

Together, this set of techniques is part of the Global illumination rendering algorithm. This considers not only a single ray (see my previous attempt) but reflections and emissions from all objects in the scene.

Over the next few weeks, I'll attempt to implement this in Clojure.

So for part 1, we'll look at reading in a scene file and converting it into a Clojure representation. The format for a model is simple. There's an example here.

Clojure really simplifies things because of the read function and because it's homioiconic (a Clojure function is a Clojure data structure).

The main gotcha I found was that read, by default, throws an exception when it reaches the end of the file. This made things slightly more complicated than I needed. Further investigation shows that read takes some optional parameters allowing you to control how EOF should be treated.

For now, I've just read the data in raw form. I've no idea if this will be a suitable data structure for actually doing anything with, but we'll see how this evolves...

(defstruct model

(defstruct triangle

(defmacro safe-read [x]
`(read ~x false nil))

(defn read-triangles
((fn [triangles]
(let [geom [(safe-read r) (safe-read r) (safe-read r)] refl (safe-read r) emit (safe-read r)]
(if (first geom)
(recur (cons (struct triangle geom refl emit) triangles))
triangles))) nil))

(defn load-model
(with-open [r (PushbackReader. (reader f))]
(.skip r (count "#MiniLight"))
(struct model
(read r) ;iterations
(read r) ;width
(read r) ;height
(read r) ;eye-position
(read r) ;look-direction
(read r) ;view-angle
(read r) ;sky-emission
(read r) ;ground-reflection
(read-triangles r)))) ;triangles


  1. I recently started a similar project to learn Clojure by implementing the minilight renderer. My current version is on github. I am also planning to write some blog posts as I go.

    I might use yours for inspiration though as your code is much cleaner and Lisp-like than my feeble attempts.

  2. Thanks for the comments!

    I'm not sure mine is more Lisp-like, I'm still learning! Let me know where your blog is and we can compare implementations. I'm not able to devote much time to learning Clojure, so I'm going to be making very slow progress :(

  3. My blog is here: http://mark.reid.name/sap/

    I haven't posted anything about minilight yet but my code at github has a complete port of Vector3fc and Triangle to clojure (from the ruby versions). Part of the reason why my version is a mess is because I'm trying to do a fairly direct port and ruby is quite different to Clojure.

    Also, a lot of the code is crunching numbers which I haven't yet figured out how to express very elegantly in Clojure. You can tell by the overabundance of `let`s in my code.

    It seems you're starting from the other end: reading the data into structures. I hadn't thought about that yet but I like your use of the `read` function. I might borrow that... :)