★ DirectX Class ★ * Chapter 04: Zバッファ *
< 2D描画の古今 >
ゲームを作っていると、画像の重なる部分が出てきます。
まず、背景があり、その上にキャラクターの画像があり、その上にアイテムの画像があったりします。
簡単なゲームならこれですみますが、凝ったゲームになると、
遠景があり、キャラクターより後ろの景色があり、アイテムがあり、キャラクターがあり、エフェクトがあり、近景があり、ステータスウインドウがあり・・・
という状況になります。
パソコンの性質上、後から描画したものが上に描かれるため、下に描画したいものから順に描画していくというルールになります。
下の図で言うと、ピンク、水色、黄色の順で描画することになりますね。

図1

これが縄文時代に一般的だったゲームプログラミングです。
しかし、図の黒い線で囲まれた部分、3つの画像が重なっている部分は3回も描画されていることになります。
それ以外にも2つ画像が重なっている部分が沢山あります。
これはずいぶんと無駄ですね。
この問題に対して出された解答が『Zバッファ』です。

Zバッファでは、基本的に画像を手前から描画していくことになります。
基本的にということであって、例外も多用しますけれど。
先程の図で言うと、黄色、水色、ピンクの順で描画します。
その際、それぞれの画像が画面から見てどの程度の距離に描画されるのかを0.0f~1.0fの間で指定します。
黄色は0.0f、水色は0.5f、ピンクは1.0fという感じのノリです。
この距離のことを『深度』といいます。

こうすることによって、DirectXはすでに描画の済んだ領域に描画する際、その深度を調べ、描画しようとするものの深度のほうが浅ければ描画し、深ければ描画しません。
描画しないということはそれだけ無駄がなくなり、高速になります。
2D描画でも十分効果はありますし、3Dでは必須とも言える技術です。

Zバッファは特殊な技術ではなく、DirectXでプログラムを組むときには普通に使用します。
< Zバッファを利用するための設定 >
Zバッファの導入に際して、難しいことはありません。
まず、デバイス作成時のD3DPRESENT_PARAMETERS構造体を設定する際、以下のメンバに対する設定を加えます。

d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

EnableAutoDepthStencilがZバッファを使用可能にするかどうかの設定です。
『TRUE』を指定することによって使用可能になります。
AutoDepthStencilFormatには作成するZバッファのフォーマットを指定します。
フォーマットはたくさんあり、どのフォーマットを使えば良いか分かりにくいです。
正攻法でいくなら、デバイスの使用可能なフォーマットを取得して、そのなかから最良のものを選ぶという方法でしょう。
使用可能なフォーマットを取得する関数がDirectXにはあったはずです。
しかし面倒なので、今回は一番エラーの発生しにくいらしい『 D3DFMT_D16 』を指定しました。
これでエラーが発生する場合には色々と変えてみてください。

次に、描画に関する設定で、実際にZバッファを使用する設定にします。
先程の設定は『Zバッファを使用可能にする』もので、今回の設定は『実際に使用』します。
使用するには以下の関数を利用します。

g_pd3dDevice->SetRenderState( D3DRS_ZENABLE , TRUE );

この関数を実行した後の描画には全てZバッファが適用されます。
処理の途中で何らかの理由によりZバッファの利用を中止したい場合には、以下のようにします。

g_pd3dDevice->SetRenderState( D3DRS_ZENABLE , FALSE );

この関数の実行した後の描画にはZバッファが適用なれなくなります。
< 描画 >
続いて実際の描画に関する部分です。
まず、g_pd3dDevice->Clear関数を実行する際、第3引数に『 D3DCLEAR_ZBUFFER 』というフラグを追加します。

g_pd3dDevice->Clear( 0 , NULL , D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER ,
                     0xFFFFFFFF , 1.0f , 0 );

これは描画されるバッファと共にZバッファもクリアするという設定です。
この後、g_pSprite->Drawの第4引数に与えるD3DXVECTOR3に対して、深度の設定を加えると見事に反映されます。

このとき、深度は0.0f~1.0fの間の小数値にしなくてはなりません。
< サンプル >
このサンプルを実行すると、クライアント領域に図のようなものが表示されます。
描画の順番と実際の重なり順を比較してみてください。
画像は以下のものを使用しました。
実行ファイルと同じディレクトリに保存して実行してください。

sample04.bmp

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

//-----------------------------------------------------------------
//
//    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;
LPD3DXSPRITE       g_pSprite    = NULL;
LPDIRECT3DTEXTURE9 g_pTexture   = NULL;



//-----------------------------------------------------------------
//    Prototypes.
//-----------------------------------------------------------------
HWND    InitApp( HINSTANCE , int );
BOOL    InitDirect3D( HWND );
BOOL    CleanupDirect3D();
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;
	
	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;
	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	
	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;
	}
	
	D3DXCreateSprite( g_pd3dDevice , &g_pSprite );
	D3DXCreateTextureFromFile( g_pd3dDevice , "sample0004.bmp" , &g_pTexture );
	
	g_pd3dDevice->SetRenderState( D3DRS_ZENABLE , TRUE );
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Cleanup Direct3D.
//-----------------------------------------------------------------
BOOL CleanupDirect3D()
{
	if ( g_pTexture != NULL )
		g_pTexture->Release();
	
	if ( g_pSprite != NULL )
		g_pSprite->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;
}



//-----------------------------------------------------------------
//    Render Direct3D.
//-----------------------------------------------------------------
BOOL RenderDirect3D()
{
	RECT rc;
	D3DXVECTOR3 center , position;
	
	g_pd3dDevice->Clear( 0 , NULL , D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER ,
	                     0xFFFFFFFF , 1.0f , 0 );
	
	g_pd3dDevice->BeginScene();
	
	g_pSprite->Begin( NULL );
	
	// Yellow.
	rc.left    = 0;
	rc.top     = 128;
	rc.right   = 128;
	rc.bottom  = 256;
	center.x   = 0.0f;
	center.y   = 0.0f;
	center.z   = 0.0f;
	position.x = 100.0f;
	position.y = 20.0f;
	position.z = 0.0f;
	g_pSprite->Draw( g_pTexture , &rc , &center , &position , 0xFFFFFFFF );
	
	// Blue.
	rc.left    = 128;
	rc.top     = 0;
	rc.right   = 256;
	rc.bottom  = 128;
	center.x   = 0.0f;
	center.y   = 0.0f;
	center.z   = 0.0f;
	position.x = 180.0f;
	position.y = 60.0f;
	position.z = 0.5f;
	g_pSprite->Draw( g_pTexture , &rc , &center , &position , 0xFFFFFFFF );
	
	// Pink.
	rc.left    = 0;
	rc.top     = 0;
	rc.right   = 128;
	rc.bottom  = 128;
	center.x   = 0.0f;
	center.y   = 0.0f;
	center.z   = 0.0f;
	position.x = 140.0f;
	position.y = 100.0f;
	position.z = 1.0f;
	g_pSprite->Draw( g_pTexture , &rc , &center , &position , 0xFFFFFFFF );
	
	g_pSprite->End();
	
	g_pd3dDevice->EndScene();
	
	g_pd3dDevice->Present( NULL , NULL , NULL , NULL );
	
	return TRUE;
}

実行結果

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