Software rendering in 500 lines of bare C++

Software rendering in 500 lines of bare C++

用 500 行纯 C++ 代码实现软件渲染

In this series of articles, I aim to demonstrate how OpenGL, Vulkan, Metal, and DirectX work by writing a simplified clone from scratch. Surprisingly, many people struggle with the initial hurdle of learning a 3D graphics API. To help with this, I have prepared a short series of lectures, after which my students are able to produce quite capable renderers. The task is as follows: using no third-party libraries (especially graphics-related ones), we will generate an image like this:

在本系列文章中,我旨在通过从零开始编写一个简化版的克隆程序,来演示 OpenGL、Vulkan、Metal 和 DirectX 的工作原理。令人惊讶的是,许多人在学习 3D 图形 API 的初期阶段都会感到困难。为了解决这个问题,我准备了一系列简短的课程,学生在学习后能够制作出功能相当强大的渲染器。任务如下:在不使用任何第三方库(尤其是与图形相关的库)的情况下,我们将生成一张像这样的图像:

Warning: This is a training material that loosely follows the structure of modern 3D graphics libraries. It is a software renderer. I do not intend to show how to write GPU applications — I want to show how they work. I firmly believe that understanding this is essential for writing efficient applications using 3D libraries.

警告:这是一份大致遵循现代 3D 图形库结构的培训材料。它是一个软件渲染器。我无意展示如何编写 GPU 应用程序,而是想展示它们是如何工作的。我坚信,理解这一点对于使用 3D 库编写高效应用程序至关重要。

The starting point: The final code consists of about 500 lines. My students typically require 10 to 20 hours of programming to start producing such renderers. The input is a 3D model composed of a triangulated mesh and textures. The output is a rendering. There is no graphical interface, the program simply generates an image.

起点:最终代码大约有 500 行。我的学生通常需要 10 到 20 个小时的编程时间才能开始制作此类渲染器。输入是一个由三角网格和纹理组成的 3D 模型。输出是一张渲染图。程序没有图形界面,只是简单地生成一张图像。

To minimize external dependencies, I provide my students with a single class for handling TGA files — one of the simplest formats supporting RGB, RGBA, and grayscale images. This serves as our foundation for image manipulation. At the beginning, the only available functionality (besides loading and saving images) is the ability to set the color of a single pixel. There are no built-in functions for drawing line segments or triangles — we will implement all of this manually.

为了最大限度地减少外部依赖,我为学生提供了一个用于处理 TGA 文件的单一类——这是支持 RGB、RGBA 和灰度图像的最简单格式之一。这构成了我们图像处理的基础。起初,除了加载和保存图像外,唯一可用的功能就是设置单个像素颜色的能力。没有用于绘制线段或三角形的内置函数——我们将手动实现所有这些功能。

While I provide my own source code, written alongside my students, I do not recommend using it directly, as doing the work yourself is essential to understanding the concepts. The complete code is available on github, and you can find the initial source code I provide to my students here. Behold, here is the starting point:

虽然我提供了与学生一起编写的源代码,但我并不建议直接使用它,因为亲自动手实践对于理解这些概念至关重要。完整的代码可以在 GitHub 上找到,你可以在此处找到我提供给学生的初始源代码。看,这就是起点:

// main.cpp
#include "tgaimage.h"

constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order
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);
    int ax = 7, ay = 3;
    int bx = 12, by = 37;
    int cx = 62, cy = 53;
    framebuffer.set(ax, ay, white);
    framebuffer.set(bx, by, white);
    framebuffer.set(cx, cy, white);
    framebuffer.write_tga_file("framebuffer.tga");
    return 0;
}

It produces the 64x64 image framebuffer.tga, here I scaled it for better readability:

它会生成 64x64 的图像 framebuffer.tga,为了方便阅读,我将其进行了缩放:

Compilation: 编译:

git clone https://github.com/ssloy/tinyrenderer.git && cd tinyrenderer && cmake -Bbuild && cmake --build build -j && build/tinyrenderer obj/diablo3_pose/diablo3_pose.obj obj/floor.obj

The rendered image is saved to framebuffer.tga. Teaser: few examples made with the renderer.

渲染后的图像将保存为 framebuffer.tga。预告:一些使用该渲染器制作的示例。