モーションアニメーション

3D アニメーション画像

X-FILE のパーツを組み合わせてモーションアニメーションを行います。

前田稔(Maeda Minoru)の超初心者のプログラム入門

モーションアニメーション

  1. パーツを組み合わせたモデルの X-File を用意して下さい。
    そのモデルを使ってモーションアニメーションに挑戦してみましょう。
  2. 全てのパーツは、ピボットポイント(回転軸の中心)を 0,0,0 に設定して下さい。
    例えば、上腕のピボットポイントは肩の付け根ですし、下肢のピボットポイントは膝になります。
    従って、そのまま表示すると全てのパーツが中央に重なって表示されます。
  3. 各パーツを本来の位置に配置して、モデル全体を組み立てて下さい。
    パーツの座標や回転情報を TEXT FILE にタイプしてプログラムで読み込めば、細かい調整が簡単にできるようになります。
    この機能を拡張して、フレーム毎のパーツの動きをデータとして与えて、モーションアニメーションを行います。
  4. さらにキーフレームを作成して、フレーム間を補完しながらアニメーションする方法がお勧めです。
    ちょっとしたアニメーションでは1000フレームぐらいのデータが必要ですが、キーフレーム間を補完しながら アニメーションするなら100フレームもあれば十分でしょう。
  5. TEXT FILE にタイプするモーションアニメーションに必要なデータを設計しましょう。
    少なくとも各パーツごとの座標(XYZ)データと、回転/移動するパーツの動きのデータは必要でしょう。
  6. メッシュの個数も多くなるので配列で定義しましょう。
    私はメッシュの最大数を30に、キーフレームの最大数を100に設定しました。
  7. キーフレームの作成は「親・子・孫」の関係を設定して操作しないと、なかなか思うようにポーズが取れません。
    例えば上腕を動かせば「下腕や手首」が連動して動くように設定するのです。
  8. 親子関係を設定したキーフレームを作成するツールに挑戦してみて下さい。
    親子関係の設定は ティーポットとコーンとトーラスを親子孫で回転する を参照して下さい。

プログラムの説明

  1. K_MAX がキーフレームの最大数で、X_MAX が組み合わせるモデルの最大数です。
    MATLIST は MyD3D に登録されている親子関係を設定する Matrix List Object Class です。
    VT[K_MAX][6] はキーフレーム毎の View 座標のテーブルです。
    MT[K_MAX][X_MAX][6] はキーフレーム毎の Motion Data のテーブルです。
    KT[K_MAX] はキーフレーム間を補完する分割数のテーブルです。
    g_Weight がブレンドの度合いで、g_dt が g_Weight の増加係数です。
        #define     K_MAX      100                  // キーフレームの最大数
        #define     X_MAX      30                   // モデルの最大数
    
        //Global Area
        MyD3D                   *myd3d   = NULL;    // MyD3D Object Class
        LPD3DXMESH              g_pMesh[X_MAX];
        D3DMATERIAL9*           g_pMat[X_MAX];
        LPDIRECT3DTEXTURE9*     g_pTex[X_MAX];
        DWORD                   g_xNum[X_MAX];
    
        //親子関係の設定
        MATLIST     *MatList[X_MAX];        // Mat List Object Class
        int         Parent[X_MAX];          // 親の設定
    
        //View(XYZ), Terg(XYZ), 移動(XYZ), 回転(XYZ)
        char        g_file[X_MAX][24];      // X-File Name
        float       VT[K_MAX][6];           // View Table
        float       MT[K_MAX][X_MAX][6];    // Motion Table
        int         KT[K_MAX];              // Key Frame Motion Count
        int         g_XN;                   // X-file(Mesh) の個数
        int         g_KN;                   // Key Frame 数
        int         g_kn;                   // 現在描画中のキーフレーム番号
        float       g_Weight= 0;            // ブレンドの度合い(1.0→0.0)
        float       g_dt;                   // g_Weight の増加係数(1.0/KT[g_kn])
        bool        g_All= FALSE;
        
  2. このプログラムをテストした Anime.txt の内容です。
    左の「上腕,下椀,手首」の X-File を親子孫の関係で組み合わせます。
    arm_upperL.x, に続く 0, が親のパーツでゼロはワールド座標を意味します。
    このモデルでは arm_upperL.x, を親にして arm_lowerL.x, と handL.x, が子と孫の関係に設定されています。
    * 100, がキーフレーム間を補完する分割数で KT[K_MAX] に格納します。
    次の行がキーフレーム毎の View 座標で VT[K_MAX][6]; に格納します。
    次の3行がキーフレーム毎の Motion Data(移動情報, 回転情報)で MT[K_MAX][X_MAX][6]; に格納します。
        // Motion Frame  Ver 1.0   by Maeda Minoru
        3,        //パーツの数
        arm_upperL.x, 0,
        arm_lowerL.x, 1,
        handL.x, 2,
    
        // キーフレーム - 0
        *  100,
            0.00,    0.00,  -70.00,     0.00,    0.00,    0.00,
            0.00,    0.00,    0.00,     0.00,    0.00,    0.00,
           10.50,    0.00,    1.20,     0.00,    0.00,    0.00,
           12.00,    0.00,    0.00,     0.00,    0.00,    0.00,
    
        // キーフレーム - 1
        *  100,
            0.00,    0.00,  -70.00,     0.00,    0.00,    0.00,
            0.00,    0.00,    0.00,     0.30,    1.60,    0.10,
           10.50,    0.00,    1.20,     0.00,    0.20,    0.00,
           12.00,    0.00,    0.00,     0.70,    0.00,    0.00,
    
        // キーフレーム - 2
        *  100,
            0.00,    0.00,  -70.00,     0.00,    0.00,    0.00,
            0.00,    0.00,    0.00,    -0.60,    3.00,    0.10,
           10.50,    0.00,    1.20,     0.00,    1.20,    0.90,
           12.00,    0.00,    0.00,    -0.10,    0.00,    0.00,
    
        // キーフレーム - 3
        *  100,
            0.00,    0.00,  -70.00,     0.00,    0.00,    0.00,
            0.00,    0.00,    0.00,    -0.30,    4.80,    0.30,
           10.50,    0.00,    1.00,     0.00,    2.20,    0.30,
           12.00,    0.00,    0.00,     0.80,    0.00,    0.00,
    
        // キーフレーム - 4
        *  100,
            0.00,    0.00,  -70.00,     0.00,    0.00,    0.00,
            0.00,    0.00,    0.00,    -0.20,    6.60,    0.00,
           10.50,    0.00,    1.20,     0.00,    0.00,    0.00,
           12.00,    0.00,    0.00,     0.00,    0.00,    0.00,
        
  3. 親子関係を設定する MatrixList Object Class の定義です。
    Object Class にしなくても構造体でプログラムすることもできます。
    MatrixStack は Stack を使って MATRIX 型の計算をする Object です。
    無理に MatrixStack を使うよりは、馴染みのある D3DXMatrixMultiply() の方が解かりやすいかも知れません。
    Mov が親からの相対位置座標で、Rot が親からの相対回転情報です。
    Up は自動回転情報を設定するのですが、今回は使用しません。
    *Parent で親の MATLIST を設定します。
    MatOne() は *model で指定されたパーツの座標を計算するメソッドです。
    親の MATLIST がリンクされているときは再帰的に呼び出します。
    MatAll() は親子関係を考慮して this Object の座標を計算するメソッドです。
    計算は孫から始めて「孫→子→親」の順に再帰的に計算しています。
        class MATLIST
        {
          protected:
            ID3DXMatrixStack    *MatStack;
          public:
            D3DXVECTOR3         Mov;        //親からの相対位置座標
            D3DXVECTOR3         Rot;        //親からの相対回転情報
            D3DXVECTOR3         Up;         //自動回転情報
            MATLIST             *Parent;    //親の MATLIST
    
            MATLIST(MATLIST *par);          //Constructor
            ~MATLIST();                     //Destructor
            void                MatOne(MATLIST *model);
            D3DXMATRIX*         MatAll();
        };
        
  4. "Anime.txt" からパーツを構成する X-File Name を読み込んでアニメーションデータを設定します。
    X-File の個数と同じだけ *MatList[X_MAX]; をインスタンス化して下さい。
    インスタンス化の段階で親を設定できるのですが、今は全てのパーツの親を NULL に設定しています。
        int  SetParam()
        {        :
            for(xn=0; xn<g_XN; xn++)
            {   p= myd3d->SetStr(g_file[xn],p);         // XーFile Name を格納
                p= myd3d->SetInt(&Parent[xn],p+1,1);    // 親の設定
                MatList[xn]= new MATLIST(NULL);         // MatList Object を生成
            }
                 :
        
  5. 描画処理で MatList[] に親子関係を設定して、モデルデータを格納します。
        void Draw(void)
        {   int     xn;
    
            if (!g_pDEV || !g_bActive)  return;
            SetParent();                            //親子関係を設定する
            SetModelData();                         //モデルデータを設定する
            g_pDEV->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,60,80),1.0f,0);
            if (SUCCEEDED(g_pDEV->BeginScene()))
            {   //描画環境の設定
                for(xn=0; xn<g_XN; xn++)
                {   SetupMatrices(xn);
                    for(DWORD i=0; i<g_xNum[xn]; i++)
                    {   g_pDEV->SetMaterial(&g_pMat[xn][i]);
                        g_pDEV->SetTexture(0,g_pTex[xn][i]);
                        g_pMesh[xn]->DrawSubset(i);     // Draw the mesh subset
                    }
                }
                g_pDEV->EndScene();
            }
            g_pDEV->Present(NULL,NULL,NULL,NULL);
        }
        
  6. SetModelData() では座標をブレンドしながら MatList[] にモデルのデータを設定します。
    g_Weight==0 または g_kn==0 のときはブレンドは行いません。
    ブレンドするのは、2番目以降のキーフレームでブレンドの割合(g_Weight)が設定された時です。
        void  SetModelData()
        {   int     i;
    
            if (g_Weight==0 || g_kn==0)
            {   myd3d->SetVect(&viewForm,&VT[g_kn][0]);
                myd3d->SetVect(&tergForm,&VT[g_kn][3]);
                for(i=0; i<g_XN; i++)
                {   myd3d->SetVect(&MatList[i]->Mov,&MT[g_kn][i][0]);
                    myd3d->SetVect(&MatList[i]->Rot,&MT[g_kn][i][3]);
                }
                return;
            }
            //Blend Frame
            myd3d->BlendVect(&viewForm,&VT[g_kn-1][0],&VT[g_kn][0],g_Weight);
            myd3d->BlendVect(&tergForm,&VT[g_kn-1][3],&VT[g_kn][3],g_Weight);
            for(i=0; i<g_XN; i++)
            {   myd3d->BlendVect(&MatList[i]->Mov,&MT[g_kn-1][i][0],&MT[g_kn][i][0],g_Weight);
                myd3d->BlendVect(&MatList[i]->Rot,&MT[g_kn-1][i][3],&MT[g_kn][i][3],g_Weight);
            }
        }
        
  7. MatList[xn]->MatAll(); で親子関係を考慮した Mesh の座標を求めて D3DTS_WORLD に設定します。
    パーツを構成する MatList[] Object(構造体) の Mov, Rot には g_Weight でブレンドした値が格納されています。
        //描画環境の設定(xn:xfileNum)
        void  SetupMatrices(int xn)
        {      :
            //World 座標の設定
            matall= MatList[xn]->MatAll();
            g_pDEV->SetTransform(D3DTS_WORLD,matall);
               :
        

Matrix Stack

Matrix Stack とは座標計算に使用する MATRIX 型の計算領域を Stack を使って解かり易く簡単に操作するものです。
沢山の部品が複雑に組み合わさってオブジェクトを構成している場合、MATRIX の計算も複雑になり多くの計算領域が必要になります。
例えば人体モデルを例に取ると、上半身→上腕→下腕→手首→5本の指などの関係を親・子・孫 の階層構造で表します。
このとき腕を振ったときの5本の指の座標計算などは、何段階もの座標の組合わせ計算が必要で、このようなときに威力を発揮します。
  1. Matrix Stack のハンドルを定義する。
    ID3DXMatrixStack* g_pMatStack = NULL;
  2. Matrix Stack の Object を生成する。
    if (FAILED(D3DXCreateMatrixStack(0, &g_pMatStack))) return E_FAIL;
  3. Matrix 領域を Stack 上に作成する。
    一度に一個ずつ Stack に積まれます。
    g_pMatStack->Push();
  4. Stack を使って座標計算をする。
    g_pMatStack->Translate(3,0,0);
    g_pMatStack->RotateYawPitchRoll(timeGetTime()/1000.0f,0,0);
  5. 計算結果を WORLD 座標に設定して描画する。
    g_p3DDev->SetTransform(D3DTS_WORLD,&(*g_pMatStack->GetTop()));
    //メッシュの描画
  6. Stack の一番上の Matrix 領域を取り出す。
    取り出すだけで、削除はされません。
    D3DXMATRIX *tmp;
    tmp= MatStack->GetTop();
  7. Stack 上の Matrix 領域を削除する。
    一度に一個ずつ Stack から取り除かれます。
    g_pMatStack->Pop();
  8. 終了時に Matrix Stack の Object を開放する。
    SAFE_RELEASE(g_pMatStack);

前田稔(Maeda Minoru)の超初心者のプログラム入門

超初心者のプログラム入門(DirectX9 game program)

超初心者のプログラム入門(DirectX10 game program)