モーション編その③ IKを適用しよう!
皆さんこんにちは。
フルスクラッチでゲームエンジンを作ろう!第十三回です。
前回3DモデルにFKを適用できるようになったので、今回はIKを実装していきます。
まずはIKとは何なのかという説明から。
前回実装したFKは肩を回転させる→肘を回転させる→手首を回転させる→手の位置を決定する、という流れでボーンを変形させるものです。
上位のボーンから子のボーンに向かって姿勢が決定するので順運動学、なのですがIKは逆運動学、名の通り逆の流れで姿勢が決定していきます。
先に最も下位のボーンの位置を決定し、そこから上位のボーンの姿勢を決定していくのです。
FKと違って単純な行列の掛け算だけでは実装できず計算も難しいものが多いため、恐らくスキンアニメーションの実装で最も難しい部分です。
とはいえ資料はネットの海に浮かんでいるので、参考にしながら進めていきます。
IKはいくつか手法が確立されており、それぞれ向き不向きがあります。
今回はその中でもボーンの連結数に左右されずアルゴリズムもわかりやすい、CCD-IKという手法を取ります。
CCD-IKはCyclic Coordinate Descent Inverse Kinematicsの略で、cyclicという通り1フレームに何回も各ボーンを動かして目標座標を目指す仕組みです。
言葉ではなんのこっちゃ分からないので図で示すと
こんな仕組みで末端のボーンを目標地点に近づけるのです。
なお、画像は学校の授業で発表するときに作ったスライドの流用なので少し邪魔な装飾がついています。ご容赦ください。
見てわかる通りCCD-IKは大変シンプルなアルゴリズムで実装することができ、目標地点への収束も早いので計算回数も多くなりにくく高速、といいことづくめに見えます。
が、この手法には「角度制限を考慮していない」という弱点も存在しています。
上の図のように全ての関節が縦横無尽に動かせるケースなんてほとんどなく、人体の3Dモデルには直接適用できません。
人間のひざが130度くらいしか曲がらないように、多くの3Dモデルの各関節は角度制限、オイラー角で各軸がどこまで曲がるかの最小、最大角度を持っています。
そこでCCD-IKでの試行計算中も角度制限を実装します。
現在のボーンの回転具合は回転行列で保持していますが、ボーンに設定されている角度制限の多くはオイラー角で定義されています。
角度制限をつけるなら
現在の回転行列→現在のオイラー角→最小、最大角度のオイラー角と比較、オーバーしているところを補正→制限後の回転行列
という手順になりそうですね。
オイラー角→回転行列は簡単にできますが、回転行列→オイラー角は回転行列をちゃんと理解していないと少し難しい上、一意に求めることができないためやや厄介です。
回転行列はX、Y、Z軸それぞれの回転行列をかけたもので、順番は任意のものです。
ここでは回転行列を作成する際X*Y*Zの順番で軸回転しているものとして話を進めます。
X軸の回転行列は
|1, 0 , 0|
|0,cos(xθ) ,-sin(xθ)|
|0,sin(xθ) ,cos(xθ) |
Y軸の回転行列は
|cos(yθ) ,0,sin(yθ)|
| 0 ,1, 0|
|-sin(yθ),0,cos(yθ)|
Z軸の回転行列は
|cos(zθ),-sin(zθ),0|
|sin(zθ),cos(zθ) ,0|
| 0 , 0 ,1|
で表されます。
これらをかけると、成分の値がそのまま残るのはY回転のsin、-sinのみです。
sinの値から角度を求めるにはarcsinを用いるしかありませんが、arcsinでは求められる角度が-90から90までなので、精度が必要な際はπからarcsinを引いたもう1パターンの角度を算出し比較する必要があります。
で、仮とはいえY軸のθが求められたので次はY軸のcosθを求められます。
合成行列の_23をY軸のcosで割ったもの、_33をY軸のcosで割ったものをatan2に渡してX軸の角度を求めます。
同じように_12と_11をY軸のcosで割り、atan2からZ軸の角度を求めれば、回転行列から求めたオイラー角の完成です。
角度制限は最小角度の各成分とmaxをとって代入、最大角度の各成分とminをとって代入でOKですね。
角度制限も適用するとこうなります…
コマ送りはこちら
序盤は正確に変形できていますが、31フレーム目で狂っていますね。
IKの目標地点を可視化するとこんな感じ。キューブが目標地点です。
そう、到達はしているが経路がおかしい、という状態なのです。
CCD-IKのアルゴリズムはどんな時でも「今いる地点からの目標地点への最短経路」を求めます。
よってこの30フレームと31フレームの間のように急激な目標地点の変化が起きた場合、自然でない曲がり方をしてしまうのです。
そして一度狂うとその後の変形にも影響を及ぼしてしまい、キレイにIKが実現できないのです。
目標地点に1フレーム単位での急激な変化が起きない、走行モーション等ならこの通り正常に動作します。
こればっかりはちょっと今すぐできる解決方法が浮かばないので(1ループごとにモデルが重なっていないかを判定するなどしか思い浮かばないのです)、今のところこのままにします…
また今回はCCD-IKというIKの中でも手軽さに対する精度、計算コストの効果が優れているものを採用しましたが、
こちらの書籍で解説されているFABRIKという手法がメチャクチャ便利そうなので、近い将来こちらに乗り換えようと思います…
次回はシェーダパーティクルかトゥーンレンダリングに手を出したいと思います。
最後まで読んで頂きありがとうございました。それでは。
この記事が気に入ったらサポートをしてみませんか?