ホーム < ゲームつくろー! < DirectX技術編
その51 スクリーンにある2D板ポリゴンを行列で操作する
DirectX技術編その2で座標変換済み頂点を用いた2D板ポリゴンの作成方法について説明しました。概要を再確認しますと、スクリーン座標を直接指定するカスタム頂点フォーマットを作り、頂点バッファに4つの頂点を定義し、D3DFVF_XYZRHWを指定すると2D板ポリゴンが画面に表示されます。
この仕組みは固定機能パイプラインが持つ便利な方法ですが、いかんせんワールド変換等の頂点変換を素通りしてしまうため表示したいスクリーン座標を自前で直接与えなければなりません。例えば位置(200,150)に幅100高さ50の板ポリゴンを少し回転させて表示させたい場合、一つ一つの頂点位置を計算してロックした頂点バッファに書き込む必要があるわけです。本来そういう頂点変換は描画デバイスに行列を与えてハードに行ってもらうべきもので、3Dモデルはもちろんそうしています。2D板ポリゴンの時だけ各頂点位置を計算しなおしてメモリをロックして書き込むというのは、そう考えるとやっぱり何だかおかしいんです。
そこで、この章ではスクリーンに2D板ポリゴンを並べる時に、板ポリゴンの頂点バッファは触れずに、いつも使っている行列で位置や大きさを制御する方法について試行錯誤してみます。
@ 擬似スクリーン空間を想定
2D板ポリゴンを行列で操作してスクリーンに並べる。これがこの章の目的です。素直に考えれば、ローカル座標で定義した板ポリゴンにワールド変換行列を掛け算してスクリーン上でスケール・回転・平行移動を行わせるわけですが、色々試してみてこれは素直に行かない事がわかりました。スクリーン座標はY軸が反転(下向き)しています。これが思いの他ワールド変換行列と相性が悪いんです。
そこで、この章では思い切って「擬似スクリーン空間」という新しい空間を設けることにしました。この空間に板ポリゴンを並べると、メンドクサイ部分がすっきりとなくなって直感とマッチした操作ができます。
まずは定義からはじめます。擬似スクリーン空間は次のような範囲の空間です:
青い枠がスクリーンです。この幅Wと高さHはスクリーンのピクセル数と一緒にします。スクリーンのど真ん中に原点があり、右方向をX軸、上方向をY軸とします。板ポリゴンはXY平面上に貼り付けられ、青枠のスクリーンにあるものだけが描画されます。ただ、板ポリを回転させたいなとも思いまして、奥行きDも一応考慮してみました。
ここに置く板ポリゴンのローカル座標を次のように定義します:
幅と高さが1で制御点が原点にある単位矩形です。奥行きZは0に設定します。この座標値をいつものように頂点バッファに書き込んだ後、ロックして再び書き換える事はありません。
この単位矩形にワールド変換行列を適用すると、擬似スクリーン空間に板ポリを置くことができます。もちろんスケール変換・回転変換・平行移動変換のすべてが使えます。後は、カメラ側の考慮です。
A 正射影変換で擬似スクリーンを射影変換する
擬似スクリーン空間に並べた2D板ポリゴンの頂点座標は、次にビュー行列によってカメラ空間に移動されます。しかし、擬似スクリーン空間はすでにカメラの目の前にあります(Direct3Dのカメラは原点にあってZ軸方向を向いていると仮定されているためです)。よって、ビュー行列は単位行列で酢素通りさせて問題ありません。
ビュー行列の後に来るのは射影変換で、これにより擬似スクリーン空間にある板ポリゴンは射影空間に移されます。
射影変換についておさらいしておきます。下の図をご覧下さい:
射影変換とはカメラが切り取った空間(視錐台)を中央の図にあるような緑色の小さな範囲にスケーリングする変換の事です。視錐台は遠くほど広くなりますが、それをX=(-1〜+1)、Y=(-1〜+1)、Z=(0〜+1)の範囲に押し込めるため、遠くの物は縮小されて小さくなります。これにより遠近感が生じます。射影空間の上の範囲にあるものが、そのままスクリーン空間に広げられて描画されます。
2D描画の多くの場合、射影変換による遠近感はいりません。そこで、カメラ空間(擬似スクリーン空間)の縦横のみを素直に射影空間の範囲に収める行列を考えます。これは簡単でして、擬似スクリーンの幅と高さで頂点のXY座標をそれぞれ割ればいいんです。それを実現する射影変換行列は例えば次のようになります:
スクリーンをW=640、H=480として、例えば擬似スクリーン空間の(320, 240, 0.0, 1.0)という右上端の点を上の行列で変換すると、ちゃんと(1.0, 1.0, 0.0, 1.0)という射影空間の右上隅に来ます。気をつけたいのがZの値です。元の頂点のZの値が0未満か1より大きいと、カメラ空間の外にあると判断されてしまいます。このような遠近感を無視してしまう射影行列を「正射影行列」と言い、2D描画では基本となる射影行列になっています。
まとめると、擬似スクリーン空間に並べた板ポリゴンをそのままスクリーン画面に出すには、単位矩形を用意して、適当なワールド変換行列で擬似スクリーンに並べ、単位行列であるビュー行列を掛け算し(何もしないと言う意味です)、そして上の正射影変換行列をさらに掛けます。この章の骨子はこれで整いました。
(※旧記事ではこの後に奥行きを考慮しましたが、煩雑なので割愛致しました。)
B ライトはオフで
さて、板ポリを単純に3D空間に置くと、ライトが有効になってしまいます。ライトの方向によって2D板ポリゴンに陰影が付いてしまうわけです。これは使い方によっては効果的ですが、大抵は不必要です。通常2Dの板ポリゴンを描画する時にはライトをオフにします:
ライトオフ // ライトオフ
g_pD3DDev->SetRenderState( D3DRS_LIGHTING, false );
こうすると板ポリゴンは真っ白になってしまいますが(DirectXの仕様?)、テクスチャを貼ればちゃんと描画されるようになります。
C 2D描画前にZバッファをクリアしよう
今回の擬似スクリーン空間は、ワールド空間の一部です。このため、描画時に3Dモデルとの奥行き関係が生じてしまいます。スクリーンに並べる2D板ポリゴンは3Dモデルの上に上書きされる絵(HUD)です。テレビのテロップのようなものです。ですから、その深度と3Dの深度を比較してはいけません。
通常2D(HUD)を描く時には3Dモデルを描いて出来た深度バッファをクリアします:
深度バッファをクリア pDev->Clear( 0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 40, 40, 80 ), 1.0f, 0 );
こうすると深度値がリセットされ2D板ポリゴンが描画済みの3Dモデルの上に上書きされるようになります。後は2D板ポリゴンのZ値を工夫すれば、2D同士の前後関係が表現できます。ただし、アルファが入っている画像は入っていない画像を描いた後に一番後方から描くようにして下さい(DirectX技術編その17「Zバッファとアルファブレンドの嫌な関係」を参照)。
この章ではスクリーンに並べる2D板ポリゴンを行列で操作する方法をあれこれ考えてきました。この章で試行錯誤してきた内容をクラス化すれば、2DHUD用のライブラリを構築する事も出来ると思います。簡単な例をサンプルプログラムに挙げましたので、興味のある方は覗いてみて下さい。