- Diffuse Shading
- Specular Shading
- Ambient lighting
- Shadows
- Reflections
- Multiple Lights
- Spheres and Triangle Meshes (Example being a tetrahedron)
- Multi-Threading
- Export individual frames or interpolate between two points with animation
- Real time viewport
- Configurable background, resolution and recursion depth
- GLFW
- OPENGL
- VCPKG
- Catch2
- C++
- lodepng
- Fundamentals of Computer Graphics 3rd Edition
- WSL2
- CMAKE
Windows
-
Clone this repo using
git clone https://github.com/haroonsyed/CpuRayTracer.git --recursive
-
Install cmake
-
Open the folder containing repo in visual studio, run main.cpp
or
-
CD into build folder and run
cmake ..
-
Open generated solution (in build folder) in visual studio and run main.cpp
Linux
- Clone this repo using
git clone https://github.com/haroonsyed/CpuRayTracer.git --recursive
- Install the following dependencies: libxmu-dev libxi-dev libgl-dev libxinerama-dev libxcursor-dev xorg-dev libglu1-mesa-dev
- Install cmake
- CD into build folder and run
cmake ..
- Run
make
to build your executable - Run
./main
Press P
to toggle perspective between perspective and orthographic views.
Press ESC
key to close program.
- The default scene consists of a tetrahedron, and two colored spheres lined up along the y axis. A mirror sphere floats above the other objects. A floor is below with a sky-blue background.
- Directional light is created by adding a light far away, in this case at -3000,2000,4000. This gives the light a downward angle at slightly more sideways than backwards from the point of view of the camera.
- The camera is located close to the origin at (-40,0,0)
- The floor is a large triangle in the scene. It is white, but has a low diffuse value, which coupled with the low light angle causes the floor to appear darker. It has a specular, which can be seen in the reflection of the mirror ball. The floor itself is reflective, which shows the other objects above.
- The tetrahedron is diffuse red and has a slight specular to it (which is not visible in the image). It consists of four triangle meshes.
- The green sphere is a pure diffuse material, which shows the shadows on the bottom side where light is occluded really well.
- The blue sphere has a high specular value, which attempts to mimic a more plastic material.
- The mirror sphere is interesting in that it shows the other spheres reflected and curved accurately. It also has a slight specular. And separately shows the floor specular toward the light source.
For this scene the blue ball has been re-arranged to better show perspective.
Notice how the blue ball, which has a radius twice that of the green ball appears similar in size in perspective view. But in orthographic view the parallel rays show the size difference. Also notice the floor disappears since it is in the same plane as the viewing rays, which causes infinite solutions and is treated as a non-hit (infinitely small edge to process). However these are still visible in the mirror ball reflections.
The examples so far have featured a directional light. Now let's try to simulate a point light by placing the light in the middle of all the objects: We can see clearly that the shadows and lighting orientation are relative to the direction of the light and not the same for each object. What should be noted is that the point light as implemented does not decrease in strength with distance, although this would be simple to implement.
The ray tracer supports an arbitrary number of light, given the hardware capacity. Now let's see the previous render with a directional light: Note the double specular reflection on the blue ball. The increased brightness of the scene. The second shadows (all facing the same way), but smaller since the light is higher up. Also note how overlapping shadows correctly become even darker.
All aspects of the program are configurable, for example shadows can be enabled or disabled with the enableShadows boolean of scene.h. Relfections can be enabled or disabled with the maxRecursions value located there as well. Lights themselves can have varying intensities upon instantiation. And materials can be composed of different properties as well. Below are a couple different scenarios demonstrating this:
No reflections. Note how the sky color is missing from the floor as well.
MULTI-THREADING:
The program features multithreading, and automatically uses the maximum number of threads available on the client. On my i5 8250u various timings are listed below:
Thread Count | Time (ms) |
---|---|
1 | 6610 |
4 | 3628 |
5 | 2957 |
6 | 2544 |
7 | 2232 |
8 | 1768 |
9 | 1593 |
16 | 809 |
One can see that the program scales well with processing power, as it should since all operations are disconnected from one another. However I suspect my multithreading implementation is not perfect, since my cpu has 8 cores and the additional threads past that continue to give performance gains. Further investigation is required. |
MATHLIB:
A basic math library was created for this project (src/math). I understand it is slow compared to using asm and sse instructions, but it was a good exercise for refreshing linear algebra knowledge.
TESTS:
Continuous integration with github actions has been implemented. Tests were written for most features of the mathematics library. No tests were written for the ray tracing itself, as I was not sure how to do this other than visually. My initial idea was to have a verified visual image to compare against with tests, but the inevitable new features (and possible floating point variations) made this unsuitable.
MODES:
singleshot
:
Takes a single frame from the current camera location, does not aniamte. And saves the frame to an image in the directory called still_render.png
.
shouldRender
:
Interpolates the camera position from startPosition to endPosition. Saves each from as frameN.png
which can be assembled into a video format later. The framerate and duration can be set as well.
Please see links below
https://youtu.be/vvICc10feRQ
The entrypoint to the program is main.cpp, which includes basic code to setup a cross platform window. Also included here is a pre-processor statement for tests to run using the catch2 library.
main creates a camera of the specified resolution, which then calculates the screen coordinates and instantiates a scene. The camera then generates rays and passes it to the scene `render` function, which loops thorugh all lights and objects of the scene (uses inheritance for a generalized interface) and returns a pixel of the correct color to assign to the data array. The camera then returns the data array to the scene.
I mainly relied on the textbook for my implementation (made tweaks as I felt)
The main thing I went to outside help for was to understand (not for implementation) how the ray triangle intersection worked. To my delight I came across a channel called Sebastian Lague with this video with visual animations of what the vector math is doing:
https://www.youtube.com/watch?v=HYAgJN3x4GA
Although I do not think the technique in the textbook with barycentric coordinates is the same it is still helpful.
Feature | Status | Extra Desc. |
---|---|---|
Solving ray-object intersection problem | ✅ | |
Add ambient/diffuse/specualr shading | ✅ | |
Glazed (mirror) surface | ✅ | |
Create a small aniamtion | ✅ | |
Add a key to switch perspective | ✅ | |
"Advanced" Feature: Multithreading & Shadows | ✅ | Possible problem with threading implementation leaving more performance on the table. |
Documentation | ✅ |
Aside from adding more features like transparency, bloom, proper point lights/spot lights, texture mapping, I mainly want to focus on the architecture of the code. I was trying to focus on learning ray tracing, and in the process wrote code that frankly abuses static and makes too many variables public. The class dependency and organization is also a mess (for example the camera owns a scene instead of the scene owning a camera) and I ran into several issues with circular dependencies. In the beginning I thought this would be okay, but as the code base grew I realized that sticking to proper software engineering principles from the start is better, and I will remember this in future projects.
THIS IS ONLY FOR DEMONSTRATION PURPOSES AND IS NOT FOR OTHER STUDENTS TAKING THE CLASS.
I DO NOT AUTHORIZE THE USE OF THIS CODE FOR CLASS PURPOSES BY ANY STUDENT OTHER THAN MYSELF OR IN ANY FORM THAT WOULD BE CONSIDERED "CHEATING" OR "PLAGARISM".
Please be ethical.