Devlog: Week 8: Writing a Ray Tracer in Julia

  |   Source

It is real long since I studied coordinate geometry or computer graphics. This last week, dusting the very little remains of what I knew, I decided to work on a ray tracer. When I started, I did not know what a ray tracer was. Slowly, I understood that it was one algorithm that is used to construct a 3D scene on a 2D screen. Broadly, this is how it works

Define the eye/camera/viewpoint

Define the screen in m*n pixels

Shoot a ray for each pixel in the screen from the eye. For each ray,
    Find nearest intersected surface
    Find a color for the point based on surface reflection/refraction or the light source and shading model
    Plot the color on the pixel

I decided to build one that would trace spheres, so I had to define 3 major types - Vector, Ray, Sphere, and a helper type Intersection to store where an object intersected with the screen.

type Vector
    x::Real
    y::Real
    z::Real
end

type Ray
    origin::Vector
    direction::Vector
end

type Sphere
    center::Vector
    radius::Real
    color::Vector
end

type Intersection
    point::Vector
    distance::Real
    normal::Vector
    object
end

Interestingly when I went about defining methods for the vector, I found Julia had inbuilt support for finding dot and cross products, so all I had to do was piggyback on those implementations and build my vector class. Also, thanks to Julia's multiple dispatch mechanism, overriding inbuilt operators was as simple as this:

function +(a::Vector, b::Vector)
    return toVec(toList(a) + toList(b))
end

That done, I went about implementing the core algorithm, and a couple of simple shading models, being Lambertian and the Blinn Phong shading models.

Once I'd implemented it all, and fixed all bugs that produced seemingly awesome but wrong images, the main loop consisted of this:

for x in 1:imageWidth, y in 1:imageHeight
    traceWorker(x, y, imageArray, cameraPos, lightSource, objects) 
end

//See full implementation <a href="https://github.com/madhuvishy/ray-tracer/blob/master/tracer.jl#L119">here</a>

I used Images.jl plugin, to build the image, and ImageView to display it, and when I ran my code with the above main loop I got something like this:

☁  ray-tracer [master] ⚡ time julia tracer.jl

julia tracer.jl  11.55s user 0.26s system 98% cpu 11.933 total

But having used Julia, I had to put it's parallel processing capabilities to test. And because each of the calculations in ray tracing are independent of each other, I could put this, to use. And now my loop became...

@parallel for x in 1:imageWidth, y in 1:imageHeight
    traceWorker(x, y, imageArray, cameraPos, lightSource, objects) 
end

//See full implementation <a href="https://github.com/madhuvishy/ray-tracer/blob/master/tracer.jl#L119">here</a>

Sweet! Let's check the new running time.

☁  ray-tracer [master] time julia tracer.jl

julia tracer.jl  6.44s user 0.11s system 99% cpu 6.566 total

Yay! Down by almost 50%. Pretty sure there are more ways to optimize my code, but this is how far I've gotten till now.

Now, for our final showdown.

Screen Shot 2014-03-31 at 5.18.54 PM

Woah, but no...

Screen Shot 2014-04-01 at 11.17.16 AM

Ummm, circles or spheres?

Screen Shot 2014-04-03 at 5.14.02 PM

Yes this.

Thanks to fellow HackerSchooler Lita Cho for patiently explaining how ray tracers work :) Check it out at: https://github.com/madhuvishy/ray-tracer/

Comments powered by Disqus
Share