1. 程式人生 > >mini3d原始碼解析及功能擴充套件

mini3d原始碼解析及功能擴充套件

簡介

mini3d是前網易員工@韋易笑開發的3d軟渲染引擎,總程式碼量不到1000行,短小精悍,適合初學者學習。

本文結合原始碼給出自己的理解,並在原作基礎上實現功能擴充套件:

  1. 補充缺少的三維變換功能(平移、縮放)
  2. 增加簡單光照(漫反射)

程式碼簡析

這個專案實現了一個方塊的3d渲染,主要功能點有:

  1. 支援平移和旋轉
  2. 支援三種不同狀態的顯示(線框、顏色、紋理) 在這裡插入圖片描述

下面來對程式碼做個簡析,主要是把握脈絡,不求深入細節。

先看主函式,第一行定義了型別為device_t的變數device。 結構體device_t包含渲染方塊用到的所有引數和資料結構:

typedef struct {
	transform_t transform;
// 座標變換器 int width; // 視窗寬度 int height; // 視窗高度 IUINT32 **framebuffer; // 畫素快取:framebuffer[y] 代表第 y行 float **zbuffer; // 深度快取:zbuffer[y] 為第 y行指標 IUINT32 **texture; // 紋理:同樣是每行索引 int tex_width; // 紋理寬度 int tex_height; // 紋理高度
float max_u; // 紋理最大寬度:tex_width - 1 float max_v; // 紋理最大高度:tex_height - 1 int render_state; // 渲染狀態 IUINT32 background; // 背景顏色 IUINT32 foreground; // 線框顏色 } device_t;

其後完成一系列初始化:

函式 作用
screen_init 初始化螢幕(windows上顯示視窗所必需)
device_init 初始化裝置(為device_t
中的成員變數賦初值)
camera_at_zero 初始化相機位置,建立相機座標系
init_texture 初始化紋理(藍白相間圖案,存在device_ttexture變數中)

隨後進入主迴圈,每次迴圈會依次做如下操作:

  • device_clear:清空framebufferz-buffer
  • camera_at_zero:重設相機位置。
  • 監聽鍵盤事件,修改旋轉角度alpha和相機x座標的值,以及切換render_state
  • draw_box:畫方塊,具體來說是從方塊 -> 平面 -> 三角形 -> 線段 -> 畫素,一層層深入;當然這個過程中還包括三維觀察座標變換,以及按z-buffer處理遮擋。最後在framebuffer中為每個畫素點設定顏色。
  • screen_update:呼叫windows api將framebuffer中的畫素顯示到螢幕上。

功能擴充套件

以上只是概述。話說要深入理解一段程式碼,最好的辦法還是動手改點東西。

我們來看看有哪些新feature是可以新增進去的。

  • 原作只支援旋轉和前後平移(其實是相機的平移,而非方塊),我們很容易想到可以完善三維變換的所有功能:平移(包括六個方向)、旋轉、縮放。
  • 現在的方塊無論從哪個角度看都一個樣,原因是缺乏光照。我們可以新增簡單的光照,如漫反射和鏡面反射。

完善三維變換功能

新增三維變換的關鍵是:在三維觀察座標變換的流水線中,找到合適的地方對頂點做三維變換。

我們先處理縮放和旋轉,通過改寫draw_box:

    void draw_box(device_t *device, float alpha, float scale) {
    	matrix_t m1;
    	matrix_set_scale(&m1, scale, scale, scale);
    	matrix_t m2;
    	matrix_set_rotate(&m2, -1, -0.5, 1, alpha);
    	matrix_t m;
    	matrix_mul(&m, &m1, &m2);
    	device->transform.world = m;

再在transform_apply中處理平移, 插入點是在完成相機座標系的變換之後:

void transform_apply(const transform_t *ts, vector_t *y, const vector_t *x, vector_t *_3d_transform) {
	vector_t t1;
	vector_t t2;
	vector_t t3;
	matrix_apply(&t1, x, &ts->world);
	matrix_apply(&t2, &t1, &ts->view);
	vector_add(&t3, &t2, _3d_transform);
	matrix_apply(y, &t3, &ts->projection);
}

增加簡單光照

這裡實現的是簡單的漫反射。

根據漫反射的實現原理,物體上的頂點在光照下的顏色取決於以下因素:

  • 材質顏色
  • 光的顏色
  • 光的入射方向
  • 頂點所在平面的法向量

按照這些因素一個個來實現。

先定義光源:

// 平行光源
typedef struct {
	color_t color;				// 顏色
	vector_t direction;			// 方向
} light_t;

再根據平面上三點計算法向量:

// 計算平面法向量
void calc_plane_normal(const transform_t *ts, vector_t *normal, const vector_t *x, const vector_t *y, const vector_t *z) {
	vector_t wx;
	vector_t wy;
	vector_t wz;
	vector_t xy;
	vector_t yz;
	matrix_apply(&wx, x, &ts->world);
	matrix_apply(&wy, y, &ts->world);
	matrix_apply(&wz, z, &ts->world);
	vector_sub(&xy, &wy, &wx);
	vector_sub(&yz, &wz, &wy);
	vector_crossproduct(normal, &xy, &yz);
	vector_normalize(normal);
}

然後將法向量傳入device_draw_scanline,插入下面一段程式碼,真正實現光照對顏色的影響:

// 處理光照
float dot = vector_dotproduct(&device->light.direction, normal);
if (dot < 0)
	dot = -dot;
b *= dot * device->light.color.b;
g *= dot * device->light.color.g;
r *= dot * device->light.color.r;

最後實現的效果如圖(迎著攝像機的一面因光線影響較亮): 在這裡插入圖片描述

程式碼提交到github了,可供參考。

小結

閱讀mini3d程式碼所需基礎要求:

  • 理解三維觀察座標變換流水線
  • 理解z-buffer的用法
  • 理解三維變換(平移、旋轉、縮放)
  • 瞭解windows程式設計

不清楚的部分需要查詢計算機圖形學書籍或相關資料。

實現這麼個軟渲染,最大意義在於理論聯絡實際,將理論知識真正落地。麻雀雖小,五臟俱全。以後還可以繼續擴充套件更多的功能。