Software Rasterizer

A C++ software rasterizer. This was a great way to learn how GPUs render triangle meshes to the screen.


Project summary

This project is a C++ software rasterizer. The goal of this project is to get a better understanding of the things a GPU does under the hood to render triangles to the screen. I am still working on this project, but it has been put on hold for a short while because I have to focus on university.

Most important things I learned

  • How to calculate whether a point lies on a triangle or not.
  • What needs to be done each frame to rasterize an entire scene.
  • Depth buffer generation.
  • Why vertex attributes are interpolated along edges.

Role:Graphics Programmer
When:2019
Duration:2 weeks
Skills:C++
Software:Git, CMake, Visual Studio
Team size:1

Writing a C++ software rasterizer

I have been using various graphics APIs for the last few years now, but the one thing I never had to do was rasterize triangles. The actual rasterization of the triangles is usually handled by the GPU driver, so there is no need for a graphics programmer to do such low-level calculations. Not even in an API such as DX12 or Vulkan.

To learn more about the rasterizer itself, I decided to start working on my own rasterizer. It is an extremely slow rasterizer, but that is fine. I learned a lot about rasterization and the things GPUs have to do after I execute a draw command in my renderer.

The goal of the project

My main goal was to write a software rasterizer capable of rendering simple meshes. I initially did not want to support shaders, vertex attributes, and so on. However, as the project progressed, I found out that it would be a great exercise to implement those features as well.

The current state of the rasterizer

As of right now, I have to focus on university again. But once I have some more spare time, I will continue with this progress and move on from these boring cubes to full meshes such as Sponza.

The renderer already supports indexed meshes, but I still have to add a lot of additional features like vertex attributes, more efficient rasterization, and model loading.

Summary of my tasks

Rasterize triangles

The most important task I had to do was the actual rasterization of triangles. I may write a blog post about this some time in the future, but all you need to know is that I looped through all vertices of a triangle and calculated which pixels overlapped the surface area of the 3D triangle in 2D space.

Image from: scratchapixel.com, not my own!

Render images to the screen

While I always enjoy writing renderers, I did not feel like using OpenGL just to render a single texture to the screen. Even though it is only a couple hundred lines of code to render an image, I did not consider this to be worth the effort. Instead, I used the awesome SFML library to handle the window creation and rendering for me. This made my life a lot easier and allowed me to focus on important things instead.

Achievements

Rendering 3D cubes

One of the very first things I did was to render a triangle. This was cool, but it was not difficult at all. Once I got cubes working properly, I really felt like I made some solid progress. This is why I consider the rendering of 3D cubes to be a real achievement of mine.

Programmable vertex shaders

I am really happy with my implementation of vertex shaders. It is not the most beautiful code you will ever see, but it works well.

What I did was I created a function that could take in any number of parameters of any type. Then, it is up to the user to pass the correct arguments in the right order when calling the vertex shader. This works very much like a regular vertex shader.

// Additional arguments used in this vertex shader:
// 0: model matrix
// 1: projection-view matrix
template<typename... Args>
inline glm::vec4 VertexShader(const glm::vec3& vertex, Args&&... args)
{
	auto input = std::make_tuple(std::forward<Args>(args)...);

	// Retrieve the vertex shader data
	const auto& model_matrix	= std::get<0>(input);
	const auto& pv_matrix		= std::get<1>(input);

	glm::vec4 vertex_output = pv_matrix * model_matrix * glm::vec4(vertex, 1.0);

	return vertex_output;
}

Source code: https://github.com/tntmeijs/Rasterizer-From-Scratch

Leave a Reply

Your email address will not be published. Required fields are marked *