Direct3Dの解説 2 ポリゴンの描画 (とにかく何かを表示してみる)

Direct3Dの解説を読んでいて思ったのはなかなか動作するプログラムができないということでした。とにかくなにか見えなければやる気が持続しないのでなにか動かしてみます。やることはたくさんあるのですが、解説しているとなかなか動かないので、動かしてから解説を四で見てください。以下のソースはメインのソースファイルを分割して解説をつけているので、すべて同じファイルに打ち込んでください。

おっとその前にプロジェクトの設定を変更しなければ。
プロジェクト−>設定−>リンク−>オブジェクト/ライブラリモジュール に
ddraw.lib dxguid.lib の二つを追加します。
設定−>C/C++の最適化を無効(デバッグ時)にしてその下のブラウザ情報を生成するにチェックをいれます。さらにデバッグ情報はプログラムデータベースを使用するにします。エディットコンティニュー用でも良いようです。
ビルドしたときにライブラリが無い、というメッセージが出たら、そのファイルをコンピュータ内で検索してどのディレクトリにあるか突き止めます。そのディレクトリを、VCのメニューのツール−>オプション−>ディレクトリ に追加します。

1で作ったプロジェクトに追加するのは青い部分です。最初はインクルードファイルと必要な変数の定義です。DDrawとD3Dで使用するオブジェクトはすべてポインタのようですね。
 

/*----------------------------------------------------------
DirectX 5
Direct3D IM
三角形のポリゴンを表示するプログラム
--------------------------------------------------------------*/
#define STRICT
#include <stdafx.h>
#include <string.h>
 
#include <ddraw.h>
#include <d3d.h>
#define RELEASE(x) if(x){x->Release();x=NULL;}
/*-------------------------------------------
外部変数
--------------------------------------------*/
HINSTANCE hInstApp;
HWND hwndApp;
char szAppName[] = "Direct3D IM test";
char szDevice[128], szDDDeviceName[128] = "default";
 
LPDIRECTDRAW2 pDDraw = NULL;
LPDIRECTDRAWSURFACE3 pScreen = NULL;
LPDIRECTDRAWSURFACE3 pBackBuffer = NULL;
LPDIRECT3D2 pD3D = NULL;
LPDIRECT3DDEVICE2 pD3DDevice = NULL;
LPDIRECT3DVIEWPORT2 pD3DVP = NULL;
LRESULT CALLBACK MainWndProc(HWND hWnd,UINT msg,UINT wParam,LONG lParam);
 
/*-------------------------------------------
アプリケーション初期化
--------------------------------------------*/
int InitApp(HINSTANCE hInst,int nCmdShow)
{
 WNDCLASS wndclass;
 hInstApp=hInst;
 /*ウィンドウクラスの登録*/
 wndclass.hCursor =LoadCursor(NULL,IDC_ARROW);
 wndclass.hIcon =NULL;
 wndclass.lpszMenuName =NULL;
 wndclass.lpszClassName ="Main Window";
 wndclass.hbrBackground =(HBRUSH)GetStockObject(BLACK_BRUSH);
 wndclass.hInstance =hInst;
 wndclass.style =CS_BYTEALIGNCLIENT|CS_VREDRAW|CS_HREDRAW;
 wndclass.lpfnWndProc =(WNDPROC)MainWndProc;
 wndclass.cbClsExtra =0;
 wndclass.cbWndExtra =0;
 if(!RegisterClass(&wndclass))
  return FALSE;
 /*メインウィンドウ*/
 hwndApp = CreateWindowEx(0,"Main Window",szAppName,
 WS_POPUP,
 0,0,640,480,
 (HWND)NULL,(HMENU)NULL,
 hInst,(LPSTR)NULL);
 ShowWindow(hwndApp,nCmdShow);
 UpdateWindow(hwndApp);
 return TRUE;
}
 

下は、DirtectDrawのデバイスを決める部分です。DirectDrawの初期化で使用されるコールバック関数です。コールバック関数というのは呼び出される関数が使用する外部の関数です。InitDDrawで ret = DirectDrawEnumerate(DDEnumCallback, &pOldDDraw);

のように使います。デバイスが見つかったら DDEnumCallback を呼び出せ、と言う意味です。
 

/*-------------------------------------------
// DirectDraw デバイスの列挙と選定
--------------------------------------------*/
BOOL CALLBACK DDEnumCallback(GUID FAR* lpGUID, LPSTR lpDriverDesc, LPSTR lpDriverName, LPVOID lpContext)
{
 LPDIRECTDRAW lpDD;
 DDCAPS DriverCaps, HELCaps;
 // DirectDraw オブジェクトを試験的に生成する
 if ( DirectDrawCreate(lpGUID, &lpDD, NULL) != DD_OK ) {
  *(LPDIRECTDRAW*)lpContext = NULL;
  return DDENUMRET_OK;
 }
 // DirectDrawの能力を取得
 ZeroMemory(&DriverCaps, sizeof(DDCAPS));
 DriverCaps.dwSize = sizeof(DDCAPS);
 ZeroMemory(&HELCaps, sizeof(DDCAPS));
 HELCaps.dwSize = sizeof(DDCAPS);
 if (lpDD->GetCaps(&DriverCaps, &HELCaps) == DD_OK) {
  // ハードウェア3D支援が期待できる場合で.
  // なおかつテクスチャが使用できる場合それを使う
   if ((DriverCaps.dwCaps & DDCAPS_3D) && (DriverCaps.ddsCaps.dwCaps & DDSCAPS_TEXTURE)) {
    *(LPDIRECTDRAW*)lpContext = lpDD;
    lstrcpy(szDDDeviceName, lpDriverDesc);
    return DDENUMRET_CANCEL;
   }
 }
 // 他のドライバを試す
 *(LPDIRECTDRAW*)lpContext = NULL;
 RELEASE(lpDD);
 return DDENUMRET_OK;
}
 

下はDirectDrawの初期化です。このあたりは誰が作っても大体同じな部分です。(というか、この章は全体的にそうだけど)変更するとすると、フルスクリーンか、ウィンドウか、解像度か、サーフェスの枚数くらいでしょう。
 

/*-------------------------------------------
DirectDraw 初期化
--------------------------------------------*/
int InitDDraw(void)
{
HRESULT ret;
LPDIRECTDRAW pOldDDraw = NULL;
// DirectDrawドライバを列挙する
ret = DirectDrawEnumerate(DDEnumCallback, &pOldDDraw);
// 列挙によってDirectDrawドライバが決定
// しなかった場合,現在アクティブなドライバを使う
if (!pOldDDraw) {
 lstrcpy(szDDDeviceName, "Active Driver");
 ret = DirectDrawCreate(NULL,&pOldDDraw,NULL);
}
if(ret != DD_OK)
 return FALSE;
 //ここでウィンドウモードを設定する
 ret = pOldDDraw->SetCooperativeLevel(hwndApp,DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
 // 新しいDirectDraw2オブジェクトを取得する
 // 古いDirectDrawオブジェクトは破棄する
 ret = pOldDDraw->QueryInterface(IID_IDirectDraw2, (LPVOID *)&pDDraw);
 RELEASE(pOldDDraw);
if(ret != DD_OK)
 return FALSE;
 //フル画面のとき画面サイズを設定する
 // (640*480-HiColorデフォルトのリフレッシュ・レート)
 ret = pDDraw->SetDisplayMode(640,480,16,0,0);
if(ret != DD_OK){
 RELEASE(pDDraw);
 return FALSE;
}
//サーフェースを作成する
DDSURFACEDESC ddsd;
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX | DDSCAPS_3DDEVICE;
ddsd.dwBackBufferCount = 1;
//プライマリサーフェース
LPDIRECTDRAWSURFACE pOldScreen;
ret = pDDraw->CreateSurface(&ddsd,&pOldScreen,NULL);
if(ret != DD_OK){
 RELEASE(pDDraw);
 return FALSE;
}
// 新しいDirectDrawSurface3を生成し古いオブジェクトは開放する
ret = pOldScreen->QueryInterface(IID_IDirectDrawSurface3, (LPVOID *)&pScreen);
RELEASE(pOldScreen);
if(ret != DD_OK) {
 RELEASE(pDDraw);
 return FALSE;
}
// バック・バッファを取得する
DDSCAPS ddscaps;
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ret = pScreen->GetAttachedSurface(&ddscaps, &pBackBuffer);
if(ret != DD_OK){
 RELEASE(pScreen);
 RELEASE(pDDraw);
 return FALSE;
}
return TRUE;
}
 

次がD3Dの初期化です。良く分からないことが書いてありますが、とりあえず分からなくても大丈夫です。間違いが無ければ動くので。
 

/*-------------------------------------------
Direct3D 初期化
--------------------------------------------*/
int InitD3D(void)
{
HRESULT ret;
//Direct3D IMオブジェクトの作成
ret = pDDraw->QueryInterface(IID_IDirect3D2,(void**)&pD3D);
if(ret != DD_OK){
 return FALSE;
}
/*---------------------------------------------------------
D3Dデバイスの取得
HAL , MMX , RGB の優先順位(速い順)で取得テスト
----------------------------------------------------------*/
//HAL
ret = pD3D->CreateDevice(IID_IDirect3DHALDevice,(LPDIRECTDRAWSURFACE)
pBackBuffer,&pD3DDevice);
strcpy(szDevice,"D3D HAL");
if(ret != DD_OK){
//MMX
ret = pD3D->CreateDevice(IID_IDirect3DMMXDevice,(LPDIRECTDRAWSURFACE)
pBackBuffer,&pD3DDevice);
strcpy(szDevice,"D3D MMX Emulation");
}
if(ret != DD_OK){
 //RGB
 ret = pD3D->CreateDevice(IID_IDirect3DRGBDevice,(LPDIRECTDRAWSURFACE)
pBackBuffer,&pD3DDevice);
 strcpy(szDevice,"D3D RGB Emulation");
}
if(ret != DD_OK)
 return FALSE; //失敗
 
//ビューポートの作成,設定
D3DVIEWPORT2 vp;
ZeroMemory(&vp,sizeof(vp));
vp.dwSize = sizeof(vp);
vp.dwX = 0;
vp.dwY = 0;
vp.dwWidth = 640;
vp.dwHeight = 480;
vp.dvClipX = -1.0f;
vp.dvClipWidth = 2.0f;
vp.dvClipY = (float)480/(float)640;
vp.dvClipHeight = vp.dvClipY * 2.0f;
vp.dvMinZ = 0.0f;
vp.dvMaxZ = 1.0f;
pD3D->CreateViewport(&pD3DVP,NULL);
pD3DDevice->AddViewport(pD3DVP);
pD3DDevice->SetCurrentViewport(pD3DVP);
pD3DVP->SetViewport2(&vp);
return TRUE;
}
 

終了の処理も追加します。DDrawもそうですが,取得したオブジェクトは開放しなければいけません。取得した順番に開放します。順番が変わるとエラーが出るので注意。
 

/*-------------------------------------------
終了の処理
--------------------------------------------*/
int EndApp(void)
{
 RELEASE(pD3DVP);
 RELEASE(pD3DDevice);
 RELEASE(pD3D);
 pBackBuffer = NULL;
 RELEASE(pScreen);
 RELEASE(pDDraw);
 return TRUE;
}
 
/*-------------------------------------------
ウィンドウ処理
--------------------------------------------*/
LRESULT CALLBACK MainWndProc(HWND hWnd,UINT msg,UINT wParam,LONG lParam)
{
 switch(msg){
 case WM_KEYDOWN:
 switch(wParam){
 case VK_ESCAPE:
 case VK_F12:
  DestroyWindow(hWnd);
  break;
 default:
  break;
 }
 break;
 case WM_DESTROY:
  PostQuitMessage(0);
  break;
 default:
  return DefWindowProc(hWnd,msg,wParam,lParam);
 }
return 0L;
}
 

アイドル時というのは車のアイドリングと同じでなにもしないときに常に回ってくる処理です。D3Dでは1秒間に何度もシーンを書き直しています。書き直すたびにポリゴンの座標を変更すると、ポリゴンが動くというわけですね。通常のウィンドウズアプリと違うのはこの部分です。通常アプリのアイドル時はほんとになにもしていません。

下の部分はポリゴンの頂点を3個指定しています。それぞれの頂点に自分でライトと色を設定しています。頂点に3原色のライトがあたったポリゴンが表示されます。ポリゴンの頂点をあらわす構造体をVERTEXといいます。D3Dではこのポリゴンデータの配列をDrawPrimitive という関数に渡してやると描画してくれます。
複雑なオブジェクト(たとえば人体など)だと、このポリゴンのデータが5000個ある、というように・・・現時点では考えられます。
 

/*--------------------------------------------
アイドル時の処理
--------------------------------------------*/
int AppIdle(void)
{
D3DTLVERTEX v[3];
DDBLTFX fx;
// 背景を塗りつぶす
ZeroMemory(&fx,sizeof(fx));
fx.dwSize = sizeof(fx);
pBackBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT | DDBLT_COLORFILL,&fx);
// ドライバ名などの文字を書き込む
HDC hdc;
char Data[256];
wsprintf(Data, "%s, [%s]", szDevice, szDDDeviceName);
 
if(pBackBuffer->GetDC(&hdc) == DD_OK){
 TextOut(hdc,0,0,Data,strlen(Data));
 pBackBuffer->ReleaseDC(hdc);
}
// DrawPrimitiveを使ってポリゴンを描画する
pD3DDevice->BeginScene();
 
v[0].sx = 160;
v[0].sy = 50;
v[0].sz = 0;
v[0].rhw = 1;
v[0].color = RGB_MAKE(255,0,0);
v[0].specular = D3DRGB(0,0,0);
v[0].tu = 0;
v[0].tv = 0;
v[1].sx = 240;
v[1].sy = 200;
v[1].sz = 0;
v[1].rhw = 1;
v[1].color = RGB_MAKE(0,255,0);
v[1].specular = D3DRGB(0,0,0);
v[1].tu = 0;
v[1].tv = 0;
v[2].sx = 120;
v[2].sy = 240;
v[2].sz = 0;
v[2].rhw = 1;
v[2].color = RGB_MAKE(0,0,255);
v[2].specular = D3DRGB(0,0,0);
v[2].tu = 0;
v[2].tv = 0;
pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,D3DVT_
TLVERTEX,(LPVOID)v,3,D3DDP_WAIT);
pD3DDevice->EndScene();
pScreen->Flip(NULL,DDFLIP_WAIT);
return TRUE;
}
 

メインの関数に追加するのは以下の部分。初期化ルーチンを呼ぶ部分とアイドル時の描画を呼ぶ部部です。
 

/*--------------------------------------------
メイン
---------------------------------------------*/
int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR lpCmdLine,int nCmdShow)
{
 MSG msg;
 if(!InitApp(hInst,nCmdShow))
  return FALSE;
 if(!InitDDraw())
  return FALSE;
 if(!InitD3D())
  return FALSE;
 while(TRUE){
  if(PeekMessage(&msg,0,0,0,PM_REMOVE)){
   if(msg.message == WM_QUIT)
    break;
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
  else{
   AppIdle();
  }
 }
EndApp();
return msg.wParam;
}
 

うまく描画されたでしょうか。

一つではさびしいので複数のポリゴンを書く方法は以下の通り。いくつかあるうちの「D3DPT_TRIANGLESTRIP」を使用した場合です。違うのは赤い部分ね。
 

/*--------------------------------------------
アイドル時の処理
--------------------------------------------*/
int AppIdle(void)
{
D3DTLVERTEX v[7];
DDBLTFX fx;
 
// 背景を塗りつぶす
ZeroMemory(&fx,sizeof(fx));
fx.dwSize = sizeof(fx);
pBackBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT | DDBLT_COLORFILL,&fx);
 
// ドライバ名などの文字を書き込む
HDC hdc;
char Data[256];
wsprintf(Data, "%s, [%s]", szDevice, szDDDeviceName);
 
if(pBackBuffer->GetDC(&hdc) == DD_OK){
 TextOut(hdc,0,0,Data,strlen(Data));
 pBackBuffer->ReleaseDC(hdc);
}
 
// DrawPrimitiveを使ってポリゴンを描画する
pD3DDevice->BeginScene();
 
v[0].sx = 50;
v[0].sy = 50;
v[0].sz = 0;
v[0].rhw = 1;
v[0].color = RGB_MAKE(255,0,0);
v[0].specular = D3DRGB(0,0,0);
v[0].tu = 0;
v[0].tv = 0;
 
v[1].sx = 100;
v[1].sy = 50;
v[1].sz = 0;
v[1].rhw = 1;
v[1].color = RGB_MAKE(0,255,0);
v[1].specular = D3DRGB(0,0,0);
v[1].tu = 0;
v[1].tv = 0;
 
v[2].sx = 50;
v[2].sy = 150;
v[2].sz = 0;
v[2].rhw = 1;
v[2].color = RGB_MAKE(0,0,255);
v[2].specular = D3DRGB(0,0,0);
v[2].tu = 0;
v[2].tv = 0;
 
v[3].sx = 180;
v[3].sy = 150;
v[3].sz = 0;
v[3].rhw = 1;
v[3].color = RGB_MAKE(255,0,0);
v[3].specular = D3DRGB(0,0,0);
v[3].tu = 0;
v[3].tv = 0;

 
v[4].sx = 50;
v[4].sy = 250;
v[4].sz = 0;
v[4].rhw = 1;
v[4].color = RGB_MAKE(0,0,255);
v[4].specular = D3DRGB(0,0,0);
v[4].tu = 0;
v[4].tv = 0;

 
v[5].sx = 100;
v[5].sy = 250;
v[5].sz = 0;
v[5].rhw = 1;
v[5].color = RGB_MAKE(128,255,128);
v[5].specular = D3DRGB(0,0,0);
v[5].tu = 0;
v[5].tv = 0;
 
v[6].sx = 50;
v[6].sy = 350;
v[6].sz = 0;
v[6].rhw = 1;
v[6].color = RGB_MAKE(255,0,255);
v[6].specular = D3DRGB(0,0,0);
v[6].tu = 0;
v[6].tv = 0;

 
pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,D3DVT_
TLVERTEX,(LPVOID)v,7,D3DDP_WAIT);
 
pD3DDevice->EndScene();
 
pScreen->Flip(NULL,DDFLIP_WAIT);
 
return TRUE;
}
 

次回はこのポリゴンを動かしてみます。