【VIVE XR ELITE】VIVE XR ELITE でパススルーを用いたゲームをハッカソンで作った話【Unity】

![220427_サムネイル_テンプレ.002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/169809af-6a32-20da-7e01-bc6312b2deb5.jpeg)
Graffity のUnityEngineerの小林です。

ついにVIVE XR ELITE が発売されましたね。
グラス型にもできてパススルーを使う事でARデバイスとしても利用可能なところが注目のデバイスですね。

去る3/25にGraffity社内で技術ハッカソンを行いました。
私の所属しているチームではHTCさん協力の元、発売前のVIVE XR ELITEの開発機をお借りして簡単なゲームを作りました。
そのときに得た知見などを紹介できればと思います。

# 作ったゲーム
<iframe width="480" height="360" src="https://www.youtube.com/embed/UyYvwIAzrBA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>



## ゲームの仕様
簡単に説明すると `現実世界に隠されたお宝(左手コントローラー)を現実世界を照らすライトを用いて探そう` というホラー＆お宝探し　なゲームです。

本当はマーカー読み込みでゴール（お宝発見）にしたかったのですが、いかんせん企画立案〜実装まで8時間しかないという制約があったため、仕方なく「ゲーム開始時に左手コントローラーをOFF」にして「左手コントローラーを見つける」ことでゴールというふうにしました

# VIVE XR ELITE でのアプリ開発話

## すごい点
### 軽量
なんたって、フルカラーパススルーを眼鏡型デバイス（ガスケット）で実現できているのがすごいです。
フルカラーパススルーだと Meta Quest Pro も機能としてはありますが、グラス型と比較すると重量が全然違います。

頭にかぶるタイプのHMDだと、どうしても「重い」と感じてしまう人が少なくないと思いますが、グラス型は本当にそのもやもや感を解消できると思います！

### パススルーの表示位置を制御可能
今回のゲームの肝となる「現実世界の表示」ですが、VIVE XR Elite のSDKである `WAVE SDK` ではパススルーの表示位置をScriptから設定する事ができます。

`Interop.WVR_ShowProjectedPassthrough(bool enable)` をコールすればパススルーの有効/無効を切り替えられ、パススルーの透過度合いは `Interop.WVR_SetProjectedPassthroughAlpha(float alpha)` で調整可能です。

個人的な体感としては透過度が中途半端の場合、アプリ内のUIとぶつかると、軽い3D酔いみたいな現象に遭遇したので、基本は0 or 1 で制御してしまった方がおすすめです。

```PassThrough.cs
using Wave.Native;

namespace Graffity
{
    public class PassThroughController : IDisposable
    {
        
        public bool IsEnable { private set; get; }
        
        public void EnablePassThrough( float alpha)
        {
            if (IsEnable) return;
            
            Interop.WVR_SetProjectedPassthroughAlpha(alpha);
            Interop.WVR_ShowProjectedPassthrough(true);
            IsEnable = true;
        }

        public void DisablePassThrough()
        {
            if (IsEnable == false) return;
            
            Interop.WVR_ShowProjectedPassthrough(false);
            IsEnable = false;
        }
    }
}
```

また、VIVE XE ELITE ではパススルーの表示位置を調整可能で、こちらは事前に表示したい形のメッシュ, UV 情報を作成して、そこに合わせてパススルーの映像をはめ込むといったやり方になります。

昔、社内で開発したときにレーダーグラフのUIを作っていたので、それを転用することで任意の正多角形のメッシュを簡単にScript で作成できるようにしておいてのは僥倖でしたw

メッシュを設定する部分は `
Interop.WVR_SetProjectedPassthroughMesh(float[] vetices, uint verticesCount, uint[] meshUVs, uint uvCount)`  で設定することができます。

vertices で作成するメッシュの頂点位置情報Vector3をそれぞれ1個ずつ(x,y,z) を格納します。
この作ったverticesをもとに頂点のUV情報をmeshUVs に入れるのですが、この辺はC#Scriptからメッシュ、UVを作る経験がない人にはかなり難しいコードになると思います。

また、メッシュを毎フレーム作るのはコスト的に非常に良くないので、今回はコンストラクタで作るようにしました。
```MeshBuilderSample.cs
public class MeshBuilderSample
{
    private float[] _vertex = null;
    private uint[] _vtxTargets = null;

    public MeshBuilderSample(ulong splitCount, float size)
    {
        // 正多角形にするので円をN等分する
        var deg = (Mathf.PI * 2) / splitCount;

        // Vector3 が使えないので.x,.y, .z格納用に3倍した長さで配列を初期化する
        _vertex = new float[splitCount * 3];
		// 頂点座標
        for (int i = 0; i < (int)splitCount * 3; i++)
        {
            var currentDeg = deg * i;
            if (i % 3 == 0)
            {
                _vertex[i] =Mathf.Sin(currentDeg) * size;
            }
            else if (i % 3 == 1)
            {
                _vertex[i] = Mathf.Cos(currentDeg) * size;
            }
            else
            {
                _vertex[i] = 0f;
            }
        }
        // UVを 円の中心, v_i, v_j という順に定義していく
        _vtxTargets = new uint[splitCount * 3];
        for (uint i = 0; i < _vtxTargets.Length; i++)
        {
            if (i % 3 == 0)
            {
                _vtxTargets[i] = 0;
            }
            else if (i % 3 == 1)
            {
                _vtxTargets[i] = i + 1;
            }
            else
            {
                _vtxTargets[i] = i + 2;
            }
        }
    }
}

```

上記で作った頂点、UV情報を先ほどのAPIである `Interop.WVR_SetProjectedPassthroughMesh` に渡す事で、任意の形状のパススルー表示エリアを作れます。

最後に、パススルー表示エリアの位置ですが、先ほどのクラスに以下のようなメソッドを生やす事で設定が可能です。
WVR_Pose_t にPositionとQuaternion のプロパティが生えているので、そこでUnityのPosition(Vector3)とRotation(Quaternion)を代入し、`Interop.WVR_SetProjectedPassthroughPose(ref pose)` で渡すことでTransform情報の更新が可能です。

```UpdatePosition.cs


        public void UpdateTransform(Vector3 position, Quaternion rotation)
        {
            WVR_Pose_t pose = new WVR_Pose_t();
            pose.position.v0 = position.x;
            pose.position.v1 = position.y;
            pose.position.v2 = position.z;
            pose.rotation.x = rotation.x;
            pose.rotation.y = rotation.y;
            pose.rotation.z = rotation.z;
            pose.rotation.w = rotation.w;
            //位置を更新
            Interop.WVR_SetProjectedPassthroughPose(ref pose);
            Interop.WVR_SetProjectedPassthroughMesh(_vertex, (uint)_vertex.Length, _vtxTargets, (uint)_vtxTargets.Length);
        }

```

なお、Scale に関しては、メッシュの頂点情報を更新する必要があるので、上記の`Interop.WVR_SetProjectedPassthroughPose(ref pose)` だけでは対応できないのでご注意ください。

## 大変だった点
### Document が不足
v5.2.0現在ですが公式ドキュメントこそありますが、Class document だったり、APIについての説明だったりが不足しています。
https://hub.vive.com/storage/docs/en-us/UnityXR/UnityXRSdk.html

また、一部のページはフォーラムへのリダイレクトで説明が終わっていたりするので、初めて開発する人はボタンの入力取得方法など基礎の基礎から確認が必要になります。

### 頻繁に床位置検出を求められる
これは開発機だったからかもしれませんが、一旦HMDをとって付け直すごとに床位置の測位を求められる事が頻発しました。
HMDの光センサーのところをガムテ等で塞ぐ事で多少は改善されますが、今度はバッテリーの消耗が激しくて充電が追いつかないという別の問題が発生するため、ここに関しては今後改善されることを期待したいと思います。

### UnityEditor につないで確認ができない
VRデバイスであればHTC Vive をはじめとしてSteamVRに接続されていれば、UnityEditorを再生すると実機デバッグができましたが、今回はapkをビルドしないとデバッグできませんでした。

VIVE Streaming 等色々試しましたがUnityEditorを再生しながらの動作チェックは厳しそうです。
これが結構困りどころで、特に「コントローラーのGameObject情報」だったり「HMDのカメラ」オブジェクトについて、正しくとれているのか検証が出来ないため試行錯誤の連続になりました。

今後、USB接続でもいいのでEditorを再生しながらデバッグができると開発が捗りそうです。


# まとめ

まだデバイスが出たばかりなので開発に関しての知見は少ない状態です。
今後様々なところが開発知見を共有していくことで、よりよい体験をつくれるようになるので、VIVE XR ELITE で開発する方はどんどん知見を共有してもらえるとありがたいです。

また、パススルーの表示位置を任意に設定できる機能はアイデア次第で様々な面白体験を作る事ができると思うので、今後も新たな体験創出のために注目のデバイスだと思います。