mini3d原始碼解析及功能擴充套件
阿新 • • 發佈:2018-12-12
簡介
mini3d是前網易員工@韋易笑開發的3d軟渲染引擎,總程式碼量不到1000行,短小精悍,適合初學者學習。
本文結合原始碼給出自己的理解,並在原作基礎上實現功能擴充套件:
- 補充缺少的三維變換功能(平移、縮放)
- 增加簡單光照(漫反射)
程式碼簡析
這個專案實現了一個方塊的3d渲染,主要功能點有:
- 支援平移和旋轉
- 支援三種不同狀態的顯示(線框、顏色、紋理)
下面來對程式碼做個簡析,主要是把握脈絡,不求深入細節。
先看主函式,第一行定義了型別為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_t 的texture 變數中) |
隨後進入主迴圈,每次迴圈會依次做如下操作:
device_clear
:清空framebuffer
和z-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程式設計
不清楚的部分需要查詢計算機圖形學書籍或相關資料。
實現這麼個軟渲染,最大意義在於理論聯絡實際,將理論知識真正落地。麻雀雖小,五臟俱全。以後還可以繼續擴充套件更多的功能。