LoginSignup
19
14

More than 1 year has passed since last update.

【Immersal × Nreal】200m規模のAR空間を実装した話

Last updated at Posted at 2021-12-05

STYLYアドベントカレンダー

この記事は『STYLYアドベントカレンダー』5日目の記事です。

 はじめに

副業で下記イベントにXRエンジニアとして参加させて頂きました。

非常に多くの知見を得たので、メモしようと思います。
発起人兼エンジニアのdaisuketakiさん、エンジニアのArakiさん、ryuichiさんにも記事執筆の許可を頂きました。

イベント詳細

ソラ水族館はNrealImmersalを用いたマーカーレスのビジョンベースARです。

Logo.png

この企画のおもしろいところは体験エリアが広いことです。
Nrealをかけたまま約200m、商店街を歩きながらのAR体験です。
マーカーレスのビジョンベースARでこの規模は事例として少ないと思います。

初回のミーティングでこの仕様を聞いたときに、一か月という短い実装期間も相まって、
正直なところこの企画大丈夫か?と不安になりました。

開発の流れ

作成の流れとしては下記です。
1. Matterportで商店街の実寸3Dモデルを作成
2. Immersalで特徴点データを作成。
3. Unityエディター上で1で作成した3Dデータを元に2で作成した特徴点データの配置を行う。
4. ARオブジェクトを1で作成した3Dデータを元に配置する。
5. アプリとしての体裁を整える。(起動後~体験までの一連の挙動、インタラクション等)
6. 現地にて実際の動作を確認する。

今回、私含め他のエンジニアの皆さんもフルリモートでの開発で、担当したのは4、5の工程でした。

開発に含まれてない重要な準備として、下記が挙げられます。
・イベントスタッフ、会場の準備、機材の準備
・イベントのSNSでの告知、告知用動画の準備
・イベント開催元との連携
・当日起こり得るトラブルのリスクマネジメント

正直ここに関して私はノータッチでしたが動作確認も含めて最もヘビーな内容だと思います。
エンジニアとしては開発に集中できる環境を用意していただいて、感謝しかありません。

開発期間が1か月に満たない状況もあり、週1回以下の短いペースでミーティングを重ね、
初期仕様にこだわらず臨機応変に"体験の質と実現可能性"を天秤にかけてベストを探りながら
進めていただきました。

開発Tips

開発期間に得た技術的知見、ノウハウをメモします。

前提として、画像のように200mを4分割して50mごとにエリアを定義してAR空間を構築しました。
スクリーンショット 2021-10-17 17.10.48.png


ホットスポット方式

今回、Immersal利用時にホットスポット方式で特徴点データを収集しました。
この手法は公式ドキュメントに記載がある利用方法とは少し異なりますが、
広域での連続的な特徴点データの撮影が難しい場合に有効です。

下記はドキュメントの画像を拝借したものですが、ホットスポットを撮影後、
若干離れた場所から再度マッピングすることで遠距離でのローカライズ精度が向上するようです。
ar_hotspots_mapping2.jpg

【引用元】:ドキュメント

Nrealの6DofとImmersalの位置補正でできるだけARコンテンツのずれが起きないように努めました。
商店街の200mの道のりを12箇所程度マップ化したそうです。

1マップにつき平均して15枚ほどの撮影枚数でした。
「15枚は少なくないかな?」と思いましたが、
看板などユニークで特徴点の多いエリアが多かったことも影響してか、
生成されたGLBデータは、はっきりときれいなものでした。

なお、最終的に使用したマップは1エリアにつき1マップのみ
他は予備としてアプリに内包したそうです。
というのも、Nrealの自己位置推定が強力で、
各エリア初回の位置合わせ以外ではほとんど使わなかったそうです。

加えて、大量のマップを1つの画角に配置すると、
複数のローカライズの処理が干渉してしまい、逆効果
となったそうです。
あくまで理論値ですが、50m程度なら歩いても大きく位置を外すことはなく、
Nrealの自己位置推定だけで十分
ということになります。(求める精度によります)

途中で大きく自己位置をロストしたり、
アプリが途中で落ちたりした場合の予備として数箇所予備のマップを仕込んだとのことです。


NRLocalizerの罠

NrealSDKとImmersalの組み合わせは、
ARFoundationとImmersalの組み合わせの際とLocalize(位置合わせ)を担うコンポーネントが異なります。

ARFoundationとImmersalの場合はARLocalizer.csというコンポーネントを利用します。

一方で、NrealとImmersalの組み合わせの場合、
NRLocalizer.csというコンポーネントがLocalize(位置合わせ)を担います。

ImmersalSDK.csというコンポーネントにOnPoseFoundというイベントが定義されており、
ローカライズ時に任意の処理を走らせることができます。
ImmersalSDK.PNG

ARMap.csというコンポーネントにもOnFirstLocalizationという
そのマップの初回ローカライズ時に呼ばれるイベントがありますが、
複数のマップをシーンに配置した際に、どのマップがローカライズされるかは制御できないので
どのマップがローカライズされても呼び出されるOnPoseFoundを使うことにしました。

OnPoseFoundを利用して、
初回の位置合わせをARコンテンツ起動のトリガーにしようと試みましたが、
うまくいきませんでした。

何がうまくいかなかったのかというとそもそも呼ばれていなかった?です。

ARFoundationとImmersalの組み合わせの際には
確実に呼ばれていることを確認していたので少々混乱しました。

そこでNRLocalizer.csの中を調査してみると
ImmersalSDK.csと同様にOnPoseFoundが存在しました。
まさかと思い適当な処理を登録してみると
ImmersalSDK.csOnPoseFoundと同様の挙動になりました。

ちょっと理不尽すぎたので公式Discordにて要望としてリクエストしました。

しかし、「こっちでは動いてますよ」という返答を頂いたので
バージョンによる違いがあるのかもしれません。

↓今回の開発環境

名前 バージョン
Unity 2020.3.18f
NRSDK 1.7.0 or 1.6.0
Immersal SDK 1.14.1

ハンドトラッキング

下記のようなものを作成しました。

実装コードは下記です。
単純にパーティクルをオンオフするだけだと機械的な挙動になるので、
"オフにする=アルファ値を徐々に小さくする"という表現でやわらかさを残しました。

適当なオブジェクトにアタッチ
using NRKernal;
using UnityEngine;

/// <summary>
/// 手にパーティクルを追従させる
/// 単純なオンオフだと見栄えが悪いのでアルファの切り替えで実装
/// </summary>
public class HandParticle : MonoBehaviour
{
    /// <summary>
    /// パーティクルを出現させる関節
    /// </summary>
    [SerializeField] private HandJointID jointID = HandJointID.Palm;

    /// <summary>
    /// 手から出るパーティクルのオブジェクト
    /// </summary>
    [SerializeField] private ParticleSystem  handParticle;

    /// <summary>
    /// 透過にかける時間
    /// </summary>
    [SerializeField,Range(0.5f,5f)] private float alphaChangeTime = 1.0f;

    /// <summary>
    /// 手のステート
    /// </summary>
    private HandState handState;

    /// <summary>
    /// パーティクルの色取得に必要なモジュール
    /// </summary>
    private ParticleSystem.MainModule main;

    /// <summary>
    /// 経過時間
    /// </summary>
    private float elapsedTime;

    /// <summary>
    /// 補間の割合
    /// </summary>
    private float rate;

    /// <summary>
    /// 反応させたいポーズとそれ以外
    /// </summary>
    private enum CustomHandState
    {
        Open,
        Other
    }

    /// <summary>
    /// 現在の手のステート
    /// </summary>
    private CustomHandState currentHandState;

    /// <summary>
    /// 前フレームの手のステート
    /// </summary>
    private CustomHandState prevHandState = CustomHandState.Other;

    void Start()
    {
        handState = NRInput.Hands.GetHandState(HandEnum.RightHand);
        main = handParticle.main;
    }

    void Update()
    {
        //ジェスチャー取得
        currentHandState = handState.currentGesture == HandGesture.OpenHand ? CustomHandState.Open : CustomHandState.Other;
        //前回とジェスチャーが異なる場合、
        if (currentHandState != prevHandState) rate = 0;

        //アルファの透過割合計算
        elapsedTime += Time.deltaTime;
        rate = Mathf.Clamp01(elapsedTime / alphaChangeTime);

        //手のジェスチャーに応じてアルファ変更
        var particleColor = Color.white;
        particleColor.a = currentHandState == CustomHandState.Open ? Mathf.Lerp(0, 1, rate) : Mathf.Lerp(1, 0, rate);

        //前回のジェスチャーとして保持
        prevHandState = currentHandState;

        //アルファ、位置補正
        main.startColor = particleColor;
        handParticle.gameObject.transform.position = handState.GetJointPose(jointID).position;
    }
}

追従する関節はInspectorでEnumの値を変更することで自由に設定可能です。
下記が認識できる関節です。

ht1-4.png

【引用元】:Joint Orientation


屋外での利用

光学式のデバイスは屋外での利用が正直厳しいです。
私もそこはもう諦めるしかないだろうと思ってましたが、
daisuketakiさんがサングラス in ARグラスという、
とてつもないアイデアで本番に向けてDIYしていました。

「理屈はわかるけどそれ本当にやるのかよ」という感じで驚きましたが、
体感的に3割増しではっきりとARオブジェクトを視認できるようになったそうです。


バッテリー残量、熱との闘い

Xperia5 ⅡというそれなりにハイスペックなAndroid端末をNrealに接続して実験したそうですが、
おおげさではなく1分に1%ペースで端末の充電が減少したそうです。
バッテリーの事情と当日の体験スケジュールを考慮して、
急速充電器やモバイルバッテリーを完備して望んだとのことです。

また、端末の放熱が著しく、冷えピタで応急措置を取るなどして工夫されていました。


世界観の構築

商店街を水族館にするという内容のARですが、
ただ魚を出すだけではなく世界観を構築したことがよりよい体験に繋がったと振り返って感じています。

構想の段階でアイデアには大筋の世界観が付与されていました。
DEPTH1 → DEPTH2 → DEPTH3 → DEPTH4 とエリアを進むにつれて
体験エリアの水深が深くなっていくというものです。

最深部では全ての生き物が光そのものに変化し、ARならでは未知の世界を作り出します。
また、水深に応じて生き物や"現実をオーバーレイしたARの表現"が変化します。

エリアごとに冒頭でボイスガイダンスを流し、世界観の説明を行いました。
(神戸電子専門学校声優学科の方にボイスの依頼をしたそうです。ご協力ありがとうございました。)

例として、下記がDEPTH4のガイダンスの文言です。
先述の"ハンドパーティクル"が出るエリアです。

「最深部に到着しました。ここではあらゆる生命が光そのものになります。
 手のひらを目の前に出してください。きっとあなたも、例外ではありません。」

世界観を壊さず、体験者に正しく情報を付与する素晴らしい文言だと思いました。

後日談

イベントは全体的にかなり好評だったそうで、私もそう聞いて嬉しい気持ちになりました。
良いフィードバックも悪いフィードバックもそれぞれありましたが、
今回は心を鬼にして悪いフィードバックに焦点を当てて振り返ります。

視野が狭い

かなりの多い意見でした。
デバイスのせいだ!で片づけることも可能ですが、
せっかくなので何が問題だったのか表現側で解消する方法がなかったか考えました。

まず、視野に対する言及が多いエリアが2箇所あったとのことなので、
そのエリアでの表現ついて深堀してみます。


現実をオーバーレイするAR表現

視野に対する言及が多かったエリアの特徴の1つ目はコースティクスです。
コースティクス.PNG

水上から差し込む光をイメージしたものです。
現実の壁面にオーバーレイする形で画面いっぱいに配置しています。

しかし、これが視野角の制限がある状態では見え方として良くなかったです。
下記はイメージですが、視野の境界がはっきりしてしまい、視野の狭さが強調されているように見えます。
視野.png

現実にオーバーレイしてARのエフェクトをかける際には、
下記のようなに視野角の切れ目をぼかすなどの工夫が必要でした。
ぼかし.png

世界観でカバーすることも可能だったかなと思います。

例えば、
「"視野角を定義した枠を作り、その中にしかAR空間は広がっていない" 窓から覗くAR水族館」
という世界観を定義した作品であれば、視野角に対する言及は減ったかもしれません。


巨大なオブジェクト

視野に対する言及が多かったエリアの特徴の2つ目は巨大なクジラです。

巨大なクジラが上空を漂うエリアがあるのですが、
クジラの迫力を重視するあまり、視野角の境界を強調させてしまう結果となりました。
クジラ.PNG

この辺りの調整は非常に難しいです。
手元のオブジェクトとの大きさの対比やオクルージョンによる奥行き感などで
ギリギリまでサイズを絞る工夫が必要だったかもしれません。


位置同期

作成したARコンテンツは位置同期の実装は行っておらず、
ソロプレイが前提でした。

一方で今回のイベントは、複数人で時間ごとに体験グループを作成し、
グループで同じコンテンツを同時に体験するという運用でした。

その際に、「あそこにカメがいる!」などと
プレイヤー間でARに関するコミュニケーションを取っても
位置を同期していないがために話がかみ合わない場面があったそうです。

魚などの動きのあるオブジェクトはランダムに遊泳するプログラムを組んでいました。
加えてコンテンツ開始のタイミングも、各々のグラスでのローカライズが完了次第でした。
複数人で同じ体験を構築する際には、ランダム性や開始タイミングが
このような形で落とし穴になるのかと勉強になりました。

今回の事象を逆手に取れば、ビジョンベースのARである以上、
ランダム性を排除して開始タイミングさえ同期すれば、位置同期が不要
となります。

短期間での実装が求められる場面では有効な手法かもしれません。

おわりに

開発後、メンバーで振り返りを行いましたが、
「比較的余裕持って開発を終えることができた」と口をそろえて仰っていました。
その理由を全員で考えた結果、本当に必要な実装だけに仕様をそぎ落として開発できたこと
最も大きい要因ではないかという結論に至りました。

冒頭でも下記のようにプロジェクトは進行したと書きましたが、本当にこれに尽きると思います。
軸だけを残し、無駄をそぎ落として洗練されていくような良いプロジェクトの進め方でした。

開発期間が1か月に満たない状況もあり、週1回以下の短いペースでミーティングを重ね、
初期仕様にこだわらず臨機応変に"体験の質と実現可能性"を天秤にかけてベストを探りながら
進めていただきました。

最後になりますが、このような機会を頂けたことに感謝します。
株式会社U.さん、daisuketakiさん、Arakiさん、ryuichiさん、ありがとうございます。

今できるXRの可能性を最大限引き出したイベントになったと思います。
今後もXRでおもしろいことをやりたいなと思いました。

参考リンク

ARグラスで商店街を水族館に / Tips

19
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
14