はじめに
ついに、米Niantic社から提供されているLightship ARDKにVPS機能が追加されました。
今回は、Immersalとの比較、Lightship VPSのセットアップ、Immersalからの移植について書いていきます。
※本稿はVPSやImmersalの基礎知識がある方向けに書かれています。
動作環境
- AndroidAPIレベル24以上
- 機能制限で iOS 11、フル機能でiPhone 12 Pro以上で iOS 14以上
- 後述するスキャンアプリ
Niantic Wayfarer
は LiDAR搭載のiOS15以上
- 後述するスキャンアプリ
開発環境
- Unity 2020.3.30f1
- ARDK 2.0.0
- Niantic Wayfarer 1.0.0
- iPhone 13 Pro Max iOS 15.0.2
- Xcode 13.2.1
Immersalとの比較
以下は、ImmersalとLightship VPSの比較です。
Immersal | Lightship VPS | |
通信環境 | 不要 | 必要 |
位置情報(GPS) | 不要 | 必要 |
位置合わせにかかる時間 | 20~30秒 | 5秒~1分 |
位置のずれ | 10cm~20cm | 撮影した対象によっては、向きが大きくずれる |
遠景のマップ化 | 可能 | 不可能 |
屋内での使用 | ◯ | △ |
取得できる姿勢情報 | マップ自身 | マップに置かれたアンカー |
通信環境、位置情報(GPS)
- Immersal – マップデータがローカルに存在しています。
- Lightship VPS – マップデータがサーバ上に存在しています。
上記のような違いがあり、Lightship VPSは、位置情報(GPS)をもとに周囲のマップをサーバから取得するようになっているので、通信環境と位置情報(GPS)が必要です。
位置合わせにかかる時間、位置のずれ
- ある程度物体の周囲を撮影できるようなもの(冷蔵庫など)の場合 – 位置合わせにかかる時間は5秒~10秒程度と非常に早く、位置のずれも10cm程度でした。
- 物体の1面しか撮影できないもの(建物の壁面など)の場合 – 位置合わせにかかる時間は30秒~1分程度、位置のずれは、向きが20~30°程度ずれました。
上記のように、位置合わせにかかる時間、位置のずれともに、マップ作成の際に撮影した対象の影響が、Immersalよりも大きいようです。
基本的には、ある程度物体の周囲を撮影できるものでマップを作成するのが良さそうです。
遠景のマップ化
- Immersal – 遠くにあるビルなども位置合わせの特徴点として使えます。
- Lightship VPS – LiDARでスキャンできる範囲(5m以内)が特徴点として使えます。
Lightship VPSの強みは、LiDARでスキャンできる範囲であれば、カメラ画像のみのImmersalよりも高精度なマップを作成できるところだと思います。
屋内での使用
- Immersal – 特に問題はありません。
- Lightship VPS – GPSなどの位置情報が必要なので、GPSがずれるような建物の中だと位置合わせが正常に行えない場合があります。
現状では、GPSがずれてしまう場所では、Immersalの方が適していますが、GPSがずれないのであれば、LiDARによるスキャンができる分、Lightship VPSの方が使いやすそうです。
取得できる姿勢情報
- Immersal – 認識できたマップの姿勢情報を取得します。
- Lightship VPS – 認識できたマップに置いた、もしくは過去に置かれたアンカーの姿勢情報を取得します。
Lightship VPS はマップの姿勢情報を直接取得できません。(2022/6 現在)
ただし、マップ上の⾃由な場所にアンカーを複数置けますし、時間をおいて復元もできます。
⼯夫次第で⾯⽩いことが出来そうです。
マップの作成とアクティベート
ここからは、実際にLightship VPSを使っていきます。
Lightship VPSで位置合わせをするためには、Niantic Wayfarer
という専用のアプリを使ってマップのスキャンとアクティベートをする必要があります。
Niantic Wayfarer のインストール
Niantic WayfarerはTestFlightで公開されているアプリです。
iOS端末で、以下の⼿順を⾏うことでNiantic Wayfarerをインストールできます。(以下は、TestFlightをイ
ンストールしたiPhone上での操作です。)
Lightship開発者ページにログインして、右上のハンバーガーメニューを開きます。

My Meshes
を開きます。

Upload more meshed in the Niantic Wayfarer app
と書かれたボタンをタップします。
Test Flightがインストールされていれば、そのままインストールの画面に進みます。

周囲のスキャン
インストールしたアプリを起動して Lightship の開発者アカウントでログインします。

SCANボタンを押下して、マップを作成したい空間をスキャンします。

マップのアクティベート
次に、今スキャンしたデータをサーバにアップロードします。
アップロード後しばらく(4時間以内)待つとマップのアクティベート処理が完了します。
今回は、20分程度で完了しました。

Lightship VPS のセットアップ手順
ARDKの導入
ARDKをUnityプロジェクトに導入するには、LightshipのホームページからARDK v2.0.0のUnityパッケージをダウンロードし、Unityプロジェクトにインポートする必要があります。
LightshipのホームページでDownloads→Download ARDK(v 2.0.0)と選択することでUnityパッケージをダウンロードできます。

ダウンロードしたUnityパッケージを、任意のプロジェクトにインポートしてください。これで
Lightship VPS の開発環境は整います。AR Foundation の導⼊は必要ありません。

APIキーの設定
まず、Lightshipのプロジェクトを作成します。
Projectsタブを開いて、 New Project
ボタンを押下します。

プロジェクトの設定画面が表示されるので、わかりやすいようにプロジェクト名を設定します。
今回は、 com.Upft.Sample
としました。
Create New Key
を押下すると新しいAPIキーが生成されます。

この後Unity側で使用するので、生成されたAPIキーをコピーしておきます。

Unityエディタで任意のフォルダ内に Resources/ARDK
とフォルダを作成します。
作成したフォルダで右クリックをし、 Create→ARDK→ArdkAuthConfig
を選択して、 ArdkAuthConfig
を生成します。

生成したArdkAuthConfigの API Key
に先程コピーしたAPIキーを設定します。

シーンのコンポーネント
まず Assets/ARDK/Extensions/Prefabs
配下の ARSceneManager
をシーン上に配置します。
このプレハブの子に ARSceneCamera
が含まれています。
少々乱暴な表現になりますが、ARFounationでいうところの AR Session Origin
、 AR Camera
に近いイメージです。

次に、カメラと位置情報の権限を要求するために、空オブジェクト AndroidPermissionRequester
を作成し、 Android Permission Requester
をアタッチします。
そして、 Permissions
に Camera
と Fine Location
を設定します。

位置合わせ処理の実装
位置合わせをする処理を実装していきます。
using Niantic.ARDK.AR;
using Niantic.ARDK.AR.ARSessionEventArgs;
using Niantic.ARDK.AR.WayspotAnchors;
using Niantic.ARDK.Extensions;
using Niantic.ARDK.LocationService;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
namespace Upft.Behaviour
{
/// <summary>
/// コンテンツ管理クラス
/// </summary>
public class UpftContentManager : MonoBehaviour
{
/// <summary>
/// 精度(m)
/// 推奨値 : 1
/// </summary>
[SerializeField] private float _desiredAccuracyInMeters = 1.0f;
/// <summary>
/// 更新距離(m)
/// 推奨値 : 0.001
/// </summary>
[SerializeField] private float _updateDistanceInMeters = 0.001f;
/// <summary>
/// コンテンツ
/// </summary>
[SerializeField] private GameObject _content;
/// <summary>
/// ARセッション管理
/// </summary>
[SerializeField] private ARSessionManager _arSessionManager;
/// <summary>
/// WayspotAnchorService
/// </summary>
private WayspotAnchorService _wayspotAnchorService = null;
/// <summary>
/// ARセッション
/// </summary>
private IARSession _arSession = null;
/// <summary>
/// アンカー
/// </summary>
private IWayspotAnchor _anchor = null;
/// <summary>
/// Start
/// </summary>
private void Start()
{
_content.SetActive(false);
startArSession();
}
/// <summary>
/// OnDestroy
/// </summary>
private void OnDestroy()
{
if (_anchor != null)
{
_anchor.TrackingStateUpdated -= onWayspotAnchorTrackingStateUpdated; // WayspotAnchor更新時の処理を削除
}
}
/// <summary>
/// ARセッションを開始する
/// </summary>
private void startArSession()
{
ARSessionFactory.SessionInitialized += onArSessionInitialized; // ARセッション初期化完了時の処理を登録
_arSessionManager.EnableFeatures(); // ARセッションの初期化と有効化
}
/// <summary>
/// ARセッション初期化完了時処理
/// </summary>
/// <param name="args"></param>
private void onArSessionInitialized(AnyARSessionInitializedArgs args)
{
ARSessionFactory.SessionInitialized -= onArSessionInitialized; // ARセッション初期化完了時の処理を解除
_arSession = args.Session;
_arSession.Ran += onSessionRan; // ARセッション開始時の処理を登録
}
/// <summary>
/// ARセッション開始時処理
/// </summary>
/// <param name="args"></param>
private void onSessionRan(ARSessionRanArgs args)
{
_arSession.Ran -= onSessionRan; // ARセッション開始時の処理を解除
startLocalize();
}
/// <summary>
/// 位置合わせを開始する
/// </summary>
/// <returns></returns>
private void startLocalize()
{
var wayspotAnchorsConfig = WayspotAnchorsConfigurationFactory.Create();
wayspotAnchorsConfig.ContinuousLocalizationEnabled = true; // 継続的な位置合わせを有効化
var locationService = LocationServiceFactory.Create(_arSession.RuntimeEnvironment);
locationService.Start(_desiredAccuracyInMeters, _updateDistanceInMeters);
_wayspotAnchorService = new WayspotAnchorService(_arSession, locationService, wayspotAnchorsConfig); // WayspotAnchorServiceのインスタンスを生成すると位置合わせが開始される
this.UpdateAsObservable()
.Where(_ => _wayspotAnchorService.LocalizationState == LocalizationState.Localized)
.First()
.Subscribe(_ => onLocalized())
.AddTo(this);
}
/// <summary>
/// 位置合わせ完了時
/// </summary>
private void onLocalized()
{
createOriginAnchor();
}
/// <summary>
/// 原点アンカーを作成する
/// </summary>
private void createOriginAnchor()
{
Matrix4x4 matrix;
if (Application.isEditor)
{
// エディタ実行時には、ARコンテンツと同じ位置にアンカーを置く
var pos = _content.transform.position;
var rot = _content.transform.rotation;
var scale = _content.transform.localScale;
matrix = Matrix4x4.TRS(pos, rot, scale);
}
else
{
// 実機実行時には、認識したマップの (0,0,0) にアンカーを置く
matrix = Matrix4x4.zero;
}
_wayspotAnchorService.CreateWayspotAnchors(onWayspotAnchorsCreated, matrix); // AR空間の原点のWayspotAnchorを作成する
}
/// <summary>
/// WayspotAnchors作成時
/// </summary>
/// <param name="anchors"></param>
private void onWayspotAnchorsCreated(IWayspotAnchor[] anchors)
{
var wayspotAnchor = anchors[0];
_anchor = wayspotAnchor;
_anchor.TrackingStateUpdated += onWayspotAnchorTrackingStateUpdated; // WayspotAnchor更新時の処理を登録
_content.SetActive(true);
}
/// <summary>
/// WayspotAnchor更新時
/// </summary>
/// <param name="args"></param>
private void onWayspotAnchorTrackingStateUpdated(WayspotAnchorResolvedArgs args)
{
if(args.ID != _anchor.ID) return;
var pos = args.Position;
var rot = args.Rotation;
_content.transform.position = pos; // 現実空間の位置に合わせる
_content.transform.rotation = rot; // 現実空間の向きに合わせる
_content.transform.localScale *= _arSession.WorldScale; // 現実空間の大きさに合わせる
}
}
}
今回は認識したマップの (0,0,0) にWayspotAnchorを作成して、その上にコンテンツの⼟台(次項で説明)
を配置することにしました。そのためアンカーの座標と⽅位をコンテンツに反映していますが、スケールは
ARSession.WorldScale に合わせています。
var pos = args.Position;
var rot = args.Rotation;
_content.transform.position = pos; // 現実空間の位置に合わせる
_content.transform.rotation = rot; // 現実空間の向きに合わせる
_content.transform.localScale *= _arSession.WorldScale; // 現実空間の大きさに合わせる
空オブジェクト UpftContentManager
を作成し、上記のコンポーネントをアタッチします。Ar Session Manager
に ARSceneManager
にアタッチされた Ar Session Manager
コンポーネントを設定します。

土台の配置
最後にコンテンツの土台となるモデルを配置していきます。
この土台は、AR空間上にARオブジェクトを配置するための目印となります。
まず、コンテンツのルートオブジェクトとなる空オブジェクト ARContent
を作成します。

ARContent
オブジェクトを、先程のコンポーネント UpftContentManager
のパラメータ Content
に設
定することで、 WayspotAnchor 更新時に⾃動でコンテンツの位置合わせが⾏われるようになります。

次に、ARContent
オブジェクト配下に最初に Niantic Wayfarer
で作成したモデルを配置します。
モデルデータはLightshipのダッシュボードからダウンロードできます。(drc形式なので、obj形式などに変換する必要があります。)

実機での実行時に、AR空間の原点に置いたアンカーで位置合わせを行うので、Positionを(0, 0, 0)に配置しておきます。
また、モデルの向きが上下逆さまになっているので、Rotationを(0, 0, 180)にしておきます。

AR Foundation × Immersal で作成したコンテンツの移植
ここまでで、位置合わせの基盤の実装ができました。
ここからは以前、 Immersalを使って作成したAR未来都市(以後 Immersalプロジェクト)を移植していきます。
以下の画像がImmersalプロジェクトの構成です。⼟台となるPLATEAUのモデルとAR空間に表⽰するコンテン
ツのプレハブをUnitypackageにしてエクスポートします。

このUnitypackageをLightship VPSのプロジェクトにインポートします。

以下の画像のように土台とコンテンツの中身を配置します。

※本記事で詳細な⽅法の説明は省きますが、⼟台のオブジェクト群はビルド時に削除されるようにして
います。
現実空間の配置に⼀致するように、 Niantic Wayfarer のモデルは動かさず、にPLATEAUのモデルとARコ
ンテンツを移動して調整します。

これで完成です。
動作確認
Unityエディタ上で確認
実機で確認する前にエディタ上で動作確認してみます。
エディタ上での操作
エディタ上では以下の操作が可能です。
- WASD & QE – 前後左右と上下のカメラ移動
- 右クリック+マウス – カメラの回転
いまのオブジェクト配置状態で実行すると、開始位置がPLATEAUのモデルに埋まってしまいました。
このままでも、WASDで移動すれば動作確認はできますが、動作確認のたびに移動するのは面倒なので、初期配置を変更します。

しかし、カメラ位置を変更しても、実行時に原点に戻されてしまいます。
なので、カメラを移動させるのではなく、ARコンテンツの方を移動させます。

実機実⾏時は IWayspotAnchor.TrackingStateUpdated
のタイミングで更新されたアンカーの座標にコンテンツを移動させています。(後述のドリフト問題の対策にもなっています。)
そこでエディタ実⾏時はその逆に、コンテンツの中⼼にアンカーが置かれるように意図的に仕向けました。
/// <summary>
/// 原点アンカーを作成する
/// </summary>
private void createOriginAnchor()
{
Matrix4x4 matrix;
if (Application.isEditor)
{
// エディタ実行時には、ARコンテンツと同じ位置にアンカーを置く
var pos = _content.transform.position;
var rot = _content.transform.rotation;
var scale = _content.transform.localScale;
matrix = Matrix4x4.TRS(pos, rot, scale);
}
else
{
// 実機実行時には、AR空間の原点にアンカーを置く
matrix = Matrix4x4.zero;
}
_wayspotAnchorService.CreateWayspotAnchors(onWayspotAnchorsCreated, matrix); // AR空間の原点のWayspotAnchorを作成する
}
これでエディタで実⾏時も快適に動作確認ができるようになりました。
実機確認
ビルドして実機で動作確認してみます。
前述のとおり、実機では更新され続ける WayspotAnchor の姿勢情報でコンテンツの位置を更新し続け
ているため、この動画のように現実空間とピッタリと重なるように⾒えます。
実機での位置合わせでは、GPSで付近のマップを取得するので、位置合わせに成功しない場合は、端末のGPSが正常に動作しているか確認してみてください。
(調査の段階では、端末の充電が少ないと位置合わせに失敗することがありました。)
ドリフト問題への対処
⼤きな距離を移動したり、端末を振ったりすると搭載されたジャイロが誤作動する、ドリフトが発⽣し
ます。
これの対処として WayspotAnchorsConfig.ContinuousLocalizationEnabled
の値を True
にして継続的にアンカーの姿勢情報を取得してコンテンツを再配置するようにしています。
おわりに
Lightship VPSを使ってみて、1番良いと思った点はマップの作成が簡単ということです。
Immersalでは、1つのマップを作成するのに、何回か撮り直して数十分かかることもあったのですが、Lightship VPSではNiantic Wayfarerでほとんどの確率で一回のみで成功するので、とてもスムーズにマップを作成することができました。
手軽にマップの作成ができるので、ぜひ皆さんも使ってみてください。