アフィリエイト

3D画面を描くということ

 Direct3Dを使えば3Dで描画できると思っている方は多いはず。確かにそうですが、何もDirect3Dを使わなくても自分で3D描画をすること可能です。(まぁ、計算が重くてやってられないと思いますが)Direct3Dは3D座標空間内のオブジェクトを2D座標へと計算して置き換え(これをグラボにまかせていますが)2Dの画像を画面上に表示しているに過ぎません。つまり、最終的には2Dなのです。

 今回は、Direct3Dを使って2Dの四角形を描画してみます。2Dの描画をするには、Direct3Dへ3Dから2Dへ座標変換したあとのデータですよ〜と偽って情報を渡してやればその通り2Dの描画を行ってくれます。Direct3Dを使って色々な表示を行う上では、この「色々な計算を行って最終的には2D座標になったデータを描画しているんだ」という感覚が結構重要になってくると思います。3Dは便利な道具ですが、ただの計算に過ぎません。

今回のサンプルプログラム

 前回(DirectGraphics@)で作った画面を塗りつぶすプログラムに手を加え、四角いポリゴンを描画します。ついでに頂点色をいじってグラデーションもさせてみましょう。

Direct3DSample2

 左の画面が今回のサンプルプログラムです。グラデーションがかかった四角形と一色で塗られた四角形がクルクル回っています。グラデーションのかかった四角はただのサンプルで、実際は1色で塗られたほうが実用的でしょう。

サンプルプログラム(dgsample2.lzh)

 プロジェクトは前回のものに改造を加えるので、前回のコードをベースに作っていきます。DirectGraphicsAのサンプルプログラムをダウンロードして置いてください。

Direct3Dの頂点の話

 3Dに興味のある方なら知っていると思いますが、3次元の物体は頂点の座標と面の情報で構成されています。頂点3つで三角形(面)を表現し、それをいくつも描画することで、ある物体を描画していきます。実際には3次元の座標を複雑な計算で2次元の座標へ写し、その変換後の三角形を2次元平面上に描画します。

 冒頭でも触れましたが、今回は「Direct3Dへ3Dから2Dへ座標変換したあとのデータですよ〜と偽って情報を渡す」方式で2次元座標の好きな位置に四角形を描画します。Direct3Dではどの状態まで変換したデータなのかを頂点のフォーマットで判断しています。頂点データと言っても、その中にはただの座標だけではなく様々な情報が含まれていて、全てDirect3Dで座標変換を行い、ライティング(照明)やテクスチャーなど思いつく情報を加えると頂点座標・法線座標・テクスチャー座標・頂点カラーなど多くの情報を含むことになります。このように、Direct3Dの頂点フォーマットは非常に複雑なものなのです。

頂点の描画手順(2Dの場合)

 さて、3D関連の薀蓄はこのくらいにしておいて、今回メインの2Dの描画方法について紹介します。2Dつまり座標変換をDirect3Dに任せない場合は以下のような手順をとります。

頂点描画の手順

 Direct3Dで描画を行う場合、頂点情報の配列をそのまま渡して描画をすることはできません。データ配列は必ず頂点バッファ(VertexBuffer)というものに書き込んでから、デバイスのDrawPrimirivesメソッドへ渡して描画を行います。配列と頂点バッファの生成は一度やっておけば十分なので、今回はコンストラクタの初期化部分に書いておくことにします。そして、B頂点データの書き込み〜Dはどのような場合も全く同じ手順で実行するためこれだけをメソッドにまとめて、座標を渡すだけで描画してくれるようなメソッドを作ることにします。

配列・頂点バッファの生成

 まずは、頂点配列とそれを書き込む頂点バッファの生成です。以下のコードをCLKsDGクラスの変数宣言部(フィールド)とコンストラクタに追加してください。

//インスタンス変数として宣言
//新しく頂点と頂点バッファを宣言
private CustomVertex.TransformedColoredTextured[] verts = null;
private VertexBuffer vertexBuffer = null;


//コンストラクタに追加 //頂点用オブジェクトの生成 verts = new CustomVertex.TransformedColoredTextured[4]; vertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColoredTextured), 4, device, 0, CustomVertex.TransformedColoredTextured.Format, Pool.Managed);

 頂点の型は「CustomVertex.TransformedColoredTextured」というものを使います。この型の頂点は、すでに座標変換された座標(つまり2D)と頂点の色、そしてテクスチャーの座標を保持します。テクスチャー座標は画像を表示する際に必要となりますので、将来を見越してこの型の頂点を使うことにました。頂点バッファはどのようなフォーマットの頂点でも「VertexBuffer」型のオブジェクトを使います。この二つはprivateのインスタンス変数として宣言しておいてください。

 オブジェクトの生成は通常通りnewを使って行います。今回は四角形を描画するので4つ分の頂点配列を生成します。頂点バッファのコンストラクタは少々面倒で、書き込む予定の頂点のフォーマットとその数、使用するDirect3Dデバイスなどを渡します。第一引数には、頂点型を、第二引数には書き込む頂点の数(今回は4)、第三引数にはDirect3Dデバイス、第五引数には書き込む頂点データのフォーマットを渡しください。typeof(〜)で「System.Type」型を取得することがでます。そのほかの引数についてはこういうものだと覚えておいてください。これで、頂点バッファの生成は完了します。

頂点バッファへの書き込み

 手順と前後しますが、頂点配列のデータを頂点バッファへと書き込む方法を先に紹介します。(メソッドの都合上です)頂点配列を受け取ってそれを描画する「drawRectPrim(CustomVertex.TransformedColoredTextured[] verts)」メソッドをCLKsDGクラスに追加します。

private void drawRectPrim(CustomVertex.TransformedColoredTextured[] verts)
{
	if (device == null) return;

	//グラフィックストリームの取得
	GraphicsStream stm = vertexBuffer.Lock(0, 0, 0);
	//頂点を頂点バッファに書き込み
	stm.Write(verts);
	//頂点バッファをアンロック
	vertexBuffer.Unlock();

	//デバイスに書き込み用頂点バッファをセット
	device.SetStreamSource( 0, vertexBuffer, 0);
	//デバイスに頂点フォーマットのセット
	device.VertexFormat = CustomVertex.TransformedColoredTextured.Format;
	//描画
	device.DrawPrimitives(PrimitiveType.TriangleStrip,0,2);

}
	
 

 DrawPrimitivesを使って描画するところまで書いてありますが、それはあとで説明します。取り合えず、前半の頂点バッファへの書き込みです。ManagedのDirect3Dでは〜バッファと呼ばれるものがいくつかありますが、大体が「GraphicsStream」オブジェクトを使って書き込みます。VertexBufferの「Lock()」メソッドを呼ぶとGraphicsStream型のインスタンスが返されます。Lockの引数も色々と機能があるのですが、取り合えず全て0で問題ないでしょう。取得したGraphicsStreamの「Write」メソッドでVertexBufferに頂点データを書き込み、頂点バッファを「Unlock」します。頂点バッファへの書き込みはロックされている間しかできません。

ポリゴンの描画

 先ほどのコードの後半部分です。Direct3Dで何かを描画する際には、描画メソッドに直接それを渡すのですはなく、デバイスに値をセットしてそのあと描画メソッドを呼ぶという形をとります。「 device.SetStreamSource( 0, vertexBuffer, 0);」で先ほど作った頂点バッファをセットし、描画する頂点のフォーマットを「device.VertexFormat = CustomVertex.TransformedColoredTextured.Format;」でセットしています。厄介なのが、頂点バッファはメソッドで設定して、フォーマットはプロパティーに代入するところですね。このように、Direct3Dデバイスを描画前に準備しておくのです。

 デバイスの準備が終わったらようやく描画メソッドの「DrawPrimitives」を呼びます。引数にはプリミティブのタイプと、描画に使う頂点データの開始インデックス、描画する三角形の数を渡してやります。プリミティブのタイプについては、もっと高度な話が出てきたときにまとめて紹介することにしましょう。取り合えず今回は「TriangleStrip」を渡してください。

頂点データの設定

 手順が逆になりましたが、頂点座標の設定をします。まずは全て同じ色の四角形を描画するメソッドを作りましょう。

///四角形を描画
public void DrawBox(int x, int y, int w, int h, Color color)
{
	verts[0].X=x-w/2;  verts[0].Y=y-h/2; 
	verts[0].Tu = 0f; verts[0].Tv = 0f;
	verts[1].X=x+w/2;  verts[1].Y=y-h/2; 
	verts[1].Tu = 1f; verts[1].Tv = 0f;
	verts[2].X=x-w/2;  verts[2].Y=y+h/2; 
	verts[2].Tu = 0f; verts[2].Tv = 1f;
	verts[3].X=x+w/2;  verts[3].Y=y+h/2; 
	verts[3].Tu = 1f; verts[3].Tv = 1f;
	
	for (int i = 0; i < 4; i++)
	{
		verts[i].Z=0f; verts[i].Color = color.ToArgb();
	}

	this.drawRectPrim(verts);
}
	

 四角形の中心座標(x,y)と大きさ(w,h)、塗りつぶす色(color)を渡してやるとそのとおりに描画する「DrawBox」メソッドバージョン1です。受け取った引数から適切な座標を算出し、最後に先ほど作った「drawRectPrim」メソッドに渡して描画しています。TransformedColoredTextured型の頂点には頂点座標(X,Y,Z)、頂点カラー(Color)、テクスチャー座標(Tu,Tv)があり、それら全てに値を設定します。頂点座標には中心座標と大きさから計算した適切な値を代入していきます。今回は変換後の2D座標なのでZは機能しませんので0を入れておけばいいでしょう。Colorはint型の変数を代入しなくてはいけませんが、.NETのSystem.Drawing.Color構造体には「ToArgb()」というint型カラーデータを返してくれる便利なメソッドがあるのでそれを使って設定します。テクスチャー座標については、次回説明します。今回は適当に値を入れてください。

 頂点配列の何番目にどこの座標を入れてもちゃんと描画してくれるというものではありません。四角を描画するといっても実際は三角を二つ描画しているだけで、三角形の作り方を間違えてしまうと変な形の図形が描かれてしまいます。どの順番で描くかはDrawPrimitivesのときに指定したプリミティブタイプで決定するのですが、ここでは取り合えず「0に左上」「1に右上」「2に左下」「3に右下」の座標をセットしてください。詳しい話はまたいつか…(ためしに代入先を変えて実行してみるといいと思います)

頂点カラーを変更

 さて、先ほどのメソッドでは頂点カラーを全て同じに設定してみましたが、全てばらばらにすることもできます。すると、各頂点間で補完された色がグラデーションで塗られます。このサンプルがDrawBoxのオーバーロードメソッドとして実装してありますので、サンプルをダウンロードしてご覧ください。解説するほどのことでもないので。

作ったメソッドの呼び出し

 最後に、今まで作ったメソッドを呼び出して描画します。サンプルでは前回同様Mainメソッド内に作ったループの中で処理を行っています。

static void Main() 
{
	//メインループ
	//講座で紹介したFPS安定タイマーを使用しています。
	Form1 frm = new Form1();
	double nextframe = (double)System.Environment.TickCount;
	float wait = 1000f/60f;
	
	frm.Show(); //フォームの表示
	frm.ClientSize = new Size(800,600);
	
	CLKsDG dgdevice = new CLKsDG(frm);
	
	while(frm.Created)
	{
	
		if ((double)System.Environment.TickCount >= nextframe)
		{
	
			if ((double)System.Environment.TickCount < nextframe + wait)
			{
				//描画処理
				dgdevice.Begin();
				int r = (int)(130 + 125.0
					*Math.Sin((double)System.Environment.TickCount/1000));
				int g = (int)(130 + 125.0
					*Math.Sin((double)System.Environment.TickCount/900));
				int b = (int)(130 + 125.0
					*Math.Sin((double)System.Environment.TickCount/1100));
				dgdevice.Clear(Color.FromArgb(r,g,b));
	
				//ポリゴンの描画
				double Rad = 
				(double)System.Environment.TickCount/1500;
				dgdevice.DrawBox(400+(int)(300*Math.Cos(Rad)),
					300+(int)(200*Math.Sin(Rad)),
					100,100,Color.FromArgb(b,r,g));
				dgdevice.DrawBox(
					400+(int)(300*Math.Cos(Rad+Math.PI)),
					300+(int)(200*Math.Sin(Rad+Math.PI)),
					100,100);
	
				dgdevice.End();
	
				dgdevice.Present();
			}
	
			nextframe += wait;
		}
	
		Application.DoEvents();
	}
}
	

 ポリゴンの描画の部分が今回追加したところです。DrawBoxメソッドに座標を渡してやるだけで好きなところに、好きなサイズの四角形を描画できます。DirectXは一つの仕事をするのに同じようなコードをたくさん書かなくてはいけないことが多いので、このようにメソッドにまとめておくとプログラムするのが楽になりますね。前回やったとおり、Begin→描画→End→Presentの順で呼んでいるのが分かると思います。

まとめ

 今回は概念的に重要なことが色々とありました。まずは、3Dは単に計算した座標を2Dとして描画しているだけということ。次に、DirectXでは必ずバッファというものにデータを書き込んでから使用すること。そして、描画はデバイスの前準備をしてから行うことです。3Dの物体を描画する際にはこの概念が非常に重要になりますので、頭の隅にでも置いておくと良いかと思います。

 次回は、今回作った四角形の上に画像を貼り付けてみます。いわゆるテクスチャーという奴です。これが出来れば、かなりゲームらしいものが作れそうですね。では。