Unityでモーショントラッカーを使う

PICO Motion Trackerはボディトラッキングだけでなく、トラッカー単体で使用するオブジェクトトラッキングモードがあります。
今回はSKDを利用してオブジェクトトラッキングモードでトラッカーを動かしてみました。
PICO4UltraとPICO Motion Trackerの準備
まずPICO4Ultraのホーム画面にあるMotion Trackerアプリを開きます。

次に画面の指示に従ってトラッカーとペアリングを行います。
ペアリングが完了すると下の画像のように接続されているトラッカーの情報が表示されます。

そして右上の設定ボタンを押して設定画面を開き、トラッキングモードをオブジェクトトラッキングモードに変更します。
これでMotionTrackerの設定は完了です。
Unityの準備
プロジェクトを作成できたらPICO Unity Integration SDKの最新版を以下のPICOのサイトからダウンロードします。
https://developer.picoxr.com/resources/
Unityのパッケージマネージャーを開き、右上の+ボタンからAdd package from diskを選択し、先ほどダウンロードしたSDKをインポートします。

ヘッドセットのセットアップ
トラッカーを制御する前にヘッドセットを使用できるようにします。
Unity上部のメニューからGameObject->XR->XR Origin(VR)を選択し追加します。

次にパススルーの設定を行います。
シーンに配置されたXR Origin->Camera Offset->Main Cameraをクリックし、インスペクターのCameraの中のEnviroment TypeをSolid Colorへ変更し、Backgroundを画像のように変更します。

次にXR OriginにPXR_Managerを追加し、Video Seethroughをオンにします。

またUniversal Render Pipeline (URP)を使用している場合はHDRをオフにしてください。
これをしないとパススルーができず真っ暗な画面になります。

次にパススルーを有効化させるためのスクリプトを作ります。
PassthroughEnablerという名前でスクリプトを作成し、以下のコードを書いてください。
using UnityEngine;
using Unity.XR.PXR;
using UnityEngine.Android;
public class PassthroughEnabler : MonoBehaviour
{
    void Start()
    {
        if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
        {
            Permission.RequestUserPermission(Permission.Camera);
        }
        // パススルー設定
        PXR_Manager.EnableVideoSeeThrough = true;
    } 
    void Update()
    {
        PXR_Manager.EnableVideoSeeThrough = true;
    }
    private void OnApplicationPause(bool pauseStatus)
    {
        // Pauseからの復帰時、再度パススルーにする
        if (!pauseStatus)
        {
            PXR_Manager.EnableVideoSeeThrough = true;
        }
    }
}
このコードを使用することでアプリ開始時にパススルーが有効になるだけでなく、ヘッドセットを一時的に外すなどアプリが一時停止され、復帰した時も自動的にパススルーが起動するようになります。
トラッカーを制御する
トラッカーの位置にCubeを出してみましょう。
まず、トラッカーを制御するためにTrackerFollowerというスクリプトを作成し、以下のコードを書いてください。
using UnityEngine;
using Unity.XR.PXR;
using System.Collections;
public class TrackerFollower : MonoBehaviour
{
    public GameObject[] trackerCubes = new GameObject[3];
    private int maxTrackers = 3;
    private bool update_motion = true;
    void Start()
    {
        for (int i = 0; i < maxTrackers; i++)
        {
            if (trackerCubes[i] != null)
            {
                trackerCubes[i].SetActive(false);
            }
        }
    }
    void Update()
    {
        MotionTrackerMode trackingMode = PXR_MotionTracking.GetMotionTrackerMode();
        // トラッカーの状態を更新
        if (update_motion && trackingMode == MotionTrackerMode.MotionTracking)
        {
            // トラッカーの接続状態を取得
            MotionTrackerConnectState mtcs = new MotionTrackerConnectState();
            PXR_MotionTracking.GetMotionTrackerConnectStateWithSN(ref mtcs);
            // 認識されたトラッカーの数に基づいてCubeを有効化
            for (int i = 0; i < maxTrackers; i++)
            {
                if (i < mtcs.trackerSum)  // 接続されているトラッカーがある場合
                {
                    string sn = mtcs.trackersSN[i].value.ToString().Trim();
                    if (!string.IsNullOrEmpty(sn))
                    {
                        MotionTrackerLocations locations = new MotionTrackerLocations();
                        MotionTrackerConfidence confidence = new MotionTrackerConfidence();
                        PXR_MotionTracking.GetMotionTrackerLocations(mtcs.trackersSN[i], ref locations, ref confidence);
                        MotionTrackerLocation localLocation = locations.localLocation;
                        // Cubeを有効化し、トラッカーの位置・回転を反映
                        if (trackerCubes[i] != null)
                        {
                            trackerCubes[i].SetActive(true); // Cubeを有効化
                            trackerCubes[i].transform.position = new Vector3(localLocation.pose.Position.x, localLocation.pose.Position.y, localLocation.pose.Position.z * -1);
                            trackerCubes[i].transform.rotation = new Quaternion(
                                -localLocation.pose.Orientation.x,
                                -localLocation.pose.Orientation.y,
                                localLocation.pose.Orientation.z,
                                localLocation.pose.Orientation.w);
                            trackerCubes[i].transform.localScale = Vector3.one * 0.1f;
                            //Debug.Log($"Cube {i + 1} - Position: {trackerCubes[i].transform.position}");
                            //Debug.Log($"Cube {i + 1} - Rotation: {trackerCubes[i].transform.rotation.eulerAngles}");
                        }
                    }
                }
                else
                {
                    // トラッカーが認識されていない場合はCubeを無効化
                    if (trackerCubes[i] != null)
                    {
                        trackerCubes[i].SetActive(false);
                    }
                }
            }
        }
    }
}
このコードでトラッカーの位置情報と回転情報を受け取り、任意のオブジェクトに反映させることができます。
またトラッカーはPICO4自体が合計3つまで認識が可能なため、maxTrackersは3となっています。
次にシーンに空のGameObjectを追加し、先ほど作ったPassthroughEnablerとTrackerFollowerをそれにアタッチします。

次にトラッカーの位置に表示させるコンテンツの格納先として空のGameObjectを作成し、MotionTrackerTransformに名前を変更します。インスペクターからPosition、Rotationを0に、Scaleが全て1に変更してください。

次にコンテンツを配置したいのですが、実はこのままだとUnityの回転方向とモーショントラッカーの回転軸が違うため角度の修正を行う必要があります、なので角度を修正するためのオブジェクトを作成します。
MotionTrackerTransformの下に空のGameObjectを作成し、3D_1に名前を変更します。
3D_1のPositionが全て0に設定し、RotationはX-90,Y180,Z0に、Scaleは全て1に設定してください。


それができたら、3D_1を2回複製し、それぞれ3D_2、3D_3と名前を変更します。
これで回転軸がトラッカーと一致するようになります。

次にシーンに3つCubeを追加し、それぞれの3Dの子に設定します。
その後、それぞれのCubeのPosition、Rotationを全て0にします。

そしてGameObjectにアタッチされているTracker FollowerのTracker Cubesにそれぞれ3D_1、3D_2、3D_3をアタッチします。

これでトラッカーの位置にCubeを出す準備ができました。
また別で用意したオブジェクトやコンテンツなどを表示したい場合は、Cubeを置き換えることで簡単に入れ替えることができます。
実機で実行
PCとPICO4UltraをUSBで接続し、開発者モードを有効化し、USBデバッグをオンにします。

UnityでPlatformをAndroidに設定し、ビルドして実行します。

アプリが立ち上がるとトラッカーの位置にCubeが表示されるようになります。
写真では手の上にトラッカーを乗せているため、手の上にCubeが表示されています。


