In the first two parts, we were able to send rays and make simple diffuse and specular shaders. (It was not always working correctly, but it was mostly correct) In the past two weeks, I have tackled these problems, tackled reflection and refraction (Also shadows), and worked on acceleration structures. In fact, we need to integrate transformations, instancing, multisampling, distribution, ray tracing issues in the current situation, but unfortunately, we cannot move on to the newest topics without finishing them because I have difficulties in the rest. The last image I got as a result of all these studies was as follows (which is quite satisfactory when you succeed :))
IT'S WORKINNGGG (Kinda)
While writing code, especially when working on a new subject, you write codes that will save the day without fully understanding the subject. And these codes start causing you trouble in the future. The problem gets bigger and bigger, and you won’t be controlling it in the end. Here, I encountered this problem in these two weeks and it was not possible for me to move forward without solving this problem. In my case, this problem was to define all objects (triangle, plane, and triangle) individually, call the hit and shadow functions separately for each and calculate the coloring one by one. This has led to a situation that both eats a lot of time and confuses the codes too much. (It has almost 3000 lines of code and unfortunately, it cannot do exactly what it needs to do) To solve this problem, I created a Generic Hittable class and invoked all objects in this class. I also started to create a new generic class and use all material properties in that class. As soon as I did this, the render times I received started to shorten noticeably. After I managed to collect the values I received in one general class, it was time to solve the lighting problems. First of all, there was no shadow in the scene and I created a function under the Ray class called ShadowRay to add the shadow. In fact, you can think of it as a structure that works in a structure similar to hit and only finds shadow. Below you can find the code snippet that does this for a sphere.
Sphere Shadow Hit Function
The image we got after adding the shadow was as follows.
Before and after shadow
Now our ray tracer can generate shadows. However, it is still early for celebrations (As we will use this more, so we can code maybe;) Now it is time to add the reflections. So how can we do that? Actually, we talked about the lighting equation in part 2. We will use it exactly and try to take care of the reflection. Reflection and refraction are called Recursive ray tracing and are based on the repeated calculation of the transmitted ray. In developing this, the important thing is to avoid endless recursive loops. Because it is possible to get a StackOverflow exception:) In order to make reflections, we need to add a little above the formula we showed in the previous part.
The code block that calculates the reflection is as follows:
After implementing reflection functions I get this images:
It seems to be getting the right results, right. Next is the refraction of light.
To understand refraction in Ray Tracing you can read some sources about Fresnel, Beer’s Law, and more. Frankly, I have not been able to fully set the subject. Unfortunately, I cannot give a correct explanation. I reviewed many resources on the internet and developed a refraction function. And the result was as follows.
Original Image - My Image
As you can see, the structure I developed does not work correctly. I have to find the errors in here and fix the refraction.
At the end of the previous part, I mentioned that the renders I made took a long time and we needed to speed up these renders. It is possible to do the acceleration work with structures called acceleration structures. There are many different ways to do this. The first thing I plan to use is called Bounding Volume Hierarchy (BVH). You can compare this method to Occlusion culling in game engines. We draw invisible boxes around the objects in the area we will render, and if the beam we send hits one of these boxes, we only make the necessary beam calculations for the area inside that box. In this way, the time loss of objects that are not related to the beam I send is prevented.
Bounding Volume Hierarchy
The photo above will be useful for you to understand the working structure of BVH. For more, it may be useful to check the link below. I started working on integrating BVH but have yet to get a result I can show. At the same time, I was able to enter the instantiation of objects, rotate them, and translation events at a minimal level. Unfortunately, there are no results I can show on them either.
Images that I get so far
At the end of this section, we have finished basic (core) ray tracer. I will clear the code and share the project on Github (I will upload the link here when I complete), but also try to add the other structures I mentioned and continue to improve the ray tracer and increase its features. This journey has shown me that (again and again) every time you solve a problem, that problem is more difficult to continue, and it continues to show. But ultimately we want to achieve this right 🙂 Take care, see you in the next part.