世界を動かす技術を、日本語で。

ゲームボーイカラーにリアルタイム3Dシェーダーを搭載しました

概要

  • Game Boy Color でリアルタイム画像レンダリングを実現した自作ゲームの技術解説
  • Blender を用いた3Dワークフローとノーマルマップ生成手法
  • 球面座標系ログ演算・テーブル参照 による高速化
  • 自己書き換えコード など、ハードウェア制約下での最適化手法
  • 実際の パフォーマンス測定 と工夫点のまとめ

Game Boy Color向けリアルタイム3Dライティングゲーム制作デモ

  • Game Boy Color 上でリアルタイムに画像をレンダリングするゲームの開発
    • プレイヤーは 軌道上の光源 を操作し、オブジェクトを回転させる体験
  • GitHub でコード・ROMファイルを公開
    • nukep/gbshaderにてソースコード・ROMダウンロード可能

3DワークフローとBlenderでのLookdev

  • Blender で事前にビジュアルを検証し、プロジェクト開始判断
  • 擬似ディザリング を法線ベクトルにランダムベクトルを加える方法で表現
  • ノーマルマップ生成には Cryptomatte やカスタムシェーダを活用
    • 特定色の維持や画面部分の合成に利用

ノーマルマップとシェーディングの数理

  • ノーマルマップは ベクトル場 として機能
    • RGBXYZ でベクトル情報をエンコード
  • Lambertシェーダ はドット積計算で表現
    • ( v = N \cdot L )(N: 法線, L: 光ベクトル)

球面座標系による高速ドット積

  • 球面座標 (r, θ, φ)を使い、計算量削減
    • 法線・光源ともに単位ベクトルと仮定し半径1で簡略化
    • ( v = \sin N_\theta \sin L_\theta \cos(N_\varphi - L_\varphi) + \cos N_\theta \cos L_\theta )

Game Boy Colorでの実装と最適化

  • (光源のθ)は定数化し、 (光源のφ)をプレイヤーが操作
  • 各ピクセルは (Nφ, log(m), b) の3バイトでエンコード
    • log(m) を使う理由は、Game BoyのCPUが乗算・浮動小数点非対応のため
  • 加算・乗算 は全て ログ空間ルックアップテーブル で処理
    • 負数対応のため符号ビットをMSBにエンコード
    • 8ビット分解能で-1.0~+1.0を表現

コア計算式とルックアップテーブル

  • コア計算: ( v = m \cos(N_\varphi - L_\varphi) + b )
    • 実装上は ( v = pow(m_{log} + cos_{log}(N_\varphi - L_\varphi)) + b )
  • 1ピクセルあたり
    • 減算1回
    • cos_log参照1回
    • 加算1回
    • pow参照1回
    • 加算1回
    • 合計: 加減算3回、テーブル参照2回

パフォーマンスと最適化

  • 1フレームで 15タイル (960ピクセル)処理
    • 1ピクセル約130サイクル、空行は3サイクル
    • 1フレームの約 89% をレンダリングに使用
  • 自己書き換えコード による高速化
    • 可変数値処理を命令書き換えで高速化
    • 1ピクセルごとに12サイクル削減、全体で約10%短縮

まとめ

  • レトロハードウェア の制約下での創意工夫
    • ログ演算・テーブル参照・自己書き換えコード活用
  • 現代的な3Dワークフロー低レベル最適化 の融合
  • GitHub でソース・ROM公開、技術的な参考資料としても有用

参考リンク

Hackerたちの意見

すごく見栄えのいい結果だね。俺の理解では、これは「3D」シェーダーっていうより、3Dに見えるけど、実際にはプリレンダリングされた2Dノーマルマップを使って、得られたワールドスペースのノーマルでライティングしてるって感じかな。フレームはこちらだよ: https://github.com/nukep/gbshader/tree/main/sequences/gbspin...

「リアル3D」レンダラーとはそんなに違わないよ。特に遅延レンダリングパイプラインでは、ラスタライザーが深度マップ、ノーマルマップ、カラーなどのバッファをたくさん作るけど、メインシェーダーはそれらの2Dバッファで動いてる。これが面白いところで、3D三角形で動作する部分はシンプルに保たれて、高価なライティングシェーダーはフラットな2D画像で一度だけ実行されるんだ。シェーダーはノーマルマップバッファが今まさにラスタライズされた3Dジオメトリから来たのか、昔にプリレンダリングされたものか、そのミックスかは気にしない。前方レンダリングパイプラインでも、フラグメントシェーダーは「リアル3D」データから頂点シェーダーとラスタライザーによって作られた暗黙の2Dピクセルで動作してる。俺の見方では、シェーダーの入力と数学が3Dベクトルで動いてるなら、それは3Dシェーダーだよ。3Dラスタライザーがあるかどうかは別の問題だね。現代の3Dゲームはそれをいろんな方法で活用してる。複数の視点から3Dモデルをプリレンダリングするのはズルに聞こえるかもしれないけど、インポスターの使用はちゃんとした3Dエンジンが使うリアルな技術だよ。

ハッカーニュースで本物のハッカー素材が見れるのは嬉しいね。

それってAIへのプロンプトだけじゃなかったの?どうやってやったの? ;)

これがHNの存在意義だよ。昔のテック雑誌をめくってる時と同じような喜びを感じる。

このGBCシェーダーは重要な真実を明らかにしてるね:すべての計算は制約の下での近似なんだ。掛け算はテーブルルックアップと足し算に変わり、精度は目が実際に見るものに譲る。

AIを使った全体的に失敗した試み > AIを使ってプロセスを試そうとしたんだけど、主に1) 業界がAIのことをうるさく言ってるから、2) 新しいプロジェクトのためにそれについての具体的で個人的な意見が欲しかったからなんだ。結局のところ、これはまだ趣味のプロジェクトだから、AIがポイントではないんだけどね!でも… > 生成AIの出力のすべての試みや実際の使用を開示することが大事だと思ってる。自分の作品のプロセスについて人を欺くのは倫理的じゃないから。そうしないと信頼が損なわれて、デマや盗作になっちゃう。開示することで、意見が違う人たちがその作品に関わることができるし、それは大事だよね。フィードバックにはオープンだから、ありがとう!素晴らしいプロジェクトだね。

面白いことに、最初はもっと中立的な表現だったんだけど、AIをただ持ち上げてると思われたから、トーンを少し懐疑的に変えたんだ。Redditの別の人が、俺がAIを十分に愛してないことにイライラしてるみたいだった。俺はこのタイプのプロジェクトのプロセスを記録したかっただけなんだよね。シュラッグ。

「動作させる」セクションが突然次のところで終わってるみたい?命令オペランドを修正することで! 2A ld a, [hl+] D6 08 sub a, 8

ああ、そうだね。それは俺の下書きから来たやつで、整理するのを忘れてた。すぐに投稿を更新するよ。

こっそり任天堂がGBCやGBAを再発売してくれたらいいな。絶対買うのに。いくつかのゲームをカートリッジに焼き込んで、買う価値を100%にしてくれたら最高だね。

中古のやつを結構安く手に入れられるよ。フラッシュカートリッジを追加すれば完了。だけど、同じ形状の安いAndroidハンドヘルドの方がいい選択肢だと思う。俺はまだゲームボーイのコレクション持ってるけど、ほとんど使ってないんだよね。最近はエミュレーターを立ち上げる方がずっと楽だし。

ModRetro ChromaticはOculus VRのクリエイターから買えるよ。任天堂が作るどんなものよりも優れてる。

これ、数ヶ月で見た中で一番クールだわ。ビールウェアとしてライセンスしてくれたら、俺はおごる義務があるね。

これにはめちゃくちゃ感動してる。なぜなら、実際にCGBで動いてるから。よく見るのは、ゲームボーイを端末として使って、カートリッジにもっと強力な処理能力を詰め込んでるハックなんだよね。

こんにちは、作者です。ここに投稿されたって聞いてアカウント作ったので、参加しに来ました。シェアしてくれてありがとう!環境マップでさらにシンプルにすることも考えてるんだけど、それをBskyにシェアしたよ: https://bsky.app/profile/dannyspencer.bsky.social/post/3mecu...