🗒️

Snapdragon SpaceとMRTK3で手とコントローラを同時に使うアプローチ

2023/03/15に公開

Snapdragon Spacesについて

Snapdragon SpacesはQualcomが進めているARデバイス向けのプラットフォームです。すでにあるARプラットフォームと同様、ポジショントラッキング、ハンドトラッキング、空間認識等を利用できるSDKです。

2月中旬にSDKがエンハンスされてV0.11.1になりました。変更履歴は以下のリンク先にあります。
https://docs.spaces.qualcomm.com/common/about/CHANGELOG.html

この修正の中でSnapdragon SpacesとMRTK3を組み合わせると面白いことができます。それが今回紹介する「Hand Tracking」と「Snapdragon Spaces Controller」を同時に使うアプローチです。

「Hand Tracking」と「Snapdragon Spaces Controller」を同時に使う

通常モーションコントローラとハンドトラッキングが利用可能なXRデバイスでは、コンテンツの中でそれぞれの入力方法は排他的になっています。つまりコントローラを使っているときはハンドトラッキングは利用できず、逆もまたしかり。
なのですが、Snapdragon Spacesについてはそういう制約が現時点ではなくなんと「右手ハンドトラッキング」、「左手Snapdragon Spaces Controller(スマホコントローラ)」という同時利用が可能です。この話についてはMixed Reality Tookit 3とSnapdragon Spacesを組み合わせる際の手順の中で触れられています。
https://docs.spaces.qualcomm.com/unity/samples/preview/MRTK3SampleWinOnly.html#hand-tracking-options

ということで右手:ハンドトラッキング、左手:Snapdragon Spaces Controllerで動作するちょっとしたコンテンツと、役に立ちそうなUXの実験をしてみたので共有したいを思います。

作ったコンテンツについて

今回はSnapdragon SpacesとMRTK3を組み合わせてハンドトラッキングとスマホを組み合わせた入力操作を試しています。右手の手のひらを上にするとハンドメニューが表示されます。表示されたメニューはテンキー風になっており、ポインターを配置しています。このポインターを左手に持っているSnapdragon Spaces Controller(スマホ)のタッチパッド機能を使いポインターを動かしてボタンを選択するというUIになります。ポインターがZファイティングしてたり、ボタンがちゃんと選択されていないように見えるのは微調整していない動画だからです(先日のハッカソンの期間中にお試しでつくったため)
https://youtu.be/rKMtf0z6vUE
また、お遊び程度の機能として、謎の飛行物体を右手でつかみ、左手のタッチパッド上で時計回りにドラッグし続けることでエネルギーをためて飛ばすというコンテンツも入れています。
https://youtu.be/ge8h_JPnKVI

サンプルコードなど

Githubに今回作成したUnityプロジェクトを公開しています。合わせて参考にしてください。

https://github.com/TakahiroMiyaura/SnapdragonSpaces-Samples/tree/main/HandAndHostControllerInteractions

開発環境

開発環境は以下の通りです。今回はコンテンツ内でハンドメニュー等を利用するためにMixed Reality Toolkit 3を利用しています。

  • Mixed Reality Feature Tool v1.0.2209.0 Preview(MRTK3セットアップ用)
  • Unity Hub 3.3.0
  • Unity 2021.3.16f1
    • Android Build Support一式
    • packages
      • SnapdragonSpaces for Unity v0.11.1
      • Mixed Reality Toolkit 3
        • MRTK Core Definittions V3.0.0-pre.14
        • MRTK Extended Assets V3.0.0-pre.14
        • MRTK Graphics Tool V0.4.0
        • MRTK Input V3.0.0-pre.14
        • MRTK Spatial Manipulation V3.0.0-pre.14
        • MRTK Standard Assets V3.0.0-pre.14
        • MRTK Tools V3.0.0-pre.14
        • MRTK UX Components V3.0.0-pre.14
        • MRTK UX Components(Non-Canvas) V3.0.0-pre.14
        • MRTK US Core Scripts V3.0.0-pre.14
      • Microsoft Mixed Reality OpenXR Plugin 1.7.0
      • OpenXR Plugin
      • Input System
      • XR Management
      • XR Interaction Toolkit

Unityプロジェクトの準備

今回はSnapdragon SpacesとMRTK3を組み合わせたUnityプロジェクトを利用します。もし新しくUnityプロジェクトを起こしたい場合は以下のサイトも参考にしてください。

https://docs.spaces.qualcomm.com/unity/samples/preview/MRTK3SampleWinOnly.html

githubからプロジェクトをダウンロードする。

以下のリポジトリをクローンしてください。このリポジトリにはSnapdragon Spacesのサンプルを複数用意しています。今回は「SnapdragonSpacesSpatialMapping」を利用します。

PS D:\WorkSpaces\Git> git clone https://github.com/TakahiroMiyaura/SnapdragonSpaces-Samples.git

https://github.com/TakahiroMiyaura/SnapdragonSpaces-Samples

Snapdragon Spaces for Unityのダウンロード

Snapdragon Spaces for Unityはサイトからダウンロード可能です。
https://spaces.qualcomm.com/sdk/

ダウンロードサイトへ行くとUnity向けとUnreal向けがダウンロードできます。Unity用のものをダウンロードしてください。

サンプルプロジェクトにMRTK3に必要なパッケージをインポートする

クローンしたUnityプロジェクトには必要なパッケージがインポートされていない状態になっています。MRTK3に関係するパッケージはUnity Editorを開く前に設定しておきます。
設定方法については以下の記事の「Mixed Reality Feature ToolによるMRTK3の導入」を参考にして、Mixed Reality Feature Toolで設定します。

https://zenn.dev/miyaura/articles/60eb4d43879df4#mixed-reality-feature-toolによるmrtk3の導入

必要なパッケージ一覧

  • Mixed Reality Toolkit 3
    • MRTK Core Definittions V3.0.0-pre.14
    • MRTK Extended Assets V3.0.0-pre.14
    • MRTK Graphics Tool V0.4.0
    • MRTK Input V3.0.0-pre.14
    • MRTK Spatial Manipulation V3.0.0-pre.14
    • MRTK Standard Assets V3.0.0-pre.14
    • MRTK Tools V3.0.0-pre.14
    • MRTK UX Components V3.0.0-pre.14
    • MRTK UX Core Scripts V3.0.0-pre.14
  • Microsoft Mixed Reality OpenXR Plugin 1.7.0

Snapdragon Spaces for Unityのインポートを行う

MRTK3のインポートが完了後にUnity Editorを起動しプロジェクトを開きます。
プロジェクトを開いたらSnapdragon Spacesパッケージのインポートを行います。
インポートの詳細は以下の記事の「Snapdragon Spaces for Unityのインポート」を参照してください。

https://zenn.dev/miyaura/articles/60eb4d43879df4#snapdragon-spaces-for-unityのインポート

プロジェクトの設定

次にプロジェクト設定を行います。メニューから[File]-[Build Settings..]を選択します。ビルド設定パネルが表示されたら[Platform]を[Android]に変更します。

変更後は[Edit]-[Project settings..]を開きます。
次に[XR Plug-in Management]を選択し、次にチェックが入っていることを確認してください。

  • OpenXR
    • Snapdragon Spaces feature group

この時点でProject Validationに沢山エラーが出るかもしれないですが、いったん無視して大丈夫です。次に[XR Plug-in Management]-[OpenXR]を選択します。[Android]タブが選択されていることを確認し、以下にチェックが入っていることを確認してください。 また、Interaction ProfileにtMicrosoft Mixed Reality Motion Controller Profile]が設定されていることを確認してください。 これによってSnapdragon Spaces Controllerの処理が検知できるようになります。

  • Base Runtime
  • Hand Tracking(Snapdragon Spaces用)
  • Hand Tracking(Mixed Reality OpenXR Plugin用)
  • Plane Detection

この際に[Base Runtime]にWarningが出るのですが、これは正しい動作ですので無視してください。

以上で、プロジェクトの設定が完了です。これをビルドし、Think Reality A3にインストールすることでコンテンツを体験することができます。

プログラムの詳細

最後にプログラム部分について解説します。今回はポイントになりそうな以下の2つについて。

Snapdragon Spaces Controllerの制御

Snapdragon Spaces Controllerの入力情報などはXRIを利用することで簡単に情報取得を行うことができます。
Snapdragon Spaces Controller用のInput ActionなどはSnapdragon Spacesのサンプルプロジェクトの中にプリセットで用意されています。事前作業としてこのInput ActionをInput Action Managerに追加します(今回作ったサンプルには設定済みです)

設定したInput Actionからの情報(=デバイスの入力)は取得したいInput Actionから情報を引き出せます。以下はSnapdragon Spaces Controllerのタッチパッドの情報を読み取る実装例です。

//タッチパッドに触れているかどうかを取得
var isTouch = TouchpadInputAction.action.triggered;
//タッチパッド操作中の座標(XY:-1~1)を取得
var pos = TouchpadInputAction.action.ReadValue<Vector2>();

タッチパッドのInput Actionは以下のようにVector2で定義されています。このようにInput Action Managerに登録されたInput Actionから情報を簡単に引き出すことができるのもXRIの便利なところですね。

今回は上記の実装を利用してタッチパッドの動きに合わせてポインターを制御しています。このポインターはColliderをつけているので、各ボタンのColliderを接触すると選択オブジェクトとして処理しています。そして、タップが解除されたときにボタンが選択されたものとして処理を実装しています。このような形で今回のUIを作成しました。

MenuController.cs
// Copyright (c) 2023 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;

[Serializable]
public class PadReleaseEvent : UnityEvent<PadObject>{}

public class PadObject {
    public Vector2 Pose { set; get; }
    public GameObject SelectedGameObject { set; get; }
}

public class MenuController : MonoBehaviour {
    private bool _isPress;
    private float _offTimer;
    private float _onTimer;

    private Vector2 _pose;
    private RectTransform _rectTransform;

    [Range(-1, 1)]
    public float DebugX;
    [Range(-1, 1)]
    public float DebugY;
    public bool IsDebug;
    public PadReleaseEvent OnPadRelease;
    public Vector2 PadSize;
    public GameObject SelectedTarget;
    public float Threashold = 0.5f;
    public InputActionReference TouchpadInputAction;

    private void Start() {
        _rectTransform = GetComponent<RectTransform>();
    }

    private void Update() {
        //Snapdragon Spaces Controllerの操作を監視
	//タッチパッドが操作中の場合、タッチパッドの動きに合わしてポインターを操作
        if (TouchpadInputAction.action.triggered || IsDebug) {
            _onTimer += Time.deltaTime;
            if (IsDebug)
                _pose = new Vector2(DebugX, DebugY);
            else
                _pose = TouchpadInputAction.action.ReadValue<Vector2>();
            _pose = new Vector2(_pose.x * PadSize.x / 2f, _pose.y * PadSize.y / 2f);
            _rectTransform.localPosition = new Vector3(_pose.x, _pose.y, _rectTransform.localPosition.z);
        } else {
            if (_onTimer > Threashold) {
                _onTimer = 0f;
                _isPress = true;
            }
            //タッチパッドの操作の終了時にReleaseイベントを発生させる
            if (_isPress) {
                if (_offTimer > Threashold) {
                    var padObject = new PadObject();
                    padObject.SelectedGameObject = SelectedTarget;
                    padObject.Pose = _pose;
                    OnPadRelease?.Invoke(padObject);
                    _offTimer = 0;
                    _onTimer = 0;
                }
                _offTimer += Time.deltaTime;
            }
        }
    }
    //トリガーイベント:ポインターがメニュー内のボタンと接触
    private void OnTriggerStay(Collider other) {
        Debug.Log(other.gameObject.name);
        SelectedTarget = other.gameObject;
    }
}

ハンドメニューの実装方法

HandMenuはMRTK3の機能を利用します。ゼロから以下のコンポーネントを設定する事でも実装することが可能ですが、テンプレートとしてボタンが4つ入ったハンドメニューがHandMenuBaseとして提供されているのでこれを利用すると手軽にできます。Unity Editorの[Project]パネル内の検索窓に[HandMenuBase]を入力し検索タイプを[All]または[In Packages]にすると候補にprefabが出てきます。

これをHierarchyにドラッグ&ドロップして追加します。その後このオブジェクトはカスタマイズするので、ポップアップメニューから[Prefab]-[Unpack Completely]を選択しPrefabの解除を行います。

ハンドメニューにかかわるコンポーネント

MRTK3のハンドメニュはSolverという機能にしたがって実現しています。この機能にはいくつかの入力操作を監視するSolver Handlerと、Handlerからの情報をもとに様々な動作を行うSolverの組合せで処理を実現します。例えば以下のような処理をコンポーネントの組合せのみで実現できます。

  • 頭の動きに合わせて視野内にオブジェクトを常に追従させて表示
  • 手のひらの動きに追従したオブジェクトを表示

ハンドメニューはこの後者の設定方法で作られるオブジェクトになります。
Solver Handlerはトラッキングする情報に関する定義を行います。設定項目は以下のようなものです。[Tracked Target Type]ではHeadやHand Jointなどトラッキングしたい部位の設定を行います。それぞれの部位に応じて詳細設定項目があります。以下はHand Jointを設定した場合の詳細設定です。

  • Tracked Handednessはトラッキングする右手/左手/両手などの設定
  • Tracked Hand Jointはトラッキングする手の部位


Additional~のパラメータはトラッキングした情報のPoseを補正するための調整値になります。
最後にUpdate Solversはチェックを外すとトラッキングの情報更新を止めることができます。一時的にSolverによる制御を解除したい場合に利用します。
Solver Handlerからの情報を受け取り実際にオブジェクトの挙動を決めるのがSolverになります。Solverは標準でいくつか提供されているのですがその1つにハンドメニュー用のコンポーネント[Hand Constraint(Palm Up)]があります。
Hand Constraint(Palm Up)は大きく以下の機能を担っています。ポイントになるのは手のトラッキングとして検出するのは 手に平を上にした状態 になります。ただ、手をトラッキングした状態ではHand Constraint(Palm Up)は動作しないようになっています。

  • Solver Handlerから手の部位のPoseにオブジェクトを追従させる
  • Solver Handlerで手のトラッキングに関するイベント制御
    • 初めのトラッキングを検出した
    • 最後のトラッキングを喪失した
    • 手がアクティブ化された
    • 手のアクティブ化が解除された
  • 手に平を上にする動作

ハンドメニューは上記の機能を利用し「手のひらを上にすると子のオブジェクトを表示」、「トラッキングを失うとメニューを非表示」といった動作を実現しています。

まとめ

今回はSnapdragon SpacesとMRTK3を利用し、ハンドトラッキングとSnapdragon Spaces Controllerを組み合わせたUIについて解説しました。今回のようにメニュー選択をスマホのコントローラとすることでハンドトラッキングよりも操作性が容易になります。
また、これはスマホ接続型のARグラスは特有の話ですが、アプリの起動操作や体験中はスマホと起動したまま保持する必要があるため扱いに困ることが多いです。
このような場合にコントローラとハンドトラッキングを合わせて使える今回の手法はUI操作の幅を広げることもできるので面白いかもしれません。

Discussion