描画
描画処理全体のコードは、下記のようになります。
HRESULT Render()
{
HRESULT hr = S_OK;
static int i = 0; // Y軸に対するの回転量を保持する
// バックバッファとZバッファのクリア
g_pd3dmDevice->Clear(0, NULL, D3DMCLEAR_TARGET | D3DMCLEAR_ZBUFFER,
D3DMCOLOR_XRGB(0,0,0), 1.0f, 0);
// 描画開始
if(SUCCEEDED(g_pd3dmDevice->BeginScene()))
{
// カリングの解除
g_pd3dmDevice->
SetRenderState( D3DMRS_CULLMODE, D3DMCULL_NONE );
// ライティングはしない
g_pd3dmDevice->SetRenderState( D3DMRS_LIGHTING, FALSE );
// Y軸回転
D3DMXMATRIX matWorld;
D3DMXMatrixRotationY( &matWorld, D3DMXToRadian(i++) );
g_pd3dmDevice->SetTransform( D3DMTS_WORLD,
(D3DMMATRIX*)&matWorld, D3DMFMT_D3DMVALUE_FLOAT );
// ビュー行列(3D空間のカメラ)
D3DMXVECTOR3 vEyePt( 0.0f, 0.0f, -4.0f );
D3DMXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DMXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
D3DMXMATRIX matView;
D3DMXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
g_pd3dmDevice->SetTransform( D3DMTS_VIEW,
(D3DMMATRIX*)&matView, D3DMFMT_D3DMVALUE_FLOAT );
// プロジェクション(射影行列)
// アスペクト比の調整はしない
D3DMXMATRIX matProj;
D3DMXMatrixPerspectiveFovLH( &matProj, D3DMX_PI/4.0f,
1.0f, 0.1f, 100.0f );
g_pd3dmDevice->SetTransform( D3DMTS_PROJECTION,
(D3DMMATRIX*)&matProj, D3DMFMT_D3DMVALUE_FLOAT );
// テクスチャのセット
hr = g_pd3dmDevice->SetTexture( 0, g_pTexture );
// 頂点バッファからレンダリング
hr = g_pd3dmDevice->SetStreamSource(0, g_pVB, sizeof(VERTEX));
hr = g_pd3dmDevice->DrawPrimitive(D3DMPT_TRIANGLESTRIP, 0, 2);
hr = g_pd3dmDevice->SetTexture( 0, NULL);
g_pd3dmDevice->EndScene();
}
// スクリーンへの転送
hr = g_pd3dmDevice->Present(NULL, NULL, NULL, NULL);
return hr;
}
流れとしては以下の様になります。
- バックバッファとZバッファのクリア
BeginScene()で描画開始- 各種行列のセット
- テクスチャのセット
- 頂点バッファから描画命令を書く
EndScene()で描画終了Present()でバックバッファを画面に出力
g_pd3dmDevice->Clear(0, NULL, D3DMCLEAR_TARGET | D3DMCLEAR_ZBUFFER,
D3DMCOLOR_XRGB(0,0,0), 1.0f, 0);
クリアは、上記の命令で行います。第3引数がクリア対象、第4引数が今回のサンプルでは背景の色になります。
// Y軸回転 D3DMXMATRIX matWorld; D3DMXMatrixRotationY( &matWorld, D3DMXToRadian(i++) ); g_pd3dmDevice->SetTransform( D3DMTS_WORLD, (D3DMMATRIX*)&matWorld, D3DMFMT_D3DMVALUE_FLOAT ); // ビュー行列(3D空間のカメラ) D3DMXVECTOR3 vEyePt( 0.0f, 0.0f, -4.0f ); D3DMXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f ); D3DMXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f ); D3DMXMATRIX matView; D3DMXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec ); g_pd3dmDevice->SetTransform( D3DMTS_VIEW, (D3DMMATRIX*)&matView, D3DMFMT_D3DMVALUE_FLOAT ); // プロジェクション(射影行列) // アスペクト比の調整はしない D3DMXMATRIX matProj; D3DMXMatrixPerspectiveFovLH( &matProj, D3DMX_PI/4.0f, 1.0f, 0.1f, 100.0f ); g_pd3dmDevice->SetTransform( D3DMTS_PROJECTION, (D3DMMATRIX*)&matProj, D3DMFMT_D3DMVALUE_FLOAT );
Direct3D Mobileでは、ポリゴンに対して、ワールド変換、ビュー変換、射影変換を行うことで3次元空間のものを2次元のスクリーンに投影します。このあたりはPC版のDirect3Dと同様の処理です。上記のコードでは、順番に各種変換に必要な行列のセットをしています。以下、細かい解説を書いていきます。
Y軸回転と書かれたコメントの箇所ですが、ここではワールド変換用の処理です。今回は、毎フレームY軸に回転します。SetTransform関数の第1引数にD3DMTS_WORLDを指定するとレンダリングパイプラインはその行列をワールド変換行列と認識します。
次に、ビュー行列のコメント以下の箇所です。ここでは、3つのベクトルを元にビュー行列を作成しています。このベクトルは、順番に、3次元空間での視点位置、注視点位置、上方向のベクトルになります。SetTransform関数の第1引数にD3DMTS_VIEWを指定するとレンダリングパイプラインはその行列をビュー変換行列と認識します。
最後に、プロジェクション(射影行列)の箇所です。ここでは、視野角、アスペクト比、ニアクリップ面までの距離、ファークリップ面の距離から行列を作成します。SetTransform関数の第1引数にD3DMTS_PROJECTIONを指定するとレンダリングパイプラインはその行列を射影変換行列と認識します
余談ですが、PC版のSetTransform関数には第3引数がありません。Direct3D Mobileは計算機リソースが少ない環境で動かすため固定小数を使うオプションがあります。
// テクスチャのセット hr = g_pd3dmDevice->SetTexture( 0, g_pTexture ); // 頂点バッファからレンダリング hr = g_pd3dmDevice->SetStreamSource(0, g_pVB, sizeof(VERTEX)); hr = g_pd3dmDevice->DrawPrimitive(D3DMPT_TRIANGLESTRIP, 0, 2);
テクスチャのセットは、SetTexture()を使います。第1引数は、マルチテクスチャ時のインデックスです。
頂点バッファを使った描画では、まずSetStreamSource()で頂点バッファをセットします。続いてDrawPrimitive()で描画します。今回は、ストリップで描画します。PC版にあるDrawPrimitiveUp()とかは無いようです。
終了処理
// Direct3D Mobileの解放
VOID CleanupD3DM()
{
SAFE_RELEASE( g_pTexture );
SAFE_RELEASE(g_pVB);
SAFE_RELEASE(g_pd3dmDevice);
SAFE_RELEASE(g_pd3d);
}
Direct3D Mobileのインターフェイスは、アプリケーション終了時にメンバ関数のRelease()関数を呼び出す必要があります(今回は、NULLチェックもかねたマクロを作って代替処理にしています)。これらの処理を忘れることはメモリリークの原因になります。
実行とデバッグ
エミュレータ上での実行とデバッグ
プログラムができたら実行してみます。ビルド後に実行してみると、自動的にエミュレータが起動してプログラムが転送され、実行されます。デバッグに関しては、ブレークポイントを使ったステップ実行なども可能です。
ただし、エミュレータはソフトウェアでエミュレーションしてる関係上、ハードウェアと異なる挙動であったり、動作速度が実機より低速な点で注意が必要です。
実機上でのデバッグ
実機上のデバッグは、まず実行したいPocketPCをPCに接続してActiveSyncが確立している必要があります(この際にパートナーシップはゲストでも大丈夫です)。接続ができていれば実行のターゲットをエミュレータから[Windows Mobile 5.0 PocetPC Device]に変えておきます。
実機上でのデバッグは実行する環境が実際のPocketPCデバイス上になりますが、Visual Studio 2005上でのデバッガの機能を使うことはできますので、実機上でのさまざまな問題に対してPCからモニタすることができます。
実機上でのデバッグは、エミュレータでは分からなかったバグの発見や実行速度の速さの面でエミュレータに比べて優位です。
CABインストーラの作成
さて、実行ファイルができたらそれをインストールするインストーラの説明したいと思います。PocketPCでは、InstallShieldなどで作成したインストーラのほかに、CAB形式でアーカイブすることでインストーラにすることができます。Visual Studio 2005では、[セットアップと配置]から[スマートデバイス CAB プロジェクト]を新規プロジェクトで選択することでインストーラを作ることができます。このプロジェクトで作成したCABファイルは、実機上に転送して実行することで自動的にファイルがインストールされます。
今回のサンプルの場合では、できあがった実行ファイル[d3dmobile.exe]をプロジェクトに加えて作成すればOKです。テクスチャをリソースファイルにしない場合などは一緒に追加します。
あとは、でき上がったCABファイルをActiveSyncやメモリーカードなどを使って転送します。転送後はPocketPC上でCABファイルを展開すればインストールは完了です。アンインストールは[プログラムの削除]からできます。
作業時の注意ですが、ビルド時に下記のようなエラーが出る場合があります。
Error: The INF file contains Unicode charactersthat could not be converted correctly to ANSI
この場合には、下記の画像のように[プロジェクト]のプロパティの[Manufacturer]を全角文字ではなく半角文字で付けてください。
まとめ
PCでDirect3Dでのプログラミングを経験したことがあれば、Direct3D Mobileの利用は、ほとんどPC版の固定機能パイプラインを利用したプログラムのやり方と同じであると気づきます。ドキュメントを見ると分かりますが、インターフェイスなどもほとんど同様です。ただ、PCとの違いとしては、プログラマブルシェーダが仕様に無いことと、Direct3DのヘルパーライブラリであるD3DXがPCほど充実していないことが挙げられます。D3DXに関しては、ベクトルや行列などの算術命令とテクスチャのみで、メッシュ系の機能がないのでPC版のように「.x」形式のモデルデータのファイルがサポートされていないようです。ただ、PC以上にリソースが限られた環境ですので、あまりリッチなデータフォーマットなどは利用できません。
Direct3D MobileはPCに比べると高性能なGPUが無いので、プログラマブルシェーダなどは利用ができません。しかし、携帯ゲーム機のようなサイズでタッチパネルを備えたハードウェアであり、なおかつ3DCGプログラミングもできるとなると、PCとは異なった性質のアプリケーションを開発できる可能性があります。
余談ですが今回は、C++での利用を解説しました。Direct3Dを使ったアプリケーションの開発者の中には、C#やVBなどの.NET Frameworkを利用した方もいらっしゃると思います。そういった開発者の方には英語ではあるのですが、MSDNにManaged Direct3D Mobileの情報がありますので参考になると思います。
参考資料
- MSDN 『MSDNのDirect3D Mobile』


that could not be converted correctly to ANSI
