LogoLogo
Cosmin-Ștefan

Rendeer
Rendeer
Rendeer is a rasterizer I developed in 2019 on a quest to learn how 3D rendering works.
Features#
  • Imports a mesh in Wavefront .obj format
  • Can render the mesh with flat shading, Gouraud shading, or as a wireframe
  • Allows you to animate the mesh and render out the frames.
Development#
The first step towards rasterization was projecting the 3D vertices of a model onto a 2D plane called the screen space.
Cube projection
Figure 1. Projection of a cube from the front.
Cube from an angle
Figure 2. Same cube viewed from an angle.
To reduce unnecessary computation later on, I applied a process known as back-face culling to discard all triangles facing away from the camera, as they are normally not visible. This is achieved by taking the dot product between the surface normal of each triangle and the camera-to-triangle vector, as described here in detail.
Wireframe cube with back-face culling
Figure 3. Triangulated wireframe cube with back-face culling.
Solid icosphere
Figure 4. Solid icosphere.
For illumination I used a simple directional light source, which looks natural on small models and is straightforward to implement. The color of any surface is thus given by the angle between the surface normal and the (fixed) direction of the light, as illustrated in Figure 5.
Directional lighting
Figure 5. Directional light cast on a sphere. Areas facing the direction of the light are better lit than others.
With this addition, the true depth of the icosphere model immediately became apparent.
Lit icosphere
Figure 6. Let there be light! Face normals visible to the naked eye.
It was time to bring other objects into the spotlight, so I wrote a tool to read arbitrary models from .obj files. The Wavefront format is very simple. Snippet 1 displays an excerpt from an .obj defining a cube.
v 0.000000 2.000000 2.000000
v 0.000000 0.000000 2.000000
v 2.000000 0.000000 2.000000
(...)
f 1 2 3 4
f 8 7 6 5
f 4 3 7 8
(...)
Lines starting with v define a vertex and specify its coordinates, while those starting with f designate the vertices that make up a face. These two elements were sufficient for my purpose, although the format allows plenty of additional properties.
Figure 7 shows two models imported with this tool and rendered in different colors.
Two imported models
Figure 7. A strawberry (left) and a higher-face count icosphere (right).
As you can see, each triangle is filled with a single color. This technique is known as flat shading, and it does depict the geometry of the model accurately, but for smooth surfaces it is unacceptable. So I went on to implement Gouraud shading, which involves computing the light intensity for each polygon vertex based on its normal and then interpolating vertex intensities bilinearly over each polygon's surface.
Smooth torus
Figure 8. Smooth torus (Gouraud shading).
Once basic rendering capabilities were done, I created a regular language – and thoroughly documented it here – for describing animations to be rendered. With this new feature I could now create mind-boggling effects like the ones below.
Abstract entity spinning
Figure 9. Abstract entity spinning.
Icosphere in motion
Figure 10. Icosphere in motion.
Cube wandering about
Figure 11. Cube wandering about.
Future directions#
In 2020 I set 3D rendering aside and moved on to study other topics in computer science, so I have not developed this project since. Here are a couple more features I worked on, although they never saw the light of a stable release.
Complex scenes#
Most 3D scenes worth rendering contain a wealth of objects. For this reason, I made it possible to import multiple meshes with different properties into the same scene as an experiment. Sadly, this required significant changes in architecture and the resulting code was way too messy to publish.
Many spheres
Figure 12. Many spheres going against one another.
Cube and sphere
Figure 13. Cube and sphere.
Depth of field#
I achieved depth of field blur by simply rendering a depth map of the scene and using it to interpolate between the original render and a blurred version thereof – a somewhat clever trick that's unfortunately not very convincing up close.
Depth of field
Figure 14. Narrow depth of field on a swarm of icospheres.
Lighting improvements#
Handling multiple sources of light is tricky when no ray tracing technique is used, so a single render came out remotely believable with naïve color averaging before I abandoned this idea.
I attempted to add specular highlights as well, but this came to nothing because Gouraud shading ruined the effect.
Many light sources
Figure 15. Multiple directional lights being cast on a sphere.
Specular highlight
Figure 16. Specular highlight fail.
3D Graphing#
I also made it possible to plot the graph of a real-valued function of (x,y)\left(x, y\right) coordinates. This one turned out well, but it was too simplistic to be useful.
Wavy surface
Figure 17. Wavy plot of f(x,y)=sin(x)sin(y)f(x, y) = \sin{(x)} \cdot \sin{(y)}.
Surface with ripples
Figure 18. Sine of (distance from center plus time) with bonus glitch in the bottom right corner.
Conclusion#
The rippling surface in Figure 18 was the culmination of my efforts to create something pretty with Rendeer. Once that animation completed rendering, it was time to take a break and work on something else.
Writing a renderer was highly instructive for me and I encourage everyone to try someday. Consider checking out a tutorial on rasterizers, like tinyrenderer, or one about ray tracers, which are more visually accurate (and more exciting). Shirley, Black, and Hollasch's Ray Tracing in One Weekend in particular is a great resource.
Share this