Part 2: Recursive Ray Tracing & Bounding Volume Hierarchy
For the second part of the ray tracer, two new features are added to my basic ray tracer:
1. Recursive Ray Tracing (Reflection & Refraction)
2. Bounding Volume Hierarchy
Besides these core features, the new ray tracer also supports the following:
1. PLY File Parsing
As the core ray tracer is improved, it will be able to deal with more and more complicated scenes and larger meshes. Since most of these advanced scenes are represented using PLY files, this version of the ray tracer also provides support for PLY file format. For parsing PLY files, an external library called hapPLY is utilized.
2. LookAt Camera
In the previous version, only the simple camera model where the image plane is represented by left, right, top and bottom parameters was supported. With this version, the ray tracer also supports LookAt Camera model where image plane is described by the angle called fovy and it is assumed that the image plane is centered with respect to the camera position. While parsing the scene, the LookAt camera is converted to the simple camera model.
3. Material Types
In order to observe the effects of reflections and refractions, three new material types are introduced: Mirror, Dielectric (e.g. glass, water) and Conductor (e.g. metal, gold).
4. Smooth Shading
In this version of the ray tracer smooth shading for the meshes is also supported whereas the previous version only supported flat shading. Smooth shading makes it possible to achieve perfectly smooth surfaces, since triangle meshes cannot represent perfectly smooth surfaces unless the triangles are very very small.
Part 2.1: Recursive Ray Tracing (Reflection & Refraction)
For mirror and conductor materials, only reflection is implemented because they are not transparent. On the other hand, for dielectric materials, both reflection and refraction are implemented since when light hits to a dielectric surface, it is reflected and refracted according to the refractive index of the mediums and the incident angle of the light. Snell's Law, Fresnel Equations and Beer's Law are applied for both dielectric and conductor materials.
Once I implemented the reflection part for mirror materials, I encountered a problem which produced the following result:
spheres.png |
After a short while, I realized that I was calculating the wo vector from the camera position to the intersection point instead of from the origin of the incoming ray to the intersection point. The camera position was only working for the primary rays since they are originated from the camera, but when recursive ray tracing comes into the scene, the calculations were becoming wrong.
Secondly, one of the most time consuming problems I encountered was about the refraction part for the dielectric objects.
cornellbox_recursive.png |
At first, I thought that my vector calculations were wrong, but then I realized the blue part on the left side of the sphere was not coming from the transmitted ray, it was the reflection coming from the gold sphere. After that, I realized that the case where the ray is exiting from an object was never being executed. The value of cosTheta was never becoming negative. The rays were not being transmitted from one side of the sphere to the other side of the sphere. That's when I noticed something was wrong with the ray-sphere intersection method. When the ray hits the sphere, I was directly returning the minimum t. However, when one of the parameters is negative, the positive one was being omitted. Once I corrected this, the refraction part started to work without any issue.
Part 2.2: Bounding Volume Hierarchy
For the second part, I implemented Bounding Volume Hierarchy as the acceleration structure. For BVH implementation, space partitioning is used. It is important to note that each leaf node in the BVH tree stores only one object. Additionally, bounding boxes of the objects are calculated when the objects are being created, and the bounding box of each object is stored as a class property so that every time it is needed, it can be fetched with a getter easily. In the previous version, Mesh objects were kept as a structure since no important operation was performed on them. However, for this part, I decided to create a derived class for the Mesh objects just like Triangle and Sphere objects, so that they can be included in the polymorphism.
Once I started testing the BVH, I realized that no shadows were being generated, although primary ray intersections were flawless. I found out that the wrong part was in the bounding box intersection method. After this mistake was corrected, all of the scenes except the science tree were correct. However, the science tree scene turned out too bright as shown below:
After many hours of trying to figure out what is wrong, I finally found out that I needed to exclude ambient, diffuse and specular shadings from the rays that are being transmitted inside an object. So, I added a flag to the ray structure to keep track of whether that ray is inside or outside of an object.
Resulting Images
cornellbox_recursive.png (800x800) Before BVH: 45 seconds 407 milliseconds 825 microseconds After BVH: 946 milliseconds 466 microseconds |
spheres.png (720x720) Before BVH: 3 seconds 974 milliseconds 573 microseconds After BVH: 699 milliseconds 673 microseconds |
bunny.png (512x512) Before BVH: 37 seconds 198 milliseconds 931 microseconds After BVH: 235 milliseconds 407 microseconds |
chinese_dragon.png (800x800) After BVH: 1 second 816 milliseconds 417 microseconds
|
tap_0142.png (1000x1000) with flat shading After BVH: 6 seconds 449 milliseconds 193 microseconds |
tap_0142.png (1000x1000) with smooth shading After BVH: 6 seconds 409 milliseconds 102 microseconds |
Comments
Post a Comment