【DirectX】FBXからシェイプアニメーションを読み込む方法【BlendShape】【ShapeMorphing】


FBXからシェイプアニメーションを読み込む方法が書かれている日本の記事が無く苦労した経験があったので
過去に実装したコードを記載しておきます。

ソースコード (※使用APIはDirectX11です)

// シェイプアニメーション用の構造体
typedef struct tagShapeAnimation
{
    std::string                         sName;          // シェイプ名
    float                               fBlendRate;     // ブレンドレート
    std::vector<ShapeAnimationVertex>   vVertices;      // シェイプ考慮時の頂点
    ID3D11Buffer*                       pVertiexBuffer; // シェイプ考慮時の頂点バッファ

    tagShapeAnimation::tagShapeAnimation() {
        sName = "NONE";
        fBlendRate = 0.0f;
        pVertiexBuffer = nullptr;
    }
}ShapeAnimation;


typedef struct tagShapeAnimationVertex :tagBaseBuffer
{
    Vector vPos;
}ShapeAnimationVertex, SHAPE_ANIMATION_VERTEX;

//+-------------------------------------------------------------------------------------------
// シェイプアニメーションを取得
void FetchShapeAnimations(const FbxMesh * pFBXMesh, std::vector<ShapeAnimation>& tagShapeAnim)
{
    // 基本の頂点インデックスの中の座標配列
    const FbxVector4 *l_aDefaultVertices = pFBXMesh->GetControlPoints();

    // シェイプアニメーションデータ数取得
    int l_iShepeCount = pFBXMesh->GetShapeCount();

    // 最大のシェイプアニメーション数リサイズする
    tagShapeAnim.resize(l_iShepeCount);
    int l_iShapeNoOffset = 0;

    // シェイプアニメーションのグループ数
    // シェイプを入れてたら1つはある
    const int l_iBlendShapeCount = pFBXMesh->GetDeformerCount(FbxDeformer::eBlendShape);
    for (int iBlendShapeIndex = 0; iBlendShapeIndex < l_iBlendShapeCount; ++iBlendShapeIndex)
    {
        FbxBlendShape *l_pBlendShape = (FbxBlendShape*)(pFBXMesh->GetDeformer(iBlendShapeIndex, FbxDeformer::eBlendShape));
        std::string l_sBlendShapeName = l_pBlendShape->GetName();// ブレンドシェイプ名取得

        // ブレンドシェイプの中に入っているチャンネル数回す
        const int l_iBlendShapeChannelCount = l_pBlendShape->GetBlendShapeChannelCount();
        for (int iBlendShapeChannelIndex = 0; iBlendShapeChannelIndex < l_iBlendShapeChannelCount; ++iBlendShapeChannelIndex)
        {
            FbxBlendShapeChannel* l_pBlendShapeChannel = l_pBlendShape->GetBlendShapeChannel(iBlendShapeChannelIndex);

            // シェイプチャンネル名取得
            std::string l_sBlendShapeName = (char*)l_pBlendShapeChannel->GetName();
            // シェイプの初期のパーセント取得
            DWORD  l_dwShapePersent = (DWORD)l_pBlendShapeChannel->DeformPercent.Get();

            int l_iTargetShapeCount = l_pBlendShapeChannel->GetTargetShapeCount();
            for (int iTargetShapeIndex = 0; iTargetShapeIndex < l_iTargetShapeCount; ++iTargetShapeIndex)
            {
                FbxShape* l_pShape = l_pBlendShapeChannel->GetTargetShape(iTargetShapeIndex);

                tagShapeAnim.at(l_iShapeNoOffset).sName = (char *)l_pShape->GetName();  // ターゲットシェイプ名取得

                // 選択されているメッシュの全頂点取得
                int l_iControlPointsCount = l_pShape->GetControlPointsCount();          // 頂点インデックス数
                fbxsdk::FbxVector4* l_vControlPoints = l_pShape->GetControlPoints();    // 頂点座標

                tagShapeAnim.at(l_iShapeNoOffset).vVertices.resize(l_iControlPointsCount);// シェイプが適応されているメッシュ分リサイズ 

                for (int j = 0; j < l_iControlPointsCount; j++)
                {
                    tagShapeAnimationVertex l_vInitVtx;
                    l_vInitVtx.pos.x = 0.0f;
                    l_vInitVtx.pos.y = 0.0f;
                    l_vInitVtx.pos.z = 0.0f;
                    // 初期化
                    tagShapeAnim.at(l_iShapeNoOffset).vVertices.at(j) = l_vInitVtx;
                }

                // アニメーションが適応されているFBXでの自動三角形分割後の頂点インデックス
                int l_iIndicesCount = l_pShape->GetControlPointIndicesCount();  // 適応している頂点インデックス数
                int* l_aIndices = l_pShape->GetControlPointIndices();           // 適応している頂点インデックス配列

                // シェイプアニメーションが適用されている頂点番号だけ変化した頂点を詰め込む
                for (int i = 0; i < l_iIndicesCount; i++)
                {
                    int iActiveIdx = l_aIndices[i];

                    // シェイプ適用時の頂点の場所と基本の頂点の場所を差し引いて移動量を抽出
                    tagShapeAnimationVertex l_vShapeMoveVec;
                    l_vShapeMoveVec.pos.x = (float)l_vControlPoints[iActiveIdx].mData[0] - (float)l_aDefaultVertices[iActiveIdx].mData[0];
                    l_vShapeMoveVec.pos.y = (float)l_vControlPoints[iActiveIdx].mData[1] - (float)l_aDefaultVertices[iActiveIdx].mData[1];
                    l_vShapeMoveVec.pos.z = (float)l_vControlPoints[iActiveIdx].mData[2] - (float)l_aDefaultVertices[iActiveIdx].mData[2];

                    // シェイプアニメーションの頂点を詰め込んでいく
                    tagShapeAnim.at(l_iShapeNoOffset).vVertices.at(iActiveIdx) = l_vShapeMoveVec;
                }

                // オフセットを加算し、別のブレンドシェイプのグループに入っていても一つの配列に統一する
                l_iShapeNoOffset++;

            }// TargetIndex
        }
    }   
}

シェイプ後の頂点さえ取得出来たら後は簡単です。元のメッシュの各頂点とシェイプ後の頂点をどれだけブレンドするか調整すればアニメーションが作れます。
少しでも実装の参考になれば幸いです。