Trace the Ray
Part 1: Trace the Ray
Hello everyone, in this series of blog posts I will be talking about my
ray tracer's development which I will be developing for ceng795. I will write every line of code in this ray tracer except
stb_image library which is used for writing png images.
First, let us talk about what actually ray tracing is for those who don't know. Ray tracing is one of the most popular rendering algorithms. It's basically a way of simulating light's behavior with computers and generating images as the result. In order to simulate lights behavior. We do the following: We send a ray from our eye (or camera) to each individual pixel and deciding where would that ray pass before reaching our eye hence the name "ray tracing". After knowing where the light travels and which objects or mediums it interacts we can decide that specific pixel's color value.
In these series of blog posts, most of the time I will be skipping the maths and geometry details because there are very good books out there so trying to explain them again would be inefficient but I will mention them when I see fit. If you are new to computer graphics or ray tracing, I can at least recommend some of them. "Fundementals of Computer Graphics" by Shirley et al. and Physically Based Rendering by Pharr et al., which is also avaliable online and free at www.pbr-book.org, is good for a start.
Scene composition
In ray tracing first of all we need to define/model a scene so that we can have the necessary information before we start rendering. Usually this information should be given to our program as input by either pre-existing scenes or by graphical artist who design these scenes. These information consists of:
- Our camera's position and orientation
- Geometry of the objects in the scene and their places in the scene, and our image plane's information.
- And last but not least our light sources, without the light sources there no point in tracing light rays.
Every other additional data other than these are used to make our ray tracer a little bit fancier and I will talk about them when their time comes.
After getting the cameras position and determining the place of the image plane Instead of sending light rays from light sources to everywhere and determining which ones bounce and come to our camera we send rays from our camera to pixels of the image. We do this because if we were to do former then we would have to send light rays to everywhere and most of them wouldn't reach our eye. So we would be doing computations for nothing. Instead we do that latter and trace only the rays that reach our eye. So we are actually tracing the ray in a backwards manner. Sometimes this is why ray tracing is called "backward rendering". There are also advanced techniques such as photon mapping that does both forward and backward ray tracing but let's just keep things simple for now. For the same purpose we will now only implement point lights.
Getting started
Now let's image we are trying to render the scene below.
For each pixel we should compute the center of the pixel and send a ray through that point from our camera. If this ray intersects with any objects in the scene, we will choose the closest object and color that pixel.
For now let us just color that pixel as the same color as the objects material. As a result we will get the following.
This is kind of boring but still a good start. Since it takes a little bit of code to get this output it is easy to get a fully black image just because of a simple bug. For instance while I was writing my first tracer a year ago I accidentally sent all the rays above the image plane. As a result I got a black image. I advise using a debugger and selecting single ray and tracing it's movements around the scene to control it's movements and do the calculations by hand.
After this step let us render this scene with Lambartian shading(aka diffuse shading). Of course there comes the question what is diffuse shading? It is the color an object reflects depending on the light sources position but independent from the eyes posiiton. Matt surfaces mostly reflect in this kind of fashion. As you can see our objects are better now.
Lights
![]() | |
| Spheres with only diffuse shading |
After this we will add ambient shading which kind of like the environment lighting. It is a constant light that is added to all objects so even the places that are not reachable by light sources are illuminated.
And now we are adding the specular shading which simulates the objects with shinier surfaces. They reflect more light when we are looking at them at a specific angle.
You have probably notices that something is still missing. That being the shadows. In order to implement shadow we just need to send a ray from objects surface to the light source , we call this a shadow ray, and if it intersects before it reaches the light we say that objects is in the shadow and do not add specular and diffuse shading.
![]() | |
| Shadows |
But you need to be careful when sending shadow rays. Because if we try to send a ray directly from the objects surface. It is quite possible that ray will intersect the object we are sending from. In order to prevent this it is common practice to move the rays starting point along the surface normal by a very small epsilon from the surface itself.
Mirror Mirror
Now that we have implemented basic lighting we can add mirror objects. In the first glance it may seem complicated but It is actually already taken care of. How? Well you see, the rays we are casting from the eye and the rays we will be sending from the mirror reflection will behave exactly in the same manner. We are asking the same question: "What color this light ray would look?". The only difference is that we will scale this color by the objects "mirrorness" and add it to the color of the original ray. So if an only half mirror like we will send the reflection ray and scale it by 0.5 and voila we have reflections. Just good old recursion.
![]() |
| Mirror reflection added |
Of course we have mentioned that shadow rays need to be sent carefully. Let us show what would happen if we fail to add epsilon to our rays starting point.
![]() |
| Failing to solve self shadowing problem |
As you can see if we fail to solve the self shadowing/reflecting problem we will get noise on our images. This would be same for the shadow rays too.
Dielectric Materials
Now that we have added mirror objects let us go a little bit further and implement dielectric materials so that we can render objects with transparency. They are simple but they need a little bit more consideration than reflecting objects.
Just like mirror objects they will reflect light but they will also refract light. Reflection is already handled. So we will move to refraction.
In order to find the refracting ray's direction we have to use the Snell's law. (The slides which has ceng795 tag below them are taken from my instructor's, Prof Dr. A. Oğuz Akyüz, lecture slides. I want to thank him for allowing me to share snippets from them)
![]() |
| Transparent objects [5] |
![]() |
| Snell's law [5] |
![]() |
| Snell's Law [5] |
![]() |
| Snell's Law [5] |
![]() |
| Snell's Law [5] |
Using Snell's law we compute cosΦ and if it the square root's inside end's up negative we know that a total internal reflection occurs. If you don't know what total internal reflection is, It is a special case when light travels from a medium to a less denser medium. For example You are in the swimming pool, completely under the water and you are unable to see the environment above the water surface.
![]() |
| A case of total internal reflection |
In this case our job is easier. We just compute the reflecting ray then finish.
Fresnel's Reflection
In general case we have both refraction and reflection and we need to find which ratio these are happening. In that case we just compute the formula below for dielectric materials (Sorry it's just physics, There is nothing much to explain).
![]() |
| Fresnel's reflection ratio [5] |
After computing the refracting and reflecting ray we just scale them by Fresnel's reflection/refraction ratio and we are done with reflecting rays of transparent objects. But There is one more thing we need to consider that being:
Light Attenuation
We are so close to rendering transparent objects. The only thing left is the light attenutation. When light travels through a medium other than a vacuum. It gets attenuated. For simplicity I will assume air also behaves like vacuum because it attenuated light so little. But when we are inside a denser medium like glass with higher absorption index we have to reduce the amount of light that is traveling. Again for simplicity we will just consider mediums with constant absorption indices. But If you want to do it in general you need to do some differential calculations. Here is the Beer's Law for lights attenuation.
If we are considering mediums with constant absorption indices ( AKA isotropic mediums) we just simplify the formula like below
Where C is the absorption coefficent and K is the initial value of the light. After adding this to the equation, we have managed to render transparent objects. Hurrayy
Conductor Materials
Now let us a new type of material: conductors. Conductor materials such as metals reflect light a little bit differently. They look shinier. In order to find how much they reflect light we will just use The Fresnel reflection for conductor materials (again just implementing physics formulas) and multiply the reflecting light with this value. Conductors do not refract light so it might sound a little weird to use Fresnel's equations but do not get confused. These materials also have a property called absorption which we just put in these equations and we are done.
![]() |
| Cornell box with both Conductor and dielectric materials |
Some other outputs from my ray tracer
Bugs that I have encountered
In the image above I have made a small mistake and after sending shadow rays I forgot to check where the objects were. Hence the objects behind the light source was able to create shadows for that light source. The solution is simple if the object is farther than the light we do not allow them to create shadows.
Implementing sphere and ray intersection casued the refracting rays to miss the sphere but the other rays went fine but it was just luck. Conductor is black because I had not implemented them yet in the image below. The problem is the refracting ray of the dielectric sphere (you can compare them with the correct outputs above)
The other mistake I made was making light calculations for an ray which was inside an dielectric object. Since the light actually doesn't contribute to dielectric materials inside directly we should emit that. As a result I got a much more bright science tree like below (yes that glass object is called science tree)
Another bug i have introduced was to do lighting computations with flipped normals. As a result I've got creepy spheres staring at us
The last bug I will be mentioning is the self shadowing/reflecting objects I have already mentioned that adding a small epsilon solves this but since it is a common bug I wanted to remind it.
This is it for now thanks for reading. In the future I am planning to post more blogs as my homeworks proceed.
References
[1] P. Shirley, et al., Fundementals of Computer Graphics. A K Peters/CRC Press; 4th edition 2015.
[2] M. Phar, W. Jakob and G. Humphreys Physically Based Rendering: From Theory To Implementation, 3rd ed.
Morgan Kaufmann, 20018. [E-book] Available: www.pbr-book.org.
[3] https://github.com/nothings/stb
[4] https://en.wikipedia.org/wiki/Ray_tracing_(graphics)
[5] A.O.Akyüz, lecture slides of ceng795: Advanced Ray Tracing
































Comments
Post a Comment