DirectX Class
Chapter 18: カメラ
* Chapter List *
* Others *
カメラ
今回はカメラについて解説していきたいと思います。

カメラとは、映画を撮るときのようなカメラです。
Direct3Dにも同じような概念として存在していて、3D空間上の座標、方向、及び上方向を持っています。
それだけ設定すれば、カメラはその視界に入るものを私たちに見せてくれるでしょう。

設定はすごく簡単なのですが、その表現手法や扱いは非常に難しいです。
ハリウッド映画なんかを研究してみれば分かりますが、非常に綿密な計算のもとで撮影が行われています。
視点一つとっても、主人公視点、敵視点、第三者視点、鳥の視点、様々です。

また、迫力のある場面であれば迫力のあるような位置から撮っています。
例えば、大爆発が起きる場面であっても、遠くのほうから撮れば『対岸の火事』感を出せますし、
爆炎や飛ばされた車の破片が迫り来るところで撮れば危機感を出せます。

…。
語っていたら何を書こうとしていたのか忘れました。
とにかく、表現手法になれば奥は深いですし、さらにそれをゲームに使おうとするとまた別の技術が必要になってきます。
しかし、その設定は非常に簡単ですので、解説致します。
カメラの設定方法
カメラの設定は以下の3つを押さえてください。
この3つだけです。

カメラの位置
注視点
上方向

よく忘れがちなのが『 上方向 』です。
上方向とは、そのままカメラの上の方向です。
例えば、戦闘機なんかに乗っていると、頭は必ず空のほうを向いているとは限りません。
旋回しているときは真横や地面のほうに頭が向くことがあります。
そのような状態のパイロットからの視線も可能性としてはあるため、カメラの上方向を考えなければなりません。

まず、カメラの位置を設定するには以下のようにします。

D3DXVECTOR3 vEyePt;

vEyePt.x = 0.0f;
vEyePt.y = 1.0f;
vEyePt.z = 5.0f;

これでカメラの位置が決まります。
ゲームでカメラを移動させるときはここの値をどんどん変えていくことになります。

続いて、注視点を設定します。
注視点とは、焦点の合っている点だと理解していただければ結構です。
このとき、焦点の合っている点とカメラの位置の差が、カメラから注視点の距離ということになります。
ゲーム等でキャラクターの後ろを追従するカメラの場合は、キャラクターの位置を注視点として、
カメラとその位置を一定に保つようにしたります。
コードは以下のようになります。

D3DXVECTOR3 vLookatPt;

vLookatPt.x = 0.0f;
vLookatPt.y = 0.1f;
vLookatPt.z = 0.0f;

最後に上方向ですが、
これはさきほど説明した通りベクトルで指定してください。

D3DXVECTOR3 vUpVec;

vUpVec.x = 0.0f;
vUpVec.y = 1.0f;
vUpVec.z = 0.0f;

これらの情報を元にマトリクス(行列)を作成し、カメラの情報として登録します。
カメラ用のマトリクスはD3DXMatrixLookAtLHという関数を使って作成します。

D3DXMATRIXA16 matView;

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

第1引数は、結果を受け取るためのD3DXMATRIXA16型変数のアドレスを指定します。
第2引数にはカメラの位置を、
第3引数には注視点を、
第4引数には上方向を指定します。

こうして作成したカメラのマトリクスを使用して、最終的に設定します。

g_pd3dDevice->SetTransform( D3DTS_VIEW , &matView );

第1引数にはセットするデータの種類を、
第2引数にはデータを指定します。

セットするデータの種類は色々ありますが、
カメラをセットするときは『 D3DTS_VIEW 』を指定します。

以上でカメラのセットが完了します。
現実世界でも当たり前のことを設定しているだけのことですので、かなり理解しやすいのではないかと思います。
必要なデータを作成し、マトリクスに変換してからセットするということを頭に叩き込んでおいてください。
カメラの動き
ゲームでは大抵カメラが動きます。
動かないものもありますが、フル3Dゲームであれば大抵動きます。
「カメラの動くゲームなんて恐ろしくて作れない!」という方はここは読み飛ばして頂いて結構です。
宗教上の理由でカメラの動かないゲームしか作れない方もおられるかもしれませんので。

まず最初に、簡単な例を見てみます。
簡単ですが、キャラクターの移動等にもガンガン使う考え方ですので、気合を入れてください。

例えば、カメラを移動させるときはどうでしょう。
ここではカメラが一定方向を向いたまま移動する例を考えます。
以下のような状態です。

カメラの平行移動

このように移動させる場合、カメラの座標と注視点の座標は平行に移動させなくてはなりません。
平行に移動させるには、カメラの移動量を注視点にも加算することになります。
具体的には以下のようなコードを書くことになります。

D3DXVECTOR3 mov;

mov = D3DXVECTOR3( 1.0f , 0.2f , 0.7f ); // カメラのxyzそれぞれの方向への移動量

vEyePt.x    += mov.x;
vEyePt.y    += mov.y;
vEyePt.z    += mov.z;

vLookatPt.x += mov.x;
vLookatPt.y += mov.y;
vLookatPt.z += mov.z;

これでカメラと注視点が一緒に動くので、常に一定方向を向いたまま移動させることが可能です。
これは逆も可能で、注視点の移動量を最初に算出して、その移動量をカメラの位置に加算しても平行移動が可能です。
例えば主人公を注視点にして動くゲームの場合なんかだと、注視点である主人公の移動量を求めてから、カメラの位置を算出することになります。

さて、これだけではゲームによっては不具合が生じます。
上記の例ではカメラは一定方向しか向いていませn。
つまり、北を向いていたら最初から最後まで北を向きっぱなしということになります。

それではそんな一定方向しか見られない束縛から解放されましょう。
まず簡単のために、カメラの位置は移動させず、周りをぐるっと見回せるようにしてみます。
カメラを中心に回転させる感じです。
上下方向も考えず、地面と平行な方向を見回すことにします。

以下のようなイメージです。

カメラの回転

このようなカメラワークのときにポイントとなるのは、カメラと注視点の距離が常に一定に保たれていることです。
これを実現するには、数学のsincosの知識が必要になります。
カメラの扱いにおけるsincosの使い方については解説しますが、
sincosがそもそも何なのかということについてはご自分で勉強してください。

さて、上記のようなカメラワークを行う場合でも例外なく、カメラの注視点の座標を設定しなくてはなりません。
この場合、最初に基準となる方向を決めておき、その方向からの回転角度から注視点を割り出すのが一番スマートです。
以下のようなノリになります。

sin、cosによる座標の算出

上図ではz軸の+方向を基準とし、そこからの回転角度を使ってxz座標を算出しています。
時計周りでも反時計周りでも、どちらでもこの式はそのまま使えます。
『 a 』はカメラから注視点までの距離です。
ここでは角度を[rad]で示していますが、これは[度]の別表現です。
文型の方はあまりご存知ないかもしれませんが、数学では角度を[度]ではなく[rad]で表すことがしばしばあります。
DirectXにおいてsincosを使う場合、引数に与える角度は[rad]で指定しなくてはなりませn。
[度]を[rad]に変換するには[度]にπ/180をかけるだけです。
分からない人は暗記でも大丈夫だと思います。

具体的には以下のようなコードになります。
カメラの位置は原点にあると考えてください。

float rad;
float range;

rad   = 60.0f; // 基準軸からの回転角度を[rad]で指定
range = 5.0f;  // カメラから注視点までの距離

vLookatPt.x = range * sin( rad );
vLookatPt.y = 0.0f;
vLookatPt.z = range * cos( rad );

カメラが原点にない場合には、これにカメラの位置を加算するだけで大丈夫です。
以下のようになります。

vLookatPt.x = vEyePt.x + range * sin( rad );
vLookatPt.y = vEyePt.y + 0.0f;
vLookatPt.z = vEyePt.z + range * cos( rad );

…正直かなり疲れてきました。
今日はこの章を書くだけで10時間くらいかかっています。
昨日からかかっているのでもう12時間は超えてます。

最後に、このようにカメラが回転した状態で、カメラの向いている方向にやその垂直方向に移動する方法です。
カメラの方向や垂直に動かせないと、最初の平行移動だけでは不具合を感じることがあります。
例えば、左キーを押したときに-x方向へ、右キーを押したときに+x方向へ移動するとすると、
+z方向を向いているときは良いのですが、-z方向を向いているときは視界の動きとキーの動きが逆になってしまいます。
斜めを向いているときは視界が斜めに移動しているようになり、とても操作しづらいです。
そこで、今度はカメラの方向やその垂直方向に移動してみましょう。
イメージは以下の通りです。

カメラの移動

これは先ほどのカメラの回転と全く同じ考え方でいけます。
さきほどの『 カメラから注視点までの距離 』『 移動距離 』に置き換えて考えます。
先ほどの図の『 a 』『 移動距離 』ということになります。

sin、cosによる座標の算出

これでカメラに向いている方向への前進・後退が可能になります。
では、左右への移動はどうなるでしょうか。
例えば、時計周りに角度が増加していくとすると、
右へ移動というのは、進行方向から右へ90°移動するのと同じですから、『 進行方向 + π/2 [rad] 』ということになります。
角度へπ/2足したらどのようになるでしょうか。
答えは以下のようになります。

右へ移動する場合

数学をやっている方にとっては当たり前のことですが、文系の方にとっては少し難しいかもしれません。
難しいと感じた方は結果のみ覚えてください。

具体的なコードは以下のようになります。

float movabs;

movabs = 2.4f; // 移動距離

vLookatPt.x += movabs * cos( rad );
vLookatPt.y += 0.0f;
vLookatPt.z -= movabs * sin( rad );

一方、左へ移動する場合は、反時計周りに、つまり角度が減少する方向へ90°回転させて考えます。
すると以下のようになります。

左へ移動する場合

float movabs;

movabs = 2.4f; // 移動距離

vLookatPt.x -= movabs * cos( rad );
vLookatPt.y += 0.0f;
vLookatPt.z += movabs * sin( rad );

これで基本的なカメラワークは可能だと思います。
お疲れ様です。
サンプル
このサンプルを実行すると、白い地面の上に白いスネイクが登場します。
そしてキーボードの上下左右のキーでカメラを移動させることができ、
Zで左回転、Xで右回転します。

グローバル変数g_Cameraには位置と注視点、基準軸から向いている方向への角度が格納されています。
ReadInput()の中で、キーの入力に応じて移動や角度を変化させ、
それをSetupMatrices()の中でカメラの情報としてセットしています。

キーを1回押したときの移動量や回転量は最初に定数として定義してみました。

LOOKATRANGE カメラから注視点までの距離
ROTATERAD キー1回あたりの回転量
MOVERANGE キー1回あたりの移動量

DirectInputも併用していますが、特に新しいことはやっていませんので読みやすいと思います。

同じ素材を使用したい方はこちらからどうぞ。
スネイクのメッシュ(テキストが開く場合は右クリックから保存)
地面のメッシュ(テキストが開く場合は右クリックから保存)

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

//-----------------------------------------------------------------
//
//    DirectX 3D
//
//-----------------------------------------------------------------
#define INITGUID

#define sqrtf sqrt
#define sinf  sin
#define cosf  cos
#define tanf  tan

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

#include <dinput.h>
#include <dinputex.h>

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

#pragma comment( lib , "dinput8.dll" )

#define LOOKATRANGE 5.0f
#define ROTATERAD   0.01f
#define MOVERANGE   0.1f



//-----------------------------------------------------------------
//    Grobal Variables.
//-----------------------------------------------------------------
LPDIRECT3D9        g_pD3D       = NULL;
LPDIRECT3DDEVICE9  g_pd3dDevice = NULL;
LPD3DXMESH         g_pMesh      = NULL;
LPD3DXMESH         g_pMeshFloor = NULL;
LPD3DXFONT         g_pFont      = NULL;

LPDIRECTINPUT8       g_lpDI;
LPDIRECTINPUTDEVICE8 g_lpDIDevice;

struct CAMERA{
	D3DXVECTOR3 position; // 位置
	D3DXVECTOR3 lookat;   // 注視点
	float       rad;      // 向き
}g_Camera;



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

BOOL    InitDirectInput( HWND );
BOOL    ReadInput();
BOOL    CleanupDirectInput();



//-----------------------------------------------------------------
//    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 ( !InitDirectInput( hWnd ) ) return FALSE;
	
	while( msg.message != WM_QUIT ){
		if ( PeekMessage( &msg , NULL , 0 , 0 , PM_REMOVE ) ){
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}else{
			ReadInput();
			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( ".\\sample0018.x" , D3DXMESH_SYSTEMMEM , g_pd3dDevice ,
	                   NULL , NULL , NULL , NULL , &g_pMesh );
	D3DXLoadMeshFromX( ".\\sample0018_2.x" , D3DXMESH_SYSTEMMEM , g_pd3dDevice ,
	                   NULL , NULL , NULL , NULL , &g_pMeshFloor );
	
	D3DXCreateFont( g_pd3dDevice , 14 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
	                "Times New Roman" , &g_pFont );
	
	// 初期化
	g_Camera.rad        = 0.0f;
	g_Camera.position.x = 0.0f;
	g_Camera.position.y = 1.2f;
	g_Camera.position.z = 0.0f - 5.0f;
	g_Camera.lookat.x   = g_Camera.position.x + ( sin( g_Camera.rad ) * LOOKATRANGE );
	g_Camera.lookat.y   = 0.0f;
	g_Camera.lookat.z   = g_Camera.position.z + ( cos( g_Camera.rad ) * LOOKATRANGE );
	
	return TRUE;
}



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



//-----------------------------------------------------------------
//    Initialize DirectInput.
//-----------------------------------------------------------------
BOOL InitDirectInput( HWND hWnd )
{
	HINSTANCE hInst;
	HRESULT   hr;
	
	hInst = (HINSTANCE)GetWindowLong( hWnd , GWL_HINSTANCE );
	
	hr = DirectInput8Create( hInst , DIRECTINPUT_VERSION , IID_IDirectInput8 ,
	                         (void**)&g_lpDI , NULL );
	if ( FAILED( hr ) ){
		MessageBox( hWnd , "Can't create DirectInput object." , "Error" , MB_OK );
		return FALSE;
	}
	
	hr = g_lpDI->CreateDevice( GUID_SysKeyboard , &g_lpDIDevice , NULL );
	if ( FAILED( hr ) ){
		CleanupDirectInput();
		MessageBox( hWnd , "Can't create DirectInput device." , "Error" , MB_OK );
		return FALSE;
	}
	
	hr = g_lpDIDevice->SetDataFormat( &c_dfDIKeyboard );
	if ( FAILED( hr ) ){
		CleanupDirectInput();
		MessageBox( hWnd , "Can't set data format." , "Error" , MB_OK );
		return FALSE;
	}
	
	hr = g_lpDIDevice->SetCooperativeLevel( hWnd ,
	                                        DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );
	if ( FAILED( hr ) ){
		CleanupDirectInput();
		MessageBox( hWnd , "Can't set cooperative level." , "Error" , MB_OK );
		return FALSE;
	}
	
	if ( g_lpDIDevice ) g_lpDIDevice->Acquire();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Cleanup DirectInput.
//-----------------------------------------------------------------
BOOL CleanupDirectInput()
{
	g_lpDIDevice->Unacquire();
	
	if ( g_lpDIDevice != NULL )
		g_lpDIDevice->Release();
	
	if ( g_lpDI != NULL )
		g_lpDI->Release();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Window Proc.
//-----------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd , UINT msg , WPARAM wp , LPARAM lp )
{
	switch( msg ){
		case WM_DESTROY:
			CleanupDirectInput();
			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.
	D3DXMatrixIdentity( &matWorld );
	g_pd3dDevice->SetTransform( D3DTS_WORLD , &matWorld );
	
	// Camera.
	vEyePt.x    = g_Camera.position.x;
	vEyePt.y    = g_Camera.position.y;
	vEyePt.z    = g_Camera.position.z;
	vLookatPt.x = g_Camera.lookat.x;
	vLookatPt.y = g_Camera.lookat.y;
	vLookatPt.z = g_Camera.lookat.z;
	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()
{
	static RECT rc;
	static char buf[128];
	
	g_pd3dDevice->Clear( 0 , NULL , D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER ,
	                     0x00000000 , 1.0f , 0 );
	
	g_pd3dDevice->BeginScene();
	
	SetupLights();
	
	SetupMatrices();
	
	g_pMesh->DrawSubset( 0 );
	g_pMeshFloor->DrawSubset( 0 );
	
	rc.left   = 10;
	rc.top    = 10;
	rc.right  = 640;
	rc.bottom = 320;
	sprintf( buf , "( x , y , z ) = ( %f , %f , %f )\n ( rot ) = %f" ,
	         g_Camera.position.z , g_Camera.position.y , g_Camera.position.z , g_Camera.rad );
	g_pFont->DrawText( NULL , buf , -1 , &rc , NULL , 0xFFFF88AA );
	
	g_pd3dDevice->EndScene();
	
	g_pd3dDevice->Present( NULL , NULL , NULL , NULL );
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Read Input.
//-----------------------------------------------------------------
BOOL ReadInput()
{
	char    buffer[256];
	HRESULT hr;
	
	hr = g_lpDIDevice->GetDeviceState( sizeof( buffer ) , (LPVOID)&buffer );
	if ( FAILED( hr ) ) return FALSE;
	
	if ( buffer[DIK_Z] & 0x80 ){
		g_Camera.rad -= ROTATERAD;
	}
	if ( buffer[DIK_X] & 0x80 ){
		g_Camera.rad += ROTATERAD;
	}
	if ( buffer[DIK_UP] & 0x80 ){
		g_Camera.position.x += sin( g_Camera.rad ) * MOVERANGE;
		g_Camera.position.z += cos( g_Camera.rad ) * MOVERANGE;
	}
	if ( buffer[DIK_DOWN] & 0x80 ){
		g_Camera.position.x -= sin( g_Camera.rad ) * MOVERANGE;
		g_Camera.position.z -= cos( g_Camera.rad ) * MOVERANGE;
	}
	if ( buffer[DIK_LEFT] & 0x80 ){
		g_Camera.position.x -= cos( g_Camera.rad ) * MOVERANGE;
		g_Camera.position.z += sin( g_Camera.rad ) * MOVERANGE;
	}
	if ( buffer[DIK_RIGHT] & 0x80 ){
		g_Camera.position.x += cos( g_Camera.rad ) * MOVERANGE;
		g_Camera.position.z -= sin( g_Camera.rad ) * MOVERANGE;
	}
	
	g_Camera.lookat.x = g_Camera.position.x + ( sin( g_Camera.rad ) * LOOKATRANGE );
	g_Camera.lookat.y = 0.0f;
	g_Camera.lookat.z = g_Camera.position.z + ( cos( g_Camera.rad ) * LOOKATRANGE );
	
	return TRUE;
}

実行結果

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