ホーム < ゲームつくろー! < DirectX技術編

その59 カメラに世界を収めるには?:サンプルプログラム



 DirectX技術編その59「カメラに世界を収めるには?」で説明した内容を踏まえたサンプルプログラムです。実行すると回転するキューブが中心で回転した画像が表示されます。


サンプルスクリーンショット。同じ位置で回転しているように見えますが動いています。

 このキューブは実はワールド空間を回転しながら前後左右に移動しているのですが、カメラが自分の方向を保ちながら追従しているため同じような見え方をしています。キューブから算出された境界球を使い、DirectX技術編その59「カメラに世界を収めるには?」で紹介した関数から、境界球をすっぽりと収めるビュー行列を算出してデバイスに適用しています。


 サンプルを動かすために必要なファイルはこちらからダウンロードできます(DXGSmp_No59.lzh)。アーカイブに含まれているヘッダーファイル(.h)、実装ファイル(.cpp)、Xファイルをプロジェクト直下に置いて、空のプロジェクトに実装ファイルを追加すると動かす事ができます。うまく動かない場合は掲示板にご連絡下さい。

// 3Dオブジェクトをカメラの真ん中に収めるサンプルプログラム

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

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

_TCHAR gName[100] = _T("3Dオブジェクトをカメラの真ん中に収めるサンプルプログラム");
const TCHAR* fileName = _T("Cube2.x");

////////////////////////////////////////
// 境界球をカメラに収めるView行列算出
//
// out    : ビュー行列(出力)
// r      : 境界球半径
// fovY   : 画角Y
// aspect : アスペクト比
// direct : カメラの向きベクトル
// up     : カメラの上ベクトル
// 戻り値 : ビュー行列
D3DXMATRIX *getViewMatrixTakingSphereInCamera(
        D3DXMATRIX* out,
        const D3DXVECTOR3& center,
        float r,
        float fovY,
        float aspect,
        const D3DXVECTOR3& direct,
        const D3DXVECTOR3& up
) {
        // fovYとfovXの小さい方をθとして選択
        float theta = (aspect >= 1.0f) ? fovY : fovY * aspect;

        // 引く距離を算出
        float d = r / sin( theta / 2.0f );

        // カメラ位置確定
        D3DXVECTOR3 normDirect;
        D3DXVec3Normalize( &normDirect, &direct );
        D3DXVECTOR3 pos = center - normDirect * d;

        // ビュー行列作成
        return D3DXMatrixLookAtLH( out, &pos, &center, &up );
}

float getBoundingSphereFromMesh( ID3DXMesh* pMesh ) {
        DWORD FVF = pMesh->GetFVF();
        DWORD vertexSize = D3DXGetFVFVertexSize( pMesh->GetFVF() );
        DWORD num = pMesh->GetNumVertices();
        D3DXVECTOR3 *p = 0;
        if ( FAILED( pMesh->LockVertexBuffer( 0, (void**)&p ) ) )
                return 0.0f;
        D3DXVECTOR3 center;
        float r = 0.0f;
        D3DXComputeBoundingSphere( p, num, vertexSize, &center, &r );
        pMesh->UnlockVertexBuffer();

        return r;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam){
   if(mes == WM_DESTROY) {PostQuitMessage(0); return 0;}
   return DefWindowProc(hWnd, mes, wParam, lParam);
}


int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
   // アプリケーションの初期化
   MSG msg; HWND hWnd;
   WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, NULL, NULL,
                                    (HBRUSH)(COLOR_WINDOW+1), NULL, (_TCHAR*)gName, NULL};
   if(!RegisterClassEx(&wcex))
      return 0;

   DWORD WndStyle = WS_OVERLAPPEDWINDOW & ~(WS_MAXIMIZEBOX | WS_SIZEBOX);
   RECT WndRect={0, 0, 640, 480};
   AdjustWindowRect( &WndRect, WndStyle, false );

   if(!(hWnd = CreateWindow(
           gName, gName, WndStyle,
           CW_USEDEFAULT, 0,
           WndRect.right-WndRect.left, WndRect.bottom-WndRect.top,
       NULL, NULL, hInstance, NULL)))
      return 0;

   // Direct3Dの初期化
   LPDIRECT3D9 g_pD3D;
   LPDIRECT3DDEVICE9 g_pD3DDev;
   if( !(g_pD3D = Direct3DCreate9( D3D_SDK_VERSION )) ) return 0;

   D3DPRESENT_PARAMETERS d3dpp = {640,480,D3DFMT_UNKNOWN,0,D3DMULTISAMPLE_NONE,0,
                                                      D3DSWAPEFFECT_DISCARD,NULL,TRUE,TRUE,D3DFMT_D24S8,0,D3DPRESENT_RATE_DEFAULT,D3DPRESENT_INTERVAL_DEFAULT}; 

   if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) )
   if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) )
   {
      g_pD3D->Release();
      return 0;
   }

   // Xファイル読み込み
   ID3DXBuffer *pMaterials;
   DWORD NumMaterials;
   ID3DXMesh *pMesh;
   if(FAILED(D3DXLoadMeshFromX( fileName, D3DXMESH_MANAGED, g_pD3DDev, NULL, &pMaterials, NULL, &NumMaterials, &pMesh))){
      g_pD3DDev->Release(); g_pD3D->Release(); return 0;
   }

   // 境界球の半径算出
   float r = getBoundingSphereFromMesh( pMesh );

   // ライト
   D3DLIGHT9 light;
   ZeroMemory(&light, sizeof(D3DLIGHT9) );
   light.Direction = D3DXVECTOR3(1, -2, 1);
   light.Type = D3DLIGHT_DIRECTIONAL; 
   light.Diffuse.r = 1.0f;
   light.Diffuse.g = 1.0f;
   light.Diffuse.b = 1.0f;
   g_pD3DDev->SetLight( 0, &light );
   g_pD3DDev->LightEnable( 0, true );
   g_pD3DDev->SetRenderState( D3DRS_LIGHTING, TRUE );
   g_pD3DDev->SetRenderState( D3DRS_AMBIENT, 0x00808080 );   // アンビエントライト

   // 射影変換行列設定
   float fovY = D3DXToRadian(45);
   float aspect = 640.0f/480.0f;
   D3DXMATRIX Persp;
   D3DXMatrixPerspectiveFovLH( &Persp, fovY, aspect, 1.0f, 10000.0f);
   g_pD3DDev->SetTransform(D3DTS_PROJECTION, &Persp);

   // ウィンドウ表示
   ShowWindow( hWnd, SW_SHOW );


   ////////////////////////////
   // メッセージループ
   /////
   D3DXMATRIX World;
   D3DXMATRIX Rot_X, Rot_Y;
   D3DXMATRIX Offset;
   D3DXMATRIX View;   // ビュー変換行列

   float a = 0.0f;
   do{
      Sleep(1);
      if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ){ DispatchMessage(&msg);}
      g_pD3DDev->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(40,40,80), 1.0f, 0 );
      g_pD3DDev->BeginScene();

      a += 0.1f;

          D3DXMatrixRotationX( &Rot_X, a * 0.15f );
          D3DXMatrixRotationY( &Rot_Y, a * 0.2f );
          D3DXVECTOR3 center( 10 * sin(a), 10 * cos( a * 0.34f), 10 * sin( a * 0.57f) );
          D3DXMatrixTranslation( &Offset, center.x, center.y, center.z );

          D3DXMatrixIdentity( &World );          // 単位行列化
          World = Rot_X * Rot_Y * Offset;

          // モデルを収めるビュー変換を取得
          getViewMatrixTakingSphereInCamera( &View, center, r, fovY, aspect, D3DXVECTOR3(0.0f, 0.0f, 1.0f), D3DXVECTOR3(0.0f, 1.0f, 0.0f) );

          // 行列登録
          g_pD3DDev->SetTransform(D3DTS_WORLD, &World);
          g_pD3DDev->SetTransform(D3DTS_VIEW, &View);

          // モデル描画
          for( DWORD i=0; i < NumMaterials ; i++ ) {
              D3DXMATERIAL *mtrl = (D3DXMATERIAL*)(pMaterials->GetBufferPointer());
              g_pD3DDev->SetMaterial( &mtrl->MatD3D );
              pMesh->DrawSubset(i); 
          };

      g_pD3DDev->EndScene();
      g_pD3DDev->Present( NULL, NULL, NULL, NULL );
   }while(msg.message != WM_QUIT);

   pMesh->Release();
   g_pD3DDev->Release();
   g_pD3D->Release();

   return 0;
}