Software rendering in 500 lines of bare C++
Building a Software Renderer: 500 Lines of Pure C++
This series of articles is designed to pull back the curtain on how modern graphics APIs—such as OpenGL, Vulkan, Metal, and DirectX—actually function. The best way to achieve this is by constructing a simplified, from-scratch clone of a rendering pipeline.
Many developers find the initial learning curve of 3D graphics APIs to be daunting. To bridge this gap, I've developed a set of short lectures that empower students to build their own functional renderers.
"I am not teaching how to write applications for the GPU; rather, I am demonstrating how those GPUs actually operate. I believe this fundamental understanding is critical for anyone wanting to write high-performance applications using 3D libraries."
The Objective
The goal is to produce a rendered image without relying on any third-party graphics libraries. We are essentially ignoring OpenGL, DirectX, and Vulkan to build the logic manually.
Project Specifications
| Metric | Detail |
|---|---|
| Total Code Volume | lines of C++ |
| Estimated Effort | hours of coding |
| Input Data | Triangulated meshes (.obj) and textures |
| Output Format | A static image file (no real-time GUI) |
The Technical Approach
To keep the project lean and minimize external dependencies, students are provided with a single helper class for managing TGA files. This format was chosen because it is one of the simplest ways to handle grayscale, RGB, and RGBA images.
At the start, the only tool available is the ability to manipulate a single pixel at coordinates .
The implementation roadmap includes:
- Implementing Bresenham’s line algorithm
- Developing triangle rasterization
- Calculating Barycentric coordinates
- Handling hidden face removal (Z-buffering)
- Creating a camera system
- Implementing shading and textures
Data Flow Pipeline
Getting Started
While I provide the full source code on GitHub (available via the complete repository and the student starter kit), I strongly advise against simply copying it. The true value lies in the struggle of implementation.
Initial Boilerplate: main.cpp
Here is the baseline code provided to students. It initializes a small framebuffer and defines a few basic colors.
#include "tgaimage.h"
// Color definitions (Note: TGA uses BGRA order)
constexpr TGAColor white = { 255, 255, 255, 255 };
constexpr TGAColor green = { 0, 255, 0, 255 };
constexpr TGAColor red = { 0, 0, 255, 255 };
constexpr TGAColor blue = { 255, 128, 64, 255 };
constexpr TGAColor yellow = { 0, 200, 255, 255 };
int main(int argc, char **argv) {
constexpr int width = 64;
constexpr int height = 64;
TGAImage framebuffer(width, height, TGAImage::RGB);
// Define three points for a triangle
int ax = 7, ay = 3;
int bx = 12, by = 37;
int cx = 62, cy = 53;
// The student will eventually implement the drawing logic here
framebuffer.write_tga_file("framebuffer.tga");
return 0;
}
The code above generates a framebuffer.tga file (shown here scaled up for visibility).
Compilation Instructions
To build the project, use the following commands in your terminal:
# Clone the repository
git clone https://github.com/ssloy/tinyrenderer.git
cd tinyrenderer
# Configure and build using CMake
cmake -Bbuild
cmake --build build -j
# Run the renderer with model files
build/tinyrenderer obj/diablo3_pose/diablo3_pose.obj obj/floor.obj
Once executed, the final result is saved directly to framebuffer.tga.