DirectX Class
Chapter 17: ライト
* Chapter List *
* Others *
3Dオブジェクトにすさまじいまでのリアリティを
生意気言ってすいません。
そこまでリアリティは出ません。
いや、でも、テクスチャと併用するとなかなかのリアリティになること受けあいです。

前回までの章でDirectInputはオッケーだと思いますので、今回からまたDirect3Dのほうに戻りたいと思います。
今回はプリミティブやメッシュにライトを当ててみたいと思います。
ライトを当てるとオブジェクトの陰影が生まれ、非常に立体的に見えるようになります。
例えば、第12章で表示したスネイクは、ライトを当てることによって以下のように見るようになります。

ライトなし(第12章 ライトあり

このようにライトを当てると陰影が生まれ、より現実に近いものとなります。
上の白いヘビであっても、ライトを当てる前はなんかのっぺりしていますが、
ライトを当てることによって折り紙のように見えます。

やっぱり明るさは大切ですね。
人生も明るく生きましょう。

今回はライトの当て方について解説していきたいと思います。
直線光
Direct3Dにおけるライトは大きく分けて3種類ありますが、今回は直線光のみ扱います。
直線光とは、どこにいても同じ方向から来る光のことです。
太陽の光がしばしば例に挙げられます。
太陽の光はどこにいても同じ方向から照り付けてきます。
100メートルや200メール歩いても、東から照りつける太陽光は東から照り付けてきます。

それに比べて、部屋の蛍光灯はどうでしょう。
北から蛍光灯の光が来ていても、部屋の反対側へ移動するだけで南から光が来ます。
これを点光源と呼びます。

また、Direct3Dにおいて光と共に重要なのが、『マテリアル』です。
重要なのは重要ですが、マテリアルまで解説していると非常に長くなりますので、今回は直線光を当てるだけにします。
「他のライトは?」
「マテリアルって何?」
「そもそもどんなメッシュでも光は使えるの?」
等様々な疑問があると思いますが、それはまた何章か確保してしっかり解説していきますので、
今回は大人の対応をお願い致します。
光を使えるように設定する
今回は第12章で使用したプログラムを改良する形で解説致します。

まず、ライトを使えるようにしなくてはなりません。
ライトを使えるようにするには、以下のメソッドを使って設定します。

g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , TRUE );

これでライトが使用可能になりました。
しかし、使用可能になったというだけで、ライトがどの方向からどのように当たるのかを設定しなくてはなりません。

最初にマテリアルの設定をします。
マテリアルに関しましては、後の章で詳しく解説しますので今回は流し読みしてください。

D3DMATERIAL9 mtrl;

ZeroMemory( &mtrl , sizeof( D3DMATERIAL9 ) );
mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
g_pd3dDevice->SetMaterial( &mtrl );

続いて、ライトを設定します。
ライトを設定するには、まず『 D3DLIGHT9 』構造体に値を入れていきます。

D3DLIGHT9 light;

light.Type      = D3DLIGHT_DIRECTIONAL;
light.Diffuse.r = 1.0f;
light.Diffuse.g = 1.0f;
light.Diffuse.b = 1.0f;
light.Direction = D3DXVECTOR3( 0.0f , -1.0f , 0.0f );
light.Range     = 1000.0f;

light.Typeにはライトの種類を設定します。今回は直線光ですので、『 D3DLIGHT_DIRECTIONAL 』を指定します。
light.Diffuse.rlight.Diffuse.glight.Diffuse.bはライトの色です。
それぞれ赤、緑、青色を、0.0f ~ 1.0fの間で指定します。
light.Directionはライトの照らす方向をベクトルで指定します。
今回は下向きのベクトルなので、D3DXVECTOR3( 0.0f , -1.0f , 0.0f )としました。
D3DXVECTOR3()は与えた値からベクトルを作成してくれる関数で、戻り値はD3DXVECTOR3型です。
light.Rangeは光の届く距離です。Direct3Dの直線光に距離は関係ないので、ぶっちゃけ1でも構いません。

次はこの構造体を使用してライトをセットします。

g_pd3dDevice->SetLight( 0 , &light );

第1引数にはライトIDを、
第2引数にはD3DL;IGHT9構造体を指定します。
これ現実世界で言うところの、『ライトの設置』にあたります。
設置しただけではライトは点きません。
ライトの電源を入れなくてはなりません。
そのメソッドが以下のものです。

g_pd3dDevice->LightEnable( 0 , TRUE );

第1引数にはライトIDを、
第2引数には『 TRUE 』または『 FALSE 』を設定します。
『 TRUE 』でライトがONになり、『 FALSE 』でOFFになります。

最後に『 アンビエント光 』を設定して完了です。
『 アンビエント光 』とは、光が当たっていない部分の明るさを表現するための色です。
例えば、太陽の光の影になっている部分でも真っ黒というわけではなく、周りよりは暗いもののちゃんと見えます。
実際は太陽の光が様々なものに乱反射して作られている明るさなのですが、
そんなものはスパコンでなければとても処理が追いつかないので、
Direct3Dでは『 アンビエント光 』という概念によって表現することになっています。
そのアンビエント光の設定方法は以下の通りです。

g_pd3dDevice->SetRenderState( D3DRS_AMBIENT , 0x00444444 );

これで直線光を当てることができます。
直線光を当てるには、上記の処理を任意のタイミングで行ってください。
但し、描画の前に設定しなければ当然ながらその効果を得られません。
サンプルではワールドマトリックスやカメラの設定よりも前に行っています。
サンプル
このサンプルを実行すると、真っ暗なウインドウの中で蛇さんが回転します。
第12章と動きは同じですので、光の当たり具合による見え方なんかを比較してみてください。
これであなた方はのっぺり3Dオブジェクトから開放されます。

同じメッシュを使用したい方はどうぞ。
ライトが適用されるメッシュには条件がありますので、
自作のメッシュの場合はライトが反映されない場合があります。

蛇さんのメッシュ(テキストが開く場合は右クリックから保存)

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

//-----------------------------------------------------------------
//
//    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;
LPD3DXMESH              g_pMesh      = NULL;



//-----------------------------------------------------------------
//    Prototypes.
//-----------------------------------------------------------------
HWND    InitApp( HINSTANCE , int );
BOOL    InitDirect3D( HWND );
BOOL    CleanupDirect3D();
BOOL    SetupLights();
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;
	
	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 Lighting 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;
	}
	
	g_pd3dDevice->SetRenderState( D3DRS_CULLMODE , D3DCULL_NONE );
	g_pd3dDevice->SetRenderState( D3DRS_ZENABLE  , TRUE );
	
	g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , TRUE );
	
	D3DXLoadMeshFromX( ".\\sample0017.x" , D3DXMESH_SYSTEMMEM , g_pd3dDevice ,
	                   NULL , NULL , NULL , NULL , &g_pMesh );
	
	return TRUE;
}



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



//-----------------------------------------------------------------
//    Setup Matrices.
//-----------------------------------------------------------------
BOOL SetupMatrices()
{
	D3DXMATRIXA16 matWorld , matView , matProj;
	D3DXVECTOR3 vEyePt , vLookatPt , vUpVec;
	
	// World Matrix.
	D3DXMatrixRotationY( &matWorld , timeGetTime()/1000.0f );
	g_pd3dDevice->SetTransform( D3DTS_WORLD , &matWorld );
	
	// Camera.
	vEyePt.x    = 0.0f;
	vEyePt.y    = 3.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;
}



//-----------------------------------------------------------------
//    Setup Lights.
//-----------------------------------------------------------------
BOOL SetupLights()
{
	D3DMATERIAL9 mtrl;
	D3DLIGHT9    light;
	
	ZeroMemory( &mtrl , sizeof( D3DMATERIAL9 ) );
	mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
	mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
	mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
	mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
	g_pd3dDevice->SetMaterial( &mtrl );
	
	ZeroMemory( &light , sizeof( D3DLIGHT9 ) );
	light.Type      = D3DLIGHT_DIRECTIONAL;
	light.Diffuse.r = 1.0f;
	light.Diffuse.g = 1.0f;
	light.Diffuse.b = 1.0f;
	light.Direction = D3DXVECTOR3( 0.0f , -1.0f , 0.0f );
	light.Range     = 1000.0f;
	g_pd3dDevice->SetLight( 0 , &light );
	g_pd3dDevice->LightEnable( 0 , TRUE );
	
	g_pd3dDevice->SetRenderState( D3DRS_AMBIENT , 0x00444444 );
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Render Direct3D.
//-----------------------------------------------------------------
BOOL RenderDirect3D()
{
	RECT rc;
	D3DXVECTOR3 center , position;
	
	g_pd3dDevice->Clear( 0 , NULL , D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER ,
	                     0x00000000 , 1.0f , 0 );
	
	g_pd3dDevice->BeginScene();
	
	SetupLights();
	
	SetupMatrices();
	
	g_pMesh->DrawSubset( 0 );
	
	g_pd3dDevice->EndScene();
	
	g_pd3dDevice->Present( NULL , NULL , NULL , NULL );
	
	return TRUE;
}

実行結果

クリックすると実物大で表示されます。