はじめに
現在開発のアバターを使ったアプリでは、VRMモデルを下記の機能を併用して制御をしています。
- Animator(全身のポーズに使用)
- VRIK(腕の動きに使用)
-
HumanPoseHandler
(指の制御に使用)
その際、下記のようにモデルがプルプルする現象が発生してしまました。
結論としてはAnimatorUpdateMode
をNormal
にすることで解決したのですが、根本的な原因は分からずじまいでした。色々と調べたこともあるので、この時の状況についてまとめておきたいと思います。
各実行タイミングについて
下記ページの図を見ながら整理しました。
必要なライフサイクルを抜粋すると、下記の流れを繰り返します。
- Physics
FixedUpdate()
- Animation実行(
AnimatorUpdateMode.AnimatePhysics
の場合)
- Game logic
Update()
- Animation実行(
AnimatorUpdateMode.Normal
の場合) LateUpdate()
- Scene rendering(実際に画面に描画される)
PhysicsはGame logicと別のタイミングで実行されていて、どちらかが連続で呼ばれることもあります。
Animator
AnimatorUpdateMode
の値によって実行タイミングが変わります。
-
AnimatorUpdateMode.Normal
:Update()
とLateUpdated()
の間で実行される -
AnimatorUpdateMode.AnimatePhysics
:FixedUpdate()
の直後に実行される
Animatorを使うと基本的にすべての値が上書きされてしまうようで、スクリプトを併用する場合はAnimatorの実行後にタイミング(LateUpdate()
など)で実行する必要があります。
AvatarMaskを使った方法についても検討しましたが、Maskした部分以外の値も上書きされてしまうようです。
なお、VRMのHumanoidはUnityのHumanoidAvatarを元にしていますが、Avatarオブジェクトは持っていないため、対応が失敗している可能性はあるかもしれません。
VRIK
VRIKはLateUpdate()
内から呼び出されるUpdateSolver()
内で、ボーンのtransformの値を直接書き換えることで姿勢の制御をしています。書き換え前の値は内部でキャッシュしておらず、同じフレーム内で取得しているようにみえました。
注目したいこととして、UpdateSolver()
はLateUpdate()
内で呼ばれる時と呼ばれないときがありました。
void FixedUpdate() {
updateFrame = true;
}
void LateUpdate() {
// Check if either animatePhysics is false or FixedUpdate has been called
if (!animatePhysics) updateFrame = true;
if (!updateFrame) return;
updateFrame = false;
UpdateSolver();
}
内容としてはこのように制御しているようです。
-
AnimatorUpdateMode.Normal
の場合、毎回のLateUpdate()
で実行 -
AnimatorUpdateMode.AnimatePhysics
の場合、FixedUpdate()
が呼ばれた後のLateUpdate()
のみで実行
HumanPoseHandler
下記のコードを一部改変して使用しています。Animationとの兼ね合いで、Update()
ではなくLateUpdate()
にて実行しています。
このコードでも、書き換え前の値は内部でキャッシュしておらず、同じフレーム内で取得しています。
var handler = new HumanPoseHandler(_HumanoidAnim.avatar, _HumanoidAnim.transform);
handler.GetHumanPose(ref targetHumanPose);
// 姿勢の変更
handler.SetHumanPose(ref targetHumanPose);
発生していたこと
AnimatorUpdateMode.AnimatePhysics
の時にVRIK内のUpdateSolver()
が呼ばれないタイミングがあり、HumanPoseHandler.SetHumanPose()
が連続で呼ばれる状況でプルプルが発生していました。
AnimatorUpdateMode.Normal
ではそれぞれが毎フレーム呼ばれるようになり、プルプルしなくなりました。同じLateUpdate()
内での実行なので呼び出し順は不定なのですが、Script Execution Orderで指定してみても特に挙動は変わりませんでした。
分からなかったこと
- プルプルする原因が分からない
- VRIKもHumanPoseHandlerもそれぞれ同じフレーム内で直前の値を取得しているはずなのに、呼び出し頻度によって挙動が変わる理由が分からない
最後に
何か原因が思い当たることがある方は是非コメントなどで教えてもらえると嬉しいです!
Twitter: @nkjzm
2023/05/22 追記
SetHumanPoseをvrik.GetIKSolver().OnPostUpdateでやった方が良さそうですがなんでプルプルするんでしょうね。
— あきら☎︎@VMC (@sh_akira) May 22, 2023
便利デリゲートを教えていただきました!実行順を制御できて呼び出し頻度も連動させられるので、こちらの方がよさそうですね。ありがとうございます!
関連