X-FILE(Tiny.x) でアニメーション

August2007 DirectX9 で X-FILE(Tiny.x) でアニメーションしながら描画します。
Java でも XFILE の Animation プログラムとファイルの形式を説明しています。
超初心者のプログラム入門(Java)/Loader の作成 /XFILE Loader を参照して下さい。

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

プロジェクトの設定

  1. Template のプロジェクトをコピーして、フォルダーの名前を Tiny に変更して下さい。
    DXUT のフォルダーから次のソースファイルをプロジェクトに組み込んで下さい。
    Template に最初から組み込まれているプログラムも併せて掲載しています。
    詳細は「超初心者のプログラム入門(C言語 Windows)/ソースコードを共有する」を参照して下さい。
    DXUT.cpp
    DXUT.h
    DXUTenum.cpp
    DXUTenum.h
    DXUTmisc.cpp
    DXUTmisc.h
    DXUTcamera.cpp
    DXUTcamera.h
    DXUTres.cpp
    DXUTres.h
  2. ヘッダファイルの include と構造体と Object Class の宣言です。
    #pragma でライブラリをリンクします。
    ソースコードが長いので、最初の部分だけ掲載しました。
    あとは DirectX SDK (August 2007) のソースコードを参照して下さい。
    C:\Program Files\Microsoft DirectX SDK (August 2007)\Samples\C++\Direct3D\SkinnedMesh\ に格納されています。
    #include "DXUT.h"
    #include "DXUTcamera.h"
    
    #pragma once
    #pragma comment(lib,"dxerr.lib")
    #pragma comment(lib,"dxguid.lib")
    #pragma comment(lib,"d3dx9d.lib")
    #pragma comment(lib,"d3dx10d.lib")
    #pragma comment(lib,"d3d9.lib")
    #pragma comment(lib,"winmm.lib")
    #pragma comment(lib,"comctl32.lib")
    
    // enum for various skinning modes possible
    enum METHOD
    {
        D3DNONINDEXED,
            :
            :
    
  3. g_ArcBall には描画環境の変換マトリックスを格納します。
    g_ArcBall を使うとマウスの操作で簡単にモデルを回転することができます。
    g_pFrameRoot はアニメーションするフレームリストのトップポインタです。
    g_pAnimController はモデルのアニメーションを制御するオブジェクトです。
    g_vObjectCenter はモデルを描画する座標(x,y,z)の設定です。
    g_fObjectRadius はメッシュのサイズです。
    Path[] に Tiny.x が格納されているパスを設定します。
    型の古いグラフィックボードでも動くように、シェダーを外して SOFT で処理してみました。 (^_^;)
    CD3DArcBall             g_ArcBall;              // Arcball for model control
    LPD3DXFRAME             g_pFrameRoot = NULL;
    ID3DXAnimationController* g_pAnimController = NULL;
    D3DXVECTOR3             g_vObjectCenter;        // Center of bounding sphere of object
    FLOAT                   g_fObjectRadius;        // Radius of bounding sphere of object
    D3DXMATRIXA16           g_matView;              // View matrix
    D3DXMATRIXA16           g_matProj;              // Projection matrix
    D3DXMATRIXA16*          g_pBoneMatrices = NULL;
    UINT                    g_NumBoneMatricesMax = 0;
    
    WCHAR     Path[MAX_PATH] = L"C:\\Data\\Model\\Tiny";
    
  4. OnD3D9CreateDevice() です。
    D3DXLoadMeshHierarchyFromX() で Tiny.x を入力して g_pFrameRoot に設定します。
    HRESULT CALLBACK OnCreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext )
    {
        HRESULT hr;
        CAllocateHierarchy Alloc;
    
        WCHAR strCWD[MAX_PATH];
        GetCurrentDirectory( MAX_PATH, strCWD );
        SetCurrentDirectory( Path );
    
        V_RETURN( D3DXLoadMeshHierarchyFromX( L"tiny.x",
           D3DXMESH_MANAGED, pd3dDevice, &Alloc, NULL, &g_pFrameRoot, &g_pAnimController ) );
        V_RETURN( SetupBoneMatrixPointers( g_pFrameRoot ) );
        V_RETURN( D3DXFrameCalculateBoundingSphere( g_pFrameRoot, &g_vObjectCenter, &g_fObjectRadius ) );
        SetCurrentDirectory( strCWD );
    
        // Obtain the behavior flags
        D3DDEVICE_CREATION_PARAMETERS cp;
        pd3dDevice->GetCreationParameters( &cp );
    
        return S_OK;
    }
    
    ※以下は DirectX9 の Help からの抜粋です。
    D3DXLoadMeshHierarchyFromX は、.x ファイルからアニメーション データとフレーム階層をロードする。
    .x ファイルを調べ、pAlloc で渡された、ID3DXAllocateHierarchy の派生オブジェクトに基づいてフレーム階層とアニメーションコントローラを構築する。
    データのロードは、次の手順で行う。
    1. ID3DXAllocateHierarchy から派生させて、各メソッドを実装する。
      これによって、フレームおよびメッシュの割り当てと解放をどのように行うかを制御する。
    2. ID3DXLoadUserData から派生させて、各メソッドを実装する。
      .x ファイル内に埋め込みのユーザー定義データが存在しないか、これを必要としない場合は、この部分は省いてよい。
    3. ID3DXAllocateHierarchy クラスのオブジェクトを作成し、必要に応じて LoadUserData クラスのオブジェクトを作成する。
      これらのオブジェクトのメソッドを呼び出す必要はない。
    4. D3DXLoadMeshHierarchyFromX を呼び出して ID3DXAllocateHierarchy オブジェクトおよび ID3DXLoadUserData オブジェクト (または NULL) を渡し、 フレーム階層とアニメーション コントローラを作成する。
      すべてのアニメーション セットとフレームが自動的にアニメーション コントローラに登録される。
    ロード時には、フレームのロードと割り当てを制御するために、各フレームに対して ID3DXAllocateHierarchy::CreateFrame と ID3DXLoadUserData::LoadFrameChildData がコールバックされる。
    アプリケーションは、これらのメソッドを定義することによって、フレームの保管方法を制御する。
    メッシュ オブジェクトのロードと割り当てを制御するために、各メッシュ オブジェクトに対して ID3DXAllocateHierarchy::CreateMeshContainer と ID3DXLoadUserData::LoadMeshChildData がコールバックされる。
    他のメソッドでロードされない最上位の各オブジェクトに対して、ID3DXLoadUserData::LoadTopLevelData がコールバックされる。
  5. OnFrameRender() 関数で Light を設定して、DrawFrame() 関数を呼び出します。
    void CALLBACK OnFrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
    {
        HRESULT hr;
    
        // Clear the backbuffer
        pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                             D3DCOLOR_ARGB(0, 66, 75, 121), 1.0f, 0L );
    
        // Setup the light
        D3DLIGHT9 light;
        D3DXVECTOR3 vecLightDirUnnormalized(0.0f, -1.0f, 1.0f);
        ZeroMemory( &light, sizeof(D3DLIGHT9) );
        light.Type        = D3DLIGHT_DIRECTIONAL;
        light.Diffuse.r   = 1.0f;
        light.Diffuse.g   = 1.0f;
        light.Diffuse.b   = 1.0f;
        D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecLightDirUnnormalized );
        light.Position.x   = 0.0f;
        light.Position.y   = -1.0f;
        light.Position.z   = 1.0f;
        light.Range        = 1000.0f;
        V( pd3dDevice->SetLight(0, &light ) );
        V( pd3dDevice->LightEnable(0, TRUE ) );
    
        // Begin the scene
        if( SUCCEEDED( pd3dDevice->BeginScene() ) )
        {
            DrawFrame( pd3dDevice, g_pFrameRoot );
            // End the scene.
            pd3dDevice->EndScene();
        }
    }
    
  6. DrawFrame() 関数でフレームを描画します。
    DrawMeshContainer() がメッシュを描画する関数です。
    void DrawFrame( IDirect3DDevice9 *pd3dDevice, LPD3DXFRAME pFrame )
    {
        LPD3DXMESHCONTAINER pMeshContainer;
    
        pMeshContainer = pFrame->pMeshContainer;
        while (pMeshContainer != NULL)
        {
            DrawMeshContainer( pd3dDevice, pMeshContainer, pFrame );
            pMeshContainer = pMeshContainer->pNextMeshContainer;
        }
    
        if (pFrame->pFrameSibling != NULL)
            DrawFrame( pd3dDevice, pFrame->pFrameSibling);
    
        if (pFrame->pFrameFirstChild != NULL)
            DrawFrame( pd3dDevice, pFrame->pFrameFirstChild );
    }
    
  7. 元のプロジェクトは、下記のフォルダーに格納されています。
    後はこちらのソースコードを参照して下さい。
    C:\Program Files\Microsoft DirectX SDK (August 2007)\Samples\C++\Direct3D\SkinnedMesh\
    プロジェクトを実行すると Tiny.X が歩くアニメーションが描画されます。

プログラムの説明

プログラムの詳細に興味の無い方は読み飛ばして下さい。
  1. 3枚の X-FILE で Dolphin の Animation では頂点座標を混ぜ合わせてアニメーションしました。
    今回は「頂点座標をプログラムでスキニング(直接操作)」しながらアニメーションします。
    Tiny.x にはモデルデータだけでなく、モデルを動かす制御ポイント(bone)と対応するアニメーションデータも格納されています。
    X-FILE は TEXT 形式で記述されているので Text Editor で開けばファイルに書かれている内容を見ることができます。
    X-FILE の簡単な説明は DirectX10 の頂点データを定義する を参照して下さい。
    Tiny.x の2/3あたりの行の「XSkinMeshHeader {」からが SkinMesh データの始まりです。
    またその後の「FrameTransformMatrix {」からは座標変換のデータが格納されています。
    さらにその後には「AnimationSet {」から Animation のデータが格納されています。
  2. Tiny.x の簡単な形式の説明です。
    1. ファイルID?
      xof 0303txt 0032
    2. template
      直接描画には関係しないようです。
    3. Frame Scene_Root
      モデル定義の開始です。
      Mesh { から頂点座標の記述が始まります。
      Frame Box01 { からが、親・子・孫の関係を設定するリスト構造の定義です。
    4. AnimationSet
      アニメーションデータです。
  3. このプログラムでは見慣れない幾つかの構造体が使われているので、簡単に説明します。
  4. アニメーションを行う OnFrameMove() 関数です。
    g_pAnimController() でアニメーションの制御を行います。
    AdvanceTime() がモデルのポーズを進める関数です。
    static 変数を使って 100 ポーズで止めてみました。
    //        g_pAnimController->AdvanceTime( fElapsedTime, NULL );
    static int cnt= 100;
    if (cnt)
    {   g_pAnimController->AdvanceTime( 0.02f, NULL );
        cnt--;
    }
    
    全てのパーツは回転軸の中心(ピボット)を 0,0,0 に置いて作成されているようで、UpdateFrameMatrices() をコメントにするとパーツが中央に集まって描画されます。
    void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
    {
        IDirect3DDevice9* pd3dDevice = DXUTGetD3D9Device();
    
         // Setup world matrix
        D3DXMATRIXA16 matWorld;
        D3DXMatrixTranslation( &matWorld, -g_vObjectCenter.x,
                                          -g_vObjectCenter.y,
                                          -g_vObjectCenter.z );
        D3DXMatrixMultiply( &matWorld, &matWorld, g_ArcBall.GetRotationMatrix() );
        D3DXMatrixMultiply( &matWorld, &matWorld, g_ArcBall.GetTranslationMatrix() );
        pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
    
        D3DXVECTOR3 vEye( 0, 0, -2*g_fObjectRadius );
        D3DXVECTOR3 vAt( 0, 0, 0 );
        D3DXVECTOR3 vUp( 0, 1, 0 );
        D3DXMatrixLookAtLH( &g_matView, &vEye, &vAt, &vUp);
    
        pd3dDevice->SetTransform( D3DTS_VIEW,  &g_matView );
    
        if( g_pAnimController != NULL )
            g_pAnimController->AdvanceTime( fElapsedTime, NULL );
    
        UpdateFrameMatrices( g_pFrameRoot, &matWorld );
    }
    
  5. UpdateFrameMatrices() でメッシュのパーツの座標を設定します。
    Sibling と Child で再帰的に呼び出しています。
    Tiny.x では Sibling をたどって「Scene_root/Body」下の FirstChild にメッシュが格納されています。
    各パーツの座標は、親・子・孫の関係に従って、掛け合わせなければなりません。
    詳細は モーションアニメーション を参照して下さい。
    void UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix )
    {
        D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;
    
        if (pParentMatrix != NULL)
            D3DXMatrixMultiply(&pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix);
        else
            pFrame->CombinedTransformationMatrix = pFrame->TransformationMatrix;
    
        if (pFrame->pFrameSibling != NULL)
            UpdateFrameMatrices(pFrame->pFrameSibling, pParentMatrix);
    
        if (pFrame->pFrameFirstChild != NULL)
            UpdateFrameMatrices(pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix);
    }
    

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

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

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