Ray Tracing with Akka – Part 2

Where we left off

In the first episode, we made a really basic (but parallel) ray tracer. It can handle spheres and use diffuse mapping. We made three types of actors, the renderer, who starts the tracing, collects the answers and saves the picture; the scene, who manages the trace requests, gets the closest object color and provides answers to the renderer, and last but not least, we have an abstract shape which can calculate the intersection with a ray, and tell its color.

In this episode we will validate our Shape-Sphere split with a new mathematical object (Plane), make some shadows, and let our objects reflect and refract.

Plane

We have a Sphere. It has only two functions: an intersect, and a getNormal. If you are good at 3D coordinate geometry, you can write the plane yourself. We need a Point, a normal vector, and we are basically done!

If we add it to our Main too, we will have a nice-looking surface. I think this proves that the initial “AddShape” decision was not a waste of time, and the Shape-Sphere split was a good move, too. We wrote just the necessary shape-specific code, created the actor and the new shape has appeared.

ray tracing with akka part 2 sphere

Shadows

The picture above is nice looking, but something is missing. Something is strange. (You can get shadowless pictures in real life, too… like this one, but this is not very common.) To make it more realistic, we will need to know if there is something between the intersection point and the light. Our communication will be modifed a bit. After that, we want to know a color of a given point of a given object, the object needs to request the scene. The scene needs to tell the object where the closest intersection is from the object’s intersection point to the light (if it has any). We also need to modify our Trace request to answer with the requested information (ColorMessageAns or IntersectMessageAns). Our new communication flow is:

ray tracing with akka part 2 object communication

(Renderer starts a ray. The scene broadcasts it to the objects. The objects compute the intersection points and answer to the scene. The scene chooses the closest one, and asks the object, what color it has on that very point. The object need to know if it is in shadow or not, so it casts a new ray. The scene broadcasts it, the objects compute intersections and answers as before. The scene tells the object the closest intersection. The object now knows if it is in a shadow or not, compute its color and answer to the scene. The scene answers to the original request from the renderer.)

The todolist is:

  • modify the Trace case-class
  • modify the tracer
  • modify the shape

If we are lucky enough, we don’t need to modify other classes.

Add one new parameter, break nearly 0 code (smile) (Only the scene will break, the renderer will produce a good Trace request because of the default value.)

Most of the modification above is simple parameter extension. The only logical modification is when we answer to a NoNeedColor trace with an IntersectionMessageAns.


Not so much modification. The getRayColor no longer answers the question, it starts a new one (//1a), and saves the context to a map (//1b). When it gets the answer, a minimal logic relays the good answer to the original question (//2). IMPORTANT: the createBaseColorAns will get as many answers as the getRayColor questions. There is no broadcast in this layer. The broadcasting and aggregating is the scene’s responsibility.

This code contains my favorite UUID implementation. I use it to generate new ids.

If we run the modified code, we will get a shadow!

ray tracing with akka part 2 sphere shadow

Nice one! Our next mission is full of shining and sparking!

Reflective surfaces

This topic was one of my first really interesting type theory problems on this project.

I want to implement the reflection once, and use it on any shape. The ReflectiveSphere needs to inhere from Sphere, and also needs to inhere from Reflective. But both Reflective and Sphere need to somehow inhere from Shape. It’s like a diamond inheritance problem. I never thought this problem can ever appear in “real life” (big grin). I tried multiple methods, and as always, the problem was not the language’s features, but how I wanted to use them. You can use traits (basically they are forsolving these types of problems). You can tell the trait what base class you want to extend, and you can override methods on them. The key question is how you separate your methods, because you can’t really fallback to super implementations anymore. And if you are not prepared for that, you will write nasty infinite recursive calls (big grin).

As the shadow before, if we want to tell the requester what is our color, we need to start a new trace, and get a reflected color from the scene, too.

(Maybe with a bit better code organization, we could make the “get reflected color” and “am I in shadow” questions paralell, but I don’t bother myself with that, maybe later when I will want better performance.)

One more thing! If I start rays again and again between two reflective objects, they will ping-pong till eternity, so I will need some iteration depth measurement, and If I hit that, I will fallback to some original shape color.

I will need to write the iteration where it is needed, split some functions in the Shape, and make my brand new Reflection trait. Create two new shapes (ReflectiveSphere and ReflectivePlane) with as minimal code as possible, and try out the whole thing.

At this point, if you fix the Scene init in the Main, you can run the code again and this will produce the same output as before. So we eventually did a minor refactor/reorganize cycle. Let’s start the magic!

As I said before, this will be a trait, and this will require a Shape. If a reflective object needs to tell its color, we need to consider whether the iteration will let us get it from the scene (3a) or just fall back (3b). If we need to compute it, we start a new Trace (4). At this point, we need to save the actual state to a map for later reuse (5), because at some point, the scene will answer us with a ColorMessageAns and we will need to know where we left off the computation (we handle this in the shape with a non-implemented function, we will need to override that). When we get the color answer, we do some color adjustment and start the shadow computing (6). That’s all. (The two monster functions at the end of the class are just some mathematical approximations to the actual Fresnel equations (I copied it from some C++ code in the past and I have no source to it (sad) )) If you want to dive into the physics you can read this.

This is our ReflectiveSphere implementation. I think this is amazingly small (big grin)


Like the Sphere, the Plane can be reflective too! (The best would be if I could add parameters to a trait too and if I could write something like new Plane(params) with Reflective(refParams) but sadly we can’t do that…)


Modify our first sphere to be reflective, add a second (white) sphere, and add reflection to the plane, too. (The reflection’s values are mostly created with try/see/modify cycles. (I made up the sphere, the planes are from a book.)ray tracing with akka part 2 sphere reflect

Refractive surfaces

Like glass balls. If something is refractive, it is reflective too. So our new trait will extend reflective, and pretty much will work like that. The main difference is that we need to decide if the ray goes into the object, or it is reflected from it. But because we have written most of the code already in the reflection, our logic is nicely adoptable here too.

Most of the time we could safely fallback to the super functions. It would be nicer in the long run if we could separate the mathematical code and the communication+state-persistence code more, but at this point they rely on each other too much (sad) (NOTE: we don’t compute shadow if we refract.)

I made a small mistake in the first post… If you look carefully, our shape can only intersect with a ray that is started from outside of the sphere. At this point, I need to fix this silly mistake and calculate the intersection point and the normal’s coherent. Need to fix the Shape and Sphere too:

Add it to the main too.

We add the Refractive option to the sphere and add a new sphere in the Main (add after the spere2 or at least before the renderer ! “start” ).ray tracing with akka sphere refract

 

Summary

We extended our previous code with a new object, and some new fancy features, such as shadows and reflection. The generated image looks really nice!

The next post will try to clusterize these methods using the Akka clusters. Stay tuned and follow us on Facebook to see more of what we do!

Any questions are welcome in the comment section. If you want to see the code altogether, check out the GitHub Akka Ray Tracer Repo.

 

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).