★ DirectX Class ★ * Chapter 06: 3D図形の表示 *
< 3Dとは >
3Dの基本単位は三角形です。
それは、三角形があれば大抵の図形、立体を表現できるからです。
例えば四角形であれば、三角形を2つつなげればできますし、直方体ならその四角形を6つつなげればできます。
細かい三角形をつなげれば擬似的に球のようなものも作れます。
平面を集めれば球になります。ミラーボールのようなイメージです。
例えば人間のような複雑なものも細かい三角形を集めて表せます。

また、三角形を利用することにより、一意の平面を表現できます。
例えばこれが四角形だった場合はどうでしょう。
4頂点が同一平面にある場合のみしか平面を表現できません。そうすると3Dの作成時には常にそのことに気をつけなければならず、ミスの発生にもつながります。

3Dに興味がおありの方はどこかでワイヤーフレームというものを見たことがあるのではないでしょうか。
あれを良く見てもらえば分かりますが、三角形で構成されています。
(最近は見た目の鬱陶しさをなくすためか、四角形単位での表現も多くなりました)
見たことのない方はGoogleのイメージ検索で『 ワイヤーフレーム 』と検索すると沢山出てきます。
検索結果

そしてこの三角形のことを、『 ポリゴン 』といいます。
ゲームのキャラクター等で、3万ポリゴンと言えば、そのキャラクターには細かな三角形が3万個も使われていることになります。
当然、三角形が多ければ多いほど滑らかになりますので、このポリゴン数はリアリティやディティールの細かさを示す指標として使われることもあります。

当然、ポリゴン数が増えればコンピューターへの負荷も高くなります。
そこで、そのコンピューターの3D処理能力を示すために、『 32,000ポリゴン/秒 』と表現したりします。
これは1秒間に3万2千個の三角形を表示できるということです。
3万ポリゴンのキャラクターであれば1体しか表示することができません。
もしキャラクターをどうしても2体表示したい場合、このキャラクターの3Dを16,000ポリゴン以下で作り直さなければなりません。
そうするとリアリティが下がります。
これが3Dゲーム開発の考え方です。

参考までに、PS2は6,600万ポリゴン/秒らしいです。
< 3Dオブジェクトの描画準備 >
それでは早速その最小単位である三角形を一つ描画してみましょう。
今回はこのような頂点座標を持つ三角形を描画してみます。

Vertices

まず、例にならって三角形の情報を保持するクラス変数を定義します。

LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;

開放処理の説明はもう省きますが、今まで通りアプリケーションの終了時にデバイスよりも先に開放してください。

さて、三角形を描くにはその頂点座標に関する情報が必要です。
そしてその色に関する情報も必要です。
そこでそれぞれ以下のように宣言/定義します。

struct CUSTOMVERTEX
{
	FLOAT x , y , z;
	DWORD color;
};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

最初の構造体は、実際に情報を格納するための構造体です。
頂点座標とその色を宣言しています。

次のdefineでは、作成した頂点情報の構造体がどのようなものであるかを定義しています。
2つの定数をまとめて1つの定数にしています。
これは構造体情報と同じにしておかなくてはなりません。
今回は頂点座標とその色を宣言したいますので、それに関する定数が使用されています。
『 D3DFVF_DIFFUSE 』というのが色を表します。
一概に『 色 』といってもDirectXでは何種類かに分かれているのですが、それはまた別の章で解説します。
今回は『 ディフューズ色 』を使っています。

次に、これは今回のみのお決まりですが、カリングの設定を無効にします。
カリングとは、ポリゴンの背面を描画するかどうかの設定です。
DirectXのポリゴンは頂点を時計回りに描画すると、その面が表になります。
そのポリゴンを裏から見ると、頂点は半時計周りに描画されているように見えます。
よって半時計周りに描画されたポリゴンは裏になります。

背面描画をなしにすると、描画の負荷が減らせます。
但し、注意が必要です。
例えば3Dゲームを開発する際、表も裏も見なければならないドア等は、カリングしてはいけません。
カリングすると反対側から見たときにドアがなくなります。
逆に、サイコロのように常に表が外側を向いているものはカリングを行ったほうが良いです。
今回は、三角形の座標を色々変えて学習できるようにするため、カリングを行わない設定にします。

g_pd3dDevice->SetRenderState( D3DRS_CULLMODE , D3DCULL_NONE );

ちなみに、カリングの有効/無効は任意のタイミングで変更することができます。

そして今回はライトをオフにします。
ただでさえ新情報が多いのにライトまでやるとパニックになりますので。
今回は何となく『 こんな設定もあるんだな 』と思ってください。

g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , FALSE );

ライトに関しては後に『 ライト 』及び『 ライトの簡易理論 』の章で詳しく取り上げます。
< 三角形の情報作成 >
それでは実際に三角形の情報を作成しましょう。
ここで作成する三角形について確認しておきましょう。

Vertices

この三角形ですね。
この三角形の情報を上で定義した構造体を使って作成してみましょう。

CUSTOMVERTEX Vertices[] =
{
	{ -1.0f , -1.0f , 0.0f , 0xFFFF00FF } ,
	{  0.0f ,  1.0f , 0.0f , 0xFFFFFF00 } ,
	{  1.0f , -1.0f , 0.0f , 0xFF00FFFF } ,
};

構造体の配列に、x、y、z、とその色情報を格納しています。

そして、頂点情報を格納するクラスを作成します。
それは以下のコードによって実現されます。

CUSTOMVERTEX Vertices[] =
HRESULT CreateVertexBuffer(
  UINT Length,
  DWORD Usage,
  DWORD FVF,
  D3DPOOL Pool,
  IDirect3DVertexBuffer9** ppVertexBuffer,
  HANDLE* pSharedHandle
);

『Length』には格納する情報のサイズを設定します。
『Usage』にはとりあえず『0』を設定してください。
『FVF』には、作成する頂点情報がどのようなものであるかを設定します。
『Pool』にはとりあえず『D3DPOOL_DEFAULT』を設定してください。
『ppVertexBuffer』には作成した情報を格納するクラス変数を指定してください。
『pSharedHandle』にはとりあえず『NULL』を指定してください。

これで頂点情報を格納するクラスができました。
しかし、未だ頂点情報は入っていません。

まず、先程作成したクラスをロックします。

VOID *pVertices;

g_pVB->Lock( 0 , sizeof( Vertices ) , ( void** )&pVertices , 0 );

そしてそこへ、構造体から頂点情報を流し込みます。

memcpy( pVertices , Vertices , sizeof( Vertices ) );

そして最後にロックを解除します。

g_pVB->Unlock();

以下の流れを覚えてください。

   ロック
    ↓
   情報の流し込み
    ↓
   ロック解除

以上で三角形の情報作成が完了しました。
< 3Dの世界とカメラ >
3Dの世界は最初混沌としています。
真っ暗な無重力の宇宙に放り出されたようなものです。
かろうじてx、y、zの座標はあるものの、あまり意味はありません。
地面もない、上下左右もない、なのでどこを向いているのかも分からない、自分がどこにいるのかも分からない。時間もない。
それらは全て自分で設定しなければなりません。
自分で世界を作るのです。
神様みたいなものですね。
何か宗教を作りたくなってきますね。『 3Dぽりごん教 』とか。

まず、世界の中心を決めます。
世界の中心はx、y、z軸が交わる原点で良いので、以下のようにして設定します。

D3DXMATRIXA16 matWorld;

D3DXMatrixIdentity( &matWorld );
g_pd3dDevice->SetTransform( D3DTS_WORLD , &matWorld );

『 D3DXMatrixIdentity 』は基本的なマトリックスを作成する関数です。
この関数により、原点に関するマトリックスが作成されます。
そして作成されたマトリックスを『 SetTransform 』で設定されています。
この関数はマトリックスを設定する関数で、第1引数に与えるフラグによって設定されるマトリックスを判断します。

次に、カメラ(自分の視点)を設定する必要があります。
カメラには3つの情報が必要です。

   カメラの位置
   見ている対象となる場所
   上

『 上 』というのはカメラの上をどの方向にするかということです。
通常はy軸方向で良いと思いますが、演出でカメラを傾けたい場合等もあると思います。
そのためにこの項目も指定する必要があります。

これらの情報はD3DXVECTOR3構造体に格納して、マトリックスを作成します。
以下にそのマトリックスを作成するコードを示します。

D3DXMATRIXA16 matView;
D3DXVECTOR3 vEyePt , vLookatPt , vUpVec;

vEyePt.x    = 5.0f;
vEyePt.y    = 5.0f;
vEyePt.z    = 0.0f-5.0f;
vLookatPt.x = 0.0f;
vLookatPt.y = 0.0f;
vLookatPt.z = 0.0f;
vUpVec.x    = 0.0f;
vUpVec.y    = 1.0f;
vUpVec.z    = 0.0f;

D3DXMatrixLookAtLH( &matView , &vEyePt , &vLookatPt , &vUpVec );

g_pd3dDevice->SetTransform( D3DTS_VIEW , &matView );

作成したマトリックスは『 SetTransform 』を利用して設定しています。

そして最後にプロジェクションマトリックスを作成します。
概念的に難しいので、今は『 とりあえずこのコードを書けば良いんだな 』と思っておいてください。

D3DXMATRIXA16 matProj;

D3DXMatrixPerspectiveFovLH( &matProj , 3.0f / 4.0f , 1.0f , 1.0f , 100.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION , &matProj );

そしてこれら一連の処理は描画を行う直前に呼び出します。
『 g_pd3dDevice->BeginScene 』を行った直後です。
< 描画 >
それでは実際にポリゴンを表示してみます。
ポリゴン描画の流れは以下の通りです。

   デバイスにポリゴンの情報を渡す
      ↓
   頂点のフォーマットを渡す
      ↓
   描画処理

まず、ポリゴンの情報を渡すコードです。

HRESULT SetStreamSource(
  UINT StreamNumber,
  IDirect3DVertexBuffer9 * pStreamData,
  UINT OffsetInBytes,
  UINT Stride
);

『StreamNumber』にはとりあえず『0』を指定してください。
『pStreamData』には頂点情報のクラスへのポインタを渡します。
『OffsetInBytes』にはオフセットを指定します。通常は『0』で良いです。
『Stride』には頂点情報構造体の、1つあたりのサイズを指定します。

実際には以下のように実行します。

g_pd3dDevice->SetStreamSource(
  0 ,
  g_pVB ,
  0 ,
  sizeof( CUSTOMVERTEX )
);

次にフォーマットを渡します。

g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

最後に描画します。
描画する関数は以下の通りです。

HRESULT DrawPrimitive(
  D3DPRIMITIVETYPE PrimitiveType,
  UINT StartVertex,
  UINT PrimitiveCount
);

『PrimitiveType』には描画する三角形の種類を指定します。
次章で詳しく説明しますので、今回は流してください。

『StartVertex』には描画する頂点のオフセットを指定します。
頂点情報が3つ以上ある場合に使用します。

『PrimitiveCount』描画する三角形の個数を指定します。

実際には以下のように実行します。

g_pd3dDevice->DrawPrimitive(
  D3DPT_TRIANGLESTRIP ,
  0 ,
  1
);

これでやっと三角形を描画できます☆
< サンプル >
このサンプルを実行すると、クライアント領域にカラフルな三角形が表示されます。
作成した三角形は左右対称な二等辺三角形ですが、斜め上( 5.0f , 5.0f ,-5.0f )から見ているので、そのような見え方になっています。

ポイントはピンク色で示してあります。

//-----------------------------------------------------------------
//
//    DirectX 3D
//
//-----------------------------------------------------------------
#define sqrtf sqrt
#define sinf  sin
#define cosf  cos
#define tanf  tan

#include <stdio.h>
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>

#pragma comment( lib , "d3d9.dll" );
#pragma comment( lib , "d3dx9.dll" );



//-----------------------------------------------------------------
//    Grobal Variables.
//-----------------------------------------------------------------
LPDIRECT3D9             g_pD3D       = NULL;
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB        = NULL;

struct CUSTOMVERTEX
{
	FLOAT x , y , z;
	DWORD color;
};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)



//-----------------------------------------------------------------
//    Prototypes.
//-----------------------------------------------------------------
HWND    InitApp( HINSTANCE , int );
BOOL    InitDirect3D( HWND );
BOOL    CleanupDirect3D();
BOOL    InitGeometry();
BOOL    SetupMatrices();
BOOL    RenderDirect3D();
LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );



//-----------------------------------------------------------------
//    Main.
//-----------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInst , HINSTANCE hPrevinst , LPSTR nCmdLine , int nCmdShow )
{
	MSG msg;
	HWND hWnd;
	
	hWnd = InitApp( hInst , nCmdShow );
	if ( !hWnd ) return FALSE;
	
	if ( !InitDirect3D( hWnd ) ) return FALSE;
	if ( !InitGeometry() ) return FALSE;
	
	while( msg.message != WM_QUIT ){
		if ( PeekMessage( &msg , NULL , 0 , 0 , PM_REMOVE ) ){
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}else{
			RenderDirect3D();
		}
		Sleep( 1 );
	}
	
	return msg.wParam;
}



//-----------------------------------------------------------------
//    Initialize Application.
//-----------------------------------------------------------------
HWND InitApp( HINSTANCE hInst , int nCmdShow )
{
	WNDCLASS wc;
	HWND hWnd;
	char szClassName[] = "Direct3D Test";
	
	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.hInstance     = hInst;
	wc.hCursor       = LoadCursor( NULL , IDC_ARROW );
	wc.hIcon         = LoadIcon( NULL , IDI_APPLICATION );
	wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
	wc.lpszClassName = szClassName;
	wc.lpszMenuName  = NULL;
	wc.lpfnWndProc   = WndProc;
	wc.cbWndExtra    = 0;
	wc.cbClsExtra    = 0;
	if ( !RegisterClass( &wc ) ) return FALSE;
	
	hWnd = CreateWindow( szClassName , "Direct3D Test" , WS_OVERLAPPEDWINDOW ,
						CW_USEDEFAULT , CW_USEDEFAULT , 640 , 480,
						NULL , NULL , hInst , NULL );
	if ( !hWnd ) return FALSE;
	
	ShowWindow( hWnd , nCmdShow );
	UpdateWindow( hWnd );
	
	return hWnd;
}



//-----------------------------------------------------------------
//    Initialize Direct3D.
//-----------------------------------------------------------------
BOOL InitDirect3D( HWND hWnd )
{
	D3DPRESENT_PARAMETERS d3dpp;
	HRESULT hr;
	
	if ( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ){
		MessageBox( hWnd , "Can't create Direct3D." , "Error" , MB_OK );
		return FALSE;
	}
	ZeroMemory( &d3dpp , sizeof( d3dpp ) );
	d3dpp.Windowed         = TRUE;
	d3dpp.SwapEffect       = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	
	hr = g_pD3D->CreateDevice( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL , hWnd ,
	                           D3DCREATE_SOFTWARE_VERTEXPROCESSING ,
	                           &d3dpp , &g_pd3dDevice );
	if ( FAILED( hr ) ){
		MessageBox( hWnd , "Can't create device." , "Error" , MB_OK );
		return FALSE;
	}
	
	g_pd3dDevice->SetRenderState( D3DRS_CULLMODE , D3DCULL_NONE );
	g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , FALSE );
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Cleanup Direct3D.
//-----------------------------------------------------------------
BOOL CleanupDirect3D()
{
	if ( g_pVB != NULL )
		g_pVB->Release();
	
	if ( g_pd3dDevice != NULL )
		g_pd3dDevice->Release();
	
	if ( g_pD3D != NULL )
		g_pD3D->Release();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Window Proc.
//-----------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd , UINT msg , WPARAM wp , LPARAM lp )
{
	switch( msg ){
		case WM_DESTROY:
			CleanupDirect3D();
			PostQuitMessage( 0 );
			break;
		default:
			return DefWindowProc( hWnd , msg , wp , lp );
	}
	
	return 0L;
}



//-----------------------------------------------------------------
//    Initialize Geometry.
//-----------------------------------------------------------------
BOOL InitGeometry()
{
	HRESULT hr;
	VOID *pVertices;
	
	CUSTOMVERTEX Vertices[] =
	{
		{ -1.0f , -1.0f , 0.0f , 0xFFFF00FF } ,
		{  0.0f ,  1.0f , 0.0f , 0xFFFFFF00 } ,
		{  1.0f , -1.0f , 0.0f , 0xFF00FFFF } ,
	};
	
	hr = g_pd3dDevice->CreateVertexBuffer(
		3 * sizeof( CUSTOMVERTEX ) ,
		0 ,
		D3DFVF_CUSTOMVERTEX ,
		D3DPOOL_DEFAULT ,
		&g_pVB ,
		NULL
	);
	if ( FAILED( hr ) ){
		MessageBox( NULL , "Couldn't create vertex buffer" , "Error" , MB_OK );
	}
	
	g_pVB->Lock( 0 , sizeof( Vertices ) , ( void** )&pVertices , 0 );
	memcpy( pVertices , Vertices , sizeof( Vertices ) );
	g_pVB->Unlock();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Setup Matrices.
//-----------------------------------------------------------------
BOOL SetupMatrices()
{
	D3DXMATRIXA16 matWorld , matView , matProj;
	D3DXVECTOR3 vEyePt , vLookatPt , vUpVec;
	
	// World Matrix.
	D3DXMatrixIdentity( &matWorld );
	g_pd3dDevice->SetTransform( D3DTS_WORLD , &matWorld );
	
	// Camera.
	vEyePt.x    = 5.0f;
	vEyePt.y    = 5.0f;
	vEyePt.z    = 0.0f-5.0f;
	vLookatPt.x = 0.0f;
	vLookatPt.y = 0.0f;
	vLookatPt.z = 0.0f;
	vUpVec.x    = 0.0f;
	vUpVec.y    = 1.0f;
	vUpVec.z    = 0.0f;
	D3DXMatrixLookAtLH( &matView , &vEyePt , &vLookatPt , &vUpVec );
	g_pd3dDevice->SetTransform( D3DTS_VIEW , &matView );
	
	// Projection Matrix.
	D3DXMatrixPerspectiveFovLH( &matProj , 3.0f / 4.0f , 1.0f , 1.0f , 100.0f );
	g_pd3dDevice->SetTransform( D3DTS_PROJECTION , &matProj );
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Render Direct3D.
//-----------------------------------------------------------------
BOOL RenderDirect3D()
{
	RECT rc;
	D3DXVECTOR3 center , position;
	
	g_pd3dDevice->Clear( 0 , NULL , D3DCLEAR_TARGET ,
	                     0x00000000 , 1.0f , 0 );
	
	g_pd3dDevice->BeginScene();
	
	SetupMatrices();
	
	g_pd3dDevice->SetStreamSource( 0 , g_pVB , 0 , sizeof( CUSTOMVERTEX ) );
	g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
	g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP , 0 , 1 );
	
	g_pd3dDevice->EndScene();
	
	g_pd3dDevice->Present( NULL , NULL , NULL , NULL );
	
	return TRUE;
}

実行結果

クリックすると実物大で表示されます。
執筆: 2008/05/06 (TUE)