ARToolKitの描画部分をDirectXにしてみる

ARToolKitの描画部分をDirectXにするときに厄介な変換の問題とかのTipsを紹介します。

ARToolkitの基本的なプログラムを見るとOpenGLを使っています。 ですが、DirectXの方が何かと便利なこともあります。(行列計算とか、見栄えを良くするのとか、Xファイルを扱う時とか)

なので、ARToolKitの描画部分をOpenGLからDirectXにしてみます。

このとき考慮しないといけないのは、ARToolKitはOpenGLを元に作られているので、色々な変数をOpenGL用からDirectX用に変換してあげないといけません。 結構めんどくさいですが、わかればなんてことはないので頑張りましょう。

なお、このページを書くにあたって、【ニコニコ動画】ARToolKitで初音ミク(その6):LIVEの方のソースを参考にさせていただきました。 この場を借りて御礼申し上げます。

原理

まず、先に数学的なところから説明しときます。

OpenGLの行列をDirectXの行列にするときに、必ず出てくるのが縦ベクトルを横ベクトルにすることです。 そもそも縦ベクトル、横ベクトルってなんじゃいな?について説明します。

縦ベクトルは、その名の通り縦のベクトル↓です。 OpenGLはこの形式を採用しています。

縦ベクトル

横ベクトルも、その名の通り横のベクトル↓です。 DirectXはこの形式を採用しています。

横ベクトル

で、この縦と横が違うと何がまずいのかといいますと、行列の掛け算の方向が逆になるんです。 OpenGLのマトリクスをmatGL、DirectXの行列をmatDXとすると、ベクトルと行列の掛け算は↓みたいになります。

matGL

matDX

で、これを一致させるためにはどうすればいいかというと、転置させれば↓大丈夫です。

matGL_for_DX

一応これで問題はないのですが、ちょっと後々面倒なことがあるので、ここでもうちょい詳しく説明しときます。 お気づきの方もいるかと思いますが、わざと書き方を変えてみました。 DirectXの行列(D3DXMATRIXとかで定義する変数)は4行4列の行列(つまり2次元の配列)になっています。 一方、OpenGLの行列はdouble型、要素16個の1次元の配列になっています。

ここでアフィン変換行列(ここでは回転と平行移動だけのもの)を思い出していただきたいんですが、

affine

↑の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を元に↓のようなビュー行列(縦ベクトル表記)が生成されます。

view_trans

図にすると、↓みたいな感じです。

view_coord_DX

ところが、ARToolKit Coordinate Systemsを見ると、カメラの上向き方向ベクトルu(このページだとYc)が下方向を向いているのがわかります。 なので、uを-1倍する必要があります。

また、マーカの座標系はZmを上方向とした座標系になっていますが、DirectXでは↑の図を見てもわかるようにYを上方向とした座標系になっています。 なので、z軸とy軸を交換する必要があります。

よって、ビュー行列は↓みたいになります。

view_trans_for_DX

これをプログラムにすると、↓みたいになります。 どうしてこうなるのかわからない方は↑の方にある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の方のソースを参考にしてみてください。)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

PAGE TOP

Twitter