本篇文章更新時間:2026/02/09
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持 。
內容目錄
在 GBC 上跑即時 3D shader?作者示範了什麼叫做真正的「硬體極限藝術」
編輯前言:這篇文章來自 I put a real-time 3D shader on the Game Boy Color。作者展示了一個令人下巴掉下來的成果:在硬體效能極度有限的 Game Boy Color 上,用 normal map、球座標、log–pow 近似法與自我修改程式碼,打造出即時 3D 光影效果。對復古硬體、圖學或效能極限優化有興趣的人,非常值得細讀。
核心觀點 (Key Takeaways)
- 作者成功在 GBC 上實作近似 Lambert shading 的即時光影,關鍵在於 normal map + 球座標 + log-space 乘法技巧。
- GBC 沒有乘法指令與浮點運算,因此作者改用 logarithm + lookup table,將乘法改寫成加法。
- 整個 shader 使用「自我修改程式碼」來加速內圈運算——在 GBC 這種等級的硬體上依然有效。
深入解析
作者從 Blender 開始實驗是否能在 Game Boy Color(以下簡稱 GBC)上呈現「看起來像 3D」的 shading。GBC 的限制非常嚴苛:
- 無浮點
- 無乘法指令
- 8-bit CPU(SM83)
- 單幀可用的 CPU 時間很有限
因此,整篇文章的核心就是:如何把現代 3D shading 的概念,壓縮成 GBC 能夠在每幀跑得動的等級?
Normal Maps 是基礎
文中展示了以 Blender 匯出 normal map 動畫序列,每個 frame 代表物體在某個角度的法線方向。作者把 normal map 變成 GBC ROM 的資料格式,讓每個像素都能運算光影。
作者寫道:
“Normal map images are secretly a vector field.”
這句話點出整個 shader 的本質:pixel-by-pixel 的法線向量存取與計算。
用球座標加速 dot product
傳統 Lambert 光照需要做 N · L。正常來說需要三個分量相乘再相加,對 GBC 來說太重。作者改用球座標表示法線與光源,讓計算簡化為:
v = sin Nθ sin Lθ cos(Nφ − Lφ) + cos Nθ cos Lθ
更巧妙的是:作者把 Lθ 固定,讓很多項變成常數,可預先編碼到 ROM 中。這樣每像素運算就簡化成 m cos(…) + b。
GBC 無乘法 → 用 log space + lookup table 取代
這部分是整篇文章最精彩也最工程味的段落。
作者指出:
The Game Boy has no multiply instruction.
於是把所有 scalar 值都以「log space」(含 sign bit)儲存,讓乘法能這樣算:
- x × y = pow(log(x) + log(y))
運算流程:
- 取 log-space 資料
- 相加
- 查 pow table
因此:
- 加法用 linear space
- 乘法用 log space
全靠查表與加法完成。
作者也展示了 staircase 式的誤差分布,並坦誠這非常 lossy,但「夠用就好」。
cos_log:因為 shading 需要 cos()×m
shader 的核心公式有 cos(Nφ − Lφ),而這個 cos 一定會跟 m 相乘。所以作者預先把 cos() 也轉成 log space。
他寫得很白:
It’s basically a combined log(cos x). This exists because in practice, cosine is always used with a multiplication.
執行效能:每幀吃掉 89% CPU 時間
作者算過:處理 15 tiles / frame 要花掉 123,972 cycles,約 89% 的 GBC 單幀可用運算量。剩下 10% 左右才是遊戲邏輯、IO 等。
自我修改程式碼:只有在這種硬體上才會看到的優化
為了讓 shader loop 更快,作者直接在 RAM 裡 patch opcode。例如把 sub a, 0 改成 sub a, 8。原因是:
在 SM83 上,sub a, immediate 比讀取變數再 sub 更快。
這種技巧幫 shader 省下約 10% 執行時間。
筆者心得與啟發
讀完這篇文章,我最大的感受是:這不是單純的效能優化,而是一種深刻理解硬體本質後的「極限創作」。
我特別喜歡作者不斷揭露背後的思考:為什麼用 normal map?為什麼用 log space?為什麼要修改指令?每件事情都不是炫技,而是因為他真正理解「硬體的代價」。
同時,文末談到他嘗試用 AI 產生 SM83 assembly,但最終只能用在小段落——這段反而很真實,也呼應整個計畫的精神:
- AI 可以協助,但難取代對系統底層的理解。
- 效能極限工作靠的不只是語法,而是整體架構與取捨。
如果你正在做任何形式的低階開發、圖形程式、或 retro hardware hack,這篇文章絕對會讓你重新思考「什麼叫做真正的優化」。
文章原文: I put a real-time 3D shader on the Game Boy Color
