Ray Tracing with Akka – Part 1

About this series

Most of the time, when you learn a new concept or a new programming language from a(n) (e-)book, you get the basics. But these are really far from the production-ready code with the new toolstack. The only good way to learn programming (like most of the other things in life) is to practice. So, because I want to be more and more confident in actor architectures, the Scala language, and Akka capabilities, I often start a half-day or a weekend toy-project just to try something out. This post (and the other ones in the series) will try to summarize my problems, and show how I solved them. If you are the type of developer who wants to see the show for it first, check out the GitHub repo, then come back to read the post.

About the topic choice

When I was in school, there was a Computer Graphics class where the students had to make four out of five (at that time really hard) assignments. The third one was always about ray-tracing, and most of the students used the skipcard for it. I think this is the most interesting part of computer graphics. (Java/Scala backend programmers are rarely heard to like computer graphics or to know how the graphic-card actually renders things. I always liked this topic, but I really hated the pain what came with shader debugging, so that’s why I ended up on backend instead of making new game engines.)

What is ray tracing?

Ray tracing (as its name says) is about tracing the path of rays based on the theory in physics of light modeled with really tiny balls. The picture creation is purely mathematical: we have light rays, that intersect objects (mostly given with mathematical formulas), we compute angles, and modify a color based on that. Most of the time we start rays from the camera and when they intersect objects we try to figure out if they are visible from the light or hidden behind other objects (a.k.a. shadow computing). When we have reflective and refractive objects in our scene we start more rays to get the intersection point’s color (because of this step this method is sometimes called “recursive ray tracing” too).

The problem with ray tracing is its computational cost. When you cast a ray, you need to compute all of the intersection points with the objects. If there are some, you need the closest one, you need minimum one different vector operation (to calculate its color). Alternatively, if you calculate shadows you need another tracing phase. Furthermore, if the intersected object is reflective/refractive you have to cast new ray(s). (With modern GPUs the picture creation is really far from this method, they are cheating at every step for speed. (big grin))

Wikipedia summarizes this correctly (btw the whole page is worth reading if you have never met this technique before).

It has lots of inner mathematics; vector and intersection point calculations between lines and other mathematical objects (like sphere, plane, cylinder, infinite cones and other general quadratic shapes). Also it has some not-so-easy physics too, like;

  • the reflection ray computing,
  • the decision between reflection or refraction, based on the intersection point’s normal vector and the angle of the ray,
  • the color mixing and shifting

 

The goal of this post is not about teaching you how to do ray tracing in general, but rather about learning Akka and Scala, so most of the time I won’t go deep into the mathematical background. I will try to include some helpful links or posts if you really want to understand why that “costheta” is computed that way, but I want to focus on the architectural design.

Why is it possible with Akka?

The basic architecture in most of the (recursive) ray tracers is (1) generate all the starting rays, and (2) compute the ray color with one function call. This function is called recursively if multiple rays are cast due to reflection or shadow computations. The basic architecture’s code is paralleled along the initial rays, and serial at the intersection computations for example. My initial idea was; what if an object in our virtual space is an actor. So when I start a ray, and want to know “which object is the closest?”, instead of iterating through all of the objects and calling blocking functions, I broadcast a message to them and wait for their responses asynchronously . When I need to know a color on an intersection point, the object can start a ray instead of recursively calling a function. So with this mindset, I can separate some logic, and make async boundaries between the calculations. In theory, I will have better options for splitting the problem to clusters, and If I use pools instead of single actors, I will have better thread usage (I can compute a single ray in parallel, not just a group of them). Of course this will have a huge memory cost compared to the standard implementation, but this is still a manageable cost (not gigabytes).

Here is all the above in two neat GIFs for you:

Ray tracing with Akka

Ray tracing with Akka recursive

Starting point

Before anything fancy, we will need to write some essential classes. We will start with the Vec and the Color (these are going to have some logic).

Some explanation:

  • The + and – are basic vector things, add or subtract vectors by coordinates
  • The * and / are basic vector things too, multiply or divide scalars by coordinate (Vec * scalar will work but scalar * Vec won’t!)
  • LengthSquared is length * length (some computations will need the length and others will need the lengthSquared so I exposed both)
  • sum is the sum of the coordinates (we will need this too, it will be useful)
  • mult is multiplication by another vector, coordinate by coordinate
  • dot is a mathematical dot product
  • distance is the distance between 2 vectors
  • we can get the normalized vector
  • NOTE: the Scala language is powerful enough to redefine symbols like + or * but you need to be consistent if you want to write clean code! You can see I redefined * in scalar multiplication, but used mult and dot for vector multiplication. It’s because in mathematics, sometimes one and sometimes the other is mentioned as “vector multiplication.” So writing the exact method with words is more traceable when you read the code.

Our Color object will be similar to Vec:

  • We can + and mult two colors together
  • We can * a color by a scalar
  • We can create java awt color from our color object (with clipping the values to the 0-1 range)

Finally here is our package object with some of the case classes “wrapped up”.

I made a a case of syntactic sugar; redefining the Vec type as a Point, too. Note that if you do only the type declaration, every function parameter can be a Point instead of a Vec, but the apply method will not work. With the val+type declaration, you can change Vec to Point anywhere you want. I use it for clarifying parameter lists, but I made this refactor in a later dev cycle so there may be some Point-Vec inconsistency (but the code will compile and function anyway, this is only for the code readers (wink) ).

I will use Akka’s props pattern in the companion objects.

First picture

We will begin with baby steps. Create a scene, add a Sphere to it, start and absorb rays, collect ray-colors, write out the picture to a file.

As a starting point, we write some communication protocols between our actors:

 

  • SceneObjects will add themselves to the scene with AddShape
  • Tracer will request the color of the ray from the Scene and the scene will answer with a ColorMessageAns after consultation with the SceneObjects
  • The Scene will broadcast IntersectMessageReq to all SceneObjects and after all of them responded, the Scene will choose the closest one. Then the Scene will ask for the color of the chosen SceneObject with ColorMessageReq.
  • Every message has an ID for traceability.
  • A picture of our communication:

Ray tracing with Akka tracer messaging

Start it with a bottom-up class creation! We will create a Sphere first (an object in the animation above), then a Scene and finally the Render. After these steps, we will be ready to create our first picture!

 

  • When we start, we add ourselves to the scene (1) (when we do cluster things or pools, this may be a regrettable decision but yolo :D)
  • If we get an IntersectMessageReq (2a), we calculate the intersection with the ray (2b) and the distance from the ray starting point, and answer to the sender
    • The language (and the vector class) is helping us write meaningful and more readable code. For example try to understand what b is from above or try to understand it from the linked page.
  • If we get a ColorMessageReq, we answer with our color (3)

 

  • If we get an AddShape message; add the shape to its inner objects list (4)
  • If we get a Trace message (5a); log its parameters to an inner map (5b), and broadcast IntersectMessageReq to all of our objects (5c)
  • If we get an IntersectMessageAns(6a); modify the map element to represent the actual closest object (6b). If all of the objects answered, get the closest object’s color (6c, 7a)
    • I use mutable maps, because this is an actor, and there’s no need to think about thread-safety, and modifying an immutable map is really processor+memory heavy if you rapidly write it.
    • When we have no intersected object, we just answer with darkblue (7b) (like the sky or something).
  • If we get the color we requested (8); we answer to the actor which requested it from us

 

  • If we get a “start” message (9a); we create all the rays (9b) and send them to the Scene to trace them (9c)
  • If the Scene answers us (10a); we update our inner ImageBuffer (10b)
  • If all of the rays come back (11a); we write out the image to a file (11b), and print saved to the output (11c)
  • We generate id-s from coordinates with string concat and we parse them back from that (9c, 10b) (not safe, but a lazy and (most importantly) working method)

Ray tracing with Akka testball1

Pretty lame, but it’s our sphere from our ray tracer… Lets polish the code a bit and make this “ball” better looking!

Some improvements

First of all, we have some really good generic code on our sphere, so split it into a general abstract “Shape” and a shrunk “Sphere”. And add some color adjustments since we are here already!

 

  • Most of the actor-specific code was split to the Shape (12a, 12b)
  • We need to know how the concrete shape intersects with a ray (13) (this will go to the Sphere)
  • We use a new fancy color adjust with the help of our light. (14) (This is diffuse shading like the 2nd point in here or the point light sources part from there )
    • We need to know the normal vector of the specific shape at a given intersection point (15)

This split will be good, because if we want to add some generic code to every single one of our shapes (just like shadows), then we can add the code to the Shape class. This way, the shape-specific computations (like intersection point computations) will belong to the concrete shape objects. In the next part of this series, we will add more code to the base Shape, and try to leave the concrete shapes as pure as possible. (So no shadow computing, or refraction handling in the Sphere, it is not its responsibility!)

RUN IT AGAIN!

Ray tracing with Akka testball2

Much better! It looks like a ball!

 

Summary

In this post we started to write a really basic ray tracer, defined some messaging protocol between actors, and it can draw pretty spheres! In the next post we will extend this tracer with a plane, shadows, reflective and refractive objects.

Come back on June 20th for this episode of Ray tracing with Akka! [the Ed.]

This is a “learning tutorial” for both the readers and the author so if you have any suggestions or questions, feel free to comment below!

Gergő Törcsvári

Gergő Törcsvári

Software Developer at Wanari
I would love to change the world, but they won’t give me the source code (yet).