なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

SteamVR + Index Controller で Finger Tracking をしたい

ということで、久々ですが SteamVR の記事です。
せっかく Index Controller があるので、指の動きを良い感じにしてみようと思います。

前提環境

  • Unity Personal 2019.2.16f1
  • Windows 10
  • Index Controller
  • SteamVR 関連が設定済みのシーン

使用するアセットは以下の通り

今回は使用する 3D モデルとして、こちらの 3D モデルを使います。

Index Controller での指トラッキングの指の曲がり具合などの値の取得には、
SteamVR Input で入力値の型が Skeleton のものを使用します。
SteamVR Input の初期設定 (default) の場合、下記が Skeleton に設定されています。

  • SkeletonLeftHand (左手)
  • SkeletonRightHand (右手)

これらを使うことで、各指の角度などが取得できます。
最大まで曲げた状態が 1f、曲げていない状態が 0f として入っています。

指を曲げるには HumanPose を使いました。

HumanPose は以下のようにして取得、設定します。

// IDisposable なので使い終わったら Dispose しよう
using (var handler = new HumanPoseHandler(Animator.avatar, Animator.transform))
{
    HumanPose humanPose = new HumanPose();
    handler.GetHumanPose(ref humanPose);

    // いじる

    handler.setHumanPose(ref humanPose);
}

HumanPoseMuscle の値は、 -1f ~ 1f なので、良い感じに変換してあげます。
変換自体は以下のコードで可能です。

Mathf.Lerp(min, max, value);

また、そのままの値を渡すと指があらぬ方向へ曲がってしまうので、 -1 をかけてあげます。
これは、 HumanPose では 1f が開いた状態なのに対して、
Skeleton 型では 0f が開いた状態に当たる為です。
そうして出来たのが以下のクラスです。

using SteamVR_Sandbox.Models;

using UnityEngine;

using Valve.VR;

namespace SteamVR_Sandbox.Scripts
{
    // ReSharper disable once InconsistentNaming
    public class SteamVRFingerInput : MonoBehaviour
    {
        public enum FingerCategory
        {
            RightIndex,

            RightLittle,

            RightMiddle,

            RightRing,

            RightThumb,

            LeftIndex,

            LeftLittle,

            LeftMiddle,

            LeftRing,

            LeftThumb
        }

        [SerializeField]
        private Animator Animator;

        [SerializeField]
        private FingerCategory Finger;

        [SerializeField]
        private SteamVR_Action_Skeleton SkeletonAction;

        [SerializeField]
        [Range(0f, 1f)]
        private float Stretch1Weight;

        [SerializeField]
        [Range(0f, 1f)]
        private float Stretch2Weight;

        [SerializeField]
        [Range(0f, 1f)]
        private float Stretch3Weight;

        // Update is called once per frame
        private void Update()
        {
            using (var handler = new HumanPoseHandler(Animator.avatar, Animator.transform))
            {
                var humanPose = new HumanPose();
                handler.GetHumanPose(ref humanPose);

                var fingerCurl = 0.0f;
                var weights = new[] { Stretch1Weight, Stretch2Weight, Stretch3Weight };
                var indexes = new MuscleName[] { };

                switch (Finger)
                {
                    case FingerCategory.RightIndex:
                        fingerCurl = SkeletonAction.indexCurl;
                        indexes = new[] { MuscleName.RightIndex1Stretched, MuscleName.RightIndex2Stretched, MuscleName.RightIndex3Stretched };
                        break;

                    case FingerCategory.RightLittle:
                        fingerCurl = SkeletonAction.pinkyCurl;
                        indexes = new[] { MuscleName.RightLittle1Stretched, MuscleName.RightLittle2Stretched, MuscleName.RightLittle3Stretched };
                        break;

                    case FingerCategory.RightMiddle:
                        fingerCurl = SkeletonAction.middleCurl;
                        indexes = new[] { MuscleName.RightMiddle1Stretched, MuscleName.RightMiddle2Stretched, MuscleName.RightMiddle3Stretched };
                        break;

                    case FingerCategory.RightRing:
                        fingerCurl = SkeletonAction.ringCurl;
                        indexes = new[] { MuscleName.RightRing1Stretched, MuscleName.RightRing2Stretched, MuscleName.RightRing3Stretched };
                        break;

                    case FingerCategory.RightThumb:
                        fingerCurl = SkeletonAction.thumbCurl;
                        indexes = new[] { MuscleName.RightThumb1Stretched, MuscleName.RightThumb2Stretched, MuscleName.RightThumb3Stretched };
                        break;

                    case FingerCategory.LeftIndex:
                        fingerCurl = SkeletonAction.indexCurl;
                        indexes = new[] { MuscleName.LeftIndex1Stretched, MuscleName.LeftIndex2Stretched, MuscleName.LeftIndex3Stretched };
                        break;

                    case FingerCategory.LeftLittle:
                        fingerCurl = SkeletonAction.pinkyCurl;
                        indexes = new[] { MuscleName.LeftLittle1Stretched, MuscleName.LeftLittle2Stretched, MuscleName.LeftLittle3Stretched };
                        break;

                    case FingerCategory.LeftMiddle:
                        fingerCurl = SkeletonAction.middleCurl;
                        indexes = new[] { MuscleName.LeftMiddle1Stretched, MuscleName.LeftMiddle2Stretched, MuscleName.LeftMiddle3Stretched };
                        break;

                    case FingerCategory.LeftRing:
                        fingerCurl = SkeletonAction.ringCurl;
                        indexes = new[] { MuscleName.LeftRing1Stretched, MuscleName.LeftRing2Stretched, MuscleName.LeftRing3Stretched };
                        break;

                    case FingerCategory.LeftThumb:
                        fingerCurl = SkeletonAction.thumbCurl;
                        indexes = new[] { MuscleName.LeftThumb1Stretched, MuscleName.LeftThumb2Stretched, MuscleName.LeftThumb3Stretched };
                        break;
                }

                for (var i = 0; i < 3; i++)
                    humanPose.muscles[(int) indexes[i]] = Mathf.Lerp(-.75f, 1f, fingerCurl * weights[i]) * -1;

                handler.SetHumanPose(ref humanPose);
            }
        }
    }
}

MuscleName は、 muscles 配列の各値を Enum で取れるようにしたものです。

Mathf.Lerp の最小値の値を -.75f にしているのは、 -1f にした際、
想定していたよりも逆方向へ曲がってしまった為、曲がりすぎないようにした値です。
上でも述べたとおり、ピンと伸ばした状態が 0f で降ってくるので、それに対応した形です。

Inspector から StretchNWeight をいじってあげることで、
各関節において曲がる量を調節できるようにしています。
設定はこんな感じですると良いです:

f:id:MikazukiFuyuno:20191219234432p:plain

設定例

実際には Index Controller の分完全に握ることは出来ないので、
そこは各 Weight をいじることで調整していきます。

いじらない場合は、画像のように完全に握った感じに出来ます。

f:id:MikazukiFuyuno:20191219234230g:plain

こんなかんじ

調整した感じ、以下のように設定すると、コントローラーを握ってる感じに見えます。
※あくまで参考値なので、良い感じに設定して下さい。

Hand Stretch 1 Stretch 2 Stretch 3
Index 0.75 0.75 0.35
Little 1 0.5 0.5
Middle 1 0.5 0.25
Ring 1 0.5 0.25
Thumb 1 0.5 0

コントローラーを握ると分かるのですが、親指の第 1 関節はほぼ曲がりませんし、
人差し指も出っ張りがある分曲がりにくいので、そう設定してあります。

設定したものがこちら

f:id:MikazukiFuyuno:20191219234501g:plain

Index Controller で小指を曲げるのが難しいので若干つらそうなことになっていますが、
Index Controller を握っている感じに調整してみました。

ということで、 Index Controller で指トラッキングしてみる記事でした。