2009年7月13日月曜日

キーフレーム


前回出力したボーンの情報では、アーマチャ座標からボーン座標へ変換する行列は書き出していましたが、アニメーションのための回転行列は書き出していませんでした。



V x Am(-1) x Fm(-1) x Fr x Fm x Um(-1) x Ur x Um x Am
のFrやUrの部分です。

この回転行列(場合によっては移動もあり)こそが実際のアニメーションの部分になるんですが、ここは時間の経過に伴い変化する部分でもありますので、ボーンの情報とは別に出力します。
Blenderのデフォルトでは、25fpsでアニメーションが作れます。これは変えられるんですが今回はこれをこのまま使います。つまり1秒間に25回描画することになります。
4秒のアニメーションだと100回の描画になります。といっても各フレーム全てでモデルにポーズをつけてやる必要はなく、動きの節目となるようなフレームだけポーズを作成して、そのフレーム間はコンピーュータに補間させてやります。これはキーフレームアニメーションと呼ばれています。
例えば振り子の場合、それぞれ最も左右に振れた位置の状態だけをキーフレームとして作成してやればその他のフレームでの振り子の位置は自動的に計算されます。この計算の方法も差を均等に割るだけの線形補間や、曲線を使った補間等あるのですが、今回は線形補間だけにしておきます(振り子で線形補間だと動き的におかしいですが...)。0℃の回転位置だったボーンが10フレーム後に30℃回転したとすると、5フレーム目では15℃と計算されます。

Blenderではキーフレームはアクション単位で作成されます。アクションというのは「歩く」とか「ジャンプ」といった、一連の動作を表す単位です。アクションを切り替えることで、それぞれのアニメーションを実行することができます。
このアクションの情報は次のフォーマットで書き出します。
A アクション名

アクション名だけです。前回、アーマチャの行にAではなくSを使ったのは、このアクション行にAを使いたかったからです。

次にキーフレームですが、キーフレームの情報として書き出す必要があるのは、各キーのフレーム番号と対象となるボーンの名前と、その時の変換行列です。
ですが、ボーンの回転については、行列よりクォータニオンを使った方が便利なのです。何が便利なのかというのは、実例で学ぶゲーム3D数学という本に詳しく書かれているんですが、簡単に言うと、クォータニオンを使うとキーフレーム間の補間が行列よりも簡単で計算も早く、ジンバルロックという厄介な問題も避けられます。そしてクォータニオンの表現には4つの数値しか使いません(行列で3Dの回転を表現するには9つの数値が必要です)。キーフレームによる回転アニメーションに非常に適した表現方法なのです。そして、Blenderでもこのクォータニオンを提供してくれています。なので回転についてはクォータニオンの4つの数値を書き出すようにします。但しクォータニオンは回転だけを表現しますので、平行移動の部分については別途書き出してやります。結果的に次のようなフォーマットになります。
K フレーム番号 ボーン名1 平行移動1(x座標 y座標 z座標) クォータニオン1(w x y z) ボーン名2 平行移動2(x座標 y座標 z座標) クォータニオン(w x y z) ...

識別子はキーフレームのKです。キーフレームのフレーム毎にこの行を出力しますが、複数のボーンが対象になるので、そのボーンの数だけ同じ行に変換情報(平行移動とクォータニオン)を出力します。
各キーフレームでのボーンの座標(平行移動とクォータニオン)は、ポーズボーン(ボーンとは別のオブジェクトです)というオブジェクトで提供されているのでこれを使います。

Pythonのコードは次のようになります。

actions = Armature.NLA.GetActions() # list of actions
:
for a in actions:
act = actions[a]
out.write("A %s\n" % a)
frames = act.getFrameNumbers()
for f in frames: # repeat each key frame
writeKeyframe(out, f, arm_ob.getPose())

アクションのリストを取得して、各アクションについて処理しています。名前を出力した後、そのアクションのキーフレームをwriteKeyframe()で出力しています。
writeKeyframe()は次のようになります。

def writeKeyframe(out, frame, pose):
out.write("K %d" % frame)
Blender.Set('curframe', frame)
pbones = pose.bones.values()
for pb in pbones:
loc = pb.loc # location of this pose bone
quat = pb.quat # quaternion of this pose bone
out.write(" %s %f %f %f %f %f %f %f" % (pb.name, loc.x, loc.y, loc.z, quat.w, quat.x, quat.y, quat.z))
out.write("\n")

渡されたフレーム番号をBlenderの現在のフレーム番号に設定して、ポーズボーンを取得しています。ポーズボーンから平行移動の座標とクォータニオンが取得できるのでそれを出力しています。
わざわざ現在のフレーム番号を設定しなくてもそのフレームのポーズを取得できる方法があるのかもしれませんが、ちょっと分かりませんでした。なので、処理の頭で、

curFrame = Blender.Get('curframe')
と、念のため、処理前のフレーム番号を保存しておいて、処理の終了時に、

Blender.Set('curframe', curFrame)
と、戻すようにしてます。

なんだか小難しい話が続きますが、ボーンアニメーションを扱おうとするとしようがないです。自分の備忘録としても書いてますので...
もしかしたらいろいろ間違ったことを書いてるかもしれませんがその場合はご容赦ください。ついでに指摘して頂けると有り難いです。

続きはまた。

0 件のコメント: