ARToolkitの基本的なプログラムを見るとOpenGLを使っています。 ですが、DirectXの方が何かと便利なこともあります。(行列計算とか、見栄えを良くするのとか、Xファイルを扱う時とか)
なので、ARToolKitの描画部分をOpenGLからDirectXにしてみます。
このとき考慮しないといけないのは、ARToolKitはOpenGLを元に作られているので、色々な変数をOpenGL用からDirectX用に変換してあげないといけません。 結構めんどくさいですが、わかればなんてことはないので頑張りましょう。
なお、このページを書くにあたって、【ニコニコ動画】ARToolKitで初音ミク(その6):LIVEの方のソースを参考にさせていただきました。 この場を借りて御礼申し上げます。
原理
まず、先に数学的なところから説明しときます。
OpenGLの行列をDirectXの行列にするときに、必ず出てくるのが縦ベクトルを横ベクトルにすることです。 そもそも縦ベクトル、横ベクトルってなんじゃいな?について説明します。
縦ベクトルは、その名の通り縦のベクトル↓です。 OpenGLはこの形式を採用しています。
横ベクトルも、その名の通り横のベクトル↓です。 DirectXはこの形式を採用しています。
で、この縦と横が違うと何がまずいのかといいますと、行列の掛け算の方向が逆になるんです。 OpenGLのマトリクスをmatGL、DirectXの行列をmatDXとすると、ベクトルと行列の掛け算は↓みたいになります。
で、これを一致させるためにはどうすればいいかというと、転置させれば↓大丈夫です。
一応これで問題はないのですが、ちょっと後々面倒なことがあるので、ここでもうちょい詳しく説明しときます。 お気づきの方もいるかと思いますが、わざと書き方を変えてみました。 DirectXの行列(D3DXMATRIXとかで定義する変数)は4行4列の行列(つまり2次元の配列)になっています。 一方、OpenGLの行列はdouble型、要素16個の1次元の配列になっています。
ここでアフィン変換行列(ここでは回転と平行移動だけのもの)を思い出していただきたいんですが、
↑のOpenGLとDirectXの配列にそれぞれの当てはめていくと、中身は以下の対応表になっています。
OpenGL | DirectX | ||
---|---|---|---|
matGL[0] | r1 | matDX11 | r1 |
matGL[1] | r4 | matDX12 | r4 |
matGL[2] | r7 | matDX13 | r7 |
matGL[3] | 0 | matDX14 | 0 |
matGL[4] | r2 | matDX21 | r2 |
matGL[5] | r5 | matDX22 | r5 |
matGL[6] | r8 | matDX23 | r8 |
matGL[7] | 0 | matDX24 | 0 |
matGL[8] | r3 | matDX31 | r3 |
matGL[9] | r6 | matDX32 | r6 |
matGL[10] | r9 | matDX33 | r9 |
matGL[11] | 0 | matDX34 | 0 |
matGL[12] | tx | matDX41 | tx |
matGL[13] | ty | matDX42 | ty |
matGL[14] | tz | matDX43 | tz |
matGL[15] | 1 | matDX44 | 1 |
このOpenGLとDirectXの行列の中身の一個一個の対応付けを覚えておいてください。
プログラム
では、プログラミングしていきます。
ARToolKitで得られるもので描画に必要なのは画像、マーカ→ビュー変換行列、ビュー→射影変換行列です。 一つ一つDirectXで使えるようにしていきます。
① ビュー→射影変換行列
ARToolKitで得られるビュー→射影変換行列は一意に決定しているので、メインループ中ではなく、初期化処理中にやってしまいます。 このビュー→射影変換行列はプログラム中のどれかというと、カメラパラメータ(ARParamで定義されてて、よくcparamとかいう変数名になっているやつ)です。
このcparamをargConvGLcpara()関数でOpenGLの行列に変えます。
main.cpp
// カメラパラメタによるプロジェクション行列の作成 double gl_para[16]; ARParam temp_cparam = cparam; argConvGLcpara( &temp_cparam, 10.0, 1000.0, gl_para ); scene.SetCameraProjMat( gl_para );
- void argConvGLcpara( ARParam* param, double near, double far, double glmat[16] )
【引数】
param:カメラパラメータ(入力)
near:カメラのnearの設定(入力)
far:カメラのfarの設定(入力)
glmat:OpenGLの行列(出力)
ここで、得られたOpenGLの行列に↑の方でやりました縦ベクトルを横ベクトルにする処理をします。
Scene.cpp
//======================================================= // OpenGL形式のカメラプロジェクション行列をDirectX型に変換してセット // 引数 // gl_cpara : double[16] OpenGL形式のカメラプロジェクション行列 //======================================================= void Scene::SetCameraProjMat( double *gl_cpara ) { // 転置 m_cam_proj._11 = (float)gl_cpara[0]; m_cam_proj._12 = (float)gl_cpara[1]; m_cam_proj._13 = (float)gl_cpara[2]; m_cam_proj._14 = (float)gl_cpara[3]; m_cam_proj._21 = (float)gl_cpara[4]; m_cam_proj._22 = (float)gl_cpara[5]; m_cam_proj._23 = (float)gl_cpara[6]; m_cam_proj._24 = (float)gl_cpara[7]; m_cam_proj._31 = (float)gl_cpara[8]; m_cam_proj._32 = (float)gl_cpara[9]; m_cam_proj._33 = (float)gl_cpara[10]; m_cam_proj._34 = (float)gl_cpara[11]; m_cam_proj._41 = (float)gl_cpara[12]; m_cam_proj._42 = (float)gl_cpara[13]; m_cam_proj._43 = (float)gl_cpara[14]; m_cam_proj._44 = (float)gl_cpara[15]; }
ビュー→射影変換行列の設定は以上です。
② マーカ→ビュー変換行列
ARToolKitで得られるマーカ→ビュー変換行列は勿論逐次変化するので、メインループ中で処理します。
arGetTransMat()関数で得られる座標変換行列patt_trans[3][4]をargConvGLpara()関数でOpenGLの行列に変換します。
main.cpp
// ビュー行列の作成 double gl_para[16]; argConvGlpara( patt_trans, gl_para ); scene.SetCameraViewMat( gl_para );
- void argConvGLpara( double para[3][4], double gl_para[16] )
【引数】
para:ARToolKitで得られた座標変換行列(入力)
glmat:OpenGLの行列(出力)
で、これもあと転置すれば大丈夫かと思われますが、これは転置だけではいきません。 ARToolKitの座標系の問題も絡んできます。
とりあえず、DirectXにおける普通のビュー行列がどんなものなのかを思い出しましょう。 大体D3DXmatrixLookAtLH()関数(左手座標系のカメラ)で得られるものを使っていると思います。 この関数内では、カメラの視線方向ベクトルe、カメラの横方向ベクトルv、カメラの上方向ベクトルuが計算され、さらにカメラのワールド座標系での位置cを元に↓のようなビュー行列(縦ベクトル表記)が生成されます。
図にすると、↓みたいな感じです。
ところが、ARToolKit Coordinate Systemsを見ると、カメラの上向き方向ベクトルu(このページだとYc)が下方向を向いているのがわかります。 なので、uを-1倍する必要があります。
また、マーカの座標系はZmを上方向とした座標系になっていますが、DirectXでは↑の図を見てもわかるようにYを上方向とした座標系になっています。 なので、z軸とy軸を交換する必要があります。
よって、ビュー行列は↓みたいになります。
これをプログラムにすると、↓みたいになります。 どうしてこうなるのかわからない方は↑の方にあるOpenGLとDirectXの行列の対応表を見てください。
Scene.cpp
//======================================================= // OpenGL形式のビュー行列をDirectX型に変換してセット // (ビュー座標系でのモデル位置・姿勢) // 引数 // gl_cpara : double[16] OpenGL形式のカメラビューイング行列 //======================================================= void Scene::SetCameraViewMat( double *gl_cpara ) { m_mat_mark_view._11 = (float)gl_cpara[0]; m_mat_mark_view._12 = (float)gl_cpara[1] * -1.0f; m_mat_mark_view._13 = (float)gl_cpara[2]; m_mat_mark_view._14 = (float)gl_cpara[3]; m_mat_mark_view._21 = (float)gl_cpara[8]; m_mat_mark_view._22 = (float)gl_cpara[9] * -1.0f; m_mat_mark_view._23 = (float)gl_cpara[10]; m_mat_mark_view._24 = (float)gl_cpara[7]; m_mat_mark_view._31 = (float)gl_cpara[4]; m_mat_mark_view._32 = (float)gl_cpara[5] * -1.0f; m_mat_mark_view._33 = (float)gl_cpara[6]; m_mat_mark_view._34 = (float)gl_cpara[11]; m_mat_mark_view._41 = (float)gl_cpara[12]; m_mat_mark_view._42 = (float)gl_cpara[13] * -1.0f; m_mat_mark_view._43 = (float)gl_cpara[14]; m_mat_mark_view._44 = (float)gl_cpara[15]; }
マーカ→ビュー変換行列の設定は以上です。
③ 画像
ARToolKitで得られる画像は勿論逐次変化するので、メインループ中で処理します。
main.cpp
if( FAILED( scene.CopyCaptureImage( dataPtr, XSIZE, YSIZE ) ) ) { continue; }
まず、DirectXの初期化部分でキャプチャ画像(背景用)テクスチャの生成をします。 “背景用”という言葉に関してですが、キャプチャ画像が3Dモデルの背景になるので、このような言葉を使用しました。
この時注意してほしいのが、画像フォーマットです。 D3DFMT_A8R8G8B8を選ばないと、後のプログラムで問題が生じるので注意しましょう。
Scene.cpp
// キャプチャ画像(背景用)のテクスチャ作成 if( FAILED( D3DXCreateTexture( pD3DDevice, XSIZE, YSIZE, // テクスチャのサイズ 0, 0, D3DFMT_A8R8G8B8, // 画像フォーマット D3DPOOL_MANAGED, &m_pCapTexture ) ) ) { MessageBox( NULL, "キャプチャ画像のテクスチャ作成に失敗しました", "エラー", MB_OK ); return E_FAIL; }
次に、ARToolKitで得たキャプチャ画像をテクスチャに入れる処理をします。 ARToolKitの画像フォーマットはBGRAというフォーマットになります。逆から読むとARGBです。 なので、これに合うように入れていけばOKです。
Scene.cpp
//======================================================= // キャプチャ画像のコピー // 引数 // dataPtr : unsigned char* ARToolKitから受け取った、カメラで撮影した画像データ // x_size : int X方向の解像度 // y_size : int Y方向の解像度 // 戻り値 // 成功したら、S_OK //======================================================= HRESULT Scene::CopyCaptureImage( unsigned char* dataPtr, int x_size, int y_size ) { if( dataPtr == NULL ) { return E_FAIL; } //テクスチャをロック D3DLOCKED_RECT TexRect; m_pCapTexture->LockRect( 0, &TexRect, NULL, 0 ); //画像データを転送 unsigned char *p1 = (unsigned char *)TexRect.pBits; DWORD pitch = TexRect.Pitch / sizeof(unsigned char); if( p1 == NULL ){ return E_FAIL; } int offset; for ( int y = 0; y < y_size; y++ ) { for ( int x = 0; x < x_size; x++ ) { offset = x * 4; p1[offset + 0] = dataPtr[offset + 0]; p1[offset + 1] = dataPtr[offset + 1]; p1[offset + 2] = dataPtr[offset + 2]; p1[offset + 3] = dataPtr[offset + 3]; } p1 += pitch; // 次の行 dataPtr += (4 * XSIZE); } // テクスチャのロック解除 m_pCapTexture->UnlockRect( 0 ); return S_OK; }
画像の設定は以上です。
以上の値を使って、描画を行います。
(この後の文章は余裕があれば書こうと思っています。ついでにソースも整理中。どうしても気になる人は、【ニコニコ動画】ARToolKitで初音ミク(その6):LIVEの方のソースを参考にしてみてください。)