我把即時 3D shader 放進 Game Boy Color?這篇文章教我的技術與浪漫

本篇文章更新時間: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))

運算流程:

  1. 取 log-space 資料
  2. 相加
  3. 查 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


Share:

作者: Chun

資訊愛好人士。主張「人人都該為了偷懶而進步」。期許自己成為斜槓到變進度條 100% 的年輕人。[///////////____36%_________]

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Apply Filters
Mastodon