【C++】DirectXにおける3Dアニメーションプログラミングを分析する~その5

前回はフライングして、IK(インバースキネマティクス)のロジックを先に分析した。

実際の処理順序としては、次に示すFK(フォワードキネマティクス)によって、ボーンの座標変換を行うのが先だ。

FKによって変換されたボーンの座標にたいして、IKを適用する。

ということで、今回はFKの処理を行っている箇所のロジックを分析してみた。

いつもの如く、詳細な分析はコメントとして追記してある。

概略を説明するとこんな感じになるだろう。

まず、前提として、下記のようなデータがすでに構築済みであるとする。

各ボーン(ボーン名、初期位置、初期回転)の配列…①

各ボーンごとのモーションデータ(キーフレーム情報の配列)の配列…②

キーフレーム情報とは、ボーン名、フレーム番号、そのフレームでの位置、回転情報

①と②の配列のインデックスは一致するものとする。

つまり、①のi番目のボーンのモーションデータは②のi番目に格納されている。

システムは絶対フレーム番号を管理していて、これは1/60秒にひとつインクリメントされるものとする。

上記前提として、まずボーンをひとつずつ処理していく。

モーションデータのキーフレームは連続しているとは限らない。

そのため、絶対フレーム番号でのキーフレーム情報は欠落している可能性がある。

欠落しているフレームの位置、回転情報は、絶対フレーム番号の直近の前後のキーフレーム情報から算出する。

算出した位置、回転情報をそれぞれ、移動行列と回転行列に変換する。

そうして求まった移動行列と回転行列をボーンの初期位置行列にかけて、親ボーン座標系の現在の位置行列としてメモリ上に保存する。

以上が、FKによるボーンの更新処理の概略だ。

実際にモデルのメッシュをこのボーンの動きに合わせて移動するのは、また別の処理である。

それには、シェーダと呼ばれるプログラムを使って、ハードウェア的に高速に計算する。

こちらの分析はまた別の機会にあつかうかもしれない。

//全てのボーンのループ
for (unsigned int i = 0; i < bones->size(); i++) {
    // キーフレーム補完
    unsigned long t0, t1;
    
    D3DXQUATERNION q0, q1;
    
    D3DXVECTOR3 p0, p1;
    
    //========================================
    //ボーン配列のインデックスと
    //キーフレーム配列のインデックスは一致している
    //つまりi番目のボーンのキーフレームイテレータは
    //i番目に格納されている
    //========================================
    if (ite_keyFrames[i] != keyFrames[i].end()) {
    
        //前回処理したモーションフレーム番号
        t0 = (*ite_keyFrames[i]).frameNo;
        
        //前回処理したモーションフレームの回転
        boneRot[i] = q0 = (*ite_keyFrames[i]).rotation;
        
        //前回処理したモーションフレームの位置
        bonePos[i] = p0 = (*ite_keyFrames[i]).position;
        
        //イテレータを進めて、次のフレームをフォーカスする
        if (++ite_keyFrames[i] != keyFrames[i].end()) {
        
            //次回のモーションフレーム
            KeyFrame k = *ite_keyFrames[i];
            
            //次回のモーションフレーム番号
            t1 = k.frameNo;
            
            //次回のモーションフレームの回転
            q1 = k.rotation;
            
            //次回のモーションフレームの位置
            p1 = k.position;

            //========================================
            //【補完率を求める】
            //現在の絶対フレーム番号から
            //前回のモーションフレーム番号を引いた値を
            //次回のモーションフレーム番号から
            //前回のモーションフレーム番号を引いた値で割る。
            //【例】
            //絶対フレーム番号が25、
            //前回処理したモーションフレーム番号が10、
            //次のモーションフレーム番号が30とすると
            //補完率は
            //(25 - 10) / 30 - 10 = 3 / 4 = 0.75 → 75%
            //========================================
            float s = (float)(time - t0) / (float)(t1 - t0);
            
            //========================================
            //前回フレームの回転から次回フレームの回転まで
            //上記で求めた補完率だけ近づける。
            //========================================
            D3DXQuaternionSlerp(&boneRot[i], &q0, &q1, k.bezie_r->GetY(s));
            
            bonePos[i].x = p0.x + (p1.x - p0.x)*k.bezie_x->GetY(s);
            
            bonePos[i].y = p0.y + (p1.y - p0.y)*k.bezie_y->GetY(s);
            
            bonePos[i].z = p0.z + (p1.z - p0.z)*k.bezie_z->GetY(s);
            
            //絶対フレーム番号が次回のモーションフレーム番号に達していなければ
            //イテレータをもとに戻す。
            if (time != t1) --ite_keyFrames[i];
        }
    }

    //========================================
    // 親ボーン座標系のボーン行列を求める
    //========================================
    D3DXMATRIX rot, trans;

    //四元数で回転させるための変換用行列を求める
    D3DXMatrixRotationQuaternion(&rot, &boneRot[i]);

    //X,Y,Z座標を移動行列に変換する
    //関節は親に接続されているため
    //原則的に単位行列になるはず
    D3DXMatrixTranslation(&trans, bonePos[i].x, bonePos[i].y, bonePos[i].z);

    //========================================
    //求めた移動行列と回転行列を
    //初期姿勢の位置行列にかけて
    //ローカル座標系における位置行列を更新する
    //========================================
    (*bones)[i].boneMatBL = rot*trans*(*bones)[i].initMatBL;

}
4年前

コメントを残す

メールアドレスが公開されることはありません。