e.blog

主にUnity/UE周りのことについてまとめていきます

Oculus Questでハンドトラッキングを使ってみる

概要

Oculus Questのハンドトラッキングが利用できるようになったので使ってみたいと思います。
そこで、実際に使用するにあたってセットアップ方法とどういう情報が取れるのか、どう使えるかなどをまとめておきます。

ドキュメントは以下です。

developer.oculus.com

ちなみにドキュメントにも注意書きが書かれていますが、Oculus Linkでのハンドトラッキングは開発用でのみ動作するようです。

注:Oculus QuestとOculus Linkを使用する場合、PC上でのハンドトラッキングの使用はUnityエディターでサポートされています。この機能は、Oculus Quest開発者の反復時間短縮のため、Unityエディターでのみサポートされています。


Table of Contents


セットアップ

まずはプロジェクトをセットアップしていきます。

ここはHand Gesture用ではなく普通のOculusのセットアップです。
なのですでに知っている方は読み飛ばしてもらって大丈夫です。

XR Managementのインストール

Oculusを利用する場合は、Package ManagerからXR Managementをインストールします。

インストールすると、Project SettingsにXR Managementの項目が追加されるので、そこからOculus用のPluginをインストールします。

こちらもインストールが終わると画面が以下のように変化するので、Plugin ProvidersOculus Loaderを追加します。

※ ちなみに、Unityエディタ上でOculus Linkを使って開発を行う場合はPC向けにもOculus Loaderを設定する必要があります。

Oculus Integrationをインポート

次に、Unity Asset StoreからOculus Integrationをインポートします。

インポートが終わったらOVRCameraRigをシーン内に配置します。
その際、シーン内のカメラと重複するので元々あったほうを削除します。

配置したらOVRCameraRigにアタッチされているOVR ManagerHand Tracking SupportのリストからControllers and Handsを選択します。

※ ちなみにPlatformの設定がAndroidなっていないと設定できないようなので注意してください。

ハンドトラッキングを有効にする

ハンドトラッキングの機能を利用するためにはOculus Quest本体側の設定も必要になります。
ドキュメントから引用すると以下のように設定します。

ユーザーが仮想環境で手を使用するには、Oculus Quest上でハンドトラッキング機能を有効にする必要があります。

Oculus Questで、[Settings(設定)] > [Device(デバイス)]に移動します。 トグルボタンをスライドすることによってハンドトラッキング機能を有効にします。 手とコントローラーの使用を自動で切り替えられるようにするには、トグルボタンをスライドすることによって手またはコントローラーの自動有効化機能を有効にします。

シーンへの手の追加

ドキュメントによると、

手を入力デバイスとして使用するには、手動でシーンに追加する必要があります。手はOVRHandPrefab prefaにより実装されています。

とのことなので、対象のPrefabを配置します。
対象のPrefabはOculus/VR/Prefabsにあります。

それを、シーンに配置したOVRCameraRig以下のLeftHandAnchorRightHandAnchorの下に配置します。

手のタイプを設定する

配置したOVRHandPrefabは両手用になっているので、以下の3つのコンポーネントの設定を適切な手のタイプ(左手 or 右手)に変更します。

  • OVRHand
  • OVRSkeleton
  • OVRMesh

以上でセットアップは終了です。
あとはOculus Linkでつないで再生ボタンを押すと以下のように手が表示されるようになります。

※ バージョンによってはこれで正常に動作しない場合があるかもしれません。その場合はOculus Integrationを最新にしてみてください。

ハンドトラッキングによるデータの取得

セットアップが終わったので、あとはハンドトラッキングシステムから得られるデータを用いて様々なコンテンツを作っていくことができます。
ここではいくつかのデータの取得方法をまとめておこうと思います。

各指のピンチ強度を測る

OVRHandには各指のピンチ強度(*)を測るAPIがあるので簡単に測ることができます。

  • ... ピンチ強度は各指が『親指とどれくらい近づいているかを測る値』です。曲がり具合ではないので注意です。
float thumbStr = _rightHand.GetFingerPinchStrength(OVRHand.HandFinger.Thumb);
float indexStr = _rightHand.GetFingerPinchStrength(OVRHand.HandFinger.Index);
float middleStr = _rightHand.GetFingerPinchStrength(OVRHand.HandFinger.Middle);
float ringStr = _rightHand.GetFingerPinchStrength(OVRHand.HandFinger.Ring);
float pinkyStr = _rightHand.GetFingerPinchStrength(OVRHand.HandFinger.Pinky);

このあたりのデータを組み合わせれば、簡単なジェスチャーなどは検知できそうですね。

ボーン情報を取得する

ボーンの各情報を得るためにはOVRSkeletonクラスを利用します。
OVRSkeletonにはOVRBone構造体を保持するリストがあり、そこから情報を取り出します。

リストのどこにどのボーン情報が入っているかはOVRSkeleton.BoneId enumによって定義されており、それをintに変換して利用します。

なお、どこにどの情報が入っているかはドキュメントに記載されています。引用すると以下のように定義されています。

Invalid          = -1
Hand_Start       = 0
Hand_WristRoot   = Hand_Start + 0 // root frame of the hand, where the wrist is located
Hand_ForearmStub = Hand_Start + 1 // frame for user's forearm
Hand_Thumb0      = Hand_Start + 2 // thumb trapezium bone
Hand_Thumb1      = Hand_Start + 3 // thumb metacarpal bone
Hand_Thumb2      = Hand_Start + 4 // thumb proximal phalange bone
Hand_Thumb3      = Hand_Start + 5 // thumb distal phalange bone
Hand_Index1      = Hand_Start + 6 // index proximal phalange bone
Hand_Index2      = Hand_Start + 7 // index intermediate phalange bone
Hand_Index3      = Hand_Start + 8 // index distal phalange bone
Hand_Middle1     = Hand_Start + 9 // middle proximal phalange bone
Hand_Middle2     = Hand_Start + 10 // middle intermediate phalange bone
Hand_Middle3     = Hand_Start + 11 // middle distal phalange bone
Hand_Ring1       = Hand_Start + 12 // ring proximal phalange bone
Hand_Ring2       = Hand_Start + 13 // ring intermediate phalange bone
Hand_Ring3       = Hand_Start + 14 // ring distal phalange bone
Hand_Pinky0      = Hand_Start + 15 // pinky metacarpal bone
Hand_Pinky1      = Hand_Start + 16 // pinky proximal phalange bone
Hand_Pinky2      = Hand_Start + 17 // pinky intermediate phalange bone
Hand_Pinky3      = Hand_Start + 18 // pinky distal phalange bone
Hand_MaxSkinnable= Hand_Start + 19
// Bone tips are position only. They are not used for skinning but are useful for hit-testing.
// NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguous
Hand_ThumbTip    = Hand_Start + Hand_MaxSkinnable + 0 // tip of the thumb
Hand_IndexTip    = Hand_Start + Hand_MaxSkinnable + 1 // tip of the index finger
Hand_MiddleTip   = Hand_Start + Hand_MaxSkinnable + 2 // tip of the middle finger
Hand_RingTip     = Hand_Start + Hand_MaxSkinnable + 3 // tip of the ring finger
Hand_PinkyTip    = Hand_Start + Hand_MaxSkinnable + 4 // tip of the pinky
Hand_End         = Hand_Start + Hand_MaxSkinnable + 5
Max              = Hand_End + 0

なお、以下の記事から画像を引用させていただくと、各IDの割り振りはこんな感じになるようです。

Finger's ID

qiita.com

簡単なハンドコントロール

OVRHandは、ホーム画面などで利用される手と同じ機能を簡単に利用するためのAPIを提供してくれています。
その情報にアクセスするにはOVRHand.PointerPoseプロパティを利用します。

これはTransform型で、手をポインタとして見た場合の位置と回転を提供してくれます。

視覚化してみたのが以下の動画です。

youtu.be

個人的にはやや直感に反する挙動だなと思っています。
手の指の向きは参考にされていないようで、基本的にポインタ方向は『手の高さ』によって算出されているような印象を受けます。

手のひらの法線を計算する

今回、個人プロジェクトで『手のひらの法線』が必要になり、それを求めるプログラムを書いたので参考までに載せておきます。

using System.Collections;
using System.Collections.Generic;

using UnityEngine;

namespace Conekton.ARUtility.Input.Application
{
    public class HandPoseController : MonoBehaviour
    {
        [SerializeField] private OVRHand _targetHand = null;
        [SerializeField] private OVRSkeleton _rightSkeleton = null;
        [SerializeField] private Transform _palmNormalTrans = null;
        [SerializeField] private float _detectLimit = 0.5f;

        private OVRSkeleton.BoneId[] _forPalmCalcTargetList = new[]
        {
            // First two of them are used for calculating palm normal.
            OVRSkeleton.BoneId.Hand_Index1,
            OVRSkeleton.BoneId.Hand_Pinky0,

            OVRSkeleton.BoneId.Hand_Middle1,
            OVRSkeleton.BoneId.Hand_Ring1,
            OVRSkeleton.BoneId.Hand_Pinky1,
            OVRSkeleton.BoneId.Hand_Thumb0,
        };

        private OVRSkeleton.BoneId BoneIDForNormalCalculation1 => OVRSkeleton.BoneId.Hand_Index1;
        private OVRSkeleton.BoneId BoneIDForNormalCalculation2 => OVRSkeleton.BoneId.Hand_Pinky0;

        public bool TryGetPositionAndNormal(out Vector3 position, out Vector3 normal)
        {
            if (!_targetHand.IsTracked)
            {
                position = Vector3.zero;
                normal = Vector3.up;
                return false;
            }

            Vector3 center = Vector3.zero;

            foreach (var id in _forPalmCalcTargetList)
            {
                OVRBone bone = GetBoneById(id);
                center += bone.Transform.position;
            }

            center /= _forPalmCalcTargetList.Length;

            position = center;

            OVRBone bone0 = GetBoneById(BoneIDForNormalCalculation1);
            OVRBone bone1 = GetBoneById(BoneIDForNormalCalculation2);

            Vector3 edge0 = bone0.Transform.position - center;
            Vector3 edge1 = bone1.Transform.position - center;

            normal = Vector3.Cross(edge0, edge1).normalized;

            return true;
        }

        private OVRBone GetBoneById(OVRSkeleton.BoneId id)
        {
            return _rightSkeleton.Bones[(int)id];
        }
    }
}

考え方はシンプルで、ハンドトラッキングから得られるボーンの位置をいくつか選び、それらの平均の位置を手のひらの位置としています。
また法線については、求めた手のひらの位置とふたつのボーンの位置との差分ベクトルを取り、それの外積を取ることで求めています。

まとめ

Oculus Questのハンドトラッキングの精度は驚異と言っていいと思います。

過去に、Leapmotionを使ったコンテンツを開発したことがありますが、Leapmotionよりも精度が高い印象です。(それ用デバイスより精度が高いって・・)
実際に体験すると、本当にVR内に自分の手があるかのように思えるくらいなめらかにトラッキングしてくれます。

ハンドトラッキングを用いたUI/UXはさらに発展していくと思うので、これからとても楽しみです。