PPU > スプライト

「PPU/スプライト」の編集履歴(バックアップ)一覧に戻る

PPU/スプライト - (2017/08/28 (月) 13:20:22) のソース

*目次 [#n610fc08]
-[[概要>#概要]]
-[[OAM>#oam]]
-[[項目フォーマット>#format]]
-[[パレット>#palette]]
-[[VRAM上のキャラクタテーブル>#g771f74b]]
-[[スプライトの優先順位>#icfc4734]]
-[[スプライトの描画>#c56fc8cd]]

*概要
SNESには128個の独立したスプライトがある。
OAM (Object Attribute Memory) にデータを書き込むことで
表示する画像や座標の設定をする。

***OAM [#oam]
OAM全体は544バイトで構成され、
512バイトの下位テーブルと、32バイトの上位テーブルに分けられる。
双方共に128項目を入れることができる。
OAM は [[0x2102>IO ポート (メイン)/0x2102]] レジスタでアクセスするアドレスを決め、
[[0x2103>IO ポート (メイン)/0x2102#p2103]] のビット 0 でテーブル選択を行い、
[[0x2104>IO ポート (メイン)/0x2104]] を通じて書き込むか、もしくは [[0x2138>IO ポート (メイン)/0x2138]] から読み込まれる。
上位テーブルは32バイト分しかなく、[[0x2102>IO ポート (メイン)/0x2102]] の下位 4 ビットのみが
テーブルのインデックスとして使われる。

内部のOAMアドレスは、スキャンラインの描画中に無効になる。
無効化は決定的に起こるが、どのようにして決定されるのかは分からない。
OAMアドレスに対して何か操作がある瞬間か、
スプライトが現在のスキャンラインに現れる瞬間が
無効化の決定に関係しているのだろうと考えられている。
内部OAMアドレスはブランク強制期間中でない場合、
V-Blank 開始時に [[0x2102>IO ポート (メイン)/0x2102]]/[[0x2103>IO ポート (メイン)/0x2102#p2103]]  からリロードされる。
[[0x2100>IO ポート (メイン)/0x2100]] のビット 7 が 1 から 0 に変更された時にも
リロード処理は発生する。

1バイトの読み込み/書き込み時にアドレスはインクリメントされる。
(内部アドレスは10ビット分あり、ビット9がテーブル選択、
ビット0~8がテーブルのインデックスを表す。)
読み込み動作は、そのまま今のバイトを読み込む。
下位テーブルに対する書き込みは、ワードサイズの''バッファ''に行き、
上位バイトも追加で書き込まれた時に、OAMの適切なワードに書き込まれる。
読み込み、書き込みが交互に起こった場合、ワード値の
上位バイトが書き込みの代わりに読み込まれることになり、
書き込み内容はOAMに反映されなくなる。
このように交互に動作させると、書き込みは上位バイトにのみ行われ、
その上、下位バイトにはゴミデータが書き込まれてしまう。

順を追って説明すると、
-OAMを全て0にして始める。
-1 を書き込み
-読み込み
-読み込み
-2 を書き込み
-読み込み
-3 を書き込み

この動作の時、
OAM : 01 00 00 02 00 03
のようなデータが書き込まれると予想することができるが、実際には

OAM : 00 00 01 02 01 03
のように書き込まれる。

上位テーブルへの書き込みの方は期待通りに動作する。

***項目フォーマット [#format]

下位テーブルの1項分目のフォーマットは次のような4バイトで構成される。
 OBJ*4+0: xxxxxxxx
 OBJ*4+1: yyyyyyyy
 OBJ*4+2: cccccccc
 OBJ*4+3: vhoopppN

上位テーブルの1項目分のフォーマットは次のような2ビットで構成される。
 bit 0/2/4/6 of byte OBJ/4: X
 bit 1/3/5/7 of byte OBJ/4: s

記号の詳細
-Xxxxxxxxx : スプライトのX座標。
符号については下記参照。
-yyyyyyyy : スプライトのY座標。
0 ~ 239 の時は画面内に表示される。
画面から隠れた部分の頭の部分が -1 ~ -63 の位置にある時、
画面の上からスプライトが出現する。
とても大きいサイズのスプライトの場合、
下の端が隠れた時にも画面の上に戻ってくる。
-cccccccc : スプライトの最初のタイル。
VRAMアドレスの計算方法は下記参照。
これは 'rrrrcccc' とも考えることができ、
16x16 サイズのキャラクタテーブルの行 (row) と列 (column) を指す。
-N : スプライトのネームテーブル。
VRAMアドレスの計算方法は下記参照。
-ppp : スプライトのパレット。
最初のパレットの位置は 128 + ppp * 16
-oo : スプライトの優先順位。
詳細は下記参照。
-h/v : 水平/垂直反転フラグ。
反転処理は個々のタイルに対してではなく、スプライト全体に及ぶ。
しかし、2つの正方形のスプライトから構成される
長方形のスプライトの場合は、垂直に反転する。
(つまり、"01234567" の行の場合、 "76543210" ではなく、 "32107654" となる)
-s : スプライトのサイズフラグ。
詳細は下記参照。

スプライトのサイズは [[0x2101>IO ポート (メイン)/0x2101]] のビット 5 ~ 7 と OAM の
サイズフラグで調節する。
[[0x2101>IO ポート (メイン)/0x2101]] では全てのスプライトのサイズを調節する。
OAM サイズフラグが 0 の時は小さい方のサイズが、
1 の時は大きいほうのサイズが選択される。

***パレット [#palette]
スプライト用に、16色のパレットが8セット用意されている。
それらは CGRAM のインデックス 128 から始まる。
OAM で指定される 'ppp' は、
(128 + ppp * 16) ~ (128 + ppp * 16 + 15) の範囲を指す。
長方形でないスプライトの場合、パレットの最初のエントリは透明色を表す。

スプライトのパレット 4 ~ 7 は色計算と共有される。

***VRAM上のキャラクタテーブル [#g771f74b]
スプライト用に 2 つの 16x16 サイズのキャラクタテーブルが VRAM に用意されている。
これらは BG タイルマップと同じように動作する。
タイル 0x00 は、タイル 0x0F の右側のタイルで、タイル 0xF0 の下側にある。
タイル 0x10 は、タイル 0x00 の下側のタイルで、タイル 0x1F の右側、
タイル 0xFF は、タイル 0xF0 の左側のタイルで、タイル 0x0F の上側にある。
どのキャラクタテーブルを使用するかは OAM の N ビットで設定される。
16x16 のサイズを使って、タイル 0xFF を指定した場合、キャラクタは
0xFF, 0xF0, 0x0F, 0x00 で構成されることになる。
最初に、テーブルは [[0x2101>IO ポート (メイン)/0x2101]] のネームベースビットで指定され、
次に [[0x2101>IO ポート (メイン)/0x2101]] のネームビットでオフセットが指定される。
VRAM 上のスプライトの最初のタイルのワードアドレスは次の式で表される。

 ((Base<<13) + (cccccccc<<4) + (N ? ((Name+1)<<12) : 0)) & 0x7fff

キャラクタデータの詳細は BG 参照。

***スプライトの優先順位 [#icfc4734]
スプライトには優先順位についての考え方が2種類ある。
1つは OAM の優先順位ビットで、BG に対してスプライトの
表示順位を調整する。詳しくは BG 参照。

もう1つは、スプライト同士の間の表示順位で、
これはスプライトのインデックスと優先順位ローテーションによって調整される。

優先順位ローテーションは [[0x2103>IO ポート (メイン)/0x2102#p2103]]  のビット 7 でセットする。
このビットが 0 の時、スプライト 0 が最初のスプライトになる。
1 の場合、現在の内部 OAM ワードアドレスから
(OAM アドレス無効化処理は影響しない) 、
スプライトに優先順位が設定される (OAMAddr&0xFE)>>1 。
[[0x2102>IO ポート (メイン)/0x2102]]/[[0x2103>IO ポート (メイン)/0x2102#p2103]]  に 0x104 が 4 バイトで書かれた時、
次のフレームでスプライト 3 が優先順位を得る。
しかし、OAM アドレスリセット によって、内部OAMアドレスは
0x104 にリセットされるので、さらに次のフレームでは
スプライト 2 が優先順位を得る。

有名な1つの奇妙な動作 : [[0x2102>IO ポート (メイン)/0x2102]]/[[0x2103>IO ポート (メイン)/0x2102#p2103]]  を 0xA に設定した時、
4n+2*(A&1)+1 バイトが書き込まれる。
(例えば、so the next byte written would go to the last byte in the 4-byte sprite record)
sprite ((OAMAddr>>1)+Y)&0x7F has priority (where Y is the
current line as addressed by sprites). Thus, if you put all 128 8x8 sprites
at Y=63, write $8000 to $2102/3, then read 3 bytes from $2138, you will see
sprites 63-70 having priority on successive scanlines.

"最初のスプライト"は、OAM の優先順位ビットの設定に関わらず
他のスプライトよりも前に(一番前に)表示される。
"最初のスプライト" + 1のスプライトは "最初のスプライト" + 2の前に、
"最初のスプライト" + 2のスプライトは "最初のスプライト" + 3の前に表示され、
これは "最初のスプライト" + 127 まで続く。
(127から0にラッピングされる。)
一番前のスプライトのみがBGに対する優先順位を設定できることに注意して欲しい。
"最初のスプライト" + 3 と  "最初のスプライト" + 4 は交換可能だが、
"最初のスプライト" + 3 が優先順位 0 、"最初のスプライト" + 4 が優先順位 3
に設定されている時、両方のスプライトがBGの裏に隠されてしまう。
"最初のスプライト" + 4 が BG の前面に来るのが普通の考え方で、
直感に反することのように見えるが、たくさんのゲームがこの挙動に従って動いている。

***スプライトの描画 [#c56fc8cd]
スプライトはスキャンライン毎に描画される。

+OBJ が X=256 の時 (X=-256)、X=0 の時と同じように"範囲"と"時間"を考慮する必要がある。
注 : 実際にX=0に描画するわけではない。
+範囲 : スキャンライン中の、 "最初のスプライト" から、32 個の最初のスプライトが描画用に選択される。
これらのスプライトは、 -size < X < 256 に入っている中から選ばれる。
1つのスキャンライン内に 32 個以上のスプライトがあった場合、[[0x213e>IO ポート (メイン)/0x213E]] のビット 6 がセットされる。
+時間 : "範囲"の中の最後のスプライトから考えて、8x8 サイズで 34 個のタイルが選択される。
(左から右へ、反転(フリップ)後の)
34 個以上のタイルがあった場合、[[0x213e>IO ポート (メイン)/0x213E]] のビット 7 がセットされる。
これは、-8 < X < 256 の範囲の中から選ばれる。
+"範囲"と"時間" により、タイルと本当のX座標が決定され、パレットと優先順位を考慮して描かれる。
(X=256,X=-256 のものは X=0 には配置されない。)

詳細は [[PPU/画面のレンダリング]] を参照