Niantic Lightship ARDKの機能調査

# 追記: 2025年4月17日
ARDKについては当記事を書いて以来、かなりの更新をして下さいました。
いずれまた改めて書くことが出来ればと思いますが、古い記事であることを踏まえていただければ幸いです。
https://lightship.dev/docs/ja/ardk/

# まずARDKの概要情報です
Lightship ARDKとは、「Pokemon Go」で知られるNianticが2021年11月8日から正式に提供を開始したUnityベースのARアプリ開発キットです。この記事ではその機能や実装してみて気づいたことなどをまとめています。

基本的には下記のドキュメントに乗っている情報を基に書いております。
https://lightship.dev/docs/

## ARDKの機能要約
#### 1. Mapping
- [ARアンカー](https://lightship.dev/docs/basic_placement_tutorial.html)
- [深度推定](https://lightship.dev/docs/depth_overview.html)
    - カラー画像入力から、カメラと映ったモノの距離を深層学習で推定。LiDAR深度データは使用されない
    - <iframe width="560" height="515" src="https://www.youtube.com/embed/m0vESD8dfFg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

- [リアルタイム・メッシュ](https://lightship.dev/docs/meshing.html)
    - 画像, カメラの向き, (LiDAR深度データ)からニューラルネットワークにより3Dジオメトリを生成
        - LiDAR搭載デバイスとそれ以外の場合の違い
            - IARWorldTrackingConfiguration.IsDepthEnabledの真否を変えると、下記表のような違いが生まれる
            - ![](https://storage.googleapis.com/zenn-user-upload/71473d699426-20211203.png)
    - <iframe width="560" height="500" src="https://www.youtube.com/embed/Wa6_eRXKfXg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

- [オクリュージョン](https://lightship.dev/docs/occlusions.html)
深度を基に障害物があったら、3Dモデルを隠す
    - ARDepthManagerのOcclusionModeで調整可能
    - ![](https://storage.googleapis.com/zenn-user-upload/d16a9e32f06e-20211203.gif)

#### 2. Understanding
- [セマンティック・セグメンテーション ](https://lightship.dev/docs/semantics.html)
    - 画像内の特定の領域にクラスラベルを割り当てる。ピクセルごとにラベルを取得可能。

| index | Channel名| メモ |
| ---  | -------- | -------- |
|0| sky | 雲が含まれています。霧は含まれていません。  |
|1| ground |人工地盤（下）のすべてのものと、土、草、砂、およびその他の自然地盤タイプが含まれます。<br>植生や葉が多い地面は地面として検出されず、代わりに葉として検出されます。 |
|2| artificial_ground|道路、歩道、線路、カーペット、敷物、フローリング、小道、砂利、およびいくつかの競技場が含まれます。|
|3| water  | 川、海、池、湖、プール、滝、いくつかの水たまりが含まれています。飲料水、カップ、ボウル、シンク、バスの水は含まれません。<br>反射の強い水は水として検出されない場合があります。 |
|4| building | 近代的および伝統的な住宅および商業ビルが含まれます。<br>壁と同義と見なされるべきではありません。|
|5| foliage|茂み、低木、木の緑豊かな部分、鉢植えの植物、花が含まれています。|
|6| grass| 背の高い草ではなく、芝生などの芝生の地面。 |
<iframe width="560" height="515" src="https://www.youtube.com/embed/vFT170iCGTg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

#### 3. Sharing
- [マルチプレイ](https://lightship.dev/docs/multiplayer_experience.html)
    - 1セッションにつき、[最大5人で5分](https://lightship.dev/docs/low_level_networking_gotchas.html)が目安
    - <iframe width="560" height="415" src="https://www.youtube.com/embed/nY9eyBzXh10" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
- [空間データの共有](https://lightship.dev/docs/ar_networking.html)
    - ![](https://storage.googleapis.com/zenn-user-upload/a392ddb26c01-20211203.png)
    - セッション開始時は、その時点のデバイスの位置を原点とした各自の座標系を作成
    - Stable以降はホストの座標系に合わせて、デバイスや物の位置を補正する。
    - <iframe width="560" height="415" src="https://www.youtube.com/embed/mjB4_KNjXkw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

#### 4. 開発ツール
[Virtual Studio](https://lightship.dev/docs/virtual_studio.html)
アプリのビルド・デプロイに時間を費やさないようにテストするための環境

1. Mock Mode
仮想で部屋を作り、その部屋で開発したARのテストを行う 
<iframe width="560" height="415" src="https://www.youtube.com/embed/K_bwE2B7SgY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

2. Remote Mode
スマホとPCを接続し、スマホのカメラで得た映像や深度を用いて、ARのテストを行う
<iframe width="560" height="415" src="https://www.youtube.com/embed/ZlCGsp_ULeY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## リリース予定
![](https://storage.googleapis.com/nianticweb-media/lightship/lightship-roadmap.png)

## 料金体系
![](https://storage.googleapis.com/nianticweb-media/lightship/lightship-pricing.png)

## 実装例
<iframe width="560" height="515" src="https://www.youtube.com/embed/PSj0zDrgYJo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

https://github.com/TomohiroHibino600/TableGarden

# 次は実装してみて重要そうだなと思ったことの羅列です

## Virtual StudioのRemote Mode構築手順
#### Remote Feed Appのインストール
1.  [ARDKExamples](https://lightship.dev/account/downloads/artifacts/ardk-examples-1.0.1.unitypackage)/VirtualStudio/ARDK Remote Feed App.Sceneだけをビルド
    - Build SettingsでAutoconnectProfiler, Develop Buildをチェック
2. Remote Feed Appをインストール

#### Remote Feed AppとテストしたいSceneを開いたUnity Editorの接続
1. PCとスマホを接続
2. ARSessionを開始しているテストすべきSceneを開く
3. Profilerでデバイスを選択
    - ![](https://storage.googleapis.com/zenn-user-upload/8844740f6556-20211203.jpg)
4. Virtual StudioでRemote → USBを選択
    - ![](https://storage.googleapis.com/zenn-user-upload/e04deec60e4c-20211203.jpg)
5. スマホでRemote Feed Appを開き、USBを選択する
    - 接続に成功すれば[Connected!]と表示される
6. Unity Editorでテストしたい挙動を行う

## MeshとSemantic Segmentation
#### Mesh
実装例
1. ARSessionManagerのあるシーンでARMeshプレハブを追加する
![](https://storage.googleapis.com/zenn-user-upload/040b1b36b00b-20211203.jpg)

#### Semantic Segmentation
実装例
1. AR Semantic Segmentation ManagerをARSceneCameraプレハブに追加
2. 下記のようなスクリプトを記述

```csharp:CheckSemantic.cs
        // Update is called once per frame
        void Update()
        {
            if (PlatformAgnosticInput.touchCount <= 0) { return; }

            var touch = PlatformAgnosticInput.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                int x = (int)touch.position.x;
                int y = (int)touch.position.y;
                DebugLogSemanticsAt(x, y);
            }
        }

        //examples of all the functions you can use to interogate the procider/biffers
        void DebugLogSemanticsAt(int x, int y)
        {
            string[] channelsNamesInPixel = semanticManager.SemanticBufferProcessor.GetChannelNamesAt(x, y);
            foreach (var i in channelsNamesInPixel)
            {
                Debug.Log($"{i}");
            }
        }
    }
}
```



## HLAPIとLocalization
#### メンバー同士での空間データの重ね合わせ
<iframe width="560" height="415" src="https://www.youtube.com/embed/mjB4_KNjXkw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

#### 生成
https://lightship.dev/docs/hlapi_network_spawning.html

#### 位置同期

```csharp:CosmosBehaviour.cs
//NetworkBehaviourを継承しているクラスでの関数です
protected override void SetupSession(out Action initializer, out int order)
{
    initializer = () =>
    {
        var auth = Owner.Auth;
        var descriptor = auth.AuthorityToObserverDescriptor(TransportType.UnreliableUnordered);

        // UnreliableBroadcastTransformPackerを作成するだけで、
        // Transform同期のためのブロードキャスト/受信が設定されます
        new UnreliableBroadcastTransformPacker
        (
           "netTransform",
            transform,
            descriptor,
            TransformPiece.Position,
            Owner.Group
         );
    };

    order = 0;
}
```
- TransformType
    - Relieable → 99.95%の信頼性を保証, UnRelieable → 99%で受け取らなかったメッセージはドロップ
    - UnreliableUnorderedとUnreliableOrdered, ReliableUnordered とReliableOrderedがある

#### メッセージの送受信
- 実装例

```csharp:タグ付きでメッセージ
void PerformActionForAllPeers(IMultipeerNetworking networking, byte[] gameData)
{
  networking.BroadcastData(GameActionMessageTags.PerformSomeGameAction, gameData, TransportType.UnreliableUnordered);
}
 
void SubscribeToPeerDataReceived(IMultipeerNetworking networking)
{
  networking.PeerDataReceived += OnPeerDataReceived;
}
 
// Since the Hlapi also uses byte messages, this will be fired each time an object is NetworkSpawned as well
void OnPeerDataReceived(PeerDataReceivedArgs args)
{
  // Check that it is a message we care about, rather than an Hlapi message
  if(args.Tag != GameActionMessageTags.PerformSomeGameAction)
  {
    return;
  }
 
  // We now know this is the message that this class cares about, so use the data to perform some update
}
```

※タグはイベントデータである[PeerDataRecievedArgs](https://lightship.dev/docs/struct_niantic_1_1_a_r_d_k_1_1_networking_1_1_multipeer_networking_event_args_1_1_peer_data_received_args.html#doc-content)の中で指定できるuint

```csharp:タグ無しでメッセージ
private void OnDidConnect(ConnectedArgs connectedArgs)
{
  // ...
  // Continuing method from second code snippet...
  
  _hitStreamReplicator =
    new MessageStreamReplicator<Vector3>
    (
      "hitMessageStream",
      _arNetworking.Networking.AnyToAnyDescriptor(TransportType.ReliableOrdered),
      group
    );
 
  _hitStreamReplicator.MessageReceived += 
    (args) =>
    {
      Debug.Log("Ball was hit");
 
      if (_auth.LocalRole != Role.Authority)
        return;
 
      _ballBehaviour.Hit(args.Message);
    };
}
```

#### HLAPIの詳細(データの送受信の際に起きていることなど)
1. [FAQ](https://lightship.dev/docs/hlapi_faqs.html)
2. [NetworkFieldなどの実装例](https://lightship.dev/docs/pong_hlapi.html)

#### その他注意点
1. 精度
	- 下記動画のスマホのように位置のずれはあった
	- <iframe width="460" height="315" src="https://www.youtube.com/embed/nY9eyBzXh10" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

2. 通信
    - サーバーメッセージの遅延は数百ms ~ 数千msになるが、ピアツーピアメッセージの遅延は数十msになる場合がある（デバイスの近さによって異なる）。
    - 下記の間にピアAからピアBにメッセージを送信した場合、ピアBはピアAをまだ有効なピアとしておらず、メッセージをドロップする。そのためPeerAddedイベントの度、数フレーム後に初期化メッセージが発生するようにするべき。
        - ピアA　←　ピアBからPeerAddedイベントを受信 
           ピアB　←　ピアAからPeerAddedイベントをまだ受信していない
    - 現在、同じピアとしてセッションに再参加することはサポートされていない。再参加した場合、デバイスは完全に新しいピアと見なされる。
        - ホストがセッションを離れる場合、新ホストは選択されない。ただし、ホストが検出したマップはセッションのタイムアウトまで残り、新しいピアは残ったマップに対してローカライズできる。
    - 全ピアが退出した後、30秒後にタイムアウトする。この時セッションに再参加しようとすると、意図しない動作が発生する可能性がある。    



## おまけ①: 2021年11月30日現在、難しかったこと
### ※ドキュメントでの記述がないことなので、真偽不明です
- WindowsのUnityでビルド + XCodeでiPhoneにインストールしようとするとエラー
    →MacのUnityでビルド + XCodeでインストールすると大丈夫だった

- Virtual Studioはどちらのモードでもリアルタイム・メッシュ機能が使えなかった
    →Depth機能とSemantic Segmentation, ネットワーク機能は使えた

- URPのままVirtual Studio・Mock Modeにすると、仮想環境と認識させるためにARDK_MockWorldレイヤーを設定しているGameObject全てが見えなくなってしまった
    ~~→その他ピンクシェーダーになる部分が多く、やはりまだ使うべきではなさそう~~
    →2025年4月17日: こちら自分がシェーダーのURP対応について知らなかったのみでした。一般的にピンクシェーダーもURP対応できれば映ります。

## おまけ②: その他
- Q. マルチプレイのメッセージ容量100MBで大きい。どう実装しているか。
    - A. 
        - メッセージ
            - byte[] 10MB
        - 永続的なKey,Value
            - Key → string 4KB, Value → byte[] 100MB
            - セッションがアクティブな限り、サーバーに保存されるデータ
            - 更新されると各デバイスでイベントが発火する
            - セッション開始時に全員が得るべきデータを保存するなどの目的で使う
            - [実装例](https://lightship.dev/docs/low_level_networking_additional.html#autotoc_md260)
        -  [※データ制限について](https://lightship.dev/docs/low_level_networking_gotchas.html#autotoc_md274)