spine morph-target
本家より良い返事が貰えたので、経緯を残しておきます。そのうち使えるようになるそうです。ありがたし!
ver3.6のspineを弄っているとメッシュデフォームがあるものの、口パクや表情でよく使うモーフターゲット(morph-target)がないので、固定的なものは力業でなんとかなるとしても、動的な表情変化などの実現が厳しいなぁと気づくと思います。
なんとかしたいですよねナナチ。
幸いランタイムのソースコードがフルオープンなので中を覗けますし、(ライセンスの範囲内で)勝手に弄ってもOKなので、なんとかできないかなーと休日眺めてみました。
サンプル
- spine-c morph-target customize sample
まず過去ログ
フォーラムの過去ログ(英語)を眺めてみたら、過去に2度ほど話題には上がったみたいです。
少なからず同じような悩みはあったみたいですね。
まずモーフターゲットが実装できるか
モーフターゲットを実現させるにはいくつかの条件が必要です。
- 変形前のベースとなる形状が必要
名前の通りベースとなる”ターゲット”が必要になります。これは変形前の形状で、変形後との差分の計算に使用されます。
差分はそのままベースに加算されていき、1つのオブジェクトに変形が複数ミックスされていきます。
diff? = Animation? - base out = base + diffA + diffB + diffC + diffD ...
みたいな感じです。
幸いspineにはベースが存在します。エディタではSETUPとして部品を組み立てるモードがありますね。あれです。あれを使いましょう。
プログラム上だとちょっとわかりにくいですがskeletonから追跡できます。Animation.cを見れば手っ取り早いかなと思います。
- 変形後の形状もベースと同じ構造をしていること
これはSETUPから変形させて作られたアニメーションのことを指します。
モーフターゲットの場合はベースと構造が同じでなくてはいけません。メッシュなら頂点数とかポリゴンの張り方とか。
幸いspineも同じガイドラインを敷いているので問題無さそうです。
- それら複数の形状を指定できる環境
複数合成するのでその土壌がなければいけません。幸いアニメーションはトラックという概念で複数扱えるので問題ありません。
- ということで
エディタ上では難しいけど、ランタイムのアニメーショントラックに仕込めば実現できそうです。
エディタだとプレビューのあそこですね。タイムラインではありません。
実装
spine-c ver3.6を使用します。実装の大半はAnimation.cになります。
「void _spDeformTimeline_apply」あたりを見てみると分かりやすいと思います。
前半と中盤は定義キーが無い前と後ろの処理、要するに例外処理なので飛ばします。
一番最後あたりにアニメーションの合成にあたるコードがあります。
for (i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; float v = prev + (nextVertices[i] - prev) * percent; vertices[i] += (v - vertices[i]) * alpha; }
prevVerticesが現在位置より後方の変形情報、nextVerticesが現在位置より前方の変形情報です。
percentがキー間のブレンド率なのでvが現在地点での変形座標。alphaがモーションのブレンド率なのでアニメーション間の合成だけ抜き出せば
out = out + (v - out) * alpha
と単純な構造です。ここをモーフターゲットに改良するだけです。
モーフターゲットはベースとなる変形前の座標が必要になります。
spineでいえばSETUPですが、このコードの上あたりを見ると―
case SP_MIX_POSE_SETUP: if (!vertexAttachment->bones) { memcpy(vertices, vertexAttachment->vertices, vertexCount * sizeof(float)); } else { for (i = 0; i < vertexCount; i++) vertices[i] = 0; } ...
まんまがあります。
「vertexAttachment->vertices」がSETUP、変形前のベース情報になります。
SP_MIX_POSE_SETUPは最初に投げられるもので、変形前の状態を変形バッファにコピーしてる感じです。
これで必要な要素は揃いました。
あとは以下の公式に変形すればいいだけです。
out = out + (A - SETUP) * alpha
なので
float* setupVertices = vertexAttachment->vertices; for (i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; float v = prev + (nextVertices[i] - prev) * percent; vertices[i] += (v - setupVertices[i]) * alpha; }
となります。おわり。
本当はもうちょっと複雑
アルゴリズムの話は以上でおしまいですが、実際はもうちょっと複雑です。計算が複雑というわけではなく、例外処理、最適化処理との相性、言語的な部分などなど。
今までアニメーションは上書きだったので、alpha1.0だとそれより前のアニメーション処理要らないですね!なども組み込まれているので対処が必要です。
実際に組み込むには20カ所ほど変更や修正が必要です。
実装されるまで待ちましょう :Q