
2026/02/09 1:28
ゲームボーイカラーにリアルタイム3Dシェーダーを組み込みました。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
(主要ポイントをすべて取り入れた)**
この記事は、作者がリアルタイムのGame Boy Colorデモを構築し、通常は機器の制限されたCPUでは過剰な負荷になるようなノーマルマップシェーディングで軌道上に光と回転オブジェクトを描画する方法について説明しています。
主要な技術的詳細は次の通りです:
-
シェーダー計算 – ランバート積(Lambertian dot product)を使用しますが、GBでは球面座標版を採用しています:
(v = \sin N_{\theta},\sin L_{\theta},\cos(N_{\phi}-L_{\phi}) + \cos N_{\theta},\cos L_{\theta})。
ここで (L_{\theta}) は固定され、プレイヤーは (L_{\phi}) を制御するため、ピクセルごとの計算量が減少します。 -
ノーマルの格納 – ノーマルは3バイトタプル
にパックされています。(Nφ, log(m), b)
はハードウェアサポートなしで乗算を可能にします。log(m) -
乗算トリック – ルックアップテーブルを使用:
、符号はx·y = pow[log(x)+log(y)]
のMSB によって処理され、XOR トグルで結果が8ビット分数範囲内に収まるよう調整します。log -
パフォーマンス – 各ピクセルは約3回の加算/減算と2回のルックアップを必要とします。シェーダーはフレームあたり少なくとも15タイル(60 fps の CPU バジェットの約89 %)を処理します。セルフモディファイングコード(
をメモリからロードする代わりに使用)はピクセルあたり約12サイクルを節約し、実行時間を約10 %改善します。sub a,8 -
ノーマルマップエクスポート – ノーマルは Blender で生成されます:ティーポット AOV 用の PNG シーケンスと Game Boy シーン用にハードコードされた暗号マッテ(cryptomattes)カラーを使用し、異なるレイアウトがパフォーマンスに与える影響を示しています。
ソースコードと ROM は GitHub (
https://github.com/nukep/gbshader) にホストされています。このプロジェクトは、制約のあるハードウェア上で高度なシェーディングが実現可能であることを示し、レトロゲーム愛好家や低消費電力デバイス開発者にとって学習ツールとなります。将来の作業では、シェーダーのさらなる最適化やより複雑なシーンへの拡張が考えられますが、現在の焦点は既存の制約内での実現可能性を証明することにあります。
本文
デモンストレーション
私はリアルタイムで画像を描画する Game Boy Color 用ゲームを作成しました。
プレイヤーは軌道上の光を操作し、オブジェクトを回転させます。
- ここから遊べる
- コードと ROM を確認・ダウンロード: https://github.com/nukep/gbshader
3D ワークフロー
初期レイアウト調整
本プロジェクトに着手する前、Blender 上で見た目を試してみました。
私の判断では十分に良く見えたので、そのまま進めることにしました!
ブレンダー・モンキーの各ノーマルに小さなランダムベクトルを足し、“擬似ディザリング” を実現しています。
Blender → ノーマルマップワークフロー
TL;DR: Cryptomatte とカスタムシェーダでノーマルマップを調整します。
ノーマルマップ作成に使うソフトウェアは重要ではありませんでした。Blender が最も手軽だったからです。
- Teapot(テープト): ただテープトを入れ、カメラを回転させて正規化された AOV を PNG シーケンスとしてエクスポートしました。
- 回転する Game Boy Color: 特定の色を固定したい場合は、コンポジターで Cryptomatte を使って対象のジオメトリを識別し、ハードコードされた値を出力します。画面ジオメトリは別途レンダリングされ、Cryptomatte で最終イメージに合成されます。
数学的背景
ノーマルマップ
ノーマルマップはベクトル場をエンコードしており、その青みがかった基準色は XYZ を RGB に対応させる(+Z が進行方向)ためです。3D グラフィックスでは不可欠な技術です。
出典: 自作品 (Danny Spencer)。 Suzanne モデル © Blender Foundation.
ランバートシェーダ(ドット積による)
最も単純なシェーディングはドット積を使用します:
v = N · L
ここで N はノーマルベクトル、L は光源位置(または負の光方向)です。
成分ごとに展開すると:
v = NxLx + NyLy + NzLz
定数光ベクトルは遠方や太陽光をモデル化します。
球面座標
Game Boy で計算を高速化するため、球面座標で別のドット積式を使用しました。
一点は半径 r、極角 θ(シータ)、方位角 φ(ファイ)で表されます: ((r,\theta,\varphi))。
二つの単位ベクトルのドット積は次のように簡略化できます:
v = sinθ₁·sinθ₂·cos(φ₁−φ₂) + cosθ₁·cosθ₂
以前使っていた変数名を当てはめると:
v = sinNθ · sinLθ · cos(Nφ−Lφ) + cosNθ · cosLθ
Game Boy で動かすための工夫
ROM にノーマルマップをエンコード
性能向上のため Lθ を定数に固定しました。プレイヤーは Lφ を操作し、軌道光の効果を作り出します。
定数 (m) と (b) を抽出:
m = sinNθ · sinLθ b = cosNθ · cosLθ v = m·cos(Nφ−Lφ) + b
ROM には各ピクセルを 3 バイトのタプル ((Nφ, \log(m), b)) として格納します。
なぜ log(m)
を使うか
log(m)Game Boy の SM83 CPU は乗算と浮動小数点演算がありません。対数を使えば積を和に変換できます:
log_b(x·y) = log_b(x) + log_b(y) x·y = b^(log_b(x)+log_b(y))
二つのルックアップが必要です:対数テーブルとべき乗テーブル。
- 対数空間: 8 ビット分数; 符号は MSB にエンコード。
- べき乗表: 符号切替を XOR で処理します。
すべてのスカラーは –1.0〜+1.0 の範囲に収まり、8 ビットで安全に保持できます。
パフォーマンス
ピクセルごとの計算
v = m·cos(Nφ−Lφ) + b
これをルックアップで書き換えると:
v = pow( m_log + cos_log(Nφ−Lφ) ) + b
ピクセルあたりの操作数:
- 減算
へのルックアップcos_log- 加算
へのルックアップpow- 加算
合計:3 回の加減算、2 回のルックアップ。
フレームタイミング
- CPU 周波数: 約8.388 MHz(≈139,810 T‑cycles/フレーム at 60 Hz)。
- 処理タイル数: ≥15 タイル/フレーム(行が空の場合は増加)。
- 1 タイルあたりのサイクル数: 約123,972(最悪ケース)→ フレーム時間の約89 %。
自己修正コード
ホットパスでは毎フレーム約960ピクセルを処理します。
実行時に読み込む値をハードコーディングした即値に置き換えると、1 ピクセルあたり12サイクル節約でき、シェーダ全体のランタイムが約10 %短縮されます。
SM83 アセンブリ例:
; 遅い: 28 サイクル ld a,[Ltheta] ; HRAM から読み込み ld b,a ld a,[hl+] sub a,b ; 速い: 16 サイクル ld a,[hl+] sub a,8 ; 即値
自己修正コードは、命令のオペランドを変更して減算を高速に保ちつつ Lθ の変更も可能にします。
AI 実験
“AI Will Be Writing 90 % of Code in 3–6 Months” – Dario Amodei (2025)
-
AI を使ったこと:
- OpenEXR レイヤーを読み込む Python スクリプト。
- Blender‑Python の一部スニペットでシーン設定。
- Game Boy Color 用の SM83 アセンブリ(ダブルスピード、VRAM DMA)スニペット。
-
試みたが失敗したこと:
- SM83 用初期シェーダコードを AI に生成させること。
-
使わなかったこと:
- 本記事の執筆、アルゴリズム設計、ルックアップ表作成、SM83 アセンブリ全般、3D アセット、プロジェクトの創造的“魂”。
Claude Sonnet 4 を試し、擬似コードから SM83 アセンブリを生成させました。機能はあるものの遅く、エラーも多く(例:SM83 は Z80 だと誤解)。最終的に手動で全て書き直しました。
結論
- パフォーマンスが重要: 自己修正コード、対数ベースのルックアップ、ハードコーディングされた即値が実際の差を生みます。
- AI はボイラープレートに有効ですが、制約が厳しい環境では手作業でチューニングしたアセンブリが不可欠です。
この記事や私の YouTube 動画(https://www.youtube.com/watch?v=SAQXEW3ePwo)を共有・コメントしていただけると幸いです。Bluesky への投稿後は更新します。