【Unity(C#)】withARハッカソンでNreal向けARアプリ開発して得た知見まとめ

## withARハッカソン
withARハッカソンに参加してきました。下記のようなイベントです。

>withARハッカソンは、ARエンジニアと異業種のコラボレーションを通じ、新しい領域に挑戦するハッカソンです。
略)

>私たちwithARの目的は「AR界を、開く。」ことです。
テクノロジーは全ての業界、全ての人々に開かれたものでなければなりません。現在は、このハッカソンを通してARの可能性を広め、ARが様々な業界にとって「選択肢のひとつ」になる世の中を目指しています。そして私たちはAR界が開かれた先、5年後の社会のあるべき姿として、2025年大阪・関西万博のパビリオンをARで彩ることを目標に活動しています。


引用元：https://withar.jp/

今回は伝統工芸品を販売されている["とらくら"](https://torakura.thebase.in/about)さんとのコラボです。

私の作品はありがたいことに2位を頂きました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">WithARハッカソン終了しました！<br><br>『贈りたくなる陶器』というテーマに沿ってエモさを重視したARを作りました！<br><br>・自然の力<br>・生命の誕生、尊さ<br>・職人の想い、魂<br>　<br>を作品に込めました‼️<br><br>Nreal、ios・Android端末で動きます(グラスが一番エモい😎)<a href="https://twitter.com/hashtag/%E4%BC%9D%E7%B5%B1%E5%B7%A5%E8%8A%B8withAR%E3%83%8F%E3%83%83%E3%82%AB%E3%82%BD%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#伝統工芸withARハッカソン</a><a href="https://twitter.com/hashtag/withAR%E3%83%8F%E3%83%83%E3%82%AB%E3%82%BD%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#withARハッカソン</a> <a href="https://t.co/szkRyWorpI">pic.twitter.com/szkRyWorpI</a></p>&mdash; KENTO⚽️XRエンジニア😎Shader100記事マラソン挑戦中30/100 (@okprogramming) <a href="https://twitter.com/okprogramming/status/1393821539561218051?ref_src=twsrc%5Etfw">May 16, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


この作品はNreal、ios、Androidで動作します。

Nrealでの開発は初めてだったので、得た知見、詰まった箇所をまとめます。

## バージョン情報
NRSDK 1.2.1
Unity 2019.2.18f1

## 導入
NrealにはDevKitと呼ばれるものが販売されています。
【参考リンク】：[Nreal Developer](https://developer.nreal.ai/dev-kit)
開発に使用するNreal Computing Unitが付属しています。

コンシューマー向けにはNreal Computing Unitが付属していない
スマホとの接続を前提としたデバイスが販売されています。
[Nebula](https://play.google.com/store/apps/details?id=ai.nreal.nebula.universal) というアプリをスマホにインストールして使用するようです。

今回はDevKitを使用しました。
まずはSDKを開発者ポータルからDLします。
しかし、ここで私は詰まりました。

NRSDK1.5.7をUnity2019.4系にインポートして試したのですが、**"Please connect Nreal Light glasses"**というエラーが出てアプリを起動するに至りませんでした。

結局打開策が見つからなかったので、最新のSDKからバージョンを落として試そうとしたのですが、
下記のようにポータルからDLすることができませんでした。

![NrealSDK_TryDL.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/5a7f1347-8838-ebd7-d61a-230ea776e0db.gif)


最終的に公式の開発者[Slackコミュニティーに有志の方がver1.2.1のSDKをGoogleDriveにアップロードされていた](https://drive.google.com/file/d/1B1UIs83fFsnWSKjO4rF2mJUOmmHeabxQ/view)ので、DLしてきて検証しました。
最終的にUnityは2019.2系、NRSDKは1.2.1という組み合わせで上手くいきました。

この問題は2021年4月頃に公式Slackコミュニティーでも検証バージョンこそ違いますが同様の書き込みがありました。

>My compute unit auto prompts me when there is an update when I plug the glasses in. If I go to settings from the glasses nreal dashboard I can see I'm running 05.106.008.20210303 and I have no updates to install.
Having said that installing and updating my projects to SDK 1.5.7 killed all my projects (they built fine/installed but I got nothing in the headset when I ran them). Even building HellorMR gave me the same problem. However reverting my UNITY projects to SDK 1.4.8 fixed it. I'm far from an expert on this (I just spend my entire life trying to build stuff that runs across nReal/HL, IOS, Android, PC & Mac) but I think something in SDK 1,.5.7 causes a camera configuration error in UNITY (I'm running UNITY 2019.4.23f1).  Unfortunately I don't have the time figure it out. 

【引用元】：https://nreal-dev.slack.com/archives/CK6B20AKZ/p1617658749068000?thread_ts=1616443750.025100&cid=CK6B20AKZ

この辺りの事象の原因はデバイス側のファームウェアに起因するものだと思われます。
【参考リンク】：[nrealアプリを作りたい人がはじめに読む記事（随時更新中）](https://qiita.com/mininobu/items/9fe23a21e628e0d347ed#%E8%AA%B2%E9%A1%8C)

## プロジェクトセットアップ

プラットフォームをAndroidに変更し、下記項目を変更します。

| 設定項目 | 設定値 |
|:-:|:-:|
|  Project Settings > Quality > V Sync Count | Don't Sync  |
| Project Settings > Player > Resolution and Presentation > Default Orientation  | Portrait  |
|  Project Settings > Player > Other Settings > Target API Level |  API level 26 ～29.  |
| Project Settings > Player > Other Settings > Package Name  | 自身でパッケージIDを設定  |
| Project Settings > Player > Other Settings > Minimum API Level  | Android 8.0 以上  |
| Project Settings > Player > Other Settings > Graphics APIs  | OpenGLES3  |
|  Project Settings > Player > Other Settings > Auto Graphics API | false  |

【引用元】：https://developer.nreal.ai/develop/unity/android-quickstart

上記に加えて2019.2系の場合、"Allow unsafe code"を変更しろというエラーが出るので変更します。


## 開発フロー

開発フローとしては下記です。
>①ビルドする
②Nreal Computing UnitをPCに接続してadbコマンドでapkを送る
③グラスをNreal Computing Unitに接続してアプリを起動し確認

都度接続が面倒な場合はAndroid Debug Bridgeと呼ばれる機能を利用してWifi経由でビルドしたapkをNreal Computing Unitに送信できます。

その前に[scrcpy](https://github.com/Genymobile/scrcpy)と呼ばれるツールでNreal Computing Unitの画面をPCにキャストできるようにする必要があります。

下記が手順です。

>①scrcpyでキャストした画面からSettings>Network&Internet>Wi-Fi>{指定のWifi}>IPアドレスの確認を開き、IPアドレスをメモ
②コマンドプロンプトからadb tcpip 5555 入力。
③をadb connect　①で調べたIPアドレス を入力
※接続解除は adb disconnect
④adb install -r アプリのディレクトリ　でインストール

【参考リンク】：[Wifi経由の接続の仕方](https://qiita.com/iwaken71/items/d7db2c246e77268d742d#wifi%E7%B5%8C%E7%94%B1%E3%81%AE%E6%8E%A5%E7%B6%9A%E3%81%AE%E4%BB%95%E6%96%B9)

## イメージアンカー

プロジェクト内の下記ディレクトリにイメージトラッキングを実装したシーンがあります。
`Assets/NRSDK/Demos/ImageTracking`

登録する画像データは
`NRCameraRig → Session Config → TrackingImageDatabase`
から変更可能です。

自前でTrackingImageDatabase作成するには、ProjectWindowの画像ファイルを選択し
`Create → NRSDK → TrackingImageDatabase`の順に選択します。
![NrealTrackingImageDatabase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/2d97d71a-72cd-2e92-eb6c-5038688ebe76.png)


>・Quality Scoreが**65以上**が好ましい
・Width(m)は実際の画像のサイズを等しくする必要がある

とのことです。
【参考リンク】：[Create the Database File (画像データベースを作ろう)](https://qiita.com/iwaken71/items/d7cc1edb84713c9c4a24#create-the-database-file-%E7%94%BB%E5%83%8F%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%82%92%E4%BD%9C%E3%82%8D%E3%81%86)

結論から言うと私は今回イメージアンカーを使用しませんでした。

イメージアンカーはグラスの正面に正対しないと私の検証環境においては精度がいまいちでした。
今回参加したハッカソンは伝統工芸品をARで拡張するというコンセプトです。机の上に置いた陶器を真上から見下ろすということを行うにはかなり低い机の上に陶器を置く必要がありました。イメージアンカーは原点として使用するつもりで、一度認識すれば追従する必要は無かったのでたった一度の認識で十分でしたが、それすらも難しかったです。そもそも、3Dの物体をある角度から見た場合のみのイメージアンカーとして扱うのも無理がありました。

## Vuforiaとの組み合わせ
現状できないようです。
公式Slackで回答がありました。
![VuforiaUsingNreal.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/0371005b-20bb-2b4b-7b70-959a7abd3dee.png)

##ARFoundationとの組み合わせ
こちらも現状できないと思われます。
公式Slack内でNreal社の方ではないですが下記のように回答がありました。
>・Is it possible to use ARFoundation?
I am not 100% about this, but I have read that some developers couldn't use external libraries or plugins like OpenXR or ARFoundation.

【引用元】：https://nreal-dev.slack.com/archives/CK40QM8DA/p1619161231207800?thread_ts=1619100674.196700&cid=CK40QM8DA

Nrealに対応している[Immersalのサンプルシーン](https://github.com/immersal/arcloud-sdk-samples/tree/nreal)においてもARSession、ARCamera等のARFoundationでお馴染みのオブジェクトは存在しておらず、
Nrealのカメラがセットされていました。

現状、他のライブラリ、プラグイン等の利用は厳しいようです。
開発者としてはARFoundation対応となればマルチプラットフォーム開発が捗るのでぜひ頑張ってほしいところです。

[Conekton](https://github.com/MESON-inc/Conekton)というライブラリはこの辺りの面倒な事象にも対応しているようです。(すごい)

## 位置合わせ
今回は先述の理由からイメージアンカーを使わずに位置合わせを行いました。

Nrealは平面認識を行うことができるのでユーザーが自身で陶器の置いてある平面を選択してARの原点として定める手法です。体験としてはいまいちですが、1週間という短い期間のハッカソンの成果物としては十分でした。
![WithAR_Torakura4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/3ad962f2-935e-7dcf-5afe-ea711eddaf67.gif)


平面を検知し、オブジェクトを配置するサンプルコードです。

```cs
using NRKernal;
using UnityEngine;

/// <summary>
/// エフェクトの初期位置をユーザーに確定させる
/// </summary>
public class DetectEffect : MonoBehaviour
{
    [SerializeField] private GameObject effects;

    void Update()
    {
        Transform laserAnchor = NRInput.AnchorsHelper.GetAnchor(NRInput.RaycastMode == RaycastModeEnum.Gaze ? ControllerAnchorEnum.GazePoseTrackerAnchor : ControllerAnchorEnum.RightLaserAnchor);

        RaycastHit hitResult;
        if (Physics.Raycast(new Ray(laserAnchor.transform.position, laserAnchor.transform.forward), out hitResult, 10))
        {
            if (hitResult.collider.gameObject != null && hitResult.collider.gameObject.GetComponent<NRTrackableBehaviour>() != null)
            {    
                if (NRInput.GetButtonDown(ControllerButton.TRIGGER))
                {
                    var behaviour = hitResult.collider.gameObject.GetComponent<NRTrackableBehaviour>();
                    if (behaviour.Trackable.GetTrackableType() != TrackableType.TRACKABLE_PLANE) return;

                    //エフェクト表示
                    effects.transform.position = hitResult.point;
                    var cameraPos = Camera.main.transform.position;
                    cameraPos.y = hitResult.point.y;
                    effects.transform.LookAt(cameraPos);
                    effects.SetActive(true);
                }
            }
        }
    }
}

```

## 録画機能
Nrealには独自の録画機能があります。Androidのネイティブ機能ではありません。

プロジェクト内の下記ディレクトリに録画機能を実装したシーンがあります。
`Assets/NRSDK/Demos/RGBCamera-Record`

ドキュメントに記載はありませんが、`Write Permission`を`External(SDCard)`に変更する必要があります。
![WritePermission.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/a7c536b7-3aca-d07f-2bc7-aeb072f1778a.png)

録画したビデオはadbコマンドで取り出します。

下記コマンドをコマンドプロンプトにて入力します。

- `adb shell`
- `cd /sdcard/RecordVideos/`
- `ls`

ここまでのコマンドでビデオ名が表示されるので次のコマンドで利用します。
`exit`を入力してshellを終了し、`adb pull /sdcard/RecordVideos/ビデオ名　保存先ディレクトリ`と入力すれば
任意のディレクトリにビデオファイルを取り出せます。

`adb shell rm -rf /sdcard/RecordVideos/ファイル名`と入力することでComputing Unit内の不要なビデオファイルを削除できます。

`NRSDK 1.2.1`の段階では**音声を録画に含めることはできません**。
SNSで共有されているキャプチャ動画にも音声後付けのもの以外で体験中の音声が含まれるものを見たことが無いです。
恐らく最新のSDKでも対応していないのではないかと思われます。

本記事冒頭の動画もモバイル端末で撮影した映像です。

公式Slackにも下記のような回答がありました。回答内容はマイクの音声ですが、ゲーム音も対応してないという認識で合っていると思います。

![nrealAudioCap.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/62d21f0c-1bd4-2708-adc9-9784385e80ca.png)

関数の引数に音声をキャプチャするかどうかのオプションが渡されていましたが、
その引数で渡ってきたオプションは関数内で使われていませんでした。
![NrealCaptureOptionSS.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/38a7b65a-280f-4df2-fec6-de740db619f3.png)

致命的な現象として、一部Shaderがキャプチャ時に黒くなる現象が発生しました。
下記が正常な状態です。ios端末でキャプチャしました。
![NrealSuccessEffect.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/87bdf5c5-7fdd-3fff-5901-e33b47da4e61.png)

しかし、Nrealで動画として書き出すと真っ黒になってしまいます。
![NrealFailedEffect.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/a13b81ef-9793-f678-6948-f2a6cb049113.png)

自力での解決が難しく、公式Slackで質問を投げましたがそれでも解決しなかったので、ハッカソンの途中でモバイル端末対応に変更することにしました。

モバイルでの対応内容は別記事にて書きます。

##NearClipへの対応
NrealはNearClipが30cm近くあります。NearClipとは**カメラから○○cm以下の距離のオブジェクトはレンダリングしない**というものです。
つまり、"きれいだな～"と思ってARオブジェクトまでの距離30cm内に接近するとARオブジェクトが消えてしまいます。

今回の受賞作品のコンセプトとして"音のAR"を構想として掲げており、
"音の空間に入り込む"という体験をデザインするつもりでした。
具体的には"球体に近づくと、その球体が巨大化し、音の空間に入り込むような演出"のような
一連のエフェクトを作成したかったです。

しかし、NearClipがそれを阻みました。

下記GIFが何も対策を講じていないエフェクトです。
伝えたい内容が分かり易いようにSphereを置いて可視化し、変化速度を落としています。
![WithAR_Torakura5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/378d4312-2448-6f21-1209-0861b7127bd9.gif)

球体の中心から非描画領域が拡大していっているのが見て取れます。
これはEditor上での様子ですが、グラスでかけてみるとさらに質の低い体験になります。

対策を講じたエフェクトのGIFが下記です。
![WithAR_Torakura6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/c89d41e0-3b2c-9154-2b67-5913a499649f.gif)

やっていることは非常に単純です。
NearClipより少し離れた地点で衝突判定を取り、一度エフェクトをフェードアウトします。
その後、拡大させながらエフェクトをフェードインすれば空間に潜り込む体験を質を落とさずに実現できます。
音の空間から出る場合も同様の演出で表現が可能です。

## Scaniverse
[Scaniverse](https://scaniverse.com/)はLiDER搭載の端末で動作する空間、及び物体のスキャンアプリです。
スキャンした空間、物体はMesh化されます。書き出してUnityにて実寸で扱うことが可能です。
![WithAR_Torakura1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/239ad953-2fa7-aa06-fc4a-23afc06f2d25.gif)

この空間のMeshを利用して、実際にエフェクトを配置した際のサイズ感、配置座標等をEditor上で確認しながら開発を行いました。

![WithAR_Torakura2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/186cd30c-6748-4044-7756-f67e18cf5e77.gif)

実機で何度も確認する手間が省けたので助かりました。

## おわりに
Nrealの開発機会を探していたので、withARハッカソンには感謝です。ありがとうございます。
伝統工芸の良さについても触れないとわからないことが多く、学びの多いハッカソンでした。

## 参考リンク
[Nreal Light のイメージトラッキング(画像タグ)実装方法](https://shuyo.hatenablog.com/entry/2020/11/11/image-tracking)