KineFX Part2：MotionClipアニメーション入門

この記事はHoudini Apprenticeアドベントカレンダー2020　25日目の記事です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/ef8a0b1e-41c0-04f6-4a41-1b2a46d217b6.png)
Part1である「[Houdiniの新しいリギングとアニメーション：KineFX機能まとめ](https://qiita.com/jyouryuusui/items/cc4c7c529fed71f03df3)」を補足する形で書ききれなかった内容を追記します。

#MotionClipとは
KineFX機能群の中で、「MotionClip」という名前から始まるノードが10種類追加されました。
![motionclip001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/02b6bbf4-75b8-dc5e-f5fb-fcd4188dbeac.png)
これらのMotionClipファミリーは、いったい何のために追加されたのでしょうか。


KineFXを紹介する動画「[Luiz Kruel による KineFX の講演](https://vimeo.com/465614203)」において、**ステートマシンにインスパイアされた**とある通り、KineFXの思想の根本には細かいアニメーションクリップを組み合わせて最終的に欲しい結果を得るようなワークフローとなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/9fa80599-3791-5a22-5432-7f1956d1ec67.png)

ステートマシンの概念は、Unityなどゲームエンジンでのアニメーションに利用されています。
その理由は、外的な要因（ゲームコントローラーの入力）によって最終的なアニメーションの結果を動的に得たいためです。

ゲームにおける、ステートマシンの概念を理解するために以下の本がお勧めです。
アニメーションに関する技術が網羅的に非常にまとまった形で掲載されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/8c3a8bb9-659b-ebc4-b844-a6a6e729deca.png)
[キャラクタアニメーションの数理とシステム - 3次元ゲームにおける身体運動生成と人工知能](https://www.amazon.co.jp/%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%95%B0%E7%90%86%E3%81%A8%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0-3%E6%AC%A1%E5%85%83%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E8%BA%AB%E4%BD%93%E9%81%8B%E5%8B%95%E7%94%9F%E6%88%90%E3%81%A8%E4%BA%BA%E5%B7%A5%E7%9F%A5%E8%83%BD-%E5%90%91%E4%BA%95-%E6%99%BA%E5%BD%A6/dp/4339029092)

本書の第4章　アニメーションシステム　の中で
**”アニメーションシステムは、大きく分けてステートマシンとブレンドツリーという二つのモジュールから構成される。まずステートマシンは、キャラクタの行動状態を表すデータの管理およびその時間変化を処理するモジュールである。例えば歩行ステートや停止ステート、攻撃ステートなどのように意味論として異なるアニメーションを分類して管理する。また、ブレンドツリーは、同一ステートに属するアニメーションのバリエーションを生成するためのデータ構造・処理機構である。”**
とあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/4dfbd5b0-5385-05ac-7beb-ec27a7d93ffb.png)

KineFXはこの考えを元に設計されています。[Houdini18.5 KeyNote](https://youtu.be/zMrvCWy85xM?t=1058)において、例として出されたシーンを見ると、ゲームエンジンのステートマシンとは少し違うけれども似通った機能である事がわかるかと思います。（※群衆のAgentのトリガーを利用すれば、状態の変化ができるかもしれませんが未検証）
![keynotede.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/b0eca557-bef6-f2d5-0e85-81b6ec2e3a23.png)
※歩行開始/歩行/歩行停止　のクリップを組み合わせてアニメーションを生成している

そして、その中の機能
・クリップ終端で巻き戻して再生を行う**ループアニメーション**
・複数のアニメーションから新しいポーズを生成する**ポーズブレンド**
・２つのアニメーションクリップを連結するための**トランジション**
などを実現するために**「MotionClip」**機能があります。

##MotionClipの利点
MotionClipを利用する利点は、以下の2点に集約されます。
・全てのアニメーションフレームが１つに結合されているためSOPで編集できる。そのため、**従来のChopを利用しなくてもアニメーションの編集ができる**。
・アニメーションが**ジオメトリキャッシュされる**。


##サンプルファイル
サンプルファイルとして、ヘルプ内にあるMotionClipExampleを使用します。
バージョンによってパスは異なりますが、デフォルトのままでインストールしていれば以下の場所にExampleのhdaが存在しています。

```
C:\Program Files\Side Effects Software\Houdini 18.5.390\houdini\help\examples\nodes\sop
```
このディレクトリ下にkinefx用のExampleからMotionClipと名前の付いたファイルを見ていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/3f8fb841-7f79-7312-a958-d575028fa57b.png)


#MotionClip(入力＆変換)
このノードはスケルトンアニメーションをMotionClipに変換します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/3138d964-2428-12f5-e756-12f69dd2f0e1.png)
入力はスケルトンアニメーションです。（例：FBXAnimationImportやSceneCharacterImportの第3アウトプットなど）
ノードへの入力数は、１つだけでなく複数のインプットも可能です。※複数入力時はPackInputにチェックを入れる必要があります。各入力のクリップが独自にパックされたプリミティブに配置されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/a4117e07-2fd3-7b79-9425-4ee3ea1a0757.png)
指定されたフレーム範囲とサンプルレートのデフォルトは以下の設定なので、必要に応じてフレーム数などを入力し、クリップ範囲を調整します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/761f5dbc-9a58-b844-7a49-4335ce1bf493.png)
各種設定は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/f93e189a-c6ab-2ab3-f5c7-f6d039f651bc.png)

```json:clipinfoの内容
{
	"left_end_behavior": {
		"type": "string",
		"value": "clamp"
	},
	"name": {
		"type": "string",
		"value": ""
	},
	"range": {
		"type": "vector2",
		"value": [
			0,
			4.291666666666667
		]
	},
	"rate": {
		"type": "float",
		"value": 24
	},
	"right_end_behavior": {
		"type": "string",
		"value": "clamp"
	},
	"source_range": {
		"type": "vector2",
		"value": [
			0,
			4.291666666666667
		]
	},
	"source_rate": {
		"type": "float",
		"value": 24
	}
}
```

出力はPackされたプリミティブのリストで構成されます。
最初のプリミティブ（primitive番号:0）は、Rest Frameで評価されたスケルトントポロジーを含みます。残りのプリミティブ（primitive番号:1～）はそれぞれキャッシュされたフレームを表し、各ジョイントの位置を表すポイントのリストを含みます。
よって、primitiveの数は、RestFrame(1)+Frame Range(1～)の数になります。また、point数は、1フレームごとにpackされているので同数になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/8becbacb-ab0b-a568-8387-84dd36fade1b.png)

#MotionClip Evaluate(出力＆評価)
ただし、MotionClipのままのデータでは使い物にならないので、最終的に、MotionClipEvaluateに入力して、単一のフレームで評価します。つまりMotionClipでの一連の処理としてはMotionClipで始まり、MotionClipEvaluateで終わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/d36fe364-db79-d47d-a516-87be83c57aca.png)
出力ジオメトリには、指定されたフレームで評価されたMotionClipのポーズで出力されたスケルトンとなります。そのフレームにジョイントが存在しない場合は、周囲のフレームからデータを補間します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/1c965fc1-bc5c-d257-59d9-5a008a9ee576.png)



#MotionClip Blend（混合）
1つのモーションクリップのアニメーションを別のモーションクリップの上にブレンドします。
ExampleのSimpleMotionClipBlendのシーンを見ながら動作を確認してきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/7ec90517-8abc-78e0-ddaa-f0fc542a5c6e.png)

このノードは、Base(input1)とLayer(input2)の2つのMotionClipsを取得し、Baseのアニメーション上にLayerのアニメーションをブレンドします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/7916a362-b171-963e-71b4-1fb729d50a46.png)
デフォルトでMotionClip Blendノードを呼び出した場合、レイヤーの重みである**Effect**が1であるため、Layer(input2)のアニメーションと同一のアニメーションとなります。
このEffectを0にするとBase(input1)のアニメーションに。0.5にするとちょうど中間を割ったようなアニメーションとなります。また、Effectで指定された最大の重みまでフェードイン（もしくはフェードアウト）させることも可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/6fec063d-282e-04e0-2c79-1d6d115ae8fb.png)


出力クリップの長さは、ベースクリップとその上にレイヤー化されているセグメントの和集合になります。
※和集合：Base = 1～15フレーム 、Layer =  10～30フレームとすると、base ∪ layer = 1～30フレームになります。

その他の機能として、ブレンドの細かい調整が可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/ea5348b6-011a-cf87-0dd1-572566ca5104.png)


#MotionClip Cycle（循環）
モーションクリップの一部をスムーズにループします。
ループといえば、最初のmotionclipノードのRightEndBehaviour：Loopや、最後のMotionClipEvaluateのEndBehaviour：Loopなどでもループ機能があるため、不要ではないかと思う人もいるかもしれません。
しかしこのノードは、単純なループだけに留まらない機能を持っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/f6da798a-4504-0fed-7cea-8a2330e60d08.png)

Esampleのファイルでは、MotionClipの終端で走り終わった位置から、さらに加えて存在しなかった延長となるアニメーションループを作り出すことができます。
![roundrun.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/d569444c-825d-d497-537a-a2a1ac84f08e.gif)

今回のケースは、走り終わった地点からまた走りだしてほしいので、それに応じた設定を行います。まずLocomotionタブ内でComputeLocomotionを指定し、固定するjointを設定。反復終了後の開始地点をLocomotion Jointに設定して、合わせて向きも一致させます。
上手く開始フレームと終了フレームで同じ動作をしていれば、特に設定は必要ないですが、異なる動作をしている場合はBlendでオーバーラップさせる期間を設ける必要があります。ここは長すぎても短すぎても変になってしまう可能性があるため、上手くMotionClipの長さやBlendFrameの数値を調整する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/3bd31788-b693-0a65-2f0a-cd2ce5e6d3c9.png)

#MotionClip Extract（抽出）
モーションクリップから一連のフレーム上のジョイントのモーションを抽出します。要求されたフレームがクリップに保存されていない場合、隣接するキャッシュされたフレーム間を補間します。
Exampleでは1～55フレームのアニメーションの前後の足切り(範囲10～40フレーム)を行っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/f35e8cb3-750b-df62-c85c-daba2c82670a.png)
このようにスケルトンを出力すること以外に、MotionTrailをOutputとして出力することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/be4a536d-ad02-0d7d-bd7e-f18e4dc05cfc.png)
後程解説しますが、このように部分的に抽出したデータを一部編集してMotionClip Updateを行うことによってMotionClipを新しいアニメーションとして作り替えることが可能です。

#MotionClip Pose Delete（削除）
指定したサンプルをモーションクリップから削除します。
評価中に、欠落しているフレームは周囲のフレームから補間されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/b219ba91-f4e4-db01-0a96-3dada7fe25b5.png)

#MotionClip Pose Insert（挿入）
指定したポーズを含む MotionClip に新しいサンプルを追加します。
ポーズには、クリップスケルトンのジョイントサブセットが含まれている必要があります。
Exampleでは、まずMotionClip Pose Deleteで12-18フレームを削除し、分岐したもう一つはtimeshiftでその中間の18フレーム目を取得。その18フレーム目をrigposeで手を挙げるポーズに変更し、再度MotionClip Pose Insertで挿入するという手順を踏んでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/7ef67645-2631-efce-7ed5-36673f150bf4.png)
この手法の注意点として、ある程度補完の効く範囲でInsertを行わないとアニメーションとして歪な動きになってしまう可能性があります。切り替わりのタイミングを上手くブレンドできるように下準備が必要になるかと思います。

#MotionClip Retime（速度調整）
モーションクリップの速度を調整（トリミング/スケーリング/変換）します。
また、MotionClipの左端と右端の動作を調整します。例えばSpeedを0.5とすると動きが半分の速度のスロー再生になります。Speedを2にすると倍速での動きとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/ee453f5f-9b75-fd27-dbb7-97f42c70f670.png)
途中のフレームからスローモーションを作る場合、Speedのパラメータをアニメーションさせてしまうと、次のフレームが前のフレームよりも前のアニメーションとして再生されてしまうため、それぞれのspeedのアニメーションを２つ作りMotionClipBlendする方が安全です。（スローspeedの方は開始地点を前倒しする）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/40f7f83c-31ac-7c88-842c-053af5275c5a.png)
![slowmo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/2f8c5535-cda0-ed98-deba-ad14a27927d3.gif)

#MotionClip Sequence（連結）
2つのモーションクリップを連結します。2番目のMotionClip入力が最初の入力に追加され、ブレンドしてスムーズなトランジションが作成されます。モーションの連結時の設定はMotionClip Cycleと同様です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/c007d005-a2ef-af1e-0a65-8baf25e2c8df.png)

#MotionClip Update（更新）
指定されたポイントリストを使用してモーションクリップを更新します。
time Attributeを持つポイントのリストを取得し、MotionClip内の一致するポイントを挿入/削除/更新します。通常はMotionClip Extractで編集が必要な個所を抽出し、SOPノードで何らかの編集を行います。
この例ではTransformでY軸を0.05加えることで両肩の位置を少し上げる更新を行っています。
Transform以外にも、ExtractのOutputをMotion Trailsにすることによって、noiseを加えたり、逆にsmoothで滑らかな動きにしたり、editで直接回転の軌跡を編集することができます。これまではアニメーションはChopでの調整であったこともあり、この自由度は非常にありがたいです。
編集が完了後は、MotionClip Updateを行うことで、MotionClipに編集データを適用することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/ccb8fb65-4fb0-a399-41e4-37cb8ae54e15.png)
差分（緑に見えている箇所が編集によって変化した部分です）
![kataagari.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/3090b8bf-2b9f-5ac2-03bc-00b91fbb0832.gif)
Updateに入力するデータはFrames、Motion Trailsどちらでもかまいません。
また、Motion Trailsを選択した場合、colorノードでtime Attributeに応じて色を変更することで時間に応じた変化をMotionClip上で確認しやすくなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/9029dd85-311e-8c77-e009-45fe66100b71.png)


#応用
mixamoからデータを2つ取得してMotionClipでアニメーションをつけてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/9ca4459f-22c3-da23-0f6c-db875e7f2468.png)
今回使用するデータは、「Walking.fbx」と「Walking Turn 180.fbx」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/430bcca5-9345-f18b-a483-ac9bed51e9cc.png)
mixamoのLocomotion Jointはmixamorig:Hipsを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/c5d0b87f-821f-7785-d3c0-72aefffcc653.png)
歩きモーションのCycleと方向転換のシーケンスを結合できました！
![urouro.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151589/73e6b1a4-0939-91ed-5cd9-bf1d80383a25.gif)

#まとめ
KineFXの機能は多岐に渡り、この分野だけでも覚えるのは一苦労です。しかし、本当に大事なのは、この後の工程のアニメーションで、如何にカッコいい動きができるかでしょう。そういった意味では、僕はアニメーションの経験値が少ないこともあったので、最大限KineFXを活用できたかというと、そうではなかったかと思います。
今後KineFXを使用してリグを構築した次に効果的なアニメーションを補助・作成する機能がもっと増えればいいなと感じました。Houdini19以降のバージョンアップが楽しみですね。

ここまで見てくれた皆様、ありがとうございます！この記事が皆様のお役に立てばうれしいです。