SteamVRの視点キャリブレーション(Recenter)をキーボードから呼び出す

 VR技術のデモをやっているとき、体験者の視点をVRシーンの中央へとリセットしたいことがあります。普通ならSteamVRのダッシュボードを開き、右下のアイコンから立ち位置のリセットを行わなければならず、体験者がVR慣れしていないときには以下のような対応が必要でした。

・体験者に視点リセット方法を教える

・SteamVRの『VRビューを表示』で表示したVRビューを見つつ、展示者がコントローラを操作して視点リセットする

 

 先日、この視点リセットをUnity内のスクリプトから呼び出す方法を知ったので書き残しておきます。

 

 ある程度前のUnity(2018以前?)なら、using UnityEngine.XRしてInputTracking.Recenter()を呼べばokです。

 

 新しい方のUnity(2020以降?)なら、以下のスクリプトのようにすればokです。これでSpaceキーを押したら視点がリセットされます。ほかのスクリプトとは独立してるので、Recenter.csを作成し、以下をコピペし、適当なオブジェクトに貼り付ければ動きます。

 

using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Management;

public class ReCenter : MonoBehaviour
{
    XRInputSubsystem xrInput;
    public void Start()
    {
        var xrSettings = XRGeneralSettings.Instance;
        xrInput = xrSettings.Manager.activeLoader.GetLoadedSubsystem<XRInputSubsystem>();
    }

    void Update(){
        if(Input.GetKeyDown(KeyCode.Space)){
            xrInput.TryRecenter();
            Debug.Log("Recenter");
        }
    }
}
 
 

 検索用キーワード:SteamVR、視点、リセット、トラッキング、Recenter、プレイスペース、センタリング、中央に配置、プレイエリアのリセット、ポジションリセンター

追記: Oculus Quest2 (Oculus Integreation)の場合
以下を参照すると上手く行った。
Recenter()のコード部分だけでも動く気がするが...?

Re: How to disable Quest recentering in Unity XR 2... - Meta Community Forums - 822781

using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Management;

public class ReCenterOculus : MonoBehaviour
{
    public Transform CameraOffset; // set to parent of main Camera

    Vector3[] mBoundary;

    public void Start()
    {
        OVRManager.display.RecenteredPose += OculusRecenter;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Delete))
        {
            Recenter();
            Debug.Log("Recenter");
        }
    }

    void RotateXZ(Vector3 a, float rot, out Vector3 b)
    {
        // rot is clockwise radians
        var sin = Mathf.Sin(rot);
        var cos = Mathf.Cos(rot);
        b = new Vector3(a.x * cos + a.z * sin, a.y, -a.x * sin + a.z * cos);
    }

    // Instead of recentering, don't recenter!
    void OculusRecenter()
    {
        var points = OVRManager.boundary.GetGeometry(OVRBoundary.BoundaryType.OuterBoundary);

        if (mBoundary != null && points.Length == mBoundary.Length)
        {
            var deltaPos0 = mBoundary[0] - points[0];
            var newPointMid = points[points.Length / 2] + deltaPos0;
            var vec0ToOldMid = mBoundary[points.Length / 2] - mBoundary[0];
            var vec0ToNewMid = newPointMid - mBoundary[0];
            var angOldMid = Mathf.Atan2(vec0ToOldMid.z, vec0ToOldMid.x);
            var angNewMid = Mathf.Atan2(vec0ToNewMid.z, vec0ToNewMid.x);
            var deltaRot = angOldMid - angNewMid;

            while (deltaRot > Mathf.PI)
                deltaRot -= 2 * Mathf.PI;
            while (deltaRot < -Mathf.PI)
                deltaRot += 2 * Mathf.PI;

            var reverse0 = -points[0];
            RotateXZ(reverse0, -deltaRot, out reverse0);
            reverse0 += points[0];
            var pos = CameraOffset.TransformPoint(deltaPos0 + reverse0);
            pos = CameraOffset.parent.InverseTransformPoint(pos);
            CameraOffset.localPosition = pos;

            var angs = CameraOffset.localEulerAngles;
            angs.y -= deltaRot * Mathf.Rad2Deg;
            CameraOffset.localEulerAngles = angs;
        }

        mBoundary = points;
    }

    public void Recenter()
    {
        mBoundary = OVRManager.boundary.GetGeometry(OVRBoundary.BoundaryType.OuterBoundary);

        Transform cam = Camera.main.transform;
        float yawOffset = -cam.localEulerAngles.y;
        float ang = -yawOffset * Mathf.Deg2Rad;
        float sinAng = Mathf.Sin(ang);
        float cosAng = Mathf.Cos(ang);
        float xOffset = cam.localPosition.z * sinAng - cam.localPosition.x * cosAng;
        float zOffset = -cam.localPosition.z * cosAng - cam.localPosition.x * sinAng;

        CameraOffset.localPosition = new Vector3(xOffset, 0, zOffset);
        CameraOffset.localEulerAngles = new Vector3(0, yawOffset, 0);
    }
}