ということで、久々ですが SteamVR の記事です。
せっかく Index Controller があるので、指の動きを良い感じにしてみようと思います。
前提環境
- Unity Personal 2019.2.16f1
- Windows 10
- Index Controller
- SteamVR 関連が設定済みのシーン
使用するアセットは以下の通り
今回は使用する 3D モデルとして、こちらの 3D モデルを使います。
- 【CC0】オリジナルアバター「シャペル」【VRChat 想定】
- 同梱されてる UnityPackage を取り込んで使います
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); }
HumanPose
の Muscle
の値は、 -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
をいじってあげることで、
各関節において曲がる量を調節できるようにしています。
設定はこんな感じですると良いです:
設定例
実際には Index Controller の分完全に握ることは出来ないので、
そこは各 Weight をいじることで調整していきます。
いじらない場合は、画像のように完全に握った感じに出来ます。
こんなかんじ
調整した感じ、以下のように設定すると、コントローラーを握ってる感じに見えます。
※あくまで参考値なので、良い感じに設定して下さい。
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 関節はほぼ曲がりませんし、
人差し指も出っ張りがある分曲がりにくいので、そう設定してあります。
設定したものがこちら
Index Controller で小指を曲げるのが難しいので若干つらそうなことになっていますが、
Index Controller を握っている感じに調整してみました。
ということで、 Index Controller で指トラッキングしてみる記事でした。