🗒️

Snapdragon Space(V0.11.1)の空間認識を試す with 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

大きな変更としては以下の3つになります。

  1. Experimental:Spatial Meshing Subsystemのサポートと新しいサンプルシーンの追加
  2. Experimental:Camera Frame Accessのサポートと新しいサンプルシーンの追加
  3. MRデバイスのパススルーをサポート

1点目は、空間マッピングの機能です。今回実際に試してみた機能になります。一般的なARやMRといったシーンで利用される「現実世界をスキャンして空間を推定しデジタルコンテンツをオクルージョンや干渉させる」ための機能です。実際に試すと以下のようなものを作ることができます。
2点はSDKからThinki Reality A3等のデバイスの正面のカメラにアクセスするための機能が追加されています。
最後の機能は少し興味深いですね。MRデバイスのパススルー機能をサポートしたというものです。Snapdragon Spacesをサポートしているデバイスの中ではパススルー方式のMRデバイスというのがまだないはずなのですが機能として提供されています。
これ以外にもMRTK3のハンドトラッキングに正式に対応しています。
今回はSnapdragon Spaces v0.11.1で提供された機能のうち空間メッシュを利用した簡単なコンテンツ開発をもとにその機能を少し紹介したいと思います。

サンプルコードなど

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

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

実際にThink Reality A3にでインストールして実行すると以下のような体験が可能です。
https://youtu.be/_UJvdcjkQTg
アプリを起動すると5秒後に空間スキャンが一定間隔で実施されます。この状態で右手をPinchジェスチャー(親指を人差し指でOKマークを作る感じです)すると自由落下するボールが空間に出現します。空間メッシュが正しく展開されていると床面を移動するボールを見ることができます。左手のPinchジェスチャーは空間に自由落下しないボールになります。ほかのボールなどで押し出すと空間内を飛んでいきます。また、手のひらを上に向けるとハンドメニューが表示されます。メニューには、空間メッシュの表示/非表示を変更できるトグルボタンと、ボールを消すボタン、アプリ終了ボタンがあります。このハンドメニューの機能はMRTK3を利用しています。
今回のアプリケーションはSnapdragon Spacesのハンドトラッキング操作ではなく、MRTK3を利用したハンドトラッキングを使っています。

開発環境

開発環境は以下の通りです。今回はコンテンツ内でハンドメニュー等を利用するために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(NNon-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 Components(NNon-Canvas) V3.0.0-pre.14
    • MRTK US 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]タブが選択されていることを確認し、以下にチェックが入っていることを確認してください。

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

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

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

プログラムの詳細

最後にプログラム部分について解説します。

ハンドトラッキングによるイベント制御

ハンドトラッキングのイベント制御についてはMRTK3のHandsAggregatorSubsystemを利用しました。このサブシステムは認識している手の各関節のposisionやrotationおよび、Pinch動作についての情報を取得することができます。この機能を利用して右手と左手のPinch動作時にオブジェクトを生成するようにしています。
HandsAggregatorSubsystemを利用する場合は次のような実装でオブジェクトを取得します。

private void Awake() {
    _aggregator = XRSubsystemHelpers.GetFirstRunningSubsystem<HandsAggregatorSubsystem>();
}

取得したHandsAggregatorSubsystemを利用してPinchジェスチャーを行っているかどうか検出するためには以下のメソッドを呼び出します。

_aggregator.TryGetPinchProgress(XRNode.LeftHand, out var isLeftReadyToPinch, out var isLeftPinching, out _);
_aggregator.TryGetPinchProgress(XRNode.RightHand, out var isRightReadyToPinch, out var isRightPinching, out _);

第1引数に検出したい手、第2引数はPinchジェスチャーの実施が可能な状態かどうかをbooleanで返します。第3引数はPinch動作を行っているかのBooleanです。一番最後はPinch動作の程度を表す数値です。モーションコントローラのトリガーの引きの強さを数値で表したものと同等のものです。
また、ハンドトラッキング時の各関節の情報を取得する場合は以下のメソッドを利用することができます。第1引数が手のどの部分かを列挙値で指定します。第2引数は右手/左手の設定、最後が指定箇所のPositionやRotationになっています。

_aggregator.TryGetJoint(TrackedHandJoint.Palm, XRNode.LeftHand, out var jointPose);

これらを利用してイベントを制御しています。詳細は[GenerateObjects.cs]を参照してください。

空間メッシュの制御

Snapdragon Spaces V0.11.1についてはまだ試験的な機能のため実際に使うためには少し工夫が必要です。公式サイトの情報は以下に公開されています。

https://docs.spaces.qualcomm.com/unity/samples/SpatialMeshingSample.html

Snapdragon Spacesの空間メッシュについてはAR Foundationの機能「ARMeshManager」を利用します。このため、空間メッシュを可視化する場合はARMeshManagerのMesh Prefabにマテリアルなどで可視化できるようにしたPrefabを設定することで利用可能です。

ただし、V0.11.1についてはARMeshManagerの挙動として、メッシュの追加と削除を繰り返すように動作する仕様になっています。ですので、そのままARMeshManagerに設定してしまうと、メッシュが生成と削除を繰り返すためちらついた感じになりますし、Colliderなどの設定も同じ挙動になります。この結果床が定期的に消えるといった動作になるため今回のサンプルのような床を転がるオブジェクトという表現ができなくなります。
そこで今回は、 ARMeshManagerのイベントから、追加されたメッシュ情報を参照し、別途Mesh RendererやColliderを生成することで、空間メッシュが途切れないようにしています。
この実装は[MRTK XR Rig]-[Camera Offset]-[SpatialManaging]オブジェクトの[Spatial Collision Controller]コンポーネントになります。

このコンポーネントでは同じオブジェクトに設定されているARMeshManagerコンポーネントを取得し空間メッシュが変更された際に発生するイベント(ArMeshManagerOnMeshesChanged)を登録します。

SpatialCollisionController.cs(抜粋)
private ARMeshManager _arMeshManager;
public GameObject Root;

private void Awake() {
    _arMeshManager = GetComponent<ARMeshManager>();
    if (Root == null) Root = new GameObject("Spatial Objects");
}

private void OnEnable() {
    _arMeshManager.meshesChanged += ArMeshManagerOnMeshesChanged;
}

private void OnDisable() {
    _arMeshManager.meshesChanged -= ArMeshManagerOnMeshesChanged;
}

private void ArMeshManagerOnMeshesChanged(ARMeshesChangedEventArgs obj) {
    StartCoroutine(MeshUpdate(obj.added, obj.updated, obj.removed));
}

Meshが変更されると非同期でMeshUpdateメソッドが呼び出されます。このメソッド内で追加/更新/削除されたMeshの情報をもとに、制御用の空間メッシュオブジェクトを生成しています。今回は空間に干渉する体験を実現するためにColliderなども設定しています。
仕組みは単純で、Meshオブジェクトの名前で管理を行うようにしています。同じ名前で既に制御用の空間メッシュを生成している場合は、メッシュの更新をするように制御することでメッシュが削除されないように処理を行っています。

SpatialCollisionController.cs(抜粋)
private IEnumerator MeshUpdate(List<MeshFilter> added, List<MeshFilter> updated, List<MeshFilter> removed) {
    //コレクション操作のための同期
    lock (_lockObj) {
        //追加されたメッシュ分ループ
        foreach (var addedMeshFilter in added) {
            var key = addedMeshFilter.gameObject.name;
	    //すでに作成時にのメッシュか確認。
	    //新規であれば、RendererとColliderを追加した新しいオブジェクトを作成
            if (!_collisions.TryGetValue(key, out var mFilter)) {
	        var colGameObject = new GameObject(key);
                colGameObject.transform.SetParent(Root.transform, false);
                mFilter = new MeshInfo(colGameObject.AddComponent<MeshFilter>(),
                    colGameObject.AddComponent<MeshCollider>(),
		    colGameObject.AddComponent<MeshRenderer>());

                    _collisions[key] = mFilter;
                }
                SetMeshInfo(addedMeshFilter.transform, mFilter,
		  addedMeshFilter.sharedMesh);
            }
	    
	//更新されたメッシュ分ループ(V0.11.1はこのロジックは動かない)
        foreach (var updMeshFilter in updated) {
            var key = updMeshFilter.gameObject.name;
            SetMeshInfo(updMeshFilter.transform, _collisions[key],
	      updMeshFilter.sharedMesh);
        }

        //削除されたメッシュの処理。制御用のメッシュが削除されたメッシュよりも
	//多い場合は、該当する制御用のメッシュを削除する
        if (_collisions.Count > _deleteMeshFilters.Length)
            foreach (var deleteMeshFilter in _deleteMeshFilters) {
                var key = deleteMeshFilter.gameObject.name;
                var removeData = _collisions[key];
                _collisions.Remove(key);
                Destroy(removeData.GameObject);
            }
        }
        yield return null;
    }

    private void SetMeshInfo(Transform sourceTransform, MeshInfo info, Mesh mesh) {
        info.GameObject.transform.SetPositionAndRotation(sourceTransform.position,
	  sourceTransform.rotation);
        info.GameObject.transform.localScale = sourceTransform.localScale;
        info.Filter.sharedMesh = mesh;
        info.Collider.sharedMesh = mesh;
        info.Renderer.material = mat;
    }

    //制御用メッシュの管理クラス
    internal class MeshInfo {
        public MeshFilter Filter { get; }

        public MeshCollider Collider { get; }
        public MeshRenderer Renderer { get; }

        public GameObject GameObject { get; }

        public MeshInfo(MeshFilter filter, MeshCollider collider, MeshRenderer renderer) {
            Filter = filter;
            Collider = collider;
            Renderer = renderer;
            GameObject = filter.gameObject;
        }
    }
}

まとめ

今回は新しくなったSnapdragon Spaces v0.11.1に追加された空間メッシュの機能について検証を行いました。また正式にMRTK3と連携したハンドトラッキング制御も合わせて利用しました。
MRTK3との組合せは様々なUX部品の活用ができるだけでなく、MRTK3自体がクロスプラットフォーム対応しており、Snapdragon Spacesと組み合わせることで活用の幅が広がります。
Snapdragon Spacesについては徐々に機能が追加されているとは言えまだSDKとしては成熟が待たれる面も多く感じました。一方で着実に機能実装が進んでおり、Snapdragon Spaces対応のデバイスでのコンテンツ開発が容易になっていく点ではこれからが楽しみなプラットフォームといえると思います。
機会があれば、ほかの新機能も検証してみたいと思います。

Discussion