ファミコンのグラフィックスの省メモリ化テクニックとは?

1983年に発売されたNintendo Entertainment System(NES、日本での商品名は「ファミリーコンピューター」、以下「ファミコン」)は安価なのに高性能だったため、大ヒット商品となりました。独自設計のピクチャー・プロセシング・ユニット(PPU)を使うことで、当時としては驚きの映像を生み出すことができました。そして、今でも特定の環境で視聴すればとてもきれいな映像が楽しめます。一番の業績はメモリの利用効率です。グラフィックスを最小限のバイト数で作成することに成功しました。それと同時にファミコンは、開発者に便利で使いやすいツールを提供しました。その点でも、それまでのテレビゲーム機とは一線を画した製品でした。ファミコンのグラフィックスの生成方式を理解すれば、システムの技術的な優れた能力のありがたみが分かるはずです。そして、現代のゲーム製作者が現在のマシンではどれだけ簡単に作業ができているかを知ることができるでしょう。

ファミコンのバックグラウンド画像は4つの異なるコンポーネントから生成されます。4つのコンポーネントを組み合わせることで、画面に画像が表示されます。個々のコンポーネントはそれぞれ異なる役割を担っており、色、場所、未加工のピクセルアートなどを扱います。こう説明するととても複雑で面倒なもののように思えるかもしれませんが、全てを使いこなせばメモリ効率は断然よくなりますし、簡単なエフェクトであればかなり短いコードだけで書けるようになります。ファミコンのグラフィックスの理解にはこの4つのコンポーネントを知ることが鍵となります。

本稿は、読者がコンピューター内部のデータの記述方法などをある程度ご存じだと想定して説明を進めます。特に、1バイトは8ビットに相当するとか、8ビットで256までの値を表現できるとか、16進数の記述形式などの知識はあるものとして進めます。とはいえ、そのような技術的な前提知識がない方が読んでも楽しめる記事になっていることを願っています。

概説

castlevania

上記の画像は1986年発売の「悪魔城ドラキュラ」のオープニングの場面で、城へ続く門を表現しています。この画像は256×240ピクセルで、色は10色を使用しています。メモリ上でこれを表現するためには、色数の限られたカラーパレットをうまく活用し、最小限の情報のみをメモリにロードして、スペースを節約したいところです。初歩的なやり方としては、インデックスカラーパレットを使って、各ピクセルを4ビットにし、1バイトでピクセル2個を表すというやり方があります。しかし、これには256*240/2 = 30720バイトが必要となるので、ファミコンがもっといい仕事をしてくれるのはすぐに分かるはずです。

ファミコンのグラフィックスの中核となるのがタイルとブロックです1。タイルは8×8のマス目で、ブロックは16×16で、それぞれのマスは格子状に同じ大きさで並んでいます。この格子状の線(グリッド線)を画像に乗せるとグラフィックスの内部構造がある程度見えてくるはずです。以下は先ほどの城門の画像にグリッド線を追加し、2倍にズームしたものです。

castlevania-grid

このグリッドでは、ブロックを薄い緑の線、タイルを濃い緑の線で、それぞれ示しています。画面の垂直軸と水平軸に沿って表示されている目盛り(ルーラ)は位置を特定するためのもので、16進数で表示されています。例えば、プレイヤーのステータスを表すハートマークは$15+$60 = $75の位置にあり、10進数でいうと117ということになります。1つの画面は16×15(240)個のブロック、32×30(960)個のタイルで構成されます。では、この画像がどのようにして表示されているのか、加工なしのピクセルアートで見ていきましょう。

CHR

CHRは色や位置の情報を持たない未加工のピクセルアートで、タイルとして表現されます。メモリページは全部で256個のCHRタイルで構成されます。また、各タイルは2ビットのビット深度を持ちます。先ほどのハートマークが以下です。

pixel-heart

CHRでは以下のように表現されます2

pixel-heart-chr

このマークを表示するために、各ピクセルで2ビットを使います。従って、8×8のサイズでは、882 = 128 ビット= 16バイトとなります。つまりページ全体の表示には、16*256 = 4096バイトが必要です。以下は全て、「悪魔城ドラキュラ」のオープニングイメージで使われているCHRです。

castlevania-chr

イメージ全体を表示するには960個タイルが必要だったことを思い出してください。ところが、CHRには256個のタイルしかありません。つまり、ほとんどのタイルは平均3.75回使い回されているということになりますが、数個ののタイルが膨大な回数繰り返されていることが少なくありません(例えば、何もない背景や無地のもの、お決まりのパターンの場合などです)。「悪魔城ドラキュラ」のイメージの場合、空白のタイルや無地の青が多く使われています。タイルがどのように割り当てられているかを把握するには、ネームテーブルを理解する必要があります。

ネームテーブル

ネームテーブルとは、画面上の所定の位置にCHRのタイルを割り当てるものです。960個のタイルを割り当てることになります。それぞれのポジションは1バイトで表すので、ネームテーブル全体では960バイトが必要です。割り当ての順番は、行ごとに左から右、上から下となっています。これは、計算で求めた値にルーラが示す値を加えて、最終的に表示するポジションに対応します。つまり、一番左上は$0で、その左が$1、その下が$20と表現されます。

ネームテーブルの値は、使われているCHRが割り当てられた順序によって変わってきます。以下は一例です3

castlevania-nt
この例では、$75の位置にあるハートは$13ということになります。

そして、色を追加するにはパレットの選択が必要です。

パレット

ファミコンは64色のパレットシステムを採用していますので4、その64色からレンダリングに使用するパレットを選択することになります。各パレットは3つの特定の色と共有の背景色で構成されています。1つの画像には最大4つのパレットを使用することができ、これで16バイトを使用します。以下が「悪魔城ドラキュラ」のパレットです。

castlevania-pal

パレットは自由自在に使えると言うわけではありません。1つのブロックに対して、1つのパレットのみが使えます。ファミコンのゲーム画面の多くがカクカクとブロックのような見た目になっているのはこれが原因です。カラーパレットによって16×16ごとに分ける必要があるからです。「悪魔城ドラキュラ」のオープニングのように、洗練されたグラフィックスを作成するには、ブロックの枠に共有の色を混ぜ込んで、グリッド線をごまかします。

それぞれのブロックでどのパレットを選択するかは、属性を使用して決定します。それでは最後のコンポーネント、属性を以下で説明します。

属性

属性は1ブロックあたり2ビットが割り当てられていて、4つのパレットのうちどれを使うかを示します。属性を用いて、各ブロックがどのパレットを使っているかを示したのが以下の図です。5

castlevania-color

気づいた方もいるかもしれませんが、パレットは部分ごとに分けられています。ただし、ある1色を別々の領域で共有したりすることで、この事実が露骨に目立たないようになっています。城門の真ん中の部分の赤は周りの壁の色に溶け込んでいます。またバックグラウンドで黒を使って、城と門の間の線をぼかしています。

ブロックごとに2ビット、つまり1バイトに4ブロックということは、属性は1画面に対して240/4=60バイトを使用します。ただし、エンコードの方式のせいで余分な4バイトも使うので、全部で64バイトになります。つまり、CHR、ネームテーブル、パレット、属性を含む1つの完全な画像を表現するのに必要なのは、4096+960+16+64 = 5136バイトです。上記で試算した30720バイトに比べれば、かなりのバイト数を節約していることになります。

MAKECHR

ファミコンで使う4つのグラフィックスコンポーネントを新たに作成する手順は、通常のビットマップAPIよりも複雑ですが、そこでツールの出番です。かつてのファミコン開発者たちはおそらく、ツールチェーンの類いを何か用意していたのでしょうが、とにかくそれは歴史の中に埋もれてしまいました。最近の開発者たちは通常、ファミコンが要求する形式にグラフィックスを変換するプログラムを自作します。

本稿で使用した画像は全て MAKECHRを使って作成しました。このMAKECHRは格闘ゲームのStar Versusを作る時に使ったツールを書き直したバージョンです。これはコマンドラインツールで、ビルドの自動化のために設計しましたが、処理の高速化、分かりやすいエラーメッセージ、移植性、明確性にも力を入れました。また、本稿で使っているもののような、興味深いビジュアルを作ることもできます。

参考文献

ファミコンのプログラミングに関する私の知識、特にグラフィックスの作成方法は、以下の文献から得ました。

脚注


  1. 用語について。文献によってはブロックを「メタタイル」と呼んでいることがあります。ただし個人的には、この呼称は「ブロック」ほどは分かりやすくはないと感じています。 

  2. CHRのエンコーディングについて。各ピクセルに対して割り当てられる2ビットの値は、隣接して格納されているわけではありません。1つの完全なCHR画像は、低位ビットのみ、高位ビットのみというように格納されています。
    従って、ハート形はこのように格納されます。
    pixel-heart-high pixel-heart-high
    各行が1バイトになります。例えば01100110 で$66、01111111は$7fとなります。そこで、ハート形全体を表すバイトは次のようになります。
    $66 $7f $ff $ff $ff $7e $3c $18 $66 $5f $bf $bf $ff $7e $3c $18 

  3. ネームテーブルについて。ここで説明したネームテーブルの使い方は、実際のゲームに使われているグラフィックスが採用している方法とは異なります。通常、アルファベットはメモリ上で順番に続けて配置されます。例えば「悪魔城ドラキュラ」ではこちらの方式を採用しています。 

  4. システムパレットについて。ファミコンはRGBパレットを使わないので、実際にレンダリングされる色は、表示するテレビの機種によって異なります。エミュレータはそれぞれ、全く異なるRGBパレットを使う傾向にあります。本稿で使用した画像の色は、MAKECHRにハードコードで実装したパレットに対応しています。 

  5. 属性のエンコーディングについて。属性は妙な順番で格納されています。左から右へ、上から下への順序ではなく、1バイトの中に2行2列のブロックのセクションがエンコードされていて、要素の順番はZ型に進みます。4バイト余分に必要な理由はこれです。最終行は8バイトをフルに使います。
    pal-block-group
    例えば、$308のブロックには他に、$30a、$348、$34aの値が格納されているとします。これらに対応するパレット値は1、2、3、3で、低位のポジションから高位に向かって格納されています。即ち11 :: 11 :: 10 :: 01 = 11111001となります。従って、これらの属性のバイト値は$f9です。