前回はフライングして、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; }