Nintendo 64における加算ブレンディング問題の解決策
多くのレトロゲーム愛好家にとって、第5世代コンソールにおける視覚効果(特に爆発、プラズマビーム、魔法の呪文など)の扱われ方には、常に顕著な違いがありました。初代PlayStation (PSX) は鮮やかで光り輝くエフェクトを多用していましたが、Nintendo 64 (N64) は、同じような「映え」に欠けることがよくありました。
この視覚的な差異は、生のパワー不足によるものではなく、むしろ2つのシステムが加算ブレンディングをどのように処理するかという根本的な違いによるものです。PhobosLabのDominic Szablewski氏は、最近の技術的な探求において、なぜN64のハードウェアが伝統的な加算ブレンディングをほぼ不可能にしたのか、そして現代のホームブリュー・ツールを使用してどのようにこれらの制限を回避できるのかを解説しています。
PSXのアプローチ:シンプルで効果的
PlayStationのGPUは、スプライトやジオメトリを既存のフレームバッファにどのように混合するかを決定するために、4つの異なるブレンドモードをサポートしていました。視覚効果において最も重要なのは src + dst モードでした。
このモードでは、ソース(スプライト)の色値が、デスティネーション(フレームバッファ)に単純に加算されます。結果の値が最大可能値(例:8ビットシステムにおける255)を超えると、GPUは自動的にその値を255に「クランプ(固定)」します。これにより、シーンに光を加えることが、シーンを明るくすることだけに繋がり、火やエネルギーに不可欠な発光エフェクトを生み出します。
N64の問題点:クランプのない柔軟性
N64のReality Display Processor (RDP) は、非常に柔軟な「Color Combiner」を備えており、これは概念的にOpenGLの glBlendFunc() と似ています。Libdragonのようなツールを使用すると、開発者は RDPQ_BLENDER マクロを使用して (P * A) + (Q * B) のような演算を実行できます。
コード上で加算ブレンディングを設定することは些細なことです(本質的にはソースのRGBをメモリのRGBに加算するだけです)。しかし、RDPには重大な欠陥があります:結果をクランプしないことです。
色値が最大制限を超えると、最大値に留まるのではなく、ラップアラウンド(値の回り込み)が起こります。例えば、青の値を226、フレームバッファの値を170とした場合、結果は396となります。255にクランプされる代わりに、N64はこの値をラップアラウンドさせ、結果として140になります。これにより、爆発の最も明るい部分が突然暗くなったり、予期せず色が変化したりするという、不快な視覚的アーティファクトが発生します。
回避策のエンジニアリング
ラップアラウンドのアーティファクトなしで真の加算ブレンディングを実現するために、Szablewski氏は、N64独自のアーキテクチャを活用した多段階のパイプラインを提案しています。
1. 32ビットバッファによるヘッドルームの確保
ほとんどのN64ゲームはメモリ帯域幅を節約するために16ビットのフレームバッファを使用していましたが、RDPは32ビットの RGBA 8888 バッファに描画することが可能です。すべての描画を元の強度の数分の一の強度で行うことで、開発者は、複数の加算スプライトの合計がラップアラウンドの閾値(255)に決して到達しないようにするための「ヘッドルーム(余裕)」を作ることができます。
アセットをオフラインで事前処理する代わりに、フォグのアルファ値を悪用してすべての色を1/8の強度で描画することで、リアルタイムで行うことができます。
rdpq_set_fog_color(RGBA32(0, 0, 0, 256/8));
rdpq_mode_blender(RDPQ_BLENDER(( IN_RGB, FOG_ALPHA, MEMORY_RGB, ONE )));
2. 変換のボトルネック
32ビットバッファへの描画は、戦いの半分に過ぎません。最終的な出力は、表示のために16ビットバッファに変換して戻す必要があります。これをCPUで行うと、320x240の解像度で1フレームあたり約70msを要し、禁止的に遅い速度になります。リアルタイムのゲーミングにはあまりにも遅い速度です。
3. RSPの活用
ここで、N64のベクトル共プロセッサであるReality Signal Processor (RSP) が不可欠になります。カスタムマイクロコードを記述することで、変換とクランプのプロセスをCPUからオフロードできます。RSPの128ビットベクトル命令を使用することで、システムは8つのピクセルを同時に処理できます。
コミュニティ(具体的には、C言語のようなコードをMIPSアセンブリにコンパイルするRSPL言語)の最適化の助けを借りて、変換時間は1フレームあたり70msからわずか 3.1ms に短縮されました。
トレードオフと技術的な洞察
この手法は「特効薬」ではありません。ほとんどの開発者が32ビットバッファを避けたのには理由があります。N64のメモリ帯域幅は非常に低いことで知られています。32ビットバッファへの描画は、RDRAMへのデータの移動量を2倍にする必要があり、パフォーマンスに大幅な影響を与える可能性があります。
しかし、その結果として得られるのは、PSXの挙動をmimic(模倣)し、ラップアラウンドのアーティファクトなしで、視覚的に優れた加算ブレンディングです。コミュニティが指摘しているように、この問題はオーディオミキシングに似ています。クランプ(飽和演算)とはなしに、信号のオーバーフローが激しいデジタルクリッピングやラップアラウンドノイズを生み出すためです。
パイプラインの要約
初期化:最終表示用の16ビットフレームバッファを準備します。
レンダリング:オーバーフローを防ぐため、二次的な32ビットバッファに1/8の強度でシーンを描画します。
処理:RSPを使用して32ビットバッファを処理し、値をクランプして16ビットの
RGBA 5551形式に変換します。表示:最終的な16-bitバッファを画面に表示します。
この手法の実装に興味がある方は、GitHubの phoboslab/n64_addblend でデモプロジェクトが利用可能です。