プログラムの解説
ここからサンプルプログラムの解説をしていきます。DirectX 8.0以降のDirect3Dの開発経験がある方であれば、Direct3D Mobile APIの使い方はすぐに覚えられると思います。Direct3D Mobile以外のウィンドウ周りの処理に関しても、Win32での開発経験がある方であればほとんど同じですので、すぐに開発ができると思います。
プログラムの準備
リンクするライブラリ
- d3dm.lib
- d3dmx.lib
- aygshell.lib
今回のサンプルでは、以上の3つのライブラリとリンクしています。「d3dm.lib」「d3dmx.lib」がDirect3D Mobileのライブラリで、前者は基本的なインターフェイスのライブラリ、後者はベクトルや行列、テクスチャローダー関数などヘルパー関数などのライブラリです。「aygshell.lib」はPocketPCのUIなどのシェルまわりのライブラリです。
インクルードファイル
#include <windows.h> #include "resource.h" #include <commctrl.h> #include <d3dm.h.> #include <d3dmx.h> #include <aygshell.h>
今回のサンプルでは上記のヘッダーファイルをインクルードしています。「d3dm.h」「d3dmx.h」がDirect3D Mobileのヘッダーファイルです。「aygshell.h」はシェルのヘッダーファイルです。
マクロ
#define SAFE_RELEASE(o) {if(o){o->Release();o = NULL;}} #define SAFE_DELETE(o) {if(o){delete o;o = NULL}} #define SAFE_DELETE_ARRAY(o) {if(o){delete[] o;o = NULL;}}
この辺は、メモリやインターフェイスの解放用に定義しておきます。
グローバル変数
// グローバル変数: HINSTANCE g_hInst; // インスタンス HWND g_hWnd; // ウィンドウハンドル bool g_bWindowed = true; // ウィンドウモードフラグ INT g_iScreenWidth = 0; // スクリーン幅 INT g_iScreenHeight = 0; // スクリーン高さ
Direct3D Mobileで使うインターフェイス以外のグローバル変数は以上です。
ウィンドウモードフラグは、trueだとDirect3D Mobileでの描画はウィンドウのクライアント領域になり、falseだと全画面に描画するフルスクリーンモードで起動するようにしています。
WinMain()関数からの流れ
WinMain()関数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { MSG msg; g_hInst = hInstance; // 画面が回転してるのであれば起動しない if(IsScreenRotated()) return 0; // ウィンドウの生成 if(FAILED(InitWindow())) return 0; // Direct3D Mobileの初期化 if(FAILED( InitD3DM())) return 0; // 頂点のセットやテクスチャの読み込み if(FAILED( InitApp())) { CleanupD3DM(); return 0; } ShowWindow(g_hWnd, nCmdShow); UpdateWindow(g_hWnd); memset( &msg, 0, sizeof(msg) ); // アプリケーションループ while( msg.message!=WM_QUIT ) { if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else Render(); } return (int) msg.wParam; }
WinMain()関数からの流れは、以下の様になります。
- 画面が回転してるかどうかのチェック
- アプリケーションのウィンドウの生成
- Direct3D Mobileの初期化
- 頂点データのセットやテクスチャのロード
- アプリケーションループ
- 描画
- 終了
となります。最初の画面の回転のチェックでは、PocketPC機には画面の回転機能がありますが、デバイスによっては回転時にDirect3D Mobileを起動できない機種もありますのでチェックしています(rx1950のように回転していても起動できる機種もあるようです)。続いて、アプリケーションのウィンドウの生成を行い、Direct3D Mobileの初期化を行います。初期化ができたら頂点データやテクスチャのロードを行います。アプリケーションループの中で描画処理を書き、終了となります。
ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONUP:
// 画面をタップすると終了
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
case WM_CLOSE:
CleanupD3DM();
break;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
ウィンドウに対するイベントを処理するウィンドウプロシージャは上記のようにしています。今回は画面のクライアント領域(フルスクリーンなら画面内)をタップする(タッチパネルを押す)と、アプリケーションが終了します。WM_CLOSEでは、Direct3D Mobileの各種インターフェイスをメモリ上から解放する処理を行う関数を呼び出します。
画面の回転のチェック
BOOL IsScreenRotated()
{
DEVMODE devMode = {0};
devMode.dmSize = sizeof(DEVMODE);
devMode.dmFields = DM_DISPLAYORIENTATION;
ChangeDisplaySettingsEx(NULL, &devMode, NULL, CDS_TEST, NULL);
if (devMode.dmDisplayOrientation != DMDO_0)
{
MessageBox(
NULL,
TEXT("画面が回転した状態では起動できません"),
TEXT("警告"),
MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND
);
return TRUE;
}
return FALSE;
}
ここでは画面が回転しているかどうかをチェックしてます。回転している状態であれば、メッセージボックスを出力してTRUEを返します。デバイスによっては画面回転時にDirect3D Mobileの初期化ができないものがありますので、このチェックをします。
ウィンドウの生成
HRESULT InitWindow()
{
HRESULT hr = S_OK;
SHInitExtraControls();
WNDCLASS wc = { 0L, WndProc, 0L, 0L,
g_hInst, NULL, NULL, NULL, NULL,
TEXT("D3DMのサンプル") };
RegisterClass( &wc );
// ウィンドウモード
g_hWnd = CreateWindow(
TEXT("D3DMのサンプル"), TEXT("D3DMのサンプル"),
WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, g_hInst, NULL);
// 画面幅と高さ
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// フルスクリーンかウィンドウモードかで生成するウィンドウを変える
if(g_bWindowed)
{
// ウィンドウモード
// SIPのバーを消去
SHFullScreen(g_hWnd, SHFS_HIDESIPBUTTON);
}
else
{
// フルスクリーン
// タスクバーとSIPのバーを消去
SHFullScreen(g_hWnd, SHFS_HIDESIPBUTTON | SHFS_HIDETASKBAR);
}
return hr;
}
このあたりは、Win32での開発経験があれば基本的には同じですが、SH~()関数というのがありますが、これはPocketPCのメニューなどのUIの部分に関連する関数です。
SHFullScreen()を呼ぶことで、SIPのバー(手書き入力やソフトウェアキーボードの呼び出しの部分)やウィンドウのタイトルなどが表示されるタスクバーを表示しないようにできます。今回は、ウィンドウモードフラグに応じて変えています。
Direct3D Mobileのインターフェイス
// Direct3D Mobileのインターフェイス IDirect3DMobile* g_pd3d = NULL; // Direct3Dオブジェクト IDirect3DMobileDevice* g_pd3dmDevice = NULL; // Direct3Dデバイス IDirect3DMobileVertexBuffer* g_pVB = NULL; // 頂点バッファ IDirect3DMobileTexture* g_pTexture = NULL; // テクスチャ
IDirect3D~ではじまるインターフェイスはDirect3D Mobileのインターフェイスになります。今回は、絶対に必要なDirect3D Mobileオブジェクトとデバイスの他に、ポリゴンの頂点データを管理する頂点バッファインターフェイスと、ポリゴン上の模様にあたるテクスチャのインターフェイスを利用します。
Direct3D Mobileの初期化
Direct3D Mobileオブジェクトの生成
if( NULL == ( g_pD3DM = Direct3DMobileCreate( D3DM_SDK_VERSION ) ) ) return E_FAIL;
ここでは、OS側にサポートするDirect3D Mobileのバージョン(D3DM_SDK_VERSIONはバージョンをあらわす定数)を問い合わせて、これから動かそうとするバージョンが実行可能であればDirect3D Mobileオブジェクトを生成します。実行可能では無い場合は、g_pD3DにはNULLが入ります。
Direct3D Mobileデバイスの作成
D3DMPRESENT_PARAMETERS d3dmpp; memset( &d3dmpp, 0, sizeof(d3dmpp) ); d3dmpp.Windowed = g_bWindowed; d3dmpp.SwapEffect = D3DMSWAPEFFECT_DISCARD; // フルスクリーンの場合 if(!g_bWindowed) { // バックバッファの幅と高さを設定 d3dmpp.BackBufferHeight = g_iScreenHeight; d3dmpp.BackBufferWidth = g_iScreenWidth; } // このフォーマットをサポートするか? hr = g_pd3d->CheckDepthStencilMatch( 0, D3DMDEVTYPE_DEFAULT, D3DMFMT_R5G6B5, D3DMFMT_R5G6B5, D3DMFMT_D24S8); if(SUCCEEDED(hr)) { // Zバッファが24bitでステンシルバッファが8bit d3dmpp.BackBufferFormat = D3DMFMT_R5G6B5; d3dmpp.EnableAutoDepthStencil = TRUE; d3dmpp.AutoDepthStencilFormat = D3DMFMT_D24S8; } else { // Zバッファが16bit d3dmpp.BackBufferFormat = D3DMFMT_R5G6B5; d3dmpp.EnableAutoDepthStencil = TRUE; d3dmpp.AutoDepthStencilFormat = D3DMFMT_D16; } // デバイスの生成 hr = g_pd3d->CreateDevice( D3DMADAPTER_DEFAULT, D3DMDEVTYPE_DEFAULT, g_hWnd, 0, &d3dmpp, &g_pd3dmDevice ); return hr;
デバイスの生成は、D3DMPRESENT_PARAMETERS構造体に設定項目のフラグをセットしていくことからはじめます。設定は、フルスクリーンやウィンドウモードでの起動やバックバッファフォーマットなどを決めます。この辺の流れはPCのDirect3Dと似た感じです。使えるフラグの組み合わせはハードウェアごとに異なるので注意が必要です。
CheckDepthStencilMatch()では、バックバッファフォーマットと深度バッファフォーマットの組み合わせが、実行するハードウェア上でサポートするかどうか問い合わせます。深度バッファフォーマットに関しては、ハードウェアによってかなりサポートのされ方が違うようなので、注意が必要です。
余談ですが筆者が実験した結果、rx1950ではD3DMFMT_D16をサポートしていなく、W-ZERO3はD3DMFMT_D24S8をサポートしていませんでした。
頂点バッファ
頂点バッファとは、メモリ上に置くポリゴンの頂点データの配列で、構造体の定義の仕方によって任意のデータ要素を持たせることができます。
頂点データの構造体
struct VERTEX { FLOAT x, y, z; // 位置座標 DWORD dwColor; // 頂点色 FLOAT tu, tv; // テクスチャ座標 static const DWORD FVF; }; const DWORD VERTEX::FVF = D3DMFVF_XYZ_FLOAT | D3DMFVF_DIFFUSE | D3DMFVF_TEX1;
Direct3D Mobileでは、PCのDirect3Dと同じく柔軟な頂点フォーマットを使って、ポリゴンの頂点のデータ型を定義します。
これは、頂点によって位置座標や色、テクスチャ座標、法線などを必要に応じて組み合わせます。レンダリングパイプラインがこれらのパラメータの意味を理解するために、FVFにフラグを設定します。
頂点バッファの作成
// VRAMに頂点バッファを置くことが可能かどうか? D3DMPOOL vbpool; if (caps.SurfaceCaps & D3DMSURFCAPS_VIDVERTEXBUFFER) { vbpool = D3DMPOOL_VIDEOMEM; } else { vbpool = D3DMPOOL_SYSTEMMEM; } // 頂点バッファの作成 if(FAILED(g_pd3dmDevice->CreateVertexBuffer( sizeof(VERTEX)*4, 0, VERTEX::FVF, vbpool, &g_pVB ))) return E_FAIL;
ここでは、頂点バッファの作成をします。
まず最初にVRAMを備えたハードウェアなどで、VRAM上に頂点バッファを作成できるかどうかチェックしています。頂点バッファはメモリ上に置かれたポリゴンの頂点配列だと思って下さい。その操作と管理を行うのが、g_pVBです。今回は、四角形のポリゴンを描画したいので。頂点データを4つ置けるサイズを確保しておきます。
頂点配列のセット
VERTEX V[] =
{
-1.0f, 1.0f, 0.0f, 0xffffffff, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0xffffffff, 1.0f, 0.0f,
-1.0, -1.0, 0.0f, 0xffffffff, 0.0f, 1.0f,
1.0, -1.0, 0.0f, 0xffffffff, 1.0f, 1.0f,
};
VOID* pV;
if(FAILED(g_pVB->Lock(0, 0, &pV, 0)))
return E_FAIL;
// 頂点データのコピー
memcpy(pV, V, sizeof(V));
g_pVB->Unlock();
今回はこのようにして、XY平面上に正方形を描画できるようにVにデータをセットします。
この段階では、先に作成した頂点バッファ上にあるわけではないので、データをコピーする必要があります。頂点バッファをLock()することでコピーをします。また、Lock()が終ったら必ずUnlock()を行います。
テクスチャ
テクスチャはポリゴン表面に貼られる模様や素材となる画像です。ビットマップファイルなどを読み込んで生成します。今回は直接ファイルパスから参照するのではなく、リソースファイルにして読み込む方法を紹介します。
リソースファイルの作成

リソースの追加は、[ソリューション エクスプローラ]の[リソース ファイル]のフォルダを右クリックするか、[プロジェクト]メニューの[リソースの追加]などから上のダイアログを呼び出すことで行えます。今回は、インポートで64x64ピクセルのビットマップを追加しました。このビットマップをテクスチャとして使います。
リソースが作成できるとプロジェクトに「resource.h」というヘッダーファイルができるので、インクルードしておきます。
テクスチャですが、PocketPCはPCに比べると解像度が低く、メモリも少ないことには気をつけなければいけません。
テクスチャのロード
// VRAMにテクスチャを置くことが可能であるかどうか? D3DMPOOL texpool; if (caps.SurfaceCaps & D3DMSURFCAPS_VIDTEXTURE) { // VRAMを利用する texpool = D3DMPOOL_VIDEOMEM; } else { // システムRAMを利用する texpool = D3DMPOOL_SYSTEMMEM; } // テクスチャのロード // リソースからの読み込み if( FAILED( D3DMXCreateTextureFromResourceEx( g_pd3dmDevice, GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_TEXTURE), D3DMX_DEFAULT,D3DMX_DEFAULT, 1, 0, D3DMFMT_UNKNOWN, texpool, D3DMX_FILTER_POINT, D3DMX_FILTER_POINT, 0, NULL, NULL, &g_pTexture ) ) ) { return E_FAIL; }
テクスチャをロードする前にcaps.SurfaceCapsフラグを調べて、VRAMにテクスチャを置けるかどうかを確かめます。
D3DMXCreateTextureFromResourceEx()関数を使うことでリソースからテクスチャを読み込みます。読み込まれたテクスチャはg_pTextureで管理と操作をします。今回はリソースから読み込んでいますが、ファイルパスを指定してテクスチャを読み込む関数としてD3DMXCreateTextureFromFileEx()関数があります。
リソースではなく外部ファイルを使う場合には、実行ファイルと一緒に実行環境に配置する必要がありますが、その設定はプロジェクトのプロパティで[構成プロパティ]の[配置]にある[追加ファイル]に記述します。

