- ゲームボーイカラーでリアルタイム3Dシェーディングを実現したプロジェクトで、プレイヤーは光の軌道を操作しながら物体を回転させられる
- 正規化ベクトルとランバートシェーディング(dot product) の計算を基に、球面座標系を用いて演算を単純化している
- 乗算命令を持たないSM83 CPUの制約を克服するため、対数変換とルックアップテーブルを活用し、8ビット精度で演算を実行
- 自己書き換えコード(self-modifying code) を使って約10%の性能向上を達成し、1フレームあたり15枚のタイルをレンダリング
- AIを活用したコード生成はほとんど失敗し、コアアルゴリズムとシェーダは自ら手書きしたコードで完成させた
プロジェクト概要
- ゲームボーイカラーでリアルタイムに画像をレンダリングするゲームを制作
- プレイヤーは軌道状の光を操作しながら物体を回転させる
- 全コードはGitHubリポジトリ(nukep/gbshader)で公開されている
3D制作プロセス
- Blenderを使って初期のルックデブ(lookdev)を行い、視覚的に満足のいく結果が得られたためプロジェクトを進行
- Cryptomatteとカスタムシェーダを用いてノーマルマップ(normal map) を生成
- ティーポット(teapot)モデルはカメラを回転させてPNGシーケンスとしてノーマルマップを出力
- ゲームボーイカラー本体モデルの画面部分は別シーンでレンダリングした後に合成
数学的基盤
- ノーマルマップは各ピクセルの法線ベクトルをエンコードするベクトル場として使う
- ランバートシェーディングは
v = N·L 形式の内積(dot product)で計算
- 球面座標系に変換して
v = sinNθ sinLθ cos(Nφ−Lφ) + cosNθ cosLθ の形に単純化
- すべてのベクトルの半径を r=1 と仮定して演算量を削減
ゲームボーイでの実装
- Lθ(光の縦角) を定数に固定し、Lφ(光の回転角) のみをプレイヤーが操作
- ROMには各ピクセルを
(Nφ, log(m), b) 形式で保存
- 乗算命令の欠如を解決するため、対数変換とルックアップテーブル(
log, pow)を使用
- 符号(sign)ビットを上位ビットに保存して負数演算に対応
- すべてのスカラー値は**-1.0〜+1.0範囲の8ビット小数**として表現
- 加算は線形空間で、乗算は対数空間で実行
- 分母に127を使うことで ±1 の両方を表現可能にしている
cos_logと中核演算
cos_log は log(cos x) 形式の結合ルックアップで、乗算を対数加算に置き換える
- 1ピクセルあたりの演算量
- 減算1回、
cos_log参照1回、加算1回、pow参照1回、加算1回
- 合計で加算/減算3回、ルックアップ2回を実行
性能
- 1フレームあたり15枚のタイルを処理し、一部の空行はより高速に計算できる
- 1ピクセルあたり約130サイクル、空行は3サイクルで処理
- CPUの約89% がシェーダ演算に使われ、残りは入力およびI/O処理に使われる
自己書き換えコード(Self-Modifying Code)
- 1フレームあたり約960ピクセルを処理する中核ループを最適化するため、命令そのものを書き換える手法を採用
- 定数を直接コードに埋め込むことで、変数ロードより高速な演算を実行
- 例:
sub a, 8 は sub a, variable より12サイクル速い
- 全体で約11,520サイクル(10%)削減
AI活用の試み
- プロジェクト全体の95%は手作業で書かれた
- AIはGame Boyアセンブリ(SM83) の記述に苦戦した
- AIの利用内容
- Python: OpenEXRレイヤーの読み取り
- Blender: シーン自動化スクリプト
- SM83: 一部機能のスニペット(例: VRAM DMA)
- 失敗した試み
- AIでシェーダのアセンブリコード生成を試みたが、非効率でエラーも多かった
- Claude Sonnet 4モデルを使い、疑似コードからアセンブリ生成も試行
- 一部は動作したが遅く、Z80とSM83を混同するなどの誤りが発生
- 最終コードは手作業で全面的に書き直した
結論と教訓
- AIは単純なスクリプトには有用だが、正確性と検証が必須
- OpenEXR処理コードではAIがチャンネル整列エラー(BGR vs RGB) を引き起こし、数週間にわたるバグの原因となった
- 経験を通じて「AIを使う際は検証が最も重要」という教訓を強調
- このプロジェクトはレガシーハードウェアの限界を突破した実験的シェーダ実装の事例として評価される
1件のコメント
Hacker Newsのコメント
HNで本当にハッカー魂のある記事を見られてうれしい
成果物が本当に素晴らしい。私の理解では、これは「3Dのように見えるが、実際には2Dノーマルマップを事前レンダリングしてライティング効果を加えたシェーダー」だ
フレームはこのGitHubリンクにある
3D三角形処理の部分は単純に保ち、高価なライティングシェーダーは2D画像上で一度だけ実行するので効率的だ
シェーダーの観点で入力が3Dベクトルなら、それは3Dシェーダーだ。3Dラスタライザーがあるかどうかは別問題だ
現代の3Dゲームもこうした方式をさまざまに活用している。複数視点から事前レンダリングしたモデルを使うimposter手法も正式な3Dエンジンで使われる技術だ
ただし今回はそれがGame Boy Colorで動く点が驚きだ
こんにちは、作者です。ここに記事が上がったと聞いてアカウントを作りました。共有してくれてありがとう
環境マップを使ってさらに単純化する実験も進めており、Bskyで共有したリンクで見られます
本当に興味深いプロジェクトだ。昔C64のアセンブリコーディングをしていたころを思い出す。
当時も乗算命令がなかったので、ハードウェア制約を回避する創造的な方法を見つけなければならなかった
AIを使ってみようとした試みだったが、結果的には失敗した実験だった。
業界がAIの話で騒がしいので自分でも体験してみたく、生成AIの使用有無を透明に公開することが重要だと考えている。
隠せば信頼を損ない、公開すれば異なる意見を持つ人たちとも開かれた対話ができる
ただこの過程を記録したかっただけだ
このGBCシェーダーは、「すべての計算は制約の中での近似値だ」という真理を示している。
乗算はテーブル参照と加算で置き換えられ、精度は目で見える結果に合わせて調整される
本当に感嘆する。特にこれが実際のGame Boy Colorハードウェアで動く点に驚く。
しばしばカートリッジに強力なプロセッサを入れてGBCを単なる端末として使うことが多いが、これはそういうハックではない
正直、NintendoにはGBCやGBAを再発売してほしい。
いくつかのゲームを内蔵したカートリッジ形式で売ってくれるならすぐ買うつもりだ
ただ最近は、同じフォームファクタのAndroid携帯ゲーム機のほうが実用的だ。
私もGame Boyのコレクションがあるが、最近はエミュレーターのほうがずっと楽だ
Nintendoが新しく作るとしても、これほど良くはならない気がする
こういう記事こそHNが存在する理由だ。
昔技術雑誌をめくっていたころの楽しさを思い出させてくれる
この作者は良い意味で狂気の天才だ