Help us understand the problem. What is going on with this article?

Unity Humanoid制御でVRアバターを作りたい

この記事は Oculus Rift Advent Calendar 2018 の16日目の記事になります。

UnityでHumanoid関連スクリプトを使ってモデル制御を行う解説をします。MikuMikuAkushuの事例から発展させ、VRアバター実装へのヒントになるような内容になります。

(筆者は現在MikuMikuAkushuのUnityプロジェクトを管理しており、最新の環境でも動作するよう定期的にメンテナンスを行っています。この記事の内容もGOROmanさんの許可を得て公開しています)

※Humanoid IKを使用するにはAnimationControllerの設定などスクリプト以外の手順も必要になりますが、今回の記事ではスクリプト制御以外については解説しません。
※Unity2017.4.15f1にて動作確認

MikuMikuAkushuにおけるHumanoid制御の事例

MikuMikuAkushuとは2013年にGOROmanさんが制作された初音ミクさんとVR空間内で握手ができるVRコンテンツで、物理フィードバックデバイス Novint Falcon を使用し、リアル空間の手オブジェクトとVR空間のミクさんの手をリンクして、HMD内のミクさんと握手をしているかのような体験ができるコンテンツです。

MikuMikuAkushuの仕組み

仕様としてはもうこのツイートでほぼ網羅されており、ここから完成ツイートまで約18時間。流石にここまでの実装速度はなかなか出せないにしても、Unityの基本機能であるHumanoidを活用すれば、同等の動きが実現できるということは理解して頂けるかと思います。MikuMikuAkushuではモーションキャプチャに相当する機材はNovint FalconとOculusRift DK1の2点トラッキングですが、機能的にはVRアバターはMikuMikuAkushuの延長線上にあると言っても決して言い過ぎではありません。

以下にMikuMikuAkushu使用されたHumanoid IK関連の機能解説を行います。Humanoid IKの関数(変数)は少し特殊でMonoBehaviour.OnAnimatorIK()コールバック内でしか機能しません。Update()内で呼び出しても無効化されますので注意してください。

void OnAnimatorIK(int layerIndex)
MonoBehaviourが持つコールバック。IK処理の直前に呼び出される。

MikuMikuAkushuに使用されたHumanoid IK関数解説

Humanoid IKをスクリプトから使用するには、「IKを有効にする」⇒「IKゴールの位置/回転を指定する」という手順を実行します。シーン上にIKゴール用GameObjectを配置して、スクリプトから位置/回転をAnimatorの関数に渡せばOKです。

MikuMikuAkushuではインスペクタからIKゴールのGameObject指定、IKを有効にする係数(Weight)を指定できるようにしてあります。また、手の動きに連動して腰の座標が少し遅れて追従する仕組みを実装し、握手すると身体も揺れる動きをプロシージャルに生成しています。
181201_MMA_IK.png

HumanoidモデルのIKゴールを有効にする

void SetIKPositionWeight(AvatarIKGoal goal, float value)
void SetIKRotationWeight(AvatarIKGoal goal, float value)
Humanoidモデルの各IKゴール位置/回転のウェイトを設定する。valueの0~1はIK処理前~IKゴールに対応。IK主体の処理でも通常アニメーションを再生したいときは一時的に0にしたりします。

HumanoidモデルのIKゴールの位置/回転を指定する

void SetIKPosition(AvatarIKGoal goal, Vector3 goalPosition)
void SetIKRotation(AvatarIKGoal goal, Quaternion goalRotation))
Humanoidモデルの各IKゴール位置/回転を設定する。ウェイト設定と対になっている。

Humanoidモデルの視線制御を有効にする

void SetLookAtWeight(float weight, float bodyWeight, float headWeight, float eyesWeight, float clampWeight)
視線制御は目だけではなく、首、胴体も視線方向に向けることができます。胴体も少しだけ視線方向に向くように調整してあげると自然な動きになります。MikuMikuAkushuでは以下のようなパラメータで関数を使用しています。
引数 数値 説明
weight 1.0 視線追従全体の度合い(0~1)
bodyWeight 0.2 身体の追従の度合い(0~1)
headWeight 0.9 首の追従の度合い(0~1)
eyesWeight 0.7 目の追従の度合い(0~1)
clampWeight 0.4 追従を外す度合い(0~1)
0だと真後ろでも追従しようとする。怖い

Humanoidモデルの視線点座標を指定する

void SetLookAtPosition (Vector3 lookAtPosition)
Humanoidモデルの視線点座標を指定する。上記、SetLookAtWeight()関数とワンセットで使用します。

Humanoidモデルの腰の位置/回転を指定する

処理としてはIKそのものの処理ではありませんが、Animatorクラスの変数で代入すると内部的にはHumanoidのHipボーンに位置/回転が適応されます。アニメーションや独自スクリプトでHipボーンにパラメータを与えている場合は上書きに注意する必要があります。

Animator.bodyPosition
ボディの重心位置を示す。
Animator.bodyRotation
ボディの重心回転を示す。

MikuMikuAkushuからVRアバターへ

MikuMikuAkushuの制作当時はOculus TouchもHTC Viveも発売されておらず、プレイヤーの情報はHMDによる頭の角度とNovint Falconによる手の位置情報のみでした。しかしながら、バーチャルな存在が自分と同一空間を共有し、握手をしてもらえるという体験はプレイヤーに強烈な印象を与えることなりました。

同システムを利用し、ニコニコ超会議2014で展示した「超ボカロキャラ握手会」では体験待ちに長蛇の列ができ、そのあまりに過酷な稼働環境からシリコン製の手には亀裂が走り、最終的にNovint Falconも5台破損するという事態へ。バーチャルな存在をリアルに召喚することで、バーチャルキャラクターも負傷してしまう事実を垣間見ることに…。

そして現在はNoitom PerceptionNeuronやHTC ViveTrackerを用いることで、従来のモーションキャプチャスタジオ利用に比べ、より多くの関節姿勢情報が安価に取得できるようになり、個人規模でも体験者を頭だけでなくその全身をVR空間に送り出すことが可能になってきました。
(KINECTについてはCPU負荷や部屋の広さ的に日本住居での全身キャプチャの難しさなどから手軽とは言えず、ノイズも多いことから、当時、VRとの共存はあまり進みませんでした…)

そしてVRアバターの実装も制御的にはMikuMikuAkushuの延長線上にあります。UnityのHumanoid機能も日々拡張されており、MikuMikuAkushu制作当時はスクリプトから制御できなかった部分も制御できるようになっています。Unity2018で実装が予定されているAnimation C# Jobsで、IK処理自体のカスタマイズができるようになるようで、とても期待しています。

開発Tips

Humanoidの機能ではないのですが、VRアバターの開発にあたり、用意しておきたい機能を紹介しておきます。

デバッグ情報のGameView描画

HMDを被った状態だとUnityのSceneビューやInspectorビューなどが見えません。HMD内でも見えるように、GameView側に表示可能な線(箱や円、球)と文字表示を行うテキストウィンドウを実装しておくことをオススメします。特にコリジョンが関係するデバッグでは必須と言っていいでしょう。UnityEditorの持つGizmo表示ではSceneViewには表示されるのですが、GameViewに表示されないんですよね。

リプレイデータの保存/再生

ボタン入力とTrackingDeviceの姿勢情報のリプレイデータの保存/再生機能を作成することを強く推奨します。不具合の再現性も確保できますし、デバイスのバッテリー駆動時間制限も緩和されます。初期に作成しておけば、リプレイ機能実装に掛かったコストの回収に使える期間も伸びますので費用対効果的にも有利になります。また、モデルの揺れ物をセットアップする際のテストモーションとして利用するのも効果的です。

HMDを被った状態だとUnityのSceneビューやInspectorビューなどが見えませんので動作確認がホントにしづらいんですよね…。不具合報告を受け取る際にもリプレイデータを送ってもらえるとデバッグめちゃくちゃ助かります。

Humanoidとは何なのか?

モデルのインポート直後はGeneric型になっており、Humanoid型を使用するには明示的にRigを変更する必要があります。その際に内部的にボーンの扱いが変わり、ボーンはHumanoid型に定義された名前に変換されます。変換後にできるようになること/できなくなることを把握することでHumanoid型とはなんなのかの理解に繋がります。

骨格の違うモデルに同じモーションファイルが流し込める

モーションデータも、Generic型のボーン名⇒Humanoid定義ボーン名に変換されるため、Humanoid型に変換されたモーションは、モデルの骨格情報によらず再利用が可能になります。
181213_BoneMapping.png

Generic型のモーションが使えなくなる

Generic型のモーションはAnimationClip内でボーンに相当するGameObject名 + パラメータ名で各ボーンの位置や角度、スケールで記述されたデータで作成されています。Humanoid型に変換すると、GameObject名はHumanoid型に定義されたボーン名に書き換えられ、その情報もMuscle情報に変換されます。GameObject名によるマッチングができなくなるため、Humanoid型のモデルにはHumanoid型のAnimationClipしか動作しなくなりす(もちろん、Generic型のモデルにもHumanoid型のAnimationClipは名前マッチングに失敗するため動作しません)。その代わり、どのモデルで変換されたモーションでもHumanoid型であれば、適応可能なモーションとして利用可能な状態になります。(厳密に言うと多少のズレは発生しており、足の接地が甘くなったり、両手武器などの握り位置がズレたりします。その辺りがデメリットとなり、メリットと比較して使用を検討することになります)
181213_AnimationClip.png

また、Humanoid変換されたモーションはMuscle表現により少ない情報でモーションが扱えるようになります。Muscle表現はHumanoidのConfigure画面の[Muscles & Settings]設定から確認できます。Humanoid型のモデルでは、ボーンは3軸の回転情報ではなく、関節を動かす筋肉の値(Muscle表現)として記述され、自由度の少ない(例えば指)関節などは1軸の0~1の数値1個で表現されるようになります。Muscleは95個定義されており、Generic型に比べて少ない情報で身体の姿勢を表現するとができるようになっています。

IK機能が使用できるようになる

所謂フルボディIKと呼ばれる機能はHumanoidにはありません。しかしながら、VRでフルトラと表現されれる10点トラッキングに関して必要なIK機能は現在のHumanoid IKでも揃っているように思います。手足のIKゴール(4点)、肘膝はIKヒント(4点)、腰はbodyPosition/Rotation(1点)、頭はLookAt(1点)にて制御可能です。より表現力を高めたい時はSetBoneLocalRotation関数を使用してHumanoid IK処理と混ざる形で独自にボーン制御を追加することができます。

TrackingDeviceノイズ対策

各種センサーでトラッキングされるデバイスは、ミリ以下の単位だったりもしますがノイズが発生し振動しています。Humanoid IKに与えるゴール座標として利用する際には、あくまで人の動きであるということを前提に、位置/回転情報の丸め処理を行うことで(掴んでいるオブジェクトを含め)アバターの動きが自然に見えるようになります。

特にハンドデバイスの回転ノイズは長い棒状のものを持った時に先端が大きく振動し、非常に目立ちます。また細かく振動すると持っている物の重さが軽く見えてしまいますので、VRアバターにおいてTrackingDeviceのノイズ対策は必須と言っていいでしょう。

デメリットとしては、ノイズ対策の効果の強さに応じてIKゴールがワンテンポ遅れて付いてくるような動作になります。ダンスや楽器演奏などをキャプチャする場合は、パラメータの調整を行うことも必要になるかと思います。

BuildHumanAvatarによるプレイヤー骨格モデルの動的生成

アバターモデルと同じ体格であるなら特に問題ありませんが、プレイヤー本人と体格が一致しないアバターモデルを使用したい場合、何かしらの方法で骨格補正(リターゲット)が必要になります。プレイヤーの骨格に合わせてアバターモデルやワールドをスケールする方法等もあるのですが、ここではHumanoidの機能を利用するために、元データとしてプレイヤー本人骨格のHumanoid型モデルを動的に生成することを想定します。

実行時に動的にHumanoid型の骨格(HumanAvatar)を生成するには以下の関数を使用します。

AvatarBuilder.BuildHumanAvatar
https://docs.unity3d.com/ja/2017.4/ScriptReference/AvatarBuilder.BuildHumanAvatar.html

サンプルとしてousttrueさんのUniHumanoidライブラリのBuildHumanAvatarを呼び出している部分が参考になるかと思います。
https://github.com/ousttrue/UniHumanoid/blob/master/Scripts/AvatarDescription.cs

ヒエラルキーにGameObjectの階層として、Humanoidボーンに必要な骨格として正しく親子関係を付けたGameObjectのルートオブジェクトを引数として与える必要があります、GameObjectとボーン対応の配列などの情報(HumanDescription)を合わせて関数を呼び出すことで、Avatarクラスが生成されます。ここでのHumanDescriptionのパラメータは[Muscles & Settings]画面の下の方にあの数値です。Avatarクラスはモデルインポート時に自動生成され、Animatorインスペクタに自動セットされるあの部分です。

HumanDescription
https://docs.unity3d.com/ja/2017.4/ScriptReference/HumanDescription.html
181213_HumanDescription.png

Avatar
https://docs.unity3d.com/ja/2017.4/ScriptReference/Avatar.html
181213_FBXtoAvatar.png

BuildHumanAvatarを呼び出す際の注意点として、与えるGameObjectはシーンのルートに配置しておく必要があり、何かのGameObjectの子供として配置してあるものは、エラーになってしまいます。パラメータに不備がある場合、しれっとConsoleにエラーメッセージが出ますのでうまく動かなかったらConsoleに注目してください(BuildHumanAvatarが実行時に発行するエラーメッセージ一覧があると、とても助かるのですが、どこかにありませんでしょうか?)

HumanPose Muscle情報によるポーズコピー

BuildAvatarによるプレイヤー骨格のHumanoidモデルが生成されることにより、PoseHandlerクラスによるHumanPose Muscle情報のコピーが行えるようになります。事前準備を除くと、2行でHumanoidの持つ骨格補正付きのポーズコピーが実装できてしまします。

Retargeter.cs
using UnityEngine;
using System.Collections;

public class Retargeter : MonoBehaviour
{
    public GameObject source;
    public Avatar destinationAvatar;

    private HumanPoseHandler sourceHandler;
    private HumanPoseHandler destinationHandler;

    private HumanPose humanPose;

    void Start()
    {
        humanPose = new HumanPose();
        sourceHandler = new HumanPoseHandler(source.GetComponent<Animator>().avatar, source.transform);
        destinationHandler = new HumanPoseHandler(destinationAvatar, transform);
    }

    void Update()
    {
        sourceHandler.GetHumanPose(ref humanPose);
        destinationHandler.SetHumanPose(ref humanPose);
    }
}

181213_HumanPoseCopy.png
流石にSDユニティちゃんくらい等身の低いモデルのモーションを転送するとポーズの再現性が悪くなってしまいますが、HumanPoseのMuscle情報コピーが機能していることは分かると思います。構造上、どうしても足の接地感やポーズの再現は甘くなってしまいますが、カメラから少し離れたアバターモデルなどにはこちらで充分かと思われます。

HumanPoseHandlerによる指ポーズ制御

HumanPoseHandlerを使用して、指のMuscle情報を書き換えることで指ポーズを制御することができます。Muscle情報書き換えを使用しないで同様のスクリプト処理を実装しようとすると、モデル制作の際の3Dツールによって操作するボーン軸が変わったり、右手/左手でボーン軸が反転・回転したりしますので、そこそこ面倒くさい処理になったりします。Muscle情報ではモデル上のボーン軸を意識することなく指を曲げることができますので、簡潔な処理で指のポーズを制御することができるようになります。
181213_MuscleHandPose.gif
このgif画像ではインスペクタのスライダーを操作するとMuscle情報が自動生成されるスクリプトを自作し、指のMuscle情報をHumanPoseHandlerで書き換えています。画像右下のfloat配列が実際に書き込まれるMuscle情報になります。

Humanoid IKによる骨格補正(リターゲット)付きポーズコピー

プレイヤー骨格とアバターモデル骨格(手足の長さ/身長など)を考慮したIKゴールの座標コピーを行うことで骨格補正(リターゲット)が実装でき、HumanPose Muscle情報のコピーに比べ、より細やかな制御ができるようになります。アバターモデル側の制御もIK処理になるため、地面と足の接地が安定したり、Trackerを付けたリアル空間のオブジェクト(ゲームPADや楽器など)を持つなど、アバターモデルの腕の伸びきり対策をしつつリアル空間との整合性を維持する処理など、ポーズコピーの独自の仕組みを実装することが可能になります。

SetBoneLocalRotation関数によるIK割込み処理

MikuMikuAkushu制作当時は無かったのですが、今は「IK処理の直前にHumanoidボーン角度を変更する」専用の関数が用意されています。この関数のおかげで、IK処理と共存できるよう各ボーンの回転のみが入った大量のFBXアニメーションを別途用意してブレンドするという面倒くさい手順を踏まなくて済むようになりました。
IKゴールによるポーズコピーとSetBoneLocalRotation関数を組み合わせて使用することで、Humanoid IKを使用した独自のアバター制御を実装することができます。

void SetBoneLocalRotation (HumanBodyBones humanBoneId, Quaternion rotation)

処理順番は、Update()関数⇒Animation再生⇒BoneLocalRotation()関数⇒IK処理⇒LateUpdate()関数となっており、Animation再生とIK処理の間にこの関数で指定した回転が適応されるようになります。Update()関数の処理だとAnimation再生に上書き無効化され、LateUpdate()で独自処理するとIK処理を上書きしてしまうため、独自処理とHumanoid IK処理を共存させるためにはこのSetBoneLocalRotation関数が必要となります。

肩上げアシスト

人間の動作として、腕を上げたとき(よほど意識をしない限り)肩もつられて上がります。しかしながら、標準的なIK処理では肩は上がらず腕のみが上がる動作になってしまいます。
SetBoneLocalRotation関数を使用して、プレイヤー骨格モデルの腕が上がったら、アバターモデルの肩を上げる処理を追加すると胴体から肩~腕へのシルエットが自然になものになります。
181213_ShoulderUp.gif
左のミクさんは肘位置による肩上げのアシストあり腕IK / 右のミクさんはアシスト無し腕IKのgif動画です。手首のIKゴールは全て同じ高さになっています。腕が水平になったあたりから肩の高さに差が出るようにアシスト量を調整しています。

最後に

今回はHumanoidの機能を中心に解説しましたが、実際のキャラクター制御には不自然な動きをしないようにHumanoid以外の部分でも細かい実装を積み重ねていく必要があります。面白い分野ではあるのですが、唯一の正解があるわけではありませんので試行錯誤を重ねていくしかありません。

最後にちょっとだけHumanoidの不満点をば。

  • IKで腕が伸び切ったときにIKヒント方向が無視されて肘が必ず外側に回転してしまう
  • 指のRoll回転のMuscle定義がなく手の表現力が足りない
  • Muscle定義名(HumanTrait.MuscleName)とAnimationClipプロパティ名とが指だけ一致してない(Left Thumb 1 Stretched ⇒ LeftHand.Thumb.1 Stretchedになってる)
  • 本当のところモデル制作時に親指の向きをどうすると綺麗に握れるのかよく分からない
  • 資料が少なく、公式マニュアルを読んでも単語の意味が分からないものも多い(´;ω;`)ウッ…



それでは皆さま、よきHumanoidライフを( ・ω・)ノシ

参考

ねとらぼ「何この未来! “初音ミクの握手会”がVRメガネと感触インタフェース装置で実現したぞ」
http://nlab.itmedia.co.jp/nl/articles/1309/27/news070.html

Unity公式ブログ「Mecanim Humanoid について」
(日本語)https://japan.unity3d.com/blog/?p=1690 (2020/02/21)リンク切れ。日本語記事見つからず…
(英語)https://blogs.unity3d.com/jp/2014/05/26/mecanim-humanoids/
親指の向きはこれで本当にいいのだろうか? と疑問は残るものの、Humanoid型モデルを制作するための数少ない情報。

MEBIUSTOSのブログ「スクリプトでアニメーションクリップファイルを作成する」
http://mebiustos.hatenablog.com/entry/2015/09/16/230000
Humanoid型AnimationClipをスクリプト作成する際のプロパティ名調査の参考にさせてもらいました。

VRM「VR向け3Dアバターファイルフォーマット」
https://dwango.github.io/vrm/

GitHub :ousttrue「UniHumanoid」
https://github.com/ousttrue/UniHumanoid
VRMに内包されているUniHumanoidライブラリ。BuildHumanAvatar関数の呼び出し方など、Humanoid関連の機能の使い方の参考に。


使用モデル>よ式初音ミク http://piapro.jp/t/QcRy
※この記事はピアプロ・キャラクター・ライセンスに基づいてクリプトン・フューチャー・メディア株式会社のキャラクター「初音ミク」を使用したものです。
SDユニティちゃん / © Unity Technologies Japan/UCL

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした