第 3 章 DirectX Graphics の特殊効果
〜 DirectX によるゲームプログラミング入門 〜
サンプルプログラムのダウンロード (msdnaa_directx03.exe、6.02 MB)
3.1 はじめに
近年、ゲーム開発の技術は、目覚しい発展を遂げ、世界全体に大きな影響を与えてきました。特に、ゲーム開発の技術の中でも、3D グラフィックス処理技術の発展が顕著に見られます。現在、ゲーム開発において、3D グラフィックス処理は、必要不可欠な技術になりました。そのような背景の中、3D グラフィックスを用いたゲームプログラムの開発環境として、DirectX が注目されています。
第 1 章と第 2 章では、3D ゲーム開発の基礎となるオブジェクトの操作やキャラクタの管理について解説しました。本章では、ゲームをより魅力的にするために、視覚的な効果を得る目的で使用する特殊効果について解説します。特に、本章では、3D シューティングゲームにおいて重要な要素である爆発について詳しく解説します。3D シューティングゲーム作成に必要な技術を図 1 に示します。

図 1 3D シューティングゲーム作成に必要な技術
3.2 ポリゴンの描画
Direct3D では、3D モデルの描画にポリゴンを使用します。ポリゴンとは、複数の頂点によって形成された多角形のことです。DirectX では、3D モデルを表示するために、3 つの手順を踏みます。まず、ポリゴンを形成する頂点データを 3 次元空間に設定します。次に、ポリゴンを描画する方法を指定します。最後に、ポリゴンを描画し、ラスタライズを行います。ラスタライズとは、3 次元空間上のポリゴンを画面に表示可能な2次元画像に変換する処理です。ラスタライズの詳細については、本連載の第 1 章を参照してください。
Direct3D の 3D モデルを描画する方法は 2 種類あります。1 つ目は、X ファイルから 3D モデルのデータを読み込む方法です。2 つ目は、頂点データを指定する方法です。頂点データについては、第 3.2.1 項を参照してください。開発者は、頂点データを指定して 3D モデルを描画することにより、特殊な映像や効果を表現できます。本節では、頂点データを指定して 3D モデルを描画する方法について解説します。
3.2.1 頂点データ
Direct3D では、ポリゴンを形成する各頂点に様々な情報を付加できます。この付加された情報を頂点データと言います。本項では、各頂点に付加できる情報について解説します。
(1) 3 次元座標
3 次元座標は、頂点の 3 次元空間内での座標を示す情報です。3 次元座標は、X 座標、Y 座標と Z 座標の 3 つの値から構成されています。Direct3D では、3 次元座標に対してワールド変換、ビュー変換および射影変換を行います。
(2) 同次座標
同次座標は、3 次元座標に射影変換を適用し、2次元座標に投影された座標です。同次座標を使用することにより、直接画面上に文字や 2 次元の画像を表示することができます。
(3) 法線ベクトル
法線ベクトルは、頂点によって形成される面に対して垂直なベクトルです。法線ベクトルは、面の光沢や光の反射度などを算出するために使用します。法線ベクトルを使用することにより、湾曲の面や凹凸のある面を表現できます。
(4) ディフューズ色とスペキュラ色
ディフューズ色とスペキュラ色は、3D モデルの表面色を表現するために使用します。ディフューズ色とスペキュラ色は、透明度を表現するアルファ値と色を表現する RGB 値を含む 32 ビットの値です。Direct3D では、ポリゴンを描画する場合に、ディフューズ色とスペキュラ色を加算した色を使用します。ディフューズ色とスペキュラ色の用途を表 1 に示します。
表1 ディフューズ色とスペキュラ色の用途
| 色の種類 | 用途 |
| ディフューズ | 3D モデルの表面の素材の色を設定 |
| スペキュラ | 3D モデルの表面の光沢を設定 |
(5) UV 座標
UV 座標は、ポリゴンに対してテクスチャを貼る際に使用する情報です。UV 座標を使用してテクスチャを貼ることにより、テクスチャの任意の部分をポリゴンに貼ることができます。
UV 座標は、U 座標、V 座標の 2 つの値から構成されます。Direct3D は、テクスチャの適用されたポリゴンを描画する場合、ポリゴンの各頂点の UV 座標とテクスチャ画像の UV 座標を対応させて描画します。UV 座標の概念を図 2 に示します。

図 2 UV 座標の概念
開発者は、ポリゴンを利用目的に即した形式で描画するために、上記 (1) 同時座標から (5) UV 座標の情報を組み合わせた頂点データを設定します。頂点データの設定には、排他関係や設定順など様々な注意が必要です。頂点データの設定方法を図 3 に示します。

図 3 頂点データの設定方法
開発者は、ポリゴンを描画する場合、頂点データの設定に関する情報を指定する必要があります。Direct3D は、指定された頂点データの設定情報を基に描画処理を行います。頂点データの設定情報は、定数の組み合わせによって指定されます。頂点データの設定情報を指定するための定数の一覧を表 2 に示します
表 2 定数の一覧
| 定数 | 解説 |
| D3DFVF_XYZRHW | 頂点データが同次座標を含む |
| D3DFVF_XYZ | 頂点データが 3 次元座標を含む |
| D3DFVF_DIFFUSE | 頂点データがディフューズ値を含む |
| D3DFVF_SPECULAR | 頂点データがスペキュラ値を含む |
| D3DFVF_NORMAL | 頂点データが法線ベクトルを含む |
D3DFVF_TEX0 D3DFVF_TEX1
D3DFVF_TEX2 D3DFVF_TEX3
D3DFVF_TEX4 D3DFVF_TEX5
D3DFVF_TEX6 D3DFVF_TEX7
D3DFVF_TEX8 |
頂点データが対応するテクスチャの枚数に応じて、いずれかを使用します。ただし、D3DFVF_TEX0 は、テクスチャの情報を持たないことを意味します。 |
開発者は、Direct3D の文法に従った頂点データの構造体を定義することにより、ポリゴンの描画形式を指定できます。頂点データの構造体の定義の例を図 4 に示します。

図 4 頂点データの構造体の定義の例
3.2.2 インデックスデータ
Direct3D では、頂点データの配列を使用してポリゴンを描画する場合、3 つの手順を踏みます。まず、ポリゴンを構成する頂点データを配列で指定します。次に、頂点データの配列の先頭から順番にデータを読み込みます。最後に、読み込んだ頂点データを使用してポリゴンを描画します。Direct3D が頂点データの配列を使用してポリゴンを描画する例を図 5 に示します。

図 5 頂点データの配列を使用してポリゴンを描画する例
図 5 では、複数のポリゴンが共有する同一の頂点データを重複して保存しています。そのため、処理を重複して行う必要があり、プログラムの処理速度が著しく低下します。そこで、頂点データの重複を避けるためにインデックスデータを使用します。インデックスデータとは、ポリゴンの頂点の番号です。インデックスデータの概念を図6に示します。

図 6 インデックスデータの概念
図 6 では、インデックスデータの順番に従って頂点データの読み込みを行なっています。そのため、重複して処理を行う必要がなくなり、処理速度の高速化につながります。
3.2.3 描画処理の流れ
Direct3D は、ポリゴンを描画する場合、頂点データやインデックスデータをシステムメモリ上からビデオカードへ転送します。Direct3D によって転送された情報を基にポリゴンの描画処理を行います。一般的な描画処理の流れを図 7 に示します。

図 7 一般的な描画処理の流れ
一般的な描画処理では、システムメモリ上からビデオカードへ情報を転送するため、システムに多大な負担がかかります。また、ビデオカードへ情報を転送する負荷は、プログラムの動作速度にも大きな影響を与えます。
Direct3D には、システムメモリ上からビデオカードへ情報を転送する負荷を軽減する方法として、ビデオカードに直接情報を保存する方法があります。ビデオカードに情報を保存する流れを図 8 に示します。

図 8 ビデオカードに情報を保存する流れ
ビデオカードは、ビデオメモリと呼ばれる記憶領域を搭載しています。ビデオメモリとは、フレームバッファやテクスチャ画像を保持するための記憶領域です。また、ビデオメモリには、頂点データやインデックスデータを確保するための領域として、それぞれ頂点バッファとインデックスバッファを搭載しています。開発者は、それぞれのバッファに情報を保持することにより、プログラムの動作速度を大幅に向上できます。
3.2.4 DrawPrimitive メソッドによる描画
Direct3D には、ポリゴンを描画するための様々な関数が用意されています。本項では、ビデオカードのバッファから情報を取得し、ポリゴンを描画する関数について解説します。
Direct3D では、頂点バッファから頂点データを取得してポリゴンを描画する場合、IDirect3DDevice9 インターフェイスの DrawPrimitive メソッドを使用します。DrawPrimitive メソッドの構文を次に示します。
HRESULT DrawPrimitive(
D 3D PRIMITIVETYPE PrimitiveType,// 図形の種類
UINT StartVertex, // 最初の頂点のインデックス
UINT PrimitiveCount// 描画する図形の数
);
また、インデックスバッファを使用してポリゴンを描画する場合は、IDirect3DDevice9 インターフェイスの DrawIndexedPrimitive メソッドを使用します。DrawIndexedPrimitive メソッドの構文を次に示します。
HRESULT DrawIndexedPrimitive(
D 3D PRIMITIVETYPE Type,// 図形の種類
INT BaseVertexIndex//頂点データのインデックス
UINT MinIndex, // 最小の頂点のインデックス
UINT NumVertices, // 頂点の数
UINT StartIndex, // 最初のインデックスデータのインデックス
UINT PrimitiveCount // 描画する図形の数
);
3.3 アルファ ブレンディング
アルファ ブレンディングとは、画像やオブジェクトの透明度を表現するアルファ値に基づいて、2 つの画像を合成する処理です。アルファ ブレンディングを使用することにより、単純なガラス板から、形のない水、雲や煙などを表現できます。ゲームプログラミングにアルファ ブレンディングを取り入れることにより、ガラス越しの背景や水の中の魚など多彩なシーンを表現できます。
DirectX には、アルファ ブレンディングを使用した様々な技術があります。本節では、アルファ ブレンディングの一般的な技術である、半透明処理と加算半透明処理について解説します。
3.3.1 半透明処理
半透明処理は、2 つの画像を重ねて透かしたような効果を表現します。開発者は、半透明処理を利用することにより、透明な 3D モデルなどを表現できます。また、ゲームに半透明処理を取り入れることにより、フェードインやフェードアウトなどの特殊効果を実現することができます。半透明処理の例を図 9 に示します。

図 9 半透明処理の例
3.3.2 加算半透明処理
加算半透明処理は、一枚の画像の上に、別の画像を映写機で投影したような効果を表現します。加算半透明処理を利用することにより、炎、爆発や稲妻などの特殊効果を実現することができます。加算半透明の例を図 10 に示します。

図 10 加算半透明の例
3.3.3 Z バッファ法の問題
(1) Z バッファ法
Direct3D では、ポリゴンを描画する場合、Z バッファ法を使用します。Z バッファ法とは、前面のポリゴンに隠れているポリゴンを検出して、描画処理を行わないようにする陰面消去法の 1 つです。DirectX では、各ポリゴンが保持する Z 値により、ポリゴンの前後関係を表現します。ポリゴンの描画例を図 11 に示します。

図 11 ポリゴンの描画例
(2) Z バッファ法の問題点
Z バッファ法は、半透明処理や加算半透明処理を実現する場合、ポリゴンの描画処理に問題が生じます。図 11 のポリゴンに半透明処理を適用した例を図 12 に示します。

図 12 Z バッファ法の問題点
図 12 では、後面のポリゴンが陰面消去処理によって描画されません。半透明のポリゴンの後面に位置するポリゴンを描画するためには、ポリゴンの描画順を変更する必要があります。半透明のポリゴンを含む 3D モデルの描画手順を図 13 に示します。

図 13 半透明のポリゴンを含む 3D モデルの描画手順
半透明のポリゴンの後面に存在するポリゴンを表示するためには、適切な順序で描画処理を行う必要があります。図 12 のポリゴンを適切な手順で描画した例を図 14 に示します。

図 14 半透明のポリゴンの描画例
3.4 特殊効果の実装
本節では、ポリゴンの描画機能とアルファ ブレンディングを利用して、爆発と花火の特殊効果を表現する方法について解説します。
3.4.1 爆発のサンプルプログラム
本項では、爆発について解説します。Direct3D では、爆発を実現する方法が数多く存在します。本項では、最も簡単に爆発を実現できる方法について解説します。ゲームプログラミングに爆発を取り入れることにより、迫力のある爆発シーンを演出できます。爆発のサンプルプログラムの画像を図 15 に示します。

図 15 爆発のサンプルプログラム
(1) 爆発の原理
爆発は、複数の画像を加算半透明処理によって重ね合わせることによって表現します。爆発に使用する画像の例を図 16 に示します。

図 16 爆発の画像
爆発のサンプルプログラムでは、図 16 の画像を拡大しながら平行移動させることにより、火球が周囲に拡大する様子を表現しています。また、画像の不透明度を減少させることにより、爆発がフェードアウトする様子を表現しています。
(2) 爆発の実装
爆発は、複数の画像に対して、拡大縮小、平行移動やアルファ ブレンディングの処理を行って表現しています。サンプルプログラムでは、第 2 章で解説したキャラクタ管理の手法を利用して、複数の画像を個別に管理しています。また、ポリゴンに図 16 の画像をテクスチャとして貼り、爆発を演出しています。
3.4.2 花火のサンプルプログラム
本項では、花火について解説します。ゲームプログラミングに花火の技法を応用して取り入れることにより、シューティングゲームの弾丸やレーザーなど迫力のある戦闘シーンを実現できます。花火のサンプルプログラムの画像を図 17 に示します。

図 17 花火のサンプルプログラム
(1) 花火の原理
花火は、爆発と同時に多数の火球を周囲に飛び散らせ、迫力のあるシーンを演出します。花火のサンプルプログラムでは、花火を演出するために、キャラクタ管理の処理を使用して、多数のキャラクタを飛び散らせる処理を行っています。多数のキャラクタを使用して実現する特殊効果をパーティクル処理と呼びます。ゲームプログラミングの世界において、パーティクル処理は、火花や煙などの多くの特殊効果を表現するために幅広く利用されています。
花火のサンプルプログラムでは、花火の火球が光の尾を引いて飛ぶ様子を表現します。そのため、各キャラクタは、キャラクタの移動した軌跡を保持します。そして、各キャラクタの軌跡を直線で結ぶことによって光の尾を表現します。光の尾の表現方法を図 18 に示します。

図 18 光の尾の表現方法
(2) 花火の実装
花火のサンプルプログラムでは、キャラクタ管理を使用してパーティクル処理を実現しています。キャラクタの情報には、移動の軌跡を保持するためのリングバッファとして配列変数があります。リングバッファとは、配列の先頭と末尾が結合した環状リストです。花火のキャラクタ管理の構造体の定義を次に示します。
#define LENGTH_POSLOG 40 // リングバッファの長さ
// キャラクタ管理用の構造体
typedef struct _tagFWParticle
{
//移動履歴のリングバッファ
struct _tagPOSLOG
{
D3DXVECTOR3 v;
} m_PosLog[LENGTH_POSLOG];
float m_fX, m_fY, m_fZ; // 位置ベクトル
float m_fVX, m_fVY, m_fVZ; // 速度ベクトル
DWORD m_dwColor; // 色
int m_iPLog; //移動履歴の現在の書き込み位置
BOOL m_bActive; //ノード使用中フラグ
int m_iAlpha; // アルファ値
int m_iFCount; // フレーム数カウント
int m_iColorH; // 色相
BOOL GetLog(int offset, D3DXVECTOR3* v); //移動履歴を取得するメソッド
BOOL AddLog(D3DXVECTOR3* v); //移動履歴にデータを追加するメソッド
} FWParticle;
花火のサンプルプログラムでは、各キャラクタの移動の軌跡を線分で描画します。線分の描画は、Direct3D の機能を利用して実現できます。しかし、Direct3D の線分には、1 ドットの幅の線分しか描画できないため、表現に限界があります。そこで、花火のサンプルプログラムでは、ポリゴンを利用して線分を描画します。ポリゴンを利用して線分を描画することにより、花火の光の尾やシューティングゲームの弾丸など、より複雑な線分を実現できます。ポリゴンを利用して線分を描画する方法を図 19 に示します。

図 19 ポリゴンを利用して線分を描画する方法
点 A と点 B 間を結ぶ線分を描画するためには、6 つの手順を踏む必要があります。第 1 に点 A から点 B を指すベクトルをベクトル AB とします。第 2 にベクトル AB を正規化しベクトル AC を求めます。第 3 にベクトル AC を -90 度、90 度回転し、ベクトル AD とベクトル AG を求めます。第 4 にベクトル ADとベクトル AG をそれぞれ描画する線分の幅で実数倍し、ベクトル AH とベクトル AK を求めます。第 5 にベクトル AH とベクトル AK を点 B まで平行移動し、ベクトル BI とベクトル BJ を求めます。第 6 に三角形 HIK と三角形 KIJ をポリゴンで描画し、線分を描画します。光の尾を表現するコード例を次に示します。
// 連続した線分を描画する
#define MAX_PLV 40
BOOL DrawPolyLine(FWVERTEX* pVertices, int iVerticesCount,
int width, LPDIRECT3DDEVICE9 pDevice)
{
int i; // ループカウンタ
D3DXVECTOR2 v, vD, vN1, vN2; // 計算用のベクタ
FWVERTEX fwv[MAX_PLV]; // 頂点データの配列
int nFWVCount = 0; // 描画する頂点の数。初期値は0
int k = 0; // 書き込み対象の配列インデックス
// 頂点の数が2より小さい場合は描画できない。FALSEを返して終了
if (iVerticesCount < 2)
return FALSE;
for (i = 0;i < iVerticesCount;i++)
{
// 最後の頂点以外の場合
if (i < (iVerticesCount - 1))
{
// 現在の頂点から次の頂点を指すベクトルをvとする
v.x = pVertices[i+1].fX - pVertices[i].fX;
v.y = pVertices[i+1].fY - pVertices[i].fY;
// ベクトルvを正規化したものをベクトルvDとする
D3DXVec2Normalize(&vD, &v);
// vDに幅widthをかける
vD *= (float)width;
// ベクトルvDを-90度回転させたものをベクトルvN1とする
vN1.x = -vD.y;
vN1.y = vD.x;
// ベクトルvDを90度回転させたものをベクトルvN2とする
vN2.x = vD.y;
vN2.y = -vD.x;
}
k = i*2; //書き込み対象の配列インデックスは[頂点のインデックス×2]
// 位置を設定
fwv[k].fX = pVertices[i].fX + vN1.x;
fwv[k].fY = pVertices[i].fY + vN1.y;
fwv[k].fRHW = 1.0f; // RHW値は1.0固定
fwv[k].dwDiffuse = pVertices[i].dwDiffuse; //ディフューズ値をコピー
// UV座標を設定
fwv[k].tu = 0.1f;
fwv[k].tv = 0.1f;
++k; // 配列インデックスをひとつインクリメント
// 位置を設定
fwv[k].fX = pVertices[i].fX + vN2.x;
fwv[k].fY = pVertices[i].fY + vN2.y;
fwv[k].fRHW = 1.0f; // RHW値は1.0固定
fwv[k].dwDiffuse = pVertices[i].dwDiffuse; // ディフューズ値をコピー
// UV座標を設定
fwv[k].tu = 0.9f;
fwv[k].tv = 0.1f;
nFWVCount += 2; // 描画する頂点の数が2つ増加
}
pDevice->SetFVF(FVF_FW); // 頂点フォーマットを設定
// レンダーステート 深度バッファ無効
pDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
// レンダーステート カリングなし
pDevice->SetRenderState(D3DRS_CULLMODE , D3DCULL_NONE);
pDevice->BeginScene(); // 描画を開始
pDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, (iVerticesCount-1)*2,
fwv, sizeof(FWVERTEX)); // ポリゴンを描画
pDevice->EndScene(); // 描画を終了
// レンダーステート 背面カリング有効(デフォルト値に戻しておく)
pDevice->SetRenderState(D3DRS_CULLMODE , D3DCULL_CCW);
// TRUEを返して終了
return TRUE;
}
3.5 まとめ
本章では、ゲームを演出するための 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 年 |
|