用 500 行純 C++ 實現軟體渲染

在本系列文章中,我打算透過從零開始撰寫簡化版克隆,來展示 OpenGL、Vulkan、Metal 與 DirectX 的運作原理。 許多人在學習 3D 圖形 API 的入門關卡時常常卡住。 為了解決這個問題,我準備了一小系列的講座,修完後我的學生就能做出相當實用的渲染器。

任務如下:不使用任何第三方函式庫(尤其是與圖形相關的),我們將產生如下圖所示的影像:

警告:這份教材大致遵循現代 3D 圖形函式庫的結構。 它是一個軟體渲染器我無意展示如何撰寫 GPU 應用程式——我想要展示它們是如何運作的。 我堅信理解這些原理對於撰寫高效能的 3D 函式庫應用程式至關重要。

起點

最終的程式碼約有 500 行。 我的學生通常需要 10 到 20 小時的程式設計時間,才能開始做出這樣的渲染器。 輸入是一個由三角網格與紋理組成的 3D 模型。 輸出是一張渲染圖。 程式沒有圖形介面,只會產生一張影像。

為了盡量減少外部依賴,我提供給學生一個單一類別,用來處理 TGA 檔案——這是最簡單的支援 RGB、RGBA 與灰階影像的格式之一。 這將成為我們操作影像的基礎。 一開始,唯一可用的功能(除了載入與儲存影像)就是設定單一像素的顏色。

沒有內建的函式來繪製線段或三角形——我們將全部手動實作。 雖然我提供了我與學生一同撰寫的原始碼,但我並不建議直接使用它,因為親自完成才能真正理解這些概念。 完整的程式碼可在 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;
}

它會產生 64x64 的影像 framebuffer.tga,這裡我已放大以利閱讀:

編譯

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
渲染後的影像會儲存為 framebuffer.tga

預告:使用此渲染器製作的幾個範例


留言