I love computer graphics, and previously had I made my own 2D software rendering engine in C without using any library. I implemented bitmap loading, linear alpha blending, and image rotation and scaling in the 2D rendering engine. I made some 2D games using that engine (Space Game, isometric fight game).
First of all, although I used some C++ features such as templates, I mostly used C without using any class in this project. This made the debugging much easier and made it easier to add advanced techniques on the existing code.
I am super interested in 3D graphics so I wondered if I could write a program completely from scratch that could render a 3D object. So I first made an equation that could convert a 2D coordinate into a 2D coordinate on the camera’s eye. Using this equation, I first rendered points that represented each vertex of an object. Then I made a function that could draw a line between any 2 points given, and using that, I modified the program so it could display objects in wire-frame form.
In order to display object file, I first needed to load object data in the program. So I wrote some code to load a wavefront(3D object file) file and store its vertex and triangle indices in a buffer.
Then I implemented the triangle rasterization algorithm, and wrote a function that could render a 2D triangle between any given 3 points.
When I used this function to render the faces, the object was was rendered with only single color and the curves on the surfaces were not apparent.
So, I decided to add light effects to make it more realistic. By using dot product between normal of each triangle and the difference between the position of the light source and the position of each triangle, I made an equation to calculate the brightness of each triangle.
I also implemented back-face culling to eliminate rendering triangles that face away from the camera. The images below are the result I got.
In the first picture above, there are some triangles that are rendered in the wrong order. To solve this problem I decided to sort each triangle based on their distance to the camera. I knew that it would not be accurately sort triangles since when they intersected, this method would fail. But I tried this method anyway to see that it wouldn’t work. Then I implemented the depth-buffering (z-buffering) technique which consists of recording the distance of each rendered pixel from the camera in a separate depth-buffer ,and using this buffer to only render the pixels that are closest to the camera. I implemented this and the result was magnificent without any error.
In the next step, I worked on rendering a perfect sphere without loading a sphere object. In the image below, the program calculates the normal vector at each pixel of the ball, and applies light calculations to determine the brightness of each pixel inside the ball. The light source is below the camera in this example, so the part of the ball that faces the light source is brighter.
Finally, I did lots of optimizations and the FPS rate tripled. I also tried tried using multi-threading by rendering triangles in separate threads. That increased the fps significantly, in fact it was so hard to debug, so I kept the program running at a single thread which was fast enough. It was always so easy to switch to multi-threading, so I decided to keep working using a single thread.
Wave Simulation Part
Since I am a sailor, I love the sea, and I wondered If I could render a dynamic ocean wave using this rendering engine. Then I found and article that explains how to implement a dynamic Gerstner 3D ocean wave, and I used these equations to generate a dynamic ocean wave.
I was satisfied with this result, in fact I had drawn a 3D boat in 3Ds-Max before, and I wanted to make it float on this ocean wave.
I first needed to find a way to rotate the 3D boat. But I didn’t know any linear algebra or any other tool that I could use for this purpose. I learned about the quaternion numbers and implemented quaternion rotation to rotate all the vertices of the boat.
The program calculates the slope of the wave at the position of the boat by using 3 points that define a surface that are displayed in the video. Then it rotates the boat using the slope value.
Although this method worked well, it was not realistic enough; so I decided to change the program so that it could approximate the actual bouyant force vector, and use the result to keep the boat above the water level.
Calculating the Bouyant Force
To calculate the bouyant force, I needed to search the triangles on the scene that contributed the bouyant force. In fact it would take so much CPU time to scan all the vertices of the boat and the ocean to find the submerged boat triangles. I decided to store each triangle in a different data structure so I could continue traversing to the neighbor triangles in a mesh at constant CPU time. I used a mesh network and used recursion which made it possible to efficiently scan and find the triangle that is closest to the desired point in the 3D space. I used this algorithm to scan all triangle of the boat that submerged under the wave. In the video below, boat triangles are colored to red depending on their depth under the wave. I used this method to debug the code. Then using submerge depth of each triangle, I was able to calculate the resultant bouyant force vector. Then I learned 3D torque equations and 3D moment of inertia calculations to improved the program to calculate the resultant torque vectors which I used to rotate the boat depending on the wave’s hit points. After adding some damping constants and doing some fine tuning on the equations, I managed to get the following result below.
Masking the water inside the boat:
One problem in the scene above is that the water was leaking inside the boat. I was expecting this problem since I programmed the rendering engine to only render the parts of each triangle that are closest to the camera.
To solve this problem, I drew a 3D mesh that exactly matched with the top of the boat and covered its top.
Instead of rendering this mesh as a 3D object, I changed the program to use this mesh to mask out the water pixels that are inside the boat. I got some pretty nice results:
I played with the with the parameters such as wave speed, inertia vectors , force multipliers ,and damping constants and got the following result:
I was planning to add ping-pong(smooth rendering) rendering and texture mapping features to the rendering engine by applying barycentric calculus, in fact I decided to stop at this point and work on other projects.
This project was pretty fun for me. One of the challenges about his projects was that as I added more on this project, the code got bigger and bigger. At some point it was so hard to add new code pieces to the existing ones, so I needed to clean the code, add lots of comments, and divide big code pieces into smaller functions to make the whole code more readable. In this way I was able to remember the old code after a month, so it took less time to add new features to the project.
Techniques that I learned and implemented:
- 3D rotational dynamics (3D rotational Inertia, 3D torque, 3D angular momentum)
- 3D transformation
- Triangles rasterization
- Painters algorithm as a partial solution for the visibility problem
- Z-buffering / depth buffering using ray-tracing
- Quaternion numbers for 3D rotation
- Gerstner Ocean wave simulation model
- Bouyant Force Approximation Technique
- 3D Simulation by using Euler’s method
- Importing Wavefront 3D object files
- Rendering a perfect sphere