LoginSignup
6
4

More than 1 year has passed since last update.

VRChatのOSC-Trackerを使ってQuest単機でフルトラを試す

Last updated at Posted at 2022-11-25

VRChat v2022.4.1にてOSC経由でのトラッカー機能が実装されました。
今回はPythonでOpenVRのトラッカーのデータを取得し、そのデータをOSC経由でQuest2のVRChatに送信してみます。
OpenVR周りはほとんど分からないので、ツッコミどころがあるかと思いますがご容赦ください...

現時点(2022/11/25)ではまだベータ版の機能なので、VRChatのクライアントはベータ版を使用する必要があります。

(SteamVRのトラッカーを使ってQuest単機でフルトラしたい人向け)
実際にプログラム書いて動かしてみたいとかでなければ、私が作ったツールの使用をおすすめします。
使い方はリンク先をご確認ください。
https://github.com/takana-v/quest_steamvr_fbt_tool

事前準備

SteamVRをHMD無しで使えるようにする

デフォルトではSteamVRはHMDが無いと動きません。
ダミーのHMDを使用するように設定を変更します。
(デフォルトではC:\Program Files (x86)\Steam\config\steamvr.vrsettingsに設定ファイルがあります)

activateMultipleDriverstrueにし、driver_nullの項目を追加します。

{
   ...(略)
   "steamvr" : {
      "activateMultipleDrivers" : true,
      ...(略)
   },
   "driver_null" : {
      "enable" : true
   },
   ...(略)
}

PCとQuestを同じネットワークに繋げる

PCから到達可能なように同じネットワークにQuestを繋げなければ、情報を送信できません。
自分の環境はこんな感じです。
以下、この環境を前提に書くので適宜読み替えてください。

[ルーター]   ----------- [PC]
192.168.11.1     |      192.168.11.2
              ------ [Quest2]
                        192.168.11.3

Pythonの環境構築

様々な流派があると思うので、好きな方法で準備してください。
私はvenvで3.8.10を使用しています。
使用するパッケージは以下の通りです。

openvr==1.23.701
python-osc==1.8.0

pipなどでインストールしておいてください。

pip install openvr python-osc

実際に試してみる

まずは、SteamVRを起動し、トラッカーなどを認識させておきます。

OpenVRのinitialize

OpenVRモジュールを使い始めるには、まずinitializeを行う必要があります。
このコードはVRChatを起動した裏で動かすプログラムから流用したので、VRApplication_Overlayを引数に指定しています。
(VRApplication_Scene辺りが本当は良さそうですが、使える関数とかが変わってきてめんどくさいので...)

import openvr
openvr.init(openvr.VRApplication_Overlay)

デバイスの確認

トラッカーが何番目のデバイスかを確認する必要があるので、情報を取得します。

i = 0
while True:
    serial = openvr.IVRSystem().getStringTrackedDeviceProperty(i, openvr.Prop_SerialNumber_String)
    if serial == "":
        break
    print(f"{i}: {serial}")
    i += 1
0: Null Serial Number
1: LHR-5BE0522E
2: LHR-4E26D999
3: LHR-D3553062
4: LHB-2866D50D
5: LHB-233754BA

LHRから始まるものがトラッカー、LHBから始まるものがベースステーションなので、1から3が目的のデバイスです。
(トラッカーの機種によって異なります。)

姿勢の取得

getLastPoses関数で、デバイスの情報が取得できます。
関数の戻り値はデバイスごとの情報のリストなので、さっき確認したトラッカーの番号を指定します。
その中のmDeviceToAbsoluteTrackingがHmdMatrix34_t型のデータなので、リストに変形して扱いやすい形にします。

poses, _ = openvr.VRCompositor().getLastPoses([], None)
pose = poses[1] # ここの1はさっき確認したデバイスの番号
m = [list(l) for l in list(pose.mDeviceToAbsoluteTracking)]
print(m)

(分かりやすいように整形済み)

[[0.5089882016181946, -0.8607551455497742, 0.005615833681076765, -0.009101968258619308], 
[0.06500636041164398, 0.03193291649222374, -0.9973737597465515, -0.2737535536289215], 
[0.8583152890205383, 0.5080165266990662, 0.07220806181430817, -2.6730711460113525]]

リンク先の通り、左の3x3の部分が回転行列、右の1x3部分が位置です。
VRChatにデータを渡すには、回転行列ではなくオイラー角にしないといけないので、こちらのページを参考に変換します。

from math import asin, atan
rot_y = asin(m[0][2])
if rot_y != 0:
    rot_x = atan(-m[1][2]/m[2][2])
    rot_z = atan(-m[0][1]/m[0][0])
else:
    rot_x = atan(m[2][1]/m[1][1])
    rot_z = 0

姿勢の方も変数に代入しておきます。

pos_x = m[0][3]
pos_y = m[1][3]
pos_z = m[2][3]

これで渡すデータの準備は完了です。

OSCでの送信

こんな感じのコードになります。
自分の場合はpos_xをマイナスにしないと正しい向きになりませんでした。
また、ルームの設定が悪いのか、トラッカーが地面に埋まってしまうので、適当にpos_y + 0.05にしています。
VRChatでOSC機能をOnにするのを忘れないようにしてください。

from pythonosc import udp_client
client = udp_client.SimpleUDPClient("192.168.11.3", 9000)

client.send_message(
    f"/tracking/trackers/1/position",
    [-pos_x, pos_y+0.05, pos_z]
)
client.send_message(
    f"/tracking/trackers/1/rotation",
    [rot_x, rot_y, rot_z]
)

実際に動くコード

このコードをちょっと変更したものです。
ビジーループが気になる方は適当にsleepを入れてください。

import math
from pythonosc import udp_client
import openvr

client = udp_client.SimpleUDPClient("192.168.XXX.XXX", 9000)

openvr.init(openvr.VRApplication_Overlay)

def get_device_index(serial):
    i = 0
    while True:
        serial_num = openvr.IVRSystem().getStringTrackedDeviceProperty(i, openvr.Prop_SerialNumber_String)
        if serial_num == serial:
            return i
        if serial_num == "":
            raise RuntimeError("指定されたデバイスが見つかりませんでした。")
        i += 1

def run_tracker_server(tracker_indexes):
    poses = []
    while True:
        poses, _ = openvr.VRCompositor().getLastPoses(poses, None)
        for i,j in enumerate(tracker_indexes):
            pose = poses[j]
            m = [list(l) for l in list(pose.mDeviceToAbsoluteTracking)]
            pos_x = m[0][3]
            pos_y = m[1][3]
            pos_z = m[2][3]
            rot_y = math.asin(m[0][2])
            if rot_y != 0:
                rot_x = math.atan(-m[1][2]/m[2][2])
                rot_z = math.atan(-m[0][1]/m[0][0])
            else:
                rot_x = math.atan(m[2][1]/m[1][1])
                rot_z = 0
            client.send_message(
                f"/tracking/trackers/{i+1}/position",
                [
                    -pos_x,
                    pos_y,
                    pos_z,
                ]
            )
            client.send_message(
                f"/tracking/trackers/{i+1}/rotation",
                [
                    rot_x,
                    rot_y,
                    rot_z,
                ]
            )

if __name__ == "__main__":
    device_serials = ["LHR-XXXXXXXX", "LHR-YYYYYYYY", "LHR-ZZZZZZZZ"]
    tracker_indexes = [get_device_index(s) for s in device_serials]
    run_tracker_server(tracker_indexes)

実際にやってみた動画

まだやってないこと

/tracking/trackers/head/にデータを送ることで、自動で位置合わせができるみたいです。
(自分はトラッカーが無いので、Auto-center OSC Trackers機能で位置合わせをしています。)

感想

やっぱりちょっとラグを感じますね...
VRChatは動かないけど、SteamVRなら動く、みたいなスペックのPCをお持ちのQuestユーザーなら有用かもしれません。
そういえばSlimeVRは公式で対応しているらしいですね。
一般ユーザーは、トラッカー公式の対応を待つのがいいかと思います。

余談

最初の方に触れた、「VRChatを起動した裏で動かすプログラム」についてです。
このプログラムは、修理中で無い左手コントローラーの代わりに仮想トラッカーを握ってることにして、フルトラ?(5点)を実現するというものです。
(両手のコントローラーが無いと強制3点になる仕様でした。)
仮想トラッカーは、こちらのVirtual Motion Trackerを使用しました。

正直OpenVRのドライバー周りを触ろうと思うと地獄なので、こちらのソフトを使用するのをおすすめします。
こんな便利なものを作ってくださった @gpsnmeajp さんには感謝してもしきれません...

リンク

公式のヘルプページ

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4