2009年7月12日日曜日

ボーン、ボーン、ボーン


いよいよ、ボーンアニメーションに挑戦です。
基本的な考え方は、ボーンに関連づけられた頂点をボーンの動き(主に回転)に追随させるということです。
しかし、各頂点に単純にボーンの行列を掛ければいいという話でもありません。各頂点の座標系(モデル座標)とボーンの座標系(ボーン座標)が異なっているため、各頂点の座標を一旦、ボーンの座標に変換した後、ボーンの行列を掛け、その後またモデル座標に戻してやる必要があります。モデル座標からボーン座標へ変換するには、モデル座標を一旦ワールド座標に変換し、それをボーン座標に変換する必要があります。結局全体的には次のような流れになります。
モデル座標 → ワールド座標変換 → ボーン座標変換 → 移動/回転 → ワールド座標変換 → モデル座標変換

なかなか大変ですね。
今までファイルに書き込んでいた頂点座標はモデル座標だったんですが、計算が増えてしまうので、モデルを作る時にモデルの初期位置を(0, 0)、回転もしない状態でつくるように制限を設けることにします。こうすれば、ファイルに書き込んだ座標がそのままワールド座標として考えることができます。すると次のように考えることができます。
モデル座標(=ワールド座標) → ボーン座標変換 → 移動/回転 → モデル座標変換


次にボーン座標についてですが、Blenderでボーンを作成するには、まずアーマチャというものを作成する必要があります。アーマチャはボーンを保持する入れ物のようなものです。アーマチャも位置や回転の情報をもっており、各ボーンはこのアーマチャ相対で位置や回転の情報をもっています。つまり、ボーン座標を求めるには、まずアーマチャ座標に変換した後、ボーン座標に変換してやる必要があります。う〜ん、分かりにくいですね。
モデル座標(=ワールド座標) → アーマチャ座標変換 → ボーン座標変換 → ボーン行列(移動/回転)適用 → アーマチャ座標変換 → モデル座標変換


さらに面倒くさいのは、ボーンは階層を構成するということです。図のモデルを腕に例えて左のボーンが上腕、それに繋がる右のボーンが前腕とします。上腕に関連づけられた頂点の座標変換は単純にボーン座標で上腕のボーンの変換行列を掛ければよいですが、前腕に関連づけられた頂点の場合は、まず前腕のボーンの変換行列を掛けたあと、さらに上腕のボーンの行列を掛けてやる必要があります。前腕は前腕だけの動きだけでなく、上腕の動きの影響もうけるからです。また、前腕の行列を掛けて上腕の行列を掛けるという順番も大事です。これが逆だとうまくいきません。行列同士の掛け算は可換ではないのです。階層がさらに深くなると最下層から上の層に向かって行列の掛け算を繰り返すことになります。例えば前腕の右に手のひらのボーンがあるとすると、手のひらのボーンに関連づけられた頂点の変換を求めるには、手のひらボーンの変換 × 前腕ボーンの変換 × 上腕ボーンの変換となります。
整理すると、前腕に関連付けられた頂点の座標を、ボーンの動きに追随させるには以下の計算が必要になります。
モデル座標(=ワールド座標) → アーマチャ座標変換 → 前腕の行列変換 × 上腕の行列変換 → モデル座標変換


モデル座標からアーマチャ座標への変換は、頂点座標にアーマチャの行列の逆行列を掛けてやれば求められます。同様にアーマチャ座標からボーン座標への変換はボーンの行列の逆行列を掛けてやります。変換が終わった座標は、ボーン行列を掛けてアーマチャ座標へ戻した後、アーマチャ座標を掛けてモデル座標へ戻してやります。

頂点座標をV、アーマチャの行列をAm、上腕の行列をUm、上腕の回転行列をUr、前腕の行列をFm、前腕の回転行列をFr とすると、前腕に関連づけられた頂点をボーン回転に追随させるための計算式は次のようになります。
V x Am(-1) x Fm(-1) x Fr x Fm x Um(-1) x Ur x Um x Am

※(-1)のついたものは逆行列です。

Pythonでアーマチャとボーンそれぞれの行列を得られるのでそれを書き出してやります。逆行列もBlenderが提供している、Mathutils.Matrixのinvert()で求められるんですが、今回はC++側で計算することにしました。なのでここでファイルに書き出すのは正行列だけです。アーマチャの情報は次のようなフォーマットで書き出します。
S アーマチャ名 アーマチャ行列

SはスケルトンのSです。アーマチャのAでもよかったんですが、ちょっとAは他で使いたいので...
ボーンのフォーマットは次のようになります。
B レベル ボーン名 ボーン行列

BはボーンのBです。レベルはボーンの階層を示す番号です。0はルートを示し、1,2と増えるごとに深い階層を示します。

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

if obj.parentType == Object.ParentTypes['ARMATURE']:
# armature
arm = arms[obj.getParent().name]
arm_ob = bpy.data.objects[arm.name]
m = arm_ob.mat # get matrix
out.write("S %s %f %f %f %f %f %f %f %f %f %f %f %f\n"
% (arm.name,
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2],
m[3][0], m[3][1], m[3][2]))
level = 0
for k in arm.bones.keys():
b = arm.bones[k]
if not b.hasParent():
# root bone
writeBone(out, mesh, b, level)
:

アーマチャオブジェクトの名前と行列を書き出しています。行列は4列目は不要なので4x3の形式で書き出しました。
次にボーンですが、ボーンはアーマチャにぶら下がる形になっているのでそのボーンのルートから始めて関数を再起的に呼び出してレベルをインクリメントしながら書き出しています。
writeBone()は次のような関数です。

def writeBone(out, mesh, bone, level):
m = bone.matrix['ARMATURESPACE']
out.write("B %d %s %f %f %f %f %f %f %f %f %f %f %f %f\n"
% (level, bone.name,
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2],
m[3][0], m[3][1], m[3][2]))
if bone.hasChildren():
for b in bone.children:
writeBone(out, mesh, b, level+1)

m = bone.matrix['ARMATURESPACE']で、アーマチャ座標相対でボーンの行列が取得できます。アーマチャと同じように4x3の行列で書き出しています。ボーンにぶら下がるボーンがあればさらにwriteBone()を再帰呼び出しします。

今日はひとまずここまで。一口にボーンアニメーションと言ってもやっぱり大変ですね。多分読んでも分かり難いと思います。実はある程度動くところまで出来てはいるのですが、説明が長くなりそうなので小出しにしていきたいと思います。

0 件のコメント: