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
に設定ファイルがあります)
activateMultipleDrivers
をtrue
にし、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 さんには感謝してもしきれません...
リンク
公式のヘルプページ