サンプルプログラムのダウンロード (msdnaa_directx01.exe、2.26 MB)
DirectX とは、Microsoft Windows 上でゲームを開発するためのコンポーネント群の総称です。ユーザは、DirectX9 を使用することによって家庭用ゲーム機にも劣らない品質のゲームを効率よく開発できます。DirectX は、フリーソフトから、市販のメーカ製品に至るまで、ほとんどの Windows 上で動作するゲーム製作に使用されています。
本連載は、C++ 言語と DirectX の基礎的な知識がある方を対象としています。本連載では、第 1 章から第 4 章を通して、ゲームプログラムの基礎的な知識や方法について解説します。また、第 5 章と第 6 章を通して、簡単な 3D シューティングゲームを作成します。
本章では、連載の目的である 3D シューティングゲームを作成する上で必要となる DirectX9 の基本的な使用方法と 3D ゲームプログラムの基礎について解説します。
3D ゲームでは、様々な 3D モデルの操作を行う必要があります。その中でも、特に重要なものに、3D モデルの移動や回転があります。本節では、3D モデルの操作方法を説明するとともに、ゲームプロブラムに役立つテクニックであるクォータニオンについて説明します。3D モデルを操作した結果を表示するためには、第 1.3 節で解説する 3D モデルの表示処理が必要です。
Direct3D において、3D モデルを操作することは、3D モデルを構成する各頂点に対して、行列を使用した座標変換を行うことです。座標変換に使用する行列は、座標変換行列と呼ばれます。座標変換行列には、主に移動行列、回転行列や射影行列などがあります。Direct3D で座標変換を行うための計算方法を図 1 に示します。
.gif)
図 1 : Direct3D での行列による座標変換の式
図 1 に示した座標変換の式では、行列 (x y z w) と座標変換行列の積が行列 (x' y' z' w') です。行列 (x y z w) の要素 x、 要素 y、 要素 z は、それぞれ座標変換を行う対象のx座標、y座標、z座標です。行列 (x y z w) の要素 w には、通常 1.0 を代入します。行列 (x' y' z' w') の要素 x' 、要素 y' 、要素 z'は、それぞれ座標変換を行った結果の x 座標、y 座標、z 座標です。
3D モデルは、移動行列を使用して座標変換を行うことにより移動します。Direct3D で使用する移動行列を図 2 に示します。
.gif)
図 2 : Direct3D で使用する移動行列
Direct3DX には、移動行列を作成するための便利なヘルパー関数が含まれています。ヘルパー関数を使用することにより、ソースコードが簡潔になります。ヘルパー関数を使用して移動処理を行うソースコードの例を次に示します。
D3DXMATRIX mat; //行列
D3DXMatrixTranslation(&mat, 0.0f, 0.0f, 10.0f); //移動行列を生成
s_lpD3DDevice->SetTransform(D3DTS_VIEW, &mat); //ビュー変換を設定Direct3D で、3D モデルの姿勢を制御する基本的な方法は、X 軸、Y 軸、Z 軸まわりの角度をそれぞれ指定することです。この角度の指定の方法は、オイラー角と呼ばれます。
オイラー角による回転処理は、回転行列を使用することにより実現されます。回転行列には、X 軸、Y 軸、Z 軸を中心に回転処理を行うものがあります。Direct3D で使用する回転行列を図 3 に示します。
.gif)
図 3 : Direct3D で使用する回転行列
Direct3DX には、回転行列を作成するための便利なヘルパー関数がいくつか含まれています。ヘルパー関数の一覧を表 1 に示します。
表 1 : 回転行列を作成するための Direct3DX ヘルパー関数
| 関数 | 機能 |
| D3DXMATRIX* D3DXMatrixRotationX(D3DXMATRIX* pOut, FLOAT Angle); | X 軸まわりに Angle ラジアン回転する行列を作成します。 |
| D3DXMATRIX* D3DXMatrixRotationY(D3DXMATRIX* pOut, FLOAT Angle); | Y 軸まわりに Angle ラジアン回転する行列を作成します。 |
| D3DXMATRIX* D3DXMatrixRotationZ(D3DXMATRIX* pOut, FLOAT Angle); | Z 軸まわりに Angle ラジアン回転する行列を作成します。 |
| D3DXMATRIX* D3DXMatrixRotationYawPitchRoll(D3DXMATRIX* pOut, FLOAT Yaw, FLOAT Pitch, FLOAT Roll); | Y 軸、X 軸、Z 軸まわりの回転角を指定して、回転行列を作成します。 |
D3DXMatrixRotationYawPitchRoll() 関数は、Y 軸、X 軸、Z 軸回りの順に回転を行います。回転の順番は、出力される行列に影響を与えるため、注意する必要があります。Y 軸、X 軸、Z 軸回り以外の順番で回転を行う場合は、それぞれ別の順番で回転行列を合成する必要があります。
ゲームを作成する場合には、任意の軸を中心とした回転を行う処理が多くあります。例えば、転がるボールを表現する場合、ボールの回転軸はボールの転がる方向や外からの力によって多種多様に変化します。しかし、任意の軸を中心とした回転処理を行う場合、オイラー角による姿勢制御だけでは困難です。そこで、任意の軸を中心とした回転処理を行えるクォータニオンが必要です。
Direct3DX には、クォータニオンを使用した回転を簡単に扱うことができるヘルパー関数が多数含まれています。D3DXQuaternionRotationAxis() 関数は、指定したベクトルを軸として指定した角度だけ回転させるクォータニオンを作成します。ただし、軸となるベクトルは単位ベクトルである必要があります。D3DXQuaternionRotationAxis() 関数により任意軸まわりの回転クォータニオンを作成するソースコードの例を次に示します。
D3DXQUATERNION qt; //回転クォータニオン
D3DXVECTOR3 v; //軸となるベクトル
qt.x = qt.y = qt.z = 0;qt.w = 1.0f; //単位クォータニオンに初期化
v.x = 0.0; v.y = 1.0; v.z = 0.0; //回転の軸を設定
D3DXQuaternionRotationAxis(&qt, &v, d); //クォータニオンを回転次に、作成したクォータニオンを使用してベクトルを回転する処理を行います。クォータニオンを使用してベクトルの回転を行う式を図 4 に示します。
.gif)
図 4 : クォータニオンを使用したベクトルの回転
図 4 に示した式は行列で表現できます。行列で表現した式は、Direct3D のレンダリングパイプラインで使用することができるため効率的です。そこで、Direct3DX に含まれている D3DXMatrixRotationQuaternion() 関数を使用します。D3DXMatrixRotationQuaternion() 関数は、クォータニオンの計算を行列に変換します。D3DXMatrixRotationQuaternion() 関数を使用するソースコードの例を次に示します。
D3DXQuaternionRotationAxis(&qt, &v, d); //クォータニオンを回転
D3DXMatrixRotationQuaternion(&mat, &qt); //クォータニオンの計算を行う行列を作成
m_tmat *= mat; //座標変換を合成上記の例では、D3DXMatrixRotationQuaternion() 関数により回転クォータニオンの計算を行列に変換し、他の座標変換と合成しています。
回転処理は、常に絶対座標系を基準として回転を行われます。しかし、ゲームプログラムでは、3D モデルを中心とした回転、つまり 3D モデルを中心とした座標系を基準に回転を行う場合が多くあります。例えば、フライトシミューレータでは、飛行機の機首の上げ下げや左右への旋廻などの場合、3D モデルを中心とした回転を行います。3D モデルを中心とした回転は、第 1.2.3 項 (2) で解説した、クォータニオンによる回転処理を利用することにより容易に実現できます。
実際に 3D モデルを中心とした回転を行う場合、3D モデルの姿勢を表現するベクトルが必要です。3D モデルの姿勢を表現するベクトルは姿勢ベクトルと呼ばれます。姿勢ベクトルは、3D モデルの回転に合わせて回転し、常に 3D モデルの姿勢を追随するようなベクトルです。姿勢ベクトルを軸として回転を行うことにより、3D モデルを基準とした回転が実現できます。
しかし、フライトシミュレータにおける飛行機の姿勢制御のような処理を行う場合は、回転の軸として使用するベクトルを用意する必要はなく、座標変換行列をそのまま利用することができます。座標変換行列から 3D モデルの姿勢ベクトルを取り出す方法を図 5 に示します。
.gif)
図 5 : 3D モデルの姿勢を表すベクトル
図 5 に示した例では、3D モデル A に対して行列 M を使用して座標変換を行っています。図 5 に示したように、座標変換行列から取り出した 3 つのベクトルをそれぞれベクトル Vx, ベクトル Vy, ベクトル Vzとします。ベクトル Vx, ベクトル Vy, ベクトル Vz は、3D モデルのローカル座標系のX 軸、Y 軸、Z 軸の向きを表しています。そこで、ベクトル Vx, ベクトル Vy, ベクトル Vz を軸として回転処理を行うことにより、3D モデルを基準とした回転を実現できます。座標変換行列から取り出したベクトルを軸として回転処理を行うソースコードの例を次に示します。
D3DXQUATERNION qt; //クォータニオン
D3DXVECTOR3 v(0.0f, 1.0f, 0.0f); //回転の軸を表すベクトル
D3DXMATRIX mat; //行列
//単位クォータニオンで初期化
qt.x = qt.y = qt.z = 0;
qt.w = 1.0f;
//座標変換行列から姿勢ベクトルを取り出す
v.x = m_tmat._11;
v.y = m_tmat._12;
v.z = m_tmat._13;
D3DXQuaternionRotationAxis(&qt, &v, d); //クォータニオンを回転
D3DXMatrixRotationQuaternion(&mat, &qt); //ベクトルとクォータニオンの計算の式を行列に変換
m_tmat *= mat; //座標変換を合成座標変換の処理の計算量は、大量の頂点によって構成される 3D モデルの座標変換を行う場合、膨大になります。そのため、プログラムの実行速度向上のためには、座標変換の回数を少なく抑える必要があります。
座標変換の合成は、座標変換の回数を少なく抑えるための最も基本的な方法です。座標変換の合成の例を図 6 に示します。
.gif)
図 6 : 座標変換の合成
図 6-a、および図 6-b のどちらも、座標変換の結果は同じになります。図 3 の例のように、あらかじめ変換を合成しておくことにより、複数の座標変換の計算を一回で行うことができ、プログラムの実行速度は飛躍的に向上します。
D3DXMATRIX 構造体は、いくつかの演算子をオーバーロードされています。行列の計算を行うソースコードは、オーバーロードされた演算子を使用することにより簡潔になります。オーバーロードされた演算子を使用したソースコードの例を次に示します。
D3DXMATRIX m1, m2;
(中略)
m1 *= m2;上記の例は、*= 演算子によって行列 m1 と行列 m2 の積を m1 に代入しています。
D3DXMATRIX m1, m2, m3, m4;
(中略)
m1 = m2 * m3 * m4;上記の例は、* 演算子を使用してm2、m3、m4、の積を m1 に代入しています。
ただし、ソースコードを C 言語で記述している場合は、C++ 特有の機能である演算子のオーバーロードは使用できません。
3D モデルの表示の処理では、射影変換とラスタライズの 2 つの処理を順番に行う必要があります。本節では、射影変換とラスタライズについて解説します。
平面のスクリーンに 3D グラフィックスを表示するためには、3D 空間上の各点がスクリーン上のどの点に対応するかを決定する必要があります。スクリーン上に点を配置する作業が射影変換です。射影変換は、3D 座標を 2D 座標に変換することです。
Direct3D では、射影変換行列と呼ばれる行列を設定することにより、自動的に射影変換が行われます。射影変換行列を作成するルーチンは、Direct3DX にヘルパー関数として用意されています。射影変換行列を設定するソースコードの例を次に示します。
static LPDIRECT3DDEVICE9 s_lpD3DDevice = NULL; //Direct3DDevice9オブジェクトへのポインタ
//※Direct3D初期化の段階でs_lpD3Ddeviceは有効な値が設定されているとします
BOOL D3D9_SetProjection(float angle, float nearclip, float backclip)
{
D3DXMATRIX mat; //射影変換行列を格納する構造体
D3DXMatrixPerspectiveFovLH(&mat, angle, 1.0f, nearclip, backclip); //射影変換行列を生成
s_lpD3DDevice->SetTransform(D3DTS_PROJECTION, &mat); //レンダリングパイプラインに射影変換行列を設定
return TRUE; //成功、TRUEを返す
}ラスタライズとは、射影変換で得られた結果を画面に表示可能な形式であるラスタデータに変換する処理です。
Direct3D でラスタライズ処理を行う場合、いくつかの方法があります。本稿では、DrawSubset() メソッドを使用します。DrawSubset() メソッドを使用することは、最も簡単なラスタライズの方法です。DrawSubset() メソッドを使用して 3D モデルをラスタライズするソースコードの例を次に示します。
//マテリアルの数だけループ。m_nMaterialsはXファイルをロードした時点で設定されています
for (unsigned int u = 0;u < m_nMaterials;u++)
m_lpMesh->DrawSubset(u); //メッシュを描画ゲームプログラムは、一般的なアプリケーションとは異なった方法で処理を進めるため、ソースコードもゲームプログラム特有の記述を行います。本節では、一般的な Windows アプリケーションのソースコードとゲームのソースコードを比較し、ゲームプログラムにおけるソースコードの記述方法を解説します。本節のソースコードの例は windows.h および winmm.h をインクルードする必要があります。
一般的な Windows のアプリケーションで用いられているウィンドウメッセージを使用したキー入力は、DirectX を使用するアプリケーションにおいても問題なく行うことができます。しかし、ゲームプログラムではリアルタイム処理を行うため、ゲームループと呼ばれる特殊なメッセージループを実装するのが一般的です。ゲームループのソースコードの例を次に示します。
while(TRUE) //ゲームループ開始
{
//メッセージキューにウィンドウメッセージが置かれているか調べる
if (PeekMessage(&msg , NULL , 0 , 0 , PM_NOREMOVE))
{
//ウィンドウメッセージを取得、置かれていたウィンドウメッセージがWM_QUITの場合はループを脱出
if (!GetMessage(&msg , NULL , 0 , 0))
break;
TranslateMessage(&msg); //ウィンドウメッセージを変換
DispatchMessage(&msg); //ウィンドウメッセージを送出
}
else
{
if (g_bAppActive) //このアプリケーションがアクティブであることを確認
{
}
}
}PeekMessage() 関数は、メッセージキュー内のメッセージの有無を調べる関数です。メッセージキューとは、ウィンドウに対して送られたメッセージが処理されるまでの間一時的に置かれる領域です。あらかじめメッセージの有無を調べておくことにより、GetMessage() 関数がメッセージを待機してプログラムの実行が中断することを防ぐことができます。
一般的なアプリケーションでは、一連のウィンドウメッセージの処理が終了すると次のウィンドウメッセージが送られてくるまで待機します。そのため、一般的なアプリケーションでは、PeekMessage() 関数を使用する必要はありません。しかし、ゲームプログラムではウィンドウメッセージを待機する代わりにゲームの処理を進めることによりリアルタイム処理を実現します。そこで、ゲームプログラムでは通常のメッセージ処理の代わりにゲームループを実装します。ゲームループのイメージを図 7 に示します。
.gif)
図 7 : ゲームループのイメージ
第1.4.1 項で解説したように、ゲームプログラムでは、ゲームループを実装することにより、ウィンドウメッセージを待機せずに処理を進行します。そこで、ゲームプログラムで入力処理をする場合は、Windows からユーザの入力を通知してもらうのではなく、プログラムの側から能動的にキーボードやマウスの状態を調べるというスタイルをとります。プログラムの側が常にイベントの発生を監視することをポーリングと呼びます。
Windows 上でポーリングによる入力処理を行う場合に使用する API を表 2 に示します。
表 2 : ポーリングによる入力処理で使用する主な API
| Win32API 関数 | 解説 |
| BOOL GetCursorPos(LPPOINT lpPoint); | 現在のマウスカーソルの位置を取得します。 |
| SHORT GetAsyncKeyState(int vKey); | 指定したキーの現在の押下状態を取得します。 |
| MMRESULT joyGetPosEx(UINT uJoyID, LPJOYINFOEX pji); | 現在のジョイスティックの状態を取得します。 |
キーボードからの入力をポーリングする場合は、GetAsyncKeyState() 関数を使用します。GetAsyncKeyState() 関数を使用することにより、2 つ以上のキーが同時に押された場合も正しく判定することができます。GetAsyncKeyState() 関数を使用してキー入力を行うソースコードの例を次に示します。
DWORD k;
k = GetAsyncKeyState(VK_SPACE); //スペースバーの押下状態を取得
if (k & 0x8000) //最上位ビットが立っていればキーが押されている
{
}マウスからの入力を監視する場合は、カーソルの位置を GetCursorPos() 関数、ボタンの状態を GetAsyncKeyState() 関数を使用して取得します。マウスカーソルの現在位置の座標を取得するソースコードの例を次に示します。
POINT pt; //座標を格納する構造体
GetCursorPos(&pt); //マウスカーソルの現在位置の座標を取得次に示すソースコードは、GetAsyncKeyState() 関数を使用してマウスボタンの状態を取得する例です。
if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) //APIの戻り値の最上位ビットが立っていればボタンが押されている
{
//左ボタンが押された場合の処理
}
if (GetAsyncKeyState(VK_RBUTTON) & 0x8000) //APIの戻り値の最上位ビットが立っていればボタンが押されている
{
//右ボタンが押された場合の処理
}Windows アプリケーションでは、ジョイスティックの入力も標準の API で処理することができます。ジョイスティックのレバー (方向キー) の状態を調べるソースコードを次に示します。
#define BORDER_LOW (32768 - 4096) //スティックの閾値 (上、左) を定義
#define BORDER_HIGH (32768 + 4096) //スティックの閾値 (下、右) を定義
JOYINFOEX ji; //ジョイスティックの状態を格納する構造体
ji.dwSize = sizeof(ji); //swSizeメンバを初期化
::joyGetPosEx(0, &ji); //joyGetPosEx APIを呼び出してジョイスティックの状態を取得
if (ji.dwXpos < BORDER_LOW) //スティックのX座標が閾値以下か調べる
{
//左が押された場合の処理
}
if (ji.dwXpos > BORDER_HIGH) //スティックのX座標が閾値以上か調べる
{
//右が押された場合の処理
}本章では、3D グラフィックスの基礎となる行列による座標変換と、より高度な回転処理を行うためのクォータニオンについて解説しました。また、ゲームプログラム特有の構造や、特殊な WindowsAPI についても解説しました。これにより、DirectX によって 3D モデルを画面に表示し、ユーザからの入力を受け付けるプログラムの作成が可能になりました。
次回は、視点の扱い方と、キャラクターの管理の方法について解説します。
| 1986 年 | 関西大学工学部土木工学科卒業 |
| 1988 年 | 関西大学大学院工学研究科 土木工学専攻博士課程前期課程修了 |
| 1996 年 | 博士 (工学) 授与,関西大学 |
| 1997 年 | 関西大学総合情報学部助教授 (現在に至る) |
| 主な著書: | やさしい C のはじめかた,オーム社,1993 年 |
| 建設技術者のための知識情報処理の実践,関西大学出版部,1999 年 | |
| DirectX8,工学社,2001 年 | |
| ステップアップ XML,工学社,2002 年 | |
| Linux アプリケーション入門,森北出版,2002年 ほか |
| 2001 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2003 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 |
| 2003 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) |
| 主な著書: | Web 工房シリーズ Perl の達人,森北出版,1999 年 |
| 決定版 Visual Basic,共立出版,2000年 | |
| DirectX8,工学社,2001 年 | |
| Linux アプリケーション入門,森北出版,2002 年 | |
| ステップアップ Visual C# .NET 入門,工学社,2002 年 ほか |
| 2000 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
| 主な著書: | DirectX8 & VC++ 3D の基礎とゲームの作り方,工学社,2002年 |
| 2000 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2002 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 |
| 2002 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) |
| 主な著書: | Web 工房シリーズ Java の達人,森北出版,1999 年 |
| デジカメ活用によるデジタル写真測量入門,森北出版,2000 年 | |
| ステップアップ XML 活用法,工学社,2002 年 |
| 2002 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
| 2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) |
| 主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |
| 2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) |
| 主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |