【Unity】Apple Vision Pro でカメラに追従するUIの作り方【PolySpatial】

Graffity でUnityエンジニアをしているcovaです。

今回はXRならではのUIに作り方でAppleVisionPro + Unity ではどう作るの？という部分を解説します

# TL;DR
- 基本的にはuGUIを利用
- ImmersiveSpace の時は XRToolKit のLazyFollow を利用しよう
- Bounded Volume の時はUI操作が不要であればBillboardを使うことでカメラの方にUIを向けることが可能


# 前提知識

# 検証環境
| 項目 | version |
|---|---|
| Unity | 6000.0.23f1|
| PolySpatial | 2.0.4|
| VisionOS | 2.0 |

# VolumeApp (Bounded Volume) でのUI
大前提として VolumeApp (Bounded Volume) では Camera の情報が一切取れません。
そのためUnityのCameraを配置しても Camera.main で得られるCameraは初期位置から全く微動だにしません。

そんな条件の中で、Cameraの位置に追従してUIが常にカメラに対して正面を向くような、いわゆる `Billboard` 機能はPolySpatial (VisionOS側)から提供されています。

https://docs.unity3d.com/Packages/com.unity.polyspatial.visionos@2.0/manual/Billboard.html

## VisionOSBillboard の制約
この `VisionOSBillboard` コンポーネントを貼り付けるだけで自動でユーザー位置に追従してくれるのですが挙動については制約があります

1. 特定の軸のロックができない
1. 角度がキツくなるとuGUIの入力が正しく処理されない

### 特定の軸のロックができない

ゲーム開発で Billboard といえば、よくあるのは `Y軸` 回転のみカメラ位置に追従して、`X軸`, `Z軸` は回転させないようにすることが多いです。
しかし、本コンポーネントは公式のリファレンスに記載の通り `BlendFactor` という角度制約しか制限できず、軸に対しての制限ができません。

![スクリーンショット 2024-11-23 17.16.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/9e7167e7-408a-1d8a-8252-88cdccef1d10.png)
(公式リファレンスより引用)

そのためX軸回転やZ軸回転を許容したく無い場合は利用できないです。

かといって代替コンポーネントがあるわけでも無いため、ここは仕様の取捨選択が求められるタイミングになってしまいます。

### 角度がキツくなるとuGUI の入力が出来ない

PlayToDevice を使うとわかるのですが、Bounded な設定のときはCanvasの当たり判定ように、動的に仮想カメラを生成します
![スクリーンショット 2024-11-28 20.06.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/dff403eb-29de-b840-c107-147e0c5260b5.png)

このカメラを用いてCanvas の当たり判定を行うのですが、 `位置が固定` でかつ `遠距離` のため、Canvasが水平に近い角度になったりすると、このCameraからのRaycast に失敗して当たり判定がうまくとれなくなるようです


# ImmersiveSpace (Unbounded App) でのUI

基本的にはUnityの XR Interaction Tool Kit にある `Lazy Follow` を用います。

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.5/api/UnityEngine.XR.Interaction.Toolkit.UI.LazyFollow.html

![05_LazyFollow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/e32d3674-be82-81c2-6f3e-004a565da87b.png)

ImmersiveSpace 向けの場合は XROrigin オブジェクトがいるはずなので、その配下にあるMainCamera をLazyFollow のTarget に設定すると動くようになります。

![06_MainCam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/bc0df50f-0aee-dd71-d20b-753ca4be53fc.png)

## 位置追従の場合

Lazy Follow の `Position Follow Mode` を `Follow` にすると、頭部位置を動かす度に Target Offsetをかけた相対位置に追従してくれます。
![07_FollowPos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/c349fbfc-1f8e-8182-a49d-0548bf8971e7.png)

Distance やTime Until Threashold Reaches... は追従するときの距離や時間のパラメータなので、適度に邪魔にならない程度に設定すると良いです。


## 方向(回転)追従の場合

回転にも追従させる場合は `RotationFollowMode` を `None` 以外に設定します。

![08_FollowRot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/a7bb4cc1-b0c1-0b45-ee63-319e6ea27b8e.png)

| Mode | 説明 |
| --- | --- |
| Look At | Quatertion.LookAt(target) と同じ挙動で、常にTargetの方を向きます |
| Look At With World Up| Y軸回転のみ追従するLookAt です。 X軸, Z軸回転は固定です |
| Follow | ターゲットと同じRotationをスムーズに維持します。 |

一般的なBillboardを実現するには `Look At With World Up` を使っておけば、角度によってはUIが見えないといった事象を防ぎやすいのでおすすめです.



# まとめ
- VisionOS は通常はプライバシー保護の観点からカメラの位置をDeveloper には通知しません
- Billboard 的なUIを作るにはアプリのモードによってコンポーネントの使い分けが必要です。
    - Bounded App : VisionOSBillboard
    - Unbounded App : Lazy Follow