AR Foundationを導入する上で学んだこと

# はじめに
この記事は[QualiArts Advent Calendar 2020](https://qiita.com/advent-calendar/2020/qualiarts)の21日目の記事になります。

クライアントエンジニアの山本優威というものです。
今回はUnity製のAR開発Packageである[ARFoundation](https://unity.com/ja/unity/features/arfoundation)を実際にプロジェクトで使って見て、ハマったことやこうやって使ってみたら上手く行ったという話を書いていきます。
後半の内容はARFoundationを触ったことがある人向けになっています。

# 環境
* Unity 2019.4.9f
* ARFoundation 2.1.10
* ARCore XR Plugin 2.1.2
* ARKit XR Plugin 2.1.2

主にスマートフォン向けにAR機能を使いたかった為、ARCoreとARKitを導入しています。

> 今回はFace Trackingを使わない想定だったので、ARKit Face Trackingは入れていません。
> ただし使うのであれば「ARKit XR Plugin 2.1.2」と合わせて「ARKit Face Tracking 1.0.2」
> を入れれば使用することができます。

# 導入するにあたって
他の記事で導入部分については触れられているので大まかなことは割愛します(Package ManagerからInstallする部分など)
そこでARFoundationを導入するにあたり、どのバージョンをインストールすればいいかという点を見ていきます。

今回はARFoundation 2.1.10を使用するので、そのバージョンのManualを確認します。
確認するサイトは[About AR Foundation](https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@2.1/manual/index.html)というUnityが用意しているサイトです。

### プラットフォームの機能確認
**Feature Support Per Platform**という項目からARCore/ARKit/MagicLeap/HoloLensに対応している機能がそれぞれ確認することができます。
![ARFoundation_Feature.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/1a1c25b2-049c-3d53-dc5b-44f4053405c3.png)

### プラットフォームのパッケージバージョン確認
**Supported Platform Packages**という項目からARCore/ARKit(XR Plugin/Face Trackingは別)/MagicLeap/HoloLensの対応パッケージバージョンを確認することができます。
![ARFoundation_Package.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/5860b6b5-df10-7ab7-5d14-3bf058a08ca9.png)

# 対応機種の確認(ARCore/ARKit)
ARCore/ARKitに関しては端末ごとに使用可能/不可の機能があるので、その点は注意して実装した方が良いです。
[jyuko](https://www.jyuko49.com/about)という方がARについて詳しく記事を書かれているので、対応機種を調べる際はよく参考にさせていただきました。

### ARCoreの対応機種確認
Google DeveloperサイトからHome > Products > ARCore > Discover - [ARCore supported devices](https://developers.google.com/ar/discover/supported-devices#google_play)
ここから最低限必要なデバイス情報を確認することができます。

[Android (Google Play)](https://developers.google.com/ar/discover/supported-devices#google_play)
>The Android devices listed here support ARCore via Google Play Services for AR, which enables augmented reality (AR) experiences built with an ARCore SDK, if the following conditions are met:
>1. The device originally shipped with the Google Play Store
>2. The device is running the minimum Android version listed in table below
>　 If no version is listed, the device must be running Android 7.0 or newer
>　・"AR Optional" apps must declare minSdkVersion ≥ 14 (API Level 14)
>　・"AR Required" apps must declare minSdkVersion ≥ 24 (API Level 24)

**簡単にまとめるとAndroid 7.0(Nougat)以降, APIレベル24以上の端末が必要とのこと**
こちらもですがARCoreの一部機能は対応している端末とそうでない端末があるとのことなので、その都度確認が必要。
端末ごとの確認については親切に[Device list (table)](https://developers.google.com/ar/discover/supported-devices#google_play_devices)としてまとめてあるようなので、自分の端末が何に対応しているかを確認しておくと良さそう。


### ARKitの対応機種確認
Apple DeveloperサイトからARKit > [デバイスのサポートとユーザーの許可の確認](https://developer.apple.com/jp/documentation/arkit/verifying_device_support_and_user_permission/#overview)
ここから最低限必要なiOSバージョン/プロセッサを確認することができます。
>ARKitには、iOS 11.0以降と、A9以降のプロセッサを搭載したiOSデバイスが必要です。ARKitの機能の中には、より新しいバージョンのiOSや特定のデバイスが必要なものもあります。また、ARKitはデバイスのカメラを使うので、iOSのプライバシー管理を設定し、ユーザーがAppのカメラアクセスを許可できるようにする必要があります。

>デバイスの互換性サポートを扱う方法は、AppでどのようにARKitを使うかによって異なります。

>・Appの基本的な機能にARが必要な場合（バックカメラを使用）： 
>AppのInfo.plistファイルのUIRequiredDeviceCapabilities（英語）セクションに、arkitキーを追加します。このキーを使用すると、ARKit対応デバイスでのみAppを利用できるようになります。
>・拡張現実がAppの二次的な機能の場合：
>適切なARConfigurationisSupported（英語）サブクラスのisSupported（英語）プロパティをテストすることによって、現在のデバイスが使いたいAR設定をサポートしているかどうか確認します。
>・AppがフェイストラッキングARを使う場合： 
>フェイストラッキングには、iPhone Xの前面TrueDepthカメラが必要です。Appはほかのデバイスでも利用可能なので、ARFaceTrackingConfiguration（英語）をテストする必要があります。isSupported（英語）プロパティで、現在のデバイスがフェイストラッキングをサポートしているかどうか判断できます。

**簡単にまとめるとARKitを使用するにはiOS11.0以降, [A9](https://ja.wikipedia.org/wiki/Apple_A9)以降のプロセッサを搭載している端末、つまりはiPhone6s以降の端末が必要とのこと**
こちらもですがARKitの一部機能は対応している端末とそうでない端末があるとのことなので、その都度確認が必要。
※ ARKitのバージョンによって最低iOSバージョンは変わります

参考：[ARKit/ARCore対応デバイス、対応機能一覧](https://www.jyuko49.com/entry/2019/06/06/224209#%E5%AF%BE%E5%BF%9C%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E4%B8%80%E8%A6%A7)

# ARSessionStateの動作
ARFoundationにはAR機能が使用できるかどうかを確認するARSessionStateという列挙型があります。
各Stateの情報については[About AR Foundation](https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@2.1/manual/index.html)で確認することができます。
![ARFoundation_session.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/541e5583-aa6a-a73b-7060-369eda263636.png)
また端末がAR機能に対応しているかどうかのチェック方法も記載されている為、非常に便利です。
![ARFoundation_support.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/fd87d019-47ad-4c9b-c2ce-69d710710d32.png)
ここで気を付けないといけないのが、もしAR機能が使えたとしてもARCameraが反応しなかった場合です。私もここで一度困ったのですが、この場合Stateの動きが**None → CheckingAvailability → None**となってしまいます。AR機能が使える端末である為、Unsupportedにはなりません。
(正常に動作した場合のStateの動きは**None → CheckingAvailability → Ready**)

そこでARFoundationが提供しているソースを改変して以下のようにしてみます。

```c#:ARSessionSupport.cs
public class ARSessionSupport {
    [SerializeField] ARSession m_Session;

    IEnumerator Start() {
        if ((ARSession.state == ARSessionState.None) || (ARSession.state == ARSessionState.CheckingAvailability))
        {
            yield return ARSession.CheckAvailability();
        }

        if(ARSession.state >= ARSessionState.Ready)
        {
            m_Session.enable = true;
        }
        else if (ARSession.state == ARSessionState.None)
        {
            // エラー処理
        }
        else if (ARSession.state == ARSessionState.Unsupported)
        {
            // サポート対象外処理
        }
        else 
        {
            // 何らかの処理
        }
    }
}
```

みたいな感じで```ARSessionState.Unsupported```以外のフォローもする形にすれば、動作する上で少しは安全性が増します。必要ならば```ARSessionState.NeedsInstall```と```ARSessionState.Installing```の場合もフォローする感じで良いと思います。その場合はif文ではなくてswitch文とかにした方が見易そうですね。

# MainCameraとの競合
ARFoundationを使うには以下の画像のようにARSession, ARSessionOrigin, その子としてARCameraを用意しなければなりません。
![Unity_ARCamera_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/798a5601-ca7e-9ec7-2555-0f7379ec42f7.png)
プロジェクトなどでAR機能を主な目的とせず一つのコンテンツ/機能として組み込む場合、ARCamera以外にも普通のMainCameraがあると思います。
その為、AR機能を使用しようとするとMainのカメラが2つ同時に存在してしまいARCameraの方が機能しないといった現象になりました。当たり前といえば当たり前なのですが、プロジェクト単位の開発となってくると同一シーンで複数のカメラを使用することも少なくないと思います。
ですので、ARCameraを使用する際はMainCameraの機能をオフにした後にARCameraを生成 or アクティブにする手順を踏む必要があります。

# Plane Trackingオブジェクトの非表示と追従停止
AR Foundationの機能の一つでPlane Tracking(平面検知)を使用した際に、ARCameraで追従,検知したPlaneObjectが生成されるかと思います。
そこで何らかの理由でPlaneObjectを常に非表示(非アクティブ)にしたい場合、PlaneObjectの非表示方法はARFoundationのサンプルプロジェクトの```SimpleAR > PlaneDetectionController.cs```にもあるのですが、以下のコードになります。

```c#:PlaneDetectionController.cs
    /// <summary>
    /// Iterates over all the existing planes and activates
    /// or deactivates their <c>GameObject</c>s'.
    /// </summary>
    /// <param name="value">Each planes' GameObject is SetActive with this value.</param>
    void SetAllPlanesActive(bool value)
    {
        foreach (var plane in m_ARPlaneManager.trackables)
        {
            plane.gameObject.SetActive(value);
        }
    }
```

これを呼び出すことで表示されている全てのPlaneObjectを非表示にすることができます。ただし、実際に試してみるとPlaneObjectは確かに非表示になったのですが***その呼び出した時に検知しているPlaneObjectのみ***になります。追加で検知した物は表示されてしまいます。
そこで追加で検知したくない場合は

* ```ARPlaneManager.cs```のenable = falseにする
* ```ARPlaneManager > PlaneDetectionMode```をNoneに変更

以上のことをすることで追加で検知はしなくなります。

# Ray TrackingのARRaycastHit格納順
こちらはARFoundationのサンプルプロジェクトの```SimpleAR > PlaceOnPlane.cs```を見て気になった部分になります。
以下のコードはサンプルプロジェクトの検知した平面に向け、Rayと衝突した部分(点)の部分にオブジェクトを生成する処理になります。

```c#:PlaceOnPlane.cs
    static List<ARRaycastHit> s_Hits = new List<ARRaycastHit>();

    void Update()
    {
        if (!TryGetTouchPosition(out Vector2 touchPosition))
            return;

        if (m_RaycastManager.Raycast(touchPosition, s_Hits, TrackableType.PlaneWithinPolygon))
        {
            var hitPose = s_Hits[0].pose;

            if (spawnedObject == null)
            {
                spawnedObject = Instantiate(m_PlacedPrefab, hitPose.position, hitPose.rotation);
            }
            else
            {
                spawnedObject.transform.position = hitPose.position;
            }
        }
    }
```

ここで気になるのが```var hitPose = s_Hits[0].pose;```の部分です。
何故s_Hits[0].poseを取得しているかと言うと```m_RaycastManager.Raycast(touchPosition, s_Hits, TrackableType.PlaneWithinPolygon)```の部分で、**s_Hitsにカメラから見たときに衝突点が近い順に格納している**からです。逆に言えば最後に格納している衝突点はカメラから遠い点ということです。
と言っても一番遠い点が取得できると知ったところで使う機会があるかどうかは分かりませんが...。

# ARSessionOriginの大きさ調整
MainCameraとの競合話でARSessionOriginを用意する必要があると話したのですが、そもそも何を管理しているものかと言うとARセッションスペースをUnityのワールドスペースに変換してくれるものになります。簡単な話、ARSessionOrigin自体のScaleを変更することで、Unityのワールドスペースの大きさ(見え方)が変わるということです。
意外とこれが便利で生成したオブジェクトが大きすぎたり小さすぎたりした時に、実際の現実空間に合わせてAR空間のサイズを調整できます。

ただし一点だけ注意が必要なのが、ARのワールドスペースを大きく(Scaleの数値を上げる)ということは空間を広げるということですのでUnityのワールドスペースでの見え方が小さくなるので、そこら辺をあべこべにしないように気をつけてください。
またScaleだけでなくRotationも変更することができるので、ここら辺は実装に合わせて変更できるようにすると良いですね。

# 最後に
まだAR開発について触れ始めたばかりですので間違ったことを書いていたら申し訳ありません。ただ一つ言えるのはAR Foundationはかなり初心者にも優しく、ほぼコードを書かなくてもAR開発ができてしまう代物です。
この記事を見て一人でもAR開発に興味を持ってくれるエンジニアがいれば良いなと思います。