Unity+HTC Vive開発メモ

最終更新日:2018年02月05日

UnityでHTC Vive対応ソフトを開発する方法やTipsをまとめています。HTC Viveの概要やLighthouseについては、「HTC Viveについて」を参照してください。

更新履歴

(2018年2月4日)SteamVRプラグインを使用しない方法についての記述を「Unity標準のVR機能(UnityEngine.XR)メモ」に分離、「簡単にフェードイン・フェードアウトをするには」を追加
(2017年12月11日)「SteamVRプラグインでWindows Mixed Realityヘッドセットを使用するには」を追加
(2017年12月10日)「トラッキングしているオブジェクトの種類を取得するには」「SteamVRプラグインにUnityの設定を変更されないようにするには」を追加
(2017年12月8日)「SteamVRプラグインでOculus Riftを使用するには」を追加


UnityでHTC Vive対応ソフトを作るには

UnityでHTC Vive対応ソフトを作るには、Asset StoreにあるSteamVRプラグインを使用します。新規プロジェクトを作成し、SteamVRプラグインをインポートしてください。

プラグインをインポートすると、SteamVR_Settingsというウィンドウが開きます。これによって、Unityのプロジェクトの設定がSteamVRの推奨設定に変更されます(64ビットビルド、非フルスクリーン、起動ダイアログを出さない、カラースペースをリニアにする等)。問題ないようでしたら、左下の「Accept All」をクリックしてください。「You made the right choice!(正しい選択をしたね!)」というノリノリのメッセージウィンドウが出ますので閉じます。

試しにサンプルシーンを開きます。Projectビューで、SteamVR/Scenes/exampleシーンを開き、再生してください。上手くいけば、HTC Viveの画面にたくさんの立方体とモーションコントローラが表示されているはずです。

また、既存のプロジェクト・シーンをHTC Vive対応にするには、Main Cameraを削除または無効化して、ProjectビューのSteamVR/Prefabs/[CameraRig]プレハブをシーンの床の中央となる位置に配置してください。

なお、Unityでシーンを再生する際にはGameビューの「Maximize on Play」をオンにしないと(エディタ画面の描画のため)フレームレートが大幅に低下しますので注意してください。


位置トラッキングについて

HTC Viveでは、ルームスケールもしくは着席状態のいずれかのトラッキングスペースを使用できます。デフォルトではルームスケールになっています。

トラッキングスペースを変更するには、SteamVR/Prefabs/[SteamVR]プレハブをシーンにドロップしてください。[SteamVR]のTracking SpaceでTracking Universe StandingまたはTracking Universe Seatedを選択します。

Tracking Universe Standing(デフォルト)

ルームスケールのモードです。[CameraRig]を配置した場所が、HTC Viveのルームセットアップで設定したプレイエリアの中央の地面になります。

通常、[CameraRig]を(0, 0, 0)等に配置してその周辺にシーンを構築することになるかと思います。[CameraRig]のTransformを変更するとプレイエリアを移動できます。

Tracking Universe Seated

着席モードです。[CameraRig]をVR空間のプレイヤーの頭の位置となる場所に配置します。

位置合わせをするには、ヘッドセットをかぶってViveコントローラのシステムボタン(電源ボタン)でダッシュボードを表示し、右下の「着席位置のリセット」をポイントしてリセットします。

また、SteamVR.instance.hmd.ResetSeatedZeroPose関数でもリセットできますので、これをキーやボタン等に割り当てておくと便利です。サンプルスクリプトです。適当なGameObjectにアタッチして使用してください。

using UnityEngine;

public class Recenter : MonoBehaviour
{
    void Update()
    {
        // 着席モードでRキーで位置トラッキングをリセットする
        if (Input.GetKeyDown(KeyCode.R))
        {
            SteamVR.instance.hmd.ResetSeatedZeroPose();
        }
    }
}

なお、この関数はルームスケールのモードでは効果がないので注意してください。


Viveコントローラ編

Viveコントローラの位置を取るには

[CameraRig]プレハブ下にコントローラのゲームオブジェクト(Controller (left)、Controller (right))があり、Transformで位置や向きを取得できます。

トリガーやボタンの入力を取るには

トリガーやボタンは、コントローラのゲームオブジェクトにアタッチされているSteamVR_TrackedObjectコンポーネントからデバイスを参照して、GetTouch / GetPress関数で状態を、GetTouchDown / GetTouchUp / GetPressDown / GetPressUp関数で状態変化を取得できます。

以下にサンプルスクリプトを示します。ControllerExample.csという名前でController (left)またはController (right)にアタッチしてください。

using UnityEngine;

public class ControllerExample : MonoBehaviour
{
    void Update()
    {
        var trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("トリガーを浅く引いた");
        }
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("トリガーを深く引いた");
        }
        if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("トリガーを離した");
        }
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("タッチパッドをクリックした");
        }
        if (device.GetPress(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("タッチパッドをクリックしている");
        }
        if (device.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("タッチパッドをクリックして離した");
        }
        if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("タッチパッドに触った");
        }
        if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("タッチパッドを離した");
        }
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.ApplicationMenu))
        {
            Debug.Log("メニューボタンをクリックした");
        }
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
        {
            Debug.Log("グリップボタンをクリックした");
        }

        if (device.GetTouch(SteamVR_Controller.ButtonMask.Trigger))
        {
            //Debug.Log("トリガーを浅く引いている");
        }
        if (device.GetPress(SteamVR_Controller.ButtonMask.Trigger))
        {
            //Debug.Log("トリガーを深く引いている");
        }
        if (device.GetTouch(SteamVR_Controller.ButtonMask.Touchpad))
        {
            //Debug.Log("タッチパッドに触っている");
        }
    }
}

トリガーの入力をアナログ値で取るには

GetAxis関数でトリガーのIDを指定して、戻り値のVector2のxで取得できます。サンプルスクリプトを示します。TriggerExample.csという名前でController (left)またはController (right)にアタッチしてください。

using UnityEngine;

public class TriggerExample : MonoBehaviour
{
    void Update()
    {
        var trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        var value = device.GetAxis(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger).x;
        Debug.Log(value);
    }
}

戻り値は0から1の範囲になります。トリガーをクリック直前まで引くと0.83前後まで上がり、カチッとクリックすると1にジャンプするようです。ただし、コントローラーの個体によってはきちんと1にならないという話があります。また、トリガーを離しても完全に0に戻らないことがあります。

タッチパッドのタッチ位置を取るには

GetAxis関数で取得できます。サンプルスクリプトを示します。TouchpadExample.csという名前でController (left)またはController (right)にアタッチしてください。

using UnityEngine;

public class TouchpadExample : MonoBehaviour
{
    void Update()
    {
        var trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        var position = device.GetAxis();
        Debug.Log("x: " + position.x + " y: " + position.y);
    }
}

GetAxisの戻り値は、X軸が-1(左)から1(右)、Y軸が-1(下)から1(上)です。また、タッチパッドから指が離れていると(0, 0)が返ってきます。

GetPressDown(SteamVR_Controller.ButtonMask.Touchpad)と組み合わせて、どのあたりをクリックしたかを取ることもできます。端の方は取れないため注意です。

Viveコントローラの表示を消すには/違うオブジェクトを表示するには

コントローラのオブジェクトの子のModelにアタッチされているSteam VR Render Modelスクリプトのチェックを切って無効化します。

異なるオブジェクトを表示するには、Controller (left)またはController (right)に子オブジェクトをアタッチします。

Viveコントローラを振動させるには

TriggerHapticPulse関数を使います。引数で振動の大きさを指定します。最大3999まで機能しますが、100~2000ほどが有効範囲かなと思います(もう少し要調査)。

サンプルスクリプトを示します。HapticFeedbackExample.csという名前でController (left)またはController (right)にアタッチしてください。

using UnityEngine;

public class HapticFeedbackExample : MonoBehaviour
{
    void Update()
    {
        var trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            // タッチパッドに触れた
            device.TriggerHapticPulse(500);
        }

        if (device.GetPress(SteamVR_Controller.ButtonMask.Trigger))
        {
            // トリガーを深く引いている
            device.TriggerHapticPulse(1000);
        }
        else if (device.GetTouch(SteamVR_Controller.ButtonMask.Trigger))
        {
            // トリガーを浅く引いている
            device.TriggerHapticPulse(100);
        }
    }
}

なお、振動させつづけるとバッテリーを大きく消耗しますので注意してください。

Viveコントローラを3つ以上使用するには

Viveコントローラは無線で2つまでしか(標準状態では)ペアリングできませんが、USBケーブルで接続すると3つ目以降のコントローラを使用できます。

SteamVRプラグインで3つ目以降のコントローラを認識・表示させるには、[Camera Rig]のController (left)またはController (right)を同じ階層に複製して、[CaneraRig]のSteam VR_Controller ManagerのObjecftsのSizeを0から増やして、複製したコントローラのオブジェクトを入れてください。

SteamVRプラグインでは、ヘッドセットと2つのViveコントローラーを含めて最大16オブジェクトまでトラッキングできるようです。

トラッキングしているオブジェクトの種類を取得するには

以下の要領で、SteamVR_TrackedObjectでトラッキングしているオブジェクトの名前(正確にはSteamVR_RenderModelで描画に使用するモデルの名前)が取得できます。

using System.Text;
using UnityEngine;
using Valve.VR;

public class RenderModelName : MonoBehaviour
{
    [SerializeField]
    SteamVR_TrackedObject trackedObject;

    public string GetRenderModelName()
    {
        var buffer = new StringBuilder(256);
        var error = ETrackedPropertyError.TrackedProp_Success;

        SteamVR.instance.hmd.GetStringTrackedDeviceProperty((uint)trackedObject.index, ETrackedDeviceProperty.Prop_RenderModelName_String, buffer, (uint)buffer.Capacity, ref error);

        if (error != ETrackedPropertyError.TrackedProp_Success)
        {
            return null;
        }
        return buffer.ToString();
    }
}

Oculus Touchなら”oculus_cv1_controller_left”などが返ってきます。


Viveトラッカー編

Viveコントローラーのほかに、小さいViveトラッカーが販売されています。これを物体等にくっつけることで位置を取得できます。カメラマウンタもついています。

Viveトラッカーをペアリングするには

トラッカーひとつにつき付属のドングルひとつが必要です。ドングルをPCに接続してください。できれば、付属のUSBケーブルとクレードルを使用してドングルをPCから離すと認識が良くなります。

トラッカー中奥の電源ボタンをクリックして電源を入れ、Steam VRのメニューで「デバイス>コントローラーのペアリング」を実行し、トラッカーの電源ボタンを長押しして青点滅状態にしてください。

ペアリングに成功するとSteam VRでトラッカーの形をしたアイコンが表示されます。

UnityでViveトラッカーを使用するには

Unityでは上記「Viveコントローラを3つ以上使用するには」と同じ方法で使用できます。

Viveトラッカーのステータスライトについて

ステータスライトの表示は以下のようになっています。

状態
接続(ペアリング済み)
未接続
青点滅 ペアリング中
赤点滅 バッテリー残量低下
オレンジ 充電中

電源ボタンの操作について

トラッカー中央の電源ボタンで以下の操作が可能です。

操作 効果
押す 電源オン
5秒間押す 電源オフ
5秒弱押す ペアリング(青点滅になる)
PCにUSB接続して10秒間押す ハードウェアリセット

その他

Steam VRを終了するとトラッカーの電源は自動的にオフになります。

Pogoピンを使用する場合の開発者向けのドキュメントがこちらにあります。


SteamVRプラグインでOculus Riftを使用するには

Steam VRはOculus Rift+Touchにも対応しています。Oculus Appを起動した状態でStaem VRを起動すると、下記画像のように認識表示されます。

SteamVRプラグインでOculus Riftを使用できるようにするには、XR SettingsのVirtual Reality SDKsでOpenVRをOculusよりも上にしてください。

トラッキングモードの変更や振動機能も動作します。ViveコントローラとTouchではコントローラのマッピングが異なることに注意です。

なお、Oculus StoreにリリースするにはOculusのSDKとUtilitiesのみ使用しなければならないという話があります(未確認)。

HTC ViveかOculus Riftか判別するには

SteamVR.instance.hmd_TrackingSystemNameで、HTC Viveなら”lighthouse”、Oculus Riftなら”oculus”が取得できます。

エディタからの再生でOpenVRの初期化に失敗する

タスクマネージャーで一度SteamVRを落とすと復活できるようです(不安定でつらい)。

Touchのトリガーやボタンの状態を取るには

下記のようなマッピングになっています。Viveコントローラと異なり、タッチパッドがスティックになっている等、若干違いがあることに注意してください。

A/XボタンはEVRButtonId.k_EButton_Aで取得できるとのことなのですが、正常に動作しない(?)ようです(B/Yボタンや中指トリガーに反応してしまう)。

using UnityEngine;
using Valve.VR;

public class TouchExample : MonoBehaviour
{
    void Update()
    {
        var trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("人差し指トリガーに触った");
        }
        if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("人差し指トリガーを離した");
        }
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("人差し指トリガーを引いた");
        }
        if (device.GetPressUp(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("人差し指トリガーを引いて離した");
        }

        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("スティックをクリックした");
        }
        if (device.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("スティックをクリックして離した");
        }
        if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("スティックに触った");
        }
        if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Debug.Log("スティックを離した");
        }

        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
        {
            Debug.Log("中指トリガーを引いた");
        }
        if (device.GetPressUp(SteamVR_Controller.ButtonMask.Grip))
        {
            Debug.Log("中指トリガーを引いて離した");
        }

        // A/Xボタンは正しく認識されない?
        //if (device.GetTouchDown((int)EVRButtonId.k_EButton_A))
        //{
        //    Debug.Log("A/Xボタンに触った");
        //}
        //if (device.GetPressDown((int)EVRButtonId.k_EButton_A))
        //{
        //    Debug.Log("A/Xボタンをクリックした");
        //}

        if (device.GetTouchDown(SteamVR_Controller.ButtonMask.ApplicationMenu))
        {
            Debug.Log("B/Yボタンに触った");
        }
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.ApplicationMenu))
        {
            Debug.Log("B/Yボタンをクリックした");
        }

        //if (device.GetPress(SteamVR_Controller.ButtonMask.Trigger))
        //{
        //    //Debug.Log("人差し指トリガーを引いている");
        //}
        //if (device.GetPress(SteamVR_Controller.ButtonMask.Grip))
        //{
        //    Debug.Log("中指トリガーを引いている");
        //}
    }
}

人差し指トリガーのアナログ値とスティックの方向については、Viveコントローラーと同じ要領で取得できます。

SteamVRプラグインでWindows Mixed Realityヘッドセットを使用するには

Windows Mixed Reality for SteamVRをインストールすると、SteamVRプラグインでWindows Mixed Realityヘッドセットを使用できるようになります。モーションコントローラーにも対応しています(振動機能は動かないようです)。

その他

イメージエフェクトを使うには

SteamVRプラグインでは、[CameraRig]の中のCamera (eye)にイメージエフェクトのスクリプトをアタッチしてください。

Unityの新しいPost Processing Stackのスクリプト(Post Processing Behaviour)も使用できます。AntialiasingのTemporal Anti-aliasing、Screen Space Reflection、Motion BlurはVRでは動作しないので注意です。

PCの画面に別視点の映像を表示するには

カメラをシーンに追加し、Target Eyeを「None (Main Display)」に設定してください。また、余分なAudio Listenerを削除してください。実行すると別視点の画面がPC側に表示されます。カメラのdepth値に注意してください。なお当然ですが、一画面余分にレンダリングしますので、そのぶん負荷が増えます。

ネットゲームの要領で別のPCで表示することを検討してもいいかもしれません。

SteamVRプラグインにUnityの設定を変更されないようにするには

SteamVR/Editor/SteamVR_Settings.csスクリプトを拡張子を変えるなどして無効化してください。

簡単にフェードイン・フェードアウトをするには

SteamVRプラグインに入っているSteamVR_FadeコンポーネントをCamera (eye)オブジェクトにアタッチすると、簡単にフェードアウトやホワイトアウトができます。下に視界を白くフラッシュさせる例を示します。なお、これはヘッドセット内の映像にしか影響しないため注意してください。

// 即座に画面を真っ白に
SteamVR_Fade.Start(Color.white, 0f);
// 2秒かけてクリアに戻す
SteamVR_Fade.Start(Color.clear, 2f);

シーンを切り替えるときに白い部屋が表示される

SceneManager.LoadSceneでシーンを普通に変更すると、HTC Viveの画面が一瞬アプリを起動していないときのデフォルト画面になる場合があります。メインのシーンを切り替えない作りにするか、または、SteamVRプラグインにSteamVR_LoadLevel.csというサポートスクリプトが入っていますので、こちらを使用してみてください。

SteamVR_LoadLevel.Begin(”[シーン名]“)でシーンを切り替えられるほか、いくつかの機能があるようです。

エディタ上で再生するとカクつくんだけど

Gameビューの「Maximize on Play」をオンにして再生してみてください。最大化せずに再生すると、インスペクタの表示などが負荷になり、フレームレートが低下します。

レンダリング解像度を変更するには?

GPUの描画負荷は、描画するピクセル数と、ピクセルあたりのシェーダの複雑さに比例することが多いです。パフォーマンスが出ない場合、eyeTextureResolutionScaleでレンダリング解像度を下げると効果がある場合があります。

UnityEngine.XR.XRSettings.eyeTextureResolutionScale = 0.5f; // デフォルトは1.0f

VR空間でシーンを編集するには

UnityからEditorVRというVR空間で直接シーンを編集できる実験ビルドが提供されています。「Unity EditorVRについて」ページを参照してください。

その他のTips

SteamVRの設定>パフォーマンス>「フレームタイミングを表示」をクリックすると、フレームごとのCPU・GPUの処理時間のグラフが表示されます。解説がこちらのページにあります。


参考リンク

書いた人:こりん(@korinVR
VR開発メモトップ