ホーム < ゲームつくろー! < DirectX技術編 < ID3DXSpriteで簡単2D描画

その41 ID3DXSpriteで簡単2D描画


 Direct3Dで2Dの画像を表示する時、DirectX技術編その2で紹介しているような「ビルボード」を用いて細かな制御を行っても良いのですが、中々そこまで用意するのも面倒だったりします。とにかく手っ取り早く画像を表示させたい。そんな時に使えるのがID3DXSpriteインターフェイスです。その2で紹介しているビルボード技術を完璧に隠蔽して、扱いやすいメソッドのみを公開してくれています。

 この章ではID3DXSpriteインターフェイスを用いて、画像を表示する方法を見てみる事にしましょう。



@ ID3DXSpriteインターフェイスの生成

 ID3DXSpriteインターフェイスは2D画像をスクリーンに描画する専門インターフェイスと位置付ける事ができます。その昔DirectDrawという2D描画コンポーネントがDirectXにはあったのですが、それに近い物があります。ただし、あまり細かな事はできません。

 ID3DXSpirteインターフェイスはD3DXCreateSprite関数で取得します:

D3DXCreateSprite関数
HRESULT D3DXCreateSprite(
   LPDIRECT3DDEVICE9 pDevice,
   LPD3DXSPRITE * ppSprite
);

pDeviceは描画デバイスへのポインタです。
ppSpriteにID3DXSpriteインターフェイスへのポインタが返ります。

 取得したインターフェイスはもちろん最後にReleaseメソッドで解放して下さい。

 ID3DXSpriteインターフェイスは、その名の通り「スプライト」です。スプライトとはいわば「透明の板」に当たります。アニメで言えばセル画です。透明な板であるスプライトにテクスチャを1枚貼り付けて、それを描画するというスタイルになります。



A 描画の位置合わせが混乱どころです

 ID3DXSpriteインターフェイスを用いて画像を描画すること自体は極めて簡単です。インターフェイスを生成し、画像をロードしたテクスチャを1枚用意して、ID3DXSprite::Drawメソッドの呼び出しでそのテクスチャを適用すると、あっという間に描画できてしまいます。大変素晴らしいと思います。問題は「貼り付ける位置と姿勢」です。これがこのインターフェイスの肝と言っても過言ではありません。

 まず透明な板の大きさは「無限大」です(概念として)。しかしそうなるとテクスチャを貼る位置に困ります。そこで「座標」が定義されるわけです。2Dなので座標系はXY座標になります。そして、テクスチャは最初常に「左上が原点に来るように」貼り付けられます。これで透明な板に貼り付いたテクスチャができあがります。尚、座標系自体が回転する事はありません。

 さて、今度はその透明な板をスクリーンに合わせる作業となります。この時に使うのがID3DXSprite::SetTransformメソッドです:

ID3DXSprite::SetTransformメソッド
HRESULT SetTransform(
   CONST D3DXMATRIX * pTransform
);

pTransformにはスプライトの位置や回転・スケールを表す行列を指定します。このメソッドを用いるとテクスチャを張り付けたままスプライト自体が動きます。もちろんスケール、回転、オフセットの3要素の掛け算順序が関係してきます。例えば、60度回転させてからオフセットすれば、その場所で回転するように見えますし、オフセットしてから回転させれば、ある点を中心に大きな円運動を描くようになります。

 ここが大切なので、もう少し詳しく見てみます。初期状態で、テクスチャは「左上がスプライトの原点に来るように」貼り付けられるのでした。

ここで30度左回り(負方向)に回転させる(これはZ軸回転です)と、次のようになります:

座標系は回転しない事に注意してください。さらにX軸方向に100、Y軸方向に50だけ並行移動させてみます:

こうして、最初原点にあったテクスチャは座標系から離れて行きました。これは透明なスプライトごと動かしたと思ってください。

 さて、ここでやった事はスプライトに仮に定義されている座標系を中心としたテクスチャ画像の移動です。しかし、まだ「スクリーン」が出てきていません。スクリーンの状態を決めるのは行列ではなくてID3DXSprite::Drawメソッドなんです。

ID3DXSprite::Drawメソッド
HRESULT Draw(
   LPDIRECT3DTEXTURE9 pTexture,
   CONST RECT * pSrcRect,
   CONST D3DXVECTOR3 * pCenter,
   CONST D3DXVECTOR3 * pPosition,
   D3DCOLOR Color
);

pTextureがスプライトに貼り付けたいテクスチャです。実はここで初めて設定するんです。
pSrcRectはテクスチャの切り取りを行ってくれます。これは大元のテクスチャを切り取ります。はみ出した部分は黒(透明)になるようです。
pCenterpPositionについては、すぐ後でじっくり説明します。
Colorは元のテクスチャの各ピクセルこの値を掛け算することで、テクスチャの色合いや透明度を変化させます。例えば0x33ffffffとすると、かなり透明なテクスチャが描画されます。

 pCenterスプライトの座標自体をずらします。先ほどまで不動だった座標がここで動かせるようになります。

注意したいのは、この座標ずらしは行列によるスプライト操作の「前」に行われると言う事です。ですから、回転行列を適用すると、この原点を中心に回転するようになるわけです。よってこのパラーメタはテクスチャの中心に制御点を取りたい時に使うと便利です。

 pPositionはスプライトの位置をずらします。「先ほどの行列と同じなの?」と思われるかもしれませんが、ちょっと違うところがあります。動く単位が「スプライトのスケールに影響されます」。どういうことか、図で説明します:

これはpPoitionにX=10、Y=20という値を入れた時の様子です。テクスチャが動いていますが、これはスプライト(透明板)が移動しただけです。赤い点線で囲った部分は画面に表示される範囲です。ここで注目は座標軸の単位です。あえて「スプライン座標系単位」と書きました。これはピクセルではありません。もっとも、行列でのスケールを1にすると、この単位はピクセルと同じになりますが、例えば行列でスケールを2倍にしてpPosiotnに同じ値を入れると、見た目は次のようになってしまいます:


 分かりますでしょうか?スケールを2倍にしたので、スプライン座標系のグリッドが荒く(大きく)なっています。もちろんテクスチャも大きくなります。テクスチャの位置がpPositionに与えた値でスケールに合わせて大きく動いています。このことから、pPositionを使う時には、行列で与えたスケールをいつも気にしておく必要が出てきます。これは、スプライン座標系でゲームの画像位置を制御をしている場合には大変に役立ちますが、スクリーンの指定の位置に画像を置こうと考えた時にはかえって煩わしい物になってしまいます。使い様と言う事です。

 そういえば、スクリーンについて触れ忘れていました。スクリーン(画像として描画される範囲)は上の図にありますようにスプライン座標系の原点を左上として、赤い点線のような範囲として取られます。スプライン座標系のスケールが1倍であれば、画面に表示されるスプライン座標軸のグリッド数は、画面のピクセル数と等しくなります。しかし、スケールをs倍にすると、軸のグリッド数は

  軸グリッド数 = [画面の縦(横)のピクセル数]/s

となります。例えばスケールを2倍にすると、縦横2分の1の狭い範囲で(2倍に拡大したように)描画されるわけです。

 以上がID3DXSpriteインターフェイスの位置合わせの全てです。行列とDrawメソッドの引数の両方があり、あやふやに使用すると制御が難しくなります。ですから、回転等をさせる事も考えますと、pCenterで画像の中心点に座標の原点を合わせて後は行列だけで制御すると考えた方がすっきりするかもしれません。



B 描画までのプロセス

 では、描画までの過程を簡単に説明してしまいます。ID3DXSpriteインターフェイスを取得したら、次にテクスチャを作成します。これはD3DXCreateTextureFromFile関数などを用いれば簡単に取得できます。この時画像の大きさを取得しておいた方が良いでしょう。一度作成したテクスチャから画像情報を得るには、次のようなプログラムを作成します:

作成したテクスチャから画像の大きさを取得する
// テクスチャをファイルから作成
IDirect3DTexture9 *pTex;
D3DXCreateTextureFromFile( pDevice, "Texture.jpg", &pTex);

// テクスチャからサーフェイスを取得
IDirect3DSurface9 *pSurface;
pTex->GetSurfaceLevel( 0, &pSurface );   // サーフェイスを取得

// サーフェイス情報から画像サイズを取得
D3DSURFACE_DESC SurfaceInfo;
pSurface->GetDesc( &SurfaceInfo );
UINT Tex_X = SurfaceInfo.Width;    // 幅(ピクセル)
UINT Tex_Y = SurfaceInfo.Height;   // 高さ(ピクセル)

pSurface->Release();

 取得した画像サイズの情報も利用して、行列を使ってテクスチャの姿勢を決めます。これは各種行列関数を利用すると楽です。特に、画像を回転させる時はD3DXMatrixRotationZ関数を用います。注意するのは、Y軸が下を向いていますので、Z軸は画面の奥から手前に突き出している状態になっている事です。よって「右回りが正」となります(Direct3Dはデフォルトで左手系です)。

 作成した行列はID3DXSprite::SetTransformメソッドに渡します:

姿勢行列を設定
// 行列作成
// 左回りに30度回転させた後に(150, 80)並行移動
// スケールは1

D3DXMATRIX mat;
D3DXMatrixRotationZ( &mat, D3DXToRadian(-30.0f) );
mat._41 = 150.0f;   // X軸
mat._42 = 80.0f;    // Y軸

pSprite->SetTransform( &mat );

 画像の中心点に制御点を持って来る事を考えて、予めそういう変数を定めておきましょう:

画像の中心点を制御点に
D3DXVECTOR3 Center( Tex_X/2.0f, Tex_Y/2.0f, 0.0f );


 これで描画準備は完了です。

 描画をする時には、まずIDirect3DDevice9::BeginSceneメソッドで描画開始を宣言します。これはどんな描画でも同じですね。次にID3DXSprite::Beginメソッドを呼び出して、スプライトの描画開始を宣言します。Beginメソッドには引数が1つあります。これはスプライトをどのように描画するかを決めるフラグを設定します。四角い画像のままでよければ0を渡して構いませんが、画像が持つアルファ情報を使った貼り付けを行いたい場合はD3DXSPRITE_ALPHABLENDフラグを渡します。より詳しくはマニュアルをご覧下さい。

 後はDrawメソッドを呼び出すだけです。今回は画像全部を用いる事にして、面倒なpPositonは使わないことにしましょう:

描画
pDevice->BeginScene()

pSprite->Begin(0);
   // 描画
   pSprite->Draw( pTex, NULL, &Center, NULL, 0xffffffff );
pSprite->End();

pDevice->EndScene();
pDevice->Present(NULL, NULL, NULL, NULL);

もちろん描画デバイスのBeginSceneメソッドやEndSceneメソッド、Presentメソッドを全てのスプライト毎に呼び出す必要はありません。1度きりの呼び出しで十分です。BeginSceneメソッドとEndSceneメソッドの間に全てのスプライトの描画を挟めばよいだけです。ただし、ID3DXSprite::BeginメソッドとEndメソッドは1つのスプライトごとに必ず呼び出さなければなりません。

 以上の描画を行うと、左回りに30度回転して中心がスクリーンの(150,80)の位置にあるテクスチャ画像がそのまま描画されます。スプライトを使い終わったらReleaseを忘れずに。




 ID3DXSpriteインターフェイスを用いた2D描画だけでも十分にゲームを作る事が可能です。背景とのアルファブレンドも簡単にできますので、非常に有用です。3Dエンジンをフルに活用するため、描画もかなり高速です。位置合わせが唯一面倒なのですが、行列+制御点移動で統一すれば、ぐっと扱いが簡単になります。3Dのゲームはちょっと敷居が高いと感じておられる方は、まずこの技術でゲーム製作をされても良いのではないでしょうか。




C 謝辞

 この記事を書くきっかけを与えて下さいましたpopさんに感謝申し上げます。