在 Brainfuck 的紙帶上手刻光線追蹤器
epestr.com(Austin Shijo)· 2026-09-24
Brainfuck 只有8個指令、沒有函式、也沒有浮點數,作者 Austin Shijo 卻靠它刻出一個會計算光線反射與金屬球陰影的光線追蹤器。他把《Ray Tracing in One Weekend》裡的 C 語言光線追蹤器,透過自製的中介語言一路編譯成純 Brainfuck 原始碼。整支程式膨脹到 23MB,跑起來比蝸牛還慢,卻真的能吐出一張 PNG 圖片。
原本的問題
Brainfuck 的記憶體模型只有一條無限延伸、由8位元儲存格組成的紙帶,程式能用的只有搬移指標、儲存格加一減一、輸出入,以及一對方括號迴圈,全部加起來就是那8個指令。光線追蹤需要處理球心座標、方向向量、反射角度這些連續數值,但紙帶上每一格只能裝 0 到 255 的整數,語言裡完全沒有浮點型別。更麻煩的是,Brainfuck 沒有函式、沒有呼叫堆疊、也沒有遞迴,任何「先算光線打到哪顆球、再算反射光」這種需要重複呼叫同一段邏輯的流程,都得攤平成單一長串指令。
採用的方法
為了裝下小數,作者採用Q16.16定點數格式,把每個數值切成高位存整數部分、低位存小數部分,換算下來解析度是1/2^16,可表示範圍落在[-2^15, 2^15)之間。乘法靠多個儲存格兩兩相乘、再依位權相加來實作,也就是把進位邏輯拆成一格一格的加法迴圈,算完後再靠右移捨去最低位;除法則反過來,用長除法的方式逐格相減、保留餘數往下一位借位。
至於函式與遞迴的缺口,作者沒有在 Brainfuck 裡手刻呼叫堆疊,而是把整條管線倒過來做:先用 C 語言寫出光線追蹤邏輯,經過自製的中介語言攤平成一長串沒有跳躍、只剩基本運算的指令,最後由編譯腳本把每一步翻譯成對應的 Brainfuck 片段。這樣一來,原本需要遞迴呼叫的「打到球面、算反射、再射一次」流程,全被展開成同一份程式裡依序執行的指令區塊。比較運算靠兩個儲存格同步遞減、看誰先歸零來判斷大小,隨機數則用線性同餘公式 A = (5*A + 1) % 256 頂替。
// 搬移:把目前儲存格的值整批加到右邊那格,自己歸零
// 等同於高階語言的 b += a; a = 0;
[->+<]
// 複製:把值同時加到右邊兩格,經典的「搬移+複製」技巧
// 等同於 b += a; c += a; a = 0;
[->+>+<<]實際效果
編譯出來的 Brainfuck 原始碼膨脹到 23MB,渲染速度大約是每分鐘 100 條光線,換算成畫面大概每分鐘只擠得出 1 個像素。要跑完整張 400×225、內容是金屬球加漫射地面的場景,作者估計得花上約 62.5 天,最後輸出的是一張標準 PNG 圖片。這些數字都是作者自己在文章裡算出來的,用來說明「能動」和「好用」之間的距離有多遠。
原始來源:epestr.com — I wrote a ray tracer in Brainfuck;原始碼:github.com/mTvare6/rayfuck