【Unity】VirtualMotionTrackerで手のトラッキングデータを取得する

先にまとめ

VMTで両手の姿勢をUnity上で取得する方法です。以下のスクリプトを任意のGameObjectにアタッチし、Inspectorで、姿勢確認用のTransform変数に任意のオブジェクトを代入し、左右のコントローラのシリアル番号を代入します。シリアル番号は、VMT ManagerのDevice画面で確認できます。

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

[RequireComponent(typeof(uOSC.uOscClient))]
[RequireComponent(typeof(uOSC.uOscServer))]
public class WatchHands : MonoBehaviour
{
    uOSC.uOscClient client;
    uOSC.uOscServer server;

    [SerializeField] Transform rightHand;
    [SerializeField] Transform leftHand;

    [SerializeField] string rightHandSerial = "";
    [SerializeField] string leftHandSerial = "";
    void Start()
    {
        client = GetComponent<uOSC.uOscClient>();
        server = GetComponent<uOSC.uOscServer>();

        //アドレス固定
        client.address = "127.0.0.1";
        client.port = 39570;
        server.port = 39571;
        //イベント追加
        server.onDataReceived.AddListener(OnDataReceived);
        server.onServerStopped.AddListener(OnServerStopped);
        //購読開始
        Subscribe();
    }

    public void OnDataReceived(uOSC.Message message)
    {
        if (message.address == "/VMT/Out/SubscribedDevice")
        {
            //受け取った情報がどのデバイスのものか、シリアル番号を取得
            string serial = (string)message.values[0];

            //シリアル番号を識別
            //受け取った姿勢情報を反映させるTransformをtargetに代入
            //どちらのコントローラでもなければreturn
            Transform target = null;
            if (serial == rightHandSerial)
                target = rightHand;
            else if (serial == leftHandSerial)
                target = leftHand;
            else
                return;

            //反映
            float x = (float)message.values[1];
            float y = (float)message.values[2];
            float z = (float)message.values[3];
            float qx = (float)message.values[4];
            float qy = (float)message.values[5];
            float qz = (float)message.values[6];
            float qw = (float)message.values[7];
            target.position = new Vector3(x, y, -z);
            target.rotation = new Quaternion(qx, qy, -qz, -qw);
        }
    }

    public void OnServerStopped(int x)
    {
        Unsubscribe();
    }

    private void OnDestroy()
    {
        Unsubscribe();
    }

    void Unsubscribe()
    {
        Debug.Log($"Unsubscribed:{rightHandSerial}");
        client.Send("/VMT/Unsubscribe/Device", rightHandSerial);
        Debug.Log($"Unsubscribed:{leftHandSerial}");
        client.Send("/VMT/Unsubscribe/Device", leftHandSerial);
    }

    void Subscribe()
    {
        Debug.Log($"Subscribed:{rightHandSerial}");
        client.Send("/VMT/Subscribe/Device", rightHandSerial);
        Debug.Log($"Subscribed:{leftHandSerial}");
        client.Send("/VMT/Subscribe/Device", leftHandSerial);
    }
}

詳しく説明

SteamVRのドライバ(OpenVR)とのデータのやり取りを簡単に行える、Virtual Motion Tracker(VMT)というアプリケーションがあります。

gpsnmeajp.github.io

VMTを使う中で、HMD位置の取得は、以下に示す公式Sampleの、WatchHMD.csを使えば簡単にできます。 github.com

しかし、コントローラなどのHMD以外の位置取得で、デバイスのシリアル番号の確認に少し迷ったので、メモとして残しておきます。

WatchHMD.csの21行目では、デバイス情報の購読を要求しています。

client.Send("/VMT/Subscribe/Device", "HMD");

つまり、デバイス情報を定期的に送信してくれるように、OpenVRへ(VMT経由で)要求しています。この時の第2引数で、購読したいデバイスを指定します。

上記のように、HMDの場合は、"HMD"という文字列を渡せばよいです。しかし、HMD以外の、コントローラやトラッカーの場合は、各デバイスのシリアル番号を渡してやる必要があります。

このシリアル番号は、VMT Manager上で確認できます。VMT ManagerのDeviceタブを開き、Reloadを押すと、現在使用しているデバイスの一覧が出ます。このリストから、取得したいデバイスを選択し、Copy to Clipboardでシリアルを取得できます。

本記事の最初に示したコードを再掲します。これをUnity上で使用し、Inspectorに、コピーしたシリアル番号を張り付ければ、SteamVR内でのコントローラ位置が、Unity上のオブジェクトに反映されるはずです。

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

[RequireComponent(typeof(uOSC.uOscClient))]
[RequireComponent(typeof(uOSC.uOscServer))]
public class WatchHands : MonoBehaviour
{
    uOSC.uOscClient client;
    uOSC.uOscServer server;

    [SerializeField] Transform rightHand;
    [SerializeField] Transform leftHand;

    [SerializeField] string rightHandSerial = "";
    [SerializeField] string leftHandSerial = "";
    void Start()
    {
        client = GetComponent<uOSC.uOscClient>();
        server = GetComponent<uOSC.uOscServer>();

        //アドレス固定
        client.address = "127.0.0.1";
        client.port = 39570;
        server.port = 39571;
        //イベント追加
        server.onDataReceived.AddListener(OnDataReceived);
        server.onServerStopped.AddListener(OnServerStopped);
        //購読開始
        Subscribe();
    }

    public void OnDataReceived(uOSC.Message message)
    {
        if (message.address == "/VMT/Out/SubscribedDevice")
        {
            //受け取った情報がどのデバイスのものか、シリアル番号を取得
            string serial = (string)message.values[0];

            //シリアル番号を識別
            //受け取った姿勢情報を反映させるTransformをtargetに代入
            //どちらのコントローラでもなければreturn
            Transform target = null;
            if (serial == rightHandSerial)
                target = rightHand;
            else if (serial == leftHandSerial)
                target = leftHand;
            else
                return;

            //反映
            float x = (float)message.values[1];
            float y = (float)message.values[2];
            float z = (float)message.values[3];
            float qx = (float)message.values[4];
            float qy = (float)message.values[5];
            float qz = (float)message.values[6];
            float qw = (float)message.values[7];
            target.position = new Vector3(x, y, -z);
            target.rotation = new Quaternion(qx, qy, -qz, -qw);
        }
    }

    public void OnServerStopped(int x)
    {
        Unsubscribe();
    }

    private void OnDestroy()
    {
        Unsubscribe();
    }

    void Unsubscribe()
    {
        Debug.Log($"Unsubscribed:{rightHandSerial}");
        client.Send("/VMT/Unsubscribe/Device", rightHandSerial);
        Debug.Log($"Unsubscribed:{leftHandSerial}");
        client.Send("/VMT/Unsubscribe/Device", leftHandSerial);
    }

    void Subscribe()
    {
        Debug.Log($"Subscribed:{rightHandSerial}");
        client.Send("/VMT/Subscribe/Device", rightHandSerial);
        Debug.Log($"Subscribed:{leftHandSerial}");
        client.Send("/VMT/Subscribe/Device", leftHandSerial);
    }
}