ホーム < ゲームつくろー! < DirectX技術編 < 3Dオブジェクト描画のおさらい


その7 3Dオブジェクト描画のおさらい


 Direct3Dはその名の通り3D描画に特化しています。ですから、3D描画が出来なくてはせっかくの道具も台無しになってしまいます。ここでは、典型的な3D描画の方法をまとめてみます。


@ Direct3DとDirect3DX(エクステンション)

 DirectXには、Direct3DとDirect3DXという2つの3D描画方法が用意されています。Direct3DXはDirect3Dの機能を吸収して扱いやすくした上位レベルのインターフェイス群です。使いやすくした分、カスタマイズ性は下がってしまいますが、それでも用意されたインターフェイスで十分に細かいことを行うことが出来ます。Direct3Dは3D描画のための下位レベルインターフェイス群で、言ってみれば何でも出来ます。あるレベル以上のゲームを作ろうと思ったら、Direct3Dを中心にプログラムを組むことになるでしょう。



A Direct3DX(エクステンション)を使う方法


 まずは簡単な方から見てみましょう。3Dを描画するためには、まず「物(3Dオブジェクト)」が必要です。物の正体はポリゴンの集まりで、手作業で作る事も出来ますし、外部ファイルから取り込むことも出来ます。一番手っ取り早いのは3Dモデリングソフトでオブジェクトを作成し、それを「Xファイル形式」で保存してDirect3DXが用意してくれているインターフェイスで読み込む事です。

 XファイルはDirect3DX(エクステンション)のヘルパー関数であるD3DXLoadMeshFromX関数によってダイレクトに読み込む事が可能です。

HRESULT D3DXLoadMeshFromX(
 LPCTSTR pFilename,
 DWORD Options,
 LPDIRECT3DDEVICE9 pDevice,
 LPD3DXBUFFER* ppAdjacency,
 LPD3DXBUFFER* ppMaterials,
 LPD3DXBUFFER* ppEffectInstance,
 DWORD* pNumMaterials,
 LPD3DXMESH* ppMesh
);

pFilenameはXファイル形式で保存されているファイルへのパスです。フルパスでも相対パスでも大丈夫ですが、通常は相対パスにします。
Optionsは頂点バッファをどういう形式で作成するかを決めるオプションで、D3DXMESHにその定義があります。D3DUSAGEとD3DPOOLを合わせたようなフラグが沢山ありますが、通常はD3DXMESH_MANAGEDを設定しておけば問題ありません。このフラグはメモリが空いていれば頂点をVRAMに作成し管理してくれます。
pDeviceはIDirect3DDevice9へのポインタです。
ppAdjacencyには「隣接性データ」と呼ばれるポリゴン情報が返されます。これは、1つのポリゴンに隣接する3つのポリゴンを表す9つの頂点データを一まとめにし、それをすべてのポリゴンについて連続的に記述したものです。これを受けるのがID3DBufferインターフェイスです。ID3DBufferは様々なデータをメモリブロックの形で格納でき、そのアクセス法を提供するメモリ管理インターフェイスです。あまり使わないデータです。NULL指定ができます。
ppMaterialsには「マテリアル」と呼ばれるポリゴンの質とテクスチャに関する情報が格納された固定長メモリをD3DXMATERIAL構造体として得る事が出来ます。マテリアルとはポリゴンの色(Diffuse)、環境光反射率(Ambient)、反射率(Specular)、発光率(Emissive)の事です。D3DXMATERIAL構造体にはこれにテクスチャへのファイル名が追加されています。1つのオブジェクトに対してマテリアルが2つ以上設定されていることも結構あります。その場合、ppMaterialsはD3DXMATERIAL構造体の配列へのポインタとなります。
ppEffectInstanceは「エフェクト」と呼ばれるレンダリング効果をカプセル化したインスタンスの配列へのポインタを取得できます。エフェクトというのは、レンダリングする時に普通では出来ないような事を外部で生成したプログラムで行う一連の方法の事でして、大変に、大変に高度な物ですかr、今はとても触れられません。ここではあまり気にせずにID3DBufferへのポインタを渡すか、NULL指定してください。
pNumMaterialsにはppMaterialsで取得できるD3DXMATERIAL構造体の数が格納されます。1つのオブジェクトのポリゴンは「属性グループ」という複数ポリゴンの塊で1つのマテリアルを共有しています。属性グループが複数ある事はしょっちゅうでして、その場合マテリアルが沢山格納されています。ここはその数が返るわけです。
ppMeshにXファイルから生成されたID3DXMeshオブジェクトが格納されます。「メッシュ」というのは三角形ポリゴンの集まりの事で、ID3DXMeshインターフェイスによってその情報を取り出したり変更したり出来ます。


 典型的な使い方の例を示します。

ID3DXBuffer *pMaterials;
DWORD NumMaterials;
ID3DXMesh pMesh;

HRESULT hr = D3DXLoadMeshFromX(
 "Object.x",
 D3DXMESH_MANAGED,
 pD3dDev,
 NULL,
 &pMaterials,
 NULL,
 &NumMaterials,
 &pMesh
);

if( FAILED(hr) )
   return 0;


 D3DXLoadMeshFromX関数によってメッシュデータを格納できたら、描画するのは非常に簡単です。極端な話以下のプログラムだけで描画できます。

for(int i=0; i<NumMaterials; i++){
   D3DXMATERIAL mat = ( (D3DXMATERIAL*)(pMaterials->GetBufferPointer()) )[i];
   pD3dDev->SetMaterial( &(mat.MatD3D) );
   pD3DMesh->DrawSubset(i);
};

 先に説明したのですが、1つのポリゴンオブジェクトは複数のサブセット(属性グループ:マテリアルを共有するポリゴンの塊)に分かれています。描画は、そのサブセット単位で行わなければなりません。そのため、マテリアルの数(NumMaterials)だけループさせています。
 pMaterialsは、ppMaterialsで返されるマテリアル配列へのポインタを格納するID3DXBufferオブジェクトポインタです。このインターフェイスのGetBufferPointer関数は、配列の先頭ポインタをLPVOID型で返します。それをD3DXMATERIAL型に型変換してmatに値を代入しています。
 IDirect3DDevice9::SetMaterials関数はレンダリングするオブジェクトのマテリアル情報を設定する関数です。これはD3DXMATERIAL9構造体へのポインタを渡すことになっていますので、D3DXMATERIAL::MatD3Dへのアドレスを渡しています。
 最後にID3DXMesh::DrawSubset関数でポリゴンの属性グループ毎に描画を行います。引数に渡すのは属性番号で、ここではループカウンタがそれを担います。属性グループはpNumMaterialsで得た数だけ存在します。


 このようにDirect3DX+Xファイルを用いると3〜4の関数を呼び出すだけで非常に簡単に3D描画が出来てしまいます。ただ、上の状態だと、運が良くて真正面を向く真っ黒なオブジェクトが見えます(運が悪いと何も映りません)。理由は簡単です。今の設定で、私たちはオブジェクトの大きさも、位置も、私たちの視点(カメラ位置)も、そして世界を照らすライトも何も設定していないからです。そのため、すべてデフォルトの値が使われてしまったのです。オブジェクトをちゃんと表示させるには、これらの設定を正しく行い、描画デバイスに登録する必要があります。実は、こっちの方がうんと面倒なのです。

 では、それらを含んだDirect3Dによる描画方法を次に説明します。



A Direct3Dで真面目に頂点から描画する方法

 「座標変換済み頂点で2D板ポリゴン」では、画面(スクリーン)の座標をそのまま指定する描画方法を紹介しました。正に2Dのためにあるかのような仕様です。座標変換済み頂点で3Dポリゴンを描画することは不可能ではありませんが、点の位置を自前で計算するわけでして、面倒極まりありません。もちろん通常はそんな事はせずに、Direct3Dが用意する方法で3Dオブジェクトの描画を行います。

 ここから、Direct3Dの描画プロセスについて説明します。以下の説明は3D描画を行うにあたり極めて重要なプロセスですから、絶対に覚えてください!
 通常、ポリゴンオブジェクトは自分を中心とした世界で作成されます。これを「ローカル座標」と呼んでいます。しかし、世の中にそれを置く時には、もちろん自分中心ではなくて、世界中心の座標に置かれることになります。ローカル座標にあるオブジェクトを世界(ワールド)座標に置くためには、何らかの座標変換をする必要があります。ローカル座標にあるオブジェクトを世界に置くための変換を特に「ワールド変換」と呼んでいます。
 ワールドは沢山のオブジェクトで構成されることになりますが、それはジオラマのような状態です。そこに「カメラ」の目線が入ると、初めて「視点」という概念が生まれます。同じワールドでもカメラの位置や視線によって見え方が変わります。これは本物の世界も同じですね。ただ、本物の世界と違うのは、何とカメラが世界を動かしてしまいます。カメラはワールド座標の(0,0,-1)という位置にいて、原点を向いて固定しています。そして、「こういうアングルで見たいなぁ」と世の中に命令すると、世の中の方がカメラが指定したアングルになるように再度変換されるのです。これはちょうど、固定カメラにジオラマを再設置するのと一緒です。この大胆な再変換の事を「ビュー変換」と呼んでいます。
 ビュー変換により、カメラのファインダからは世界が切り取られる事になります。ところが、これもまたDirect3Dの独特な世界なのですが、このカメラには遠近感がありません。つまり1cm先の一円玉も、10km向こうの一円玉も、全く同じ大きさに映るカメラなのです。さらに、このカメラはある距離以上遠いオブジェクトを無視してしまいます。この妙な見え方をしている世界を、私たちの知っている遠近感のある世界に変換する。これを「透視変換」と呼びます。透視変換では、遠い距離にあるオブジェクトほどぎゅっと小さくします。それによって世界は擬似的に遠近感がある世界に見えるのです。ここでも世界のオブジェクトがそうなるように変換されます。この透視変換により世の中は2Dのスクリーンから見える遠近感のある世界になります。3Dが2Dに変換されたわけです。こういう次元を下げる変換のことを「射影変換」と呼びます。透視変換はその働きから射影変換の1つと考えられます。
 透視変換された段階で、実は世界は(-1.0f, -1.0f, 0 )という点から(1.0f, 1.0f, 1,0f)という点を対角線とする直方体の世界に収められてしまっています。ここでz座標(奥行き)を無視して、スクリーンにぴったり収まるように直方体(長方形)を引き伸ばしてはめ込むと、初めて私たちの目に映る2Dのウィンドウに表示されることになります。

 ローカル座標にあるオブジェクトから見ると、「ワールド変換」「ビュー変換」「透視変換」という3回の変換を行う事で2D座標に変換される事になります。Direct3Dが用意しているのは、これら変換を自動的に行ってくれる機構です。これを「レンダリングパイプライン」と呼んでいます。実際にこれらの変換を行うのは4×4の行列です。それぞれ「ワールド変換行列」「ビュー変換行列」「透視(もしくは射影)変換行列」と呼ばれています。レンダリングパイプラインに設定するのは、これらの行列になります。

 では次から、これらの変換(行列)を1つずつ紹介していきます。


(1) ワールド変換

 ワールド変換行列を含む3つの行列は、すべてIDirect3DDevice9::SetTransform関数を通してレンダリングパイプラインに登録されます。

HRESULT SetTransform(
   D3DTRANSFORMSTATETYPE State,
   CONST D3DMATRIX* pMatrix
);

Stateはどの変換行列を設定するのかを指示するフラグです。D3DTRANSFORMSTATETYPE列挙型に定義があります。ワールド変換の場合はD3DTS_WORLDというこの列挙型に実は無いマクロを設定します。
pMatrixに変換行列へのポインタを渡します。D3DMATRIXは4×4の行列を定義できる構造体です。

 登録はともかくとして、私たちの問題は「どうやってローカル座標にあるオブジェクトを思った場所に思った向きで思った大きさでワールド座標に変換するか」ですよね。Direct3DXにはこれらの行列を作成してくれるヘルパー関数が沢山ありまして、一般にはそれらを駆使します。

 まず、D3DXMATRIX型の変数を宣言し、それを単位行列に初期化します。これはヘルパー関数であるD3DXMatrixIdentity関数を用いると簡単です。

D3DXMATRIX* D3DXMatrixIdentity(
   D3DXMATRIX* pOut,
);

pOutに宣言したD3DMATRIX型の変数へのポインタを渡すと、単位行列に初期化してくれます。この行列を少しずつ作りこんで、ワールド変換行列に成長させます。

 まず、大きさ(スケール)変換からいきましょう。スケールとはローカル座標にあるオブジェクトの大きさを定数倍する変換です。厳密には、ローカル座標の各軸をぐいーっと引き伸ばします。スケール変換行列を作成するにはD3DXMatrixScaling関数を用います。

D3DXMATRIX* D3DXMatrixScaling(
   D3DXMATRIX* pOut,
   FLOAT sx,
   FLOAT sy,
   FLOAT sz
);

sxsyszがそれぞれの軸の引き伸ばし率で、pOutにそれを実現する行列が返ります。

 次に回転です。回転とは原点を中心に各軸を回すことを表します。この軸回転により、オブジェクトの向きが変わります。回転の変換行列を作ってくれるヘルパー関数は沢山あります。基本的な関数を以下に列挙します。

X軸回転
D3DXMATRIX* D3DXMatrixRotationX(
   D3DXMATIRX* pOut,
   FLOAT Angle
);
Y軸回転
D3DXMATRIX* D3DXMatrixRotationY(
   D3DXMATIRX* pOut,
   FLOAT Angle
);
Z軸回転
D3DXMATRIX* D3DXMatrixRotationZ(
   D3DXMATIRX* pOut,
   FLOAT Angle
);

 これらはそれぞれX,Y及びZ軸を回転軸としてオブジェクトを回す変換行列を生成してくれます。Angleがその回転角度でラジアン角で渡します。「ラジアン角ってどうやって算出するんだっけ?」という方はD3DXToRadianというマクロがちゃんと用意されておりますので、それを使いましょう。pOutにそれぞれの軸回転行列が返されます。
 回転行列は実は2つ使えばどの方向にでも向ける事が可能です。自分の分かりやすい関数を使えば良いでしょう。

 最後に平行移動です。これはローカル座標にあるオブジェクトの各座標に値を足す行列です。これはD3DXMatrixTranslation関数を用います。

D3DXMATRIX* D3DXMatrixTranslation(
   D3DXMATRIX* pOut,
   FLOAT x,
   FLOAT y,
   FLOAT z
);

pOutに渡された行列はx,y,zの平行移動変換を実現する行列になります。

 以上スケール行列、回転変換行列及び平行移動行列(オフセット行列とも言います)を用いると、ワールド変換行列を生成する事ができます。具体的な生成方法は、各行列を掛け合わせるだけです。ただし、これにはとても重要な注意があります。

 行列というのは掛ける順番が大切です。例えば、

〔ワールド変換行列〕=〔X軸回転行列〕〔平行移動行列〕

という順番で掛け算すると、

 ・ X軸回転をして、
 ・ 平行移動する

という変換がこの順番で行われます。ところが、

〔ワールド変換行列〕=〔平行移動行列〕〔X軸回転行列〕

という順番で掛け算すると、

 ・ 平行移動して、
 ・ X軸回転する

という変換順序になります。これは先ほどと全く違う結果になってしまいます。オブジェクトが原点にあるとして、先ほどのはまずオブジェクトを原点を中心に回転させて向きを変え、その後に平行移動をします。ところが後者は、先に平行移動させてから、原点を中心に同心円回転を行います。その位置や向きが両者で異なるのは明らかです。ですから、思ったところに思った向きにオブジェクトを置くためにも、掛ける順番は慎重に選択して下さい。


(2) ビュー変換

 ワールド座標にオブジェクトを置いた後、今度はカメラの位置にオブジェクトを再配置します。これを行うのがビュー変換行列です。ワールド変換行列はやけに面倒だったのですが、ビュー変換行列はとても便利なヘルパー関数が用意されています。D3DXMatrixLookAtLH関数及びD3DXMatrixLookAtRH関数です。違うのは「LH」か「RH」だけです。これは「左手系」及び「右手系」座標を意味しています。ただDirect3Dはデフォルトが左手系で統一されていますので、混乱しないようにここではLHのみを紹介します。

D3DXMATRIX* D3DXMatrixLookAtLH(
   D3DXMATRIX* pOut,
   CONST D3DXVECTOR3* pEye,
   CONST D3DXVECTOR3* pAt,
   CONST D3DXVECTOR3* pUp
);

pOutにはビュー変換行列が返されます。
pEyeは、カメラの位置を表します。これはD3DXVECTOR3というベクトルで指定します。3次元座標をよく(x,y,z)と表記しますが、D3DXVECTOR3というのは正にそれです。
pAtは「注視点」の座標を設定します。注視点とはカメラが見つめる1点です。これによって、カメラは自分のいる位置から好きな所をピンポイントで見つめる事が出来ます。
pUpはカメラの「上方向」を定義します。これは少しイメージしにくいパラメータです。同じ位置にいて、同じ点を見つめていたとしても、カメラをくるくる回すと撮られる映像は変わってきます。カメラを横にして背の高い木が入らないときに、私たちはカメラを縦にしますよね。それと同じです。ここのベクトルには、カメラの上となる方向を定義します。一般的に、3D空間は空に向かう軸をY軸と定めます。よって、普通ここは(0, 1, 0)とY軸が上になるように定義します。フライトシミュレータや戦闘機のようなぐるぐる回るゲームを作る場合は、このベクトルがとても重要になってきます。

 「そういえば、世界がカメラの前に来るんじゃなかったけか?」と思われたかもしれません。実際に生成される行列は正にそういう変換を行ってくれるのですが、さすがにそれは人の感覚に合いません。よって、このような「人間感覚」のヘルパー関数があるわけです。

 「LH」と「RH」というのは、それぞれLeft Hand及びRight Handの頭文字です。これは一般には「左手系」及び「右手系」などと呼ばれています。まず親指、人差し指、中指をそれぞれX軸、Y軸、Z軸とみなして指を広げます。両方の親指(X軸)と人差し指(Y軸)の方向を無理やり合わせると、残り中指(Z軸)がちょうど鏡写しのように逆を向く事がわかると思います。左手の中指が向こうへ、右手の中指がこちらへ向いているはずです。3Dの世界ではこの2つの座標系が存在しているのですが、Direct3Dでは「左手系」が一般です。どうして左手系なのか、それについては後日談としましょう。

 このD3DXMatrixLookAtLH関数によって生成されたビュー変換行列をIDirect3DDevice9::SetTrasform関数で登録します。StateにはD3DTS_VIEWを設定します。



(3) 透視(射影)変換

 透視変換は、カメラが切り取った遠近感の無い空間を、遠近感のある空間に変換してくれます。「遠近感の無い」というのがどういう意味かをまず説明しましょう。下の図をご覧下さい。

 現段階で、私たちは上の図の台形型をした水色の空間を見ているのですが、遠近感を考えないので、2つの球の直径は右の四角のように見えています。射影変換を行うと、下の図のようになります。

 台形空間を遠くのクリップ面の両サイドからぐぐっと押し縮めて、近くのクリップ面と同じ大きさにしてしまい、無理やり長方形(直方体)にしてしまいます。こうすると右の図のように遠くのオブジェクトが小さく縮まって遠近感が生まれるのです。

 この変換行列を作る関数もちゃんと用意されています。D3DXMatrixPerspectiveFovLH関数です。

D3DXMATRIX* D3DXMatrixPerspectiveFovLH(
   D3DXMATRIX* pOut,
   FLOAT fovY,
   FLOAT Aspect,
   FLOAT zn,
   FLOAT zf,
);

pOutには射影変換行列が返ります。
fovYというのは「視野角度」の事です。上の図で言うカメラから斜めに延びる視野角になります。0〜90度までで、ラジアン角で指定します。
Aspectは画面の縦横比です。上の図で言えばカメラの右にある絵の縦横比でして、高さ/幅で計算します。640×480のクライアント領域を持つウィンドウいっぱいに画面を出したい場合は、480/640=0.75となります。
znは近くのクリップ面までの距離です。上の図だとカメラに近い赤いラインまでの距離に相当します。これは任意の値を定義してかまいませんが、0を入れてはいけません。
zfは遠くのクリップ面までの距離です。ここにはznよりも大きい値を入れます。

 zfが遠すぎると大変広い空間を見る事になるので、レンダリング時間が非常に長くなってしまいます。かと言って短い距離にすると向こうに行ったオブジェクトが急に見えなくなります。ちょうど良い大きさはワールド空間にあるオブジェクト次第と言えるでしょう。例えば家の中のスケールだとそれほど遠くにする必要はありません。広い野原などでは多少遠くまで見通せるようにしないと違和感が強くなってしまいます。このzn及びzfの値は、後々に出てくる「深度バッファ」というものと深く深くかかわって来ますので、覚えておいた方が良いパラメータです。
 視野角度は45度とか30度とかが標準です。60度というのも出来ますが、射影変換すると結構歪みます。視野角が大きくなるほど遠近感(パース)がきつくなるはずです。

 射影変換の登録もIDirect3DDevice9::SetTransform関数で行います。Stateに設定するフラグはD3DTS_PROJECTIONです。


 以上で3つの変換行列をレンダリングパイプラインに全て登録し終わりました。後は描画処理を行いますと、画面に遠近感のあるオブジェクトが描画されます。これはDirect3DXのID3DXMeshインターフェイスを用いた描画も同様です。ただし、表示されるオブジェクトはまだ真っ黒です。それはAでも述べましたが世界を照らす「ライト」が設定されていないからです。


(4) ライト

 3D描画の最後の仕事はライトを設定して世の中を照らしてあげる事です。現実の世界には沢山のライトがありますが、Direct3Dの世界では4種類のライト(ポイントライト、ディレクショナルライト、スポットライト、アンビエントライト)が設定可能です。
 ポイントライトは空間のある1点から放射状に光を放つ裸電球のようなライトをです。
 ディレクショナルライトは太陽光のような「平行光」を再現するライトです。
 スポットライトは懐中電灯や電柱の明かりのようにある点から放射状に広がるライトです。
 アンビエントライトは「環境光」とも言い、空間全体を照らすライトです。ありとあらゆる方向から同じ強度で光をともします。アンビエントライトを強くすると、暗い部分が少なくなり、影で見えなかった部分なども見えるようになります。ただし最強にすると、世界が真っ白になります(笑)

 ライトは、D3DLIGHT9構造体を使って設定します。

typedef struct _D3DLIGHT9(
   D3DLIGHTTYPE Type;
   D3DCOLORVALUE Diffuse,
   D3DCOLORVALUE Specular,
   D3DCOLORVALUE Ambient;
   D3DVECTOR Position;
   D3DVECTOR Direction;
   float Range;
   float Falloff;
   float Attenuation0;
   float Attenuation1;
   float Attenuation2;
   float Theta;
   float Phi;
} D3DLIGHT9;


この構造体、やけにメンバ変数が多いんです(-_-;

Typeはライトのタイプを指定します。これはD3DLIGHTTYPE列挙型に定義されていて、D3DLIGHT_POINT(ポイントライト)、D3DLIGHT_SPOT(スポットライト)、D3DLIGHT_DIRECTIONAL(ディレクショナルライト)のいずれかを指定します。アンビエントライトは、実は全く別の設定方法を用います。
Diffuseにはライトの色を設定します。D3DCOLORVALUEは4つのfloat型であるr,g,b,aというメンバを持った構造体で、各々の色を0〜1の間で設定します。
Specularは反射光の色を設定します。
Ambientはライトがもたらす環境光の色を設定します。
Positionは光源の位置をD3DVECTOR型で指定します。
Directionは光が指す方向をD3DVECTOR型で指定します。ベクトルで方向を指し示すので少し面倒な事もあります。これは全方向であるポイントライトには適用されません(無視されます)。
Rangeは光源の有効距離を指定します。この範囲よりも遠い位置にあるオブジェクトへはどれだけ光が強くても届きません。しかし、ディレクショナルライトはこの値によらず常にオブジェクトに光が届きます。
Falloffはスポットライトに影響を与えます。スポットライトは内側と外側の2重ライトになっていて、内側の方が通常は明るくなります。Falloffは内側から外側への光の減衰度を数値で表します。マニュアルにあるのですが、この差は微妙で気付きにくい事もあり、大抵は1.0fを設定するそうです。
Attenuation0〜2はライトからの距離に対する減衰の変化を設定します。ディレクショナルライトの場合減衰しないので、この値は無視されます。ここにどのような値を入れるかは難しいのですが、Attenuation1=1、残りを0という設定が良くあるとの事です。
Theta及びPhiはスポットライトの広がりに影響を与えます。Thetaが内側の光、Phiが外側の光の広がりで、ラジアン角でその広がりを設定します。

 この構造体に使用したいライトに関する設定を全てした後に、IDirect3DDevice9::SetLight関数に構造体を渡せば、ライトの設定完了です。

HRESULT SetLight(
   DWORD Index,
   CONST D3DLIGHT9* pLight
);

Indexはライト番号です。Direct3Dは複数のライトを同時に使って世界を照らす事が出来ます。そのライト番号をここで指定します。
pLightはライトの設定がされたD3DLIGHT9構造体へのポインタを渡します。

 設定したライトはOn/Offが出来ます。これはIDirect3DDevice9::LightEnable関数で切り替えます。

HRESULT LightEnable(
   DWORD LightIndex,
   BOOL bEnable
);

LightIndexがライト番号、bEnableにOn/Offを論理値で指定します。もちろんtrueがOnです。

 これでオブジェクトをジオメトリパイプラインに通せば、設定したライトに照らされたオブジェクトが出現します。

 ところで、アンビエントライト(環境光)がありましたが、これは全く別の方法で設定します。これには、IDirect3DDevice9::SetRenderState関数を用います。この関数はレンダリング(描画処理)に対して実に様々な設定を行えるマルチ関数なのですが、その1つであるアンビエントライトの設定を用います。

D3DCOLOR ambient = {0.3f, 0.3f, 0.3f, 1.0f};

p_D3DDev->SerRenderState(
   D3DRS_AMBIENT,
   ambient
);

 第2引数にアンビエントライトの強さをD3DCOLOR型で指定します。これはRGBAの光の強さを小数点で指定します。0が黒、1が白ですが、実はそれ以上も以下にもできます。これを用いると、陰で真っ黒になった部分がもやっと見えてきます。



 以上が3Dオブジェクトを画面に出す基本の基本になります。かなり大変に思えてしまうのですが、実際は行列3つとライトの設定だけですから、思ったよりは楽なんです。難しいのは、自分の思った位置にオブジェクトを出す事で、こちらの方がずっと大変です。ローカル座標にあるオブジェクトを生き生きと動かすには、どうしてもベクトルと行列の知識が必要になってしまいます。これをどこまで極められるか?それがDirect3Dにおけるゲームの「質」を決めると言っても過言ではありません。今回は相当に長いテキストになってしまいました。ここまで読まれた皆様、お疲れ様でした〜。