はじめに
今回は、 AR 空間のアンカーをクラウドで共有できる Azure Spatial Anchors
を触ってみました。
ARCore や ARKit にも Cloud を介してアンカー共有をする機能は存在しますが、 Azure Spatial Anchors
を使えば、 Android 、 iOS 、 Hololens のプラットフォームの垣根を超えて、同じアンカーを複数端末で共有することができます。
※2019年9月18日現在では、まだプレビュー版なので、今後この記事の内容から変わる可能性があります。
開発環境
- Unity 2019.1.1f1
- Azure Spatial Anchors 1.1.1
- ARCore
- サンプルプロジェクトに元から入っていたので正確なバージョンはわかりませんが、正面カメラに対応しているので、おそらく1.7だと思います。
実装方法
Azure への登録と、 Azure からの取得の実装方法を見ていきましょう。
※ Azure 上での DB 作成などの手順は、詳しく解説してくれているサイトがあったので、今回は省略します。
※ここから下のコードでは、サンプルの最低限の箇所のみを抜き出していきます。
Anchor の作成・登録
private void Update() { #if UNITY_ANDROID long latestFrameTimeStamp = _nativeSession.FrameApi.GetTimestamp(); bool newFrameToProcess = latestFrameTimeStamp > _lastFrameProcessedTimeStamp; if (newFrameToProcess) { _cloudSession.ProcessFrame(_nativeSession.FrameHandle); _lastFrameProcessedTimeStamp = latestFrameTimeStamp; } #endif #if UNITY_IOS UnityARSessionNativeInterface.ARFrameUpdatedEvent += UnityARSessionNativeInterface_ARFrameUpdatedEvent; void UnityARSessionNativeInterface_ARFrameUpdatedEvent(UnityARCamera camera) { _cloudSession.ProcessFrame(_arkitSession.GetNativeFramePtr()); } } #endif
Hololens では必要ありませんが、 Android 、 iOS では、 ARCore 、 ARKit から CloudSpatialAnchorSession
にフレームを提供してもらう必要があります。
private void Start() { CreateNewCloudSession(); } private void CreateNewCloudSession() { cloudSpatialAnchorSession = new CloudSpatialAnchorSession(); cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim(); cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim(); cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated; cloudSpatialAnchorSession.Start(); } private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args) { SessionStatusIndicators[(int)SessionStatusIndicatorType.ReadyForCreate] = args.Status.ReadyForCreateProgress; SessionStatusIndicators[(int)SessionStatusIndicatorType.RecommendedForCreate] = args.Status.RecommendedForCreateProgress; }
CloudSession
を作成します。
cloudSpatialAnchorSession.SessionUpdated
に RecommendedForCreateProgress
関数(または、 ReadyForCreateProgress
関数)を呼び出す処理(上のコードだと CloudSpatialAnchorSession_SessionUpdated
)を登録します。
これで、 cloudSpatialAnchorSession.SessionUpdated
のタイミングで、登録に必要な周辺情報(頂点情報?)の収集割合が更新されるようになります。
コード中の SpatialAnchorsAccountId
は上の画像の矢印の部分、
SpatialAnchorsAccountKey
は上の画像の矢印の部分の文字列が格納されています。
public float ReadyForCreateProgress { get { float result; NativeLibraryHelpers.CheckStatus(this.handle, NativeLibrary.sscsessionstatusgetreadyforcreate_progress(this.handle, out result)); return result; } } public float RecommendedForCreateProgress { get { float result; NativeLibraryHelpers.CheckStatus(this.handle, NativeLibrary.sscsessionstatusgetrecommendedforcreate_progress(this.handle, out result)); return result; } }
cloudSpatialAnchorSession.SessionUpdated
に登録した ReadyForCreateProgress
関数と RecommendedForCreateProgress
関数の中身です。
2つの内のどちらか(RecommendedForCreateProgress の方が精度が高い?)の戻り値が1以上になると登録に必要な周辺情報が集まったことになります。
CloudSpatialAnchor cloudSpatialAnchor = new CloudSpatialAnchor(); cloudSpatialAnchor.LocalAnchor = spawnedObject.GetNativeAnchorPointer();
spawnObject
AR 空間に生成されたGameObject
- ※ AR 空間に
GameObject
を生成する方法は省略します。
- ※ AR 空間に
GetNativeAnchorPointer
関数ARKit
、ARCore
と通してspawnedObject
のPose
からローカルアンカーを作成します。
cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(7);
アンカーの保存期限を設定。
↑のコードでは7日後まで保存されます。
この期限を過ぎると、アンカーが見つけられなくなります。
await cloudSpatialAnchorSession.CreateAnchorAsync(cloudSpatialAnchor);
ローカルアンカーを基に新たな Spatial Anchor が作成され cloudSpatialAnchor
に格納されます。
HttpClient client = new HttpClient(); var response = await client.PostAsync(appServiceUrl, new StringContent(cloudSpatialAnchor.Identifier)); if (response.IsSuccessStatusCode) { string responseBody = await response.Content.ReadAsStringAsync(); }
Azure 上に作成した AppService に SpatialAnchor
の Key を送信する。
appServiceUrl
は https://[アプリケーション名].azurewebsites.net/api/anchor
みたいになると思います。
※通信部分等の例外処理は適宜おこなってください。
Anchor の取得
long anchorNumber; long.TryParse(inputText, out long anchorNumber);
取得したいアンカーの ID を Input Field
などで入力し long
型に変換します。
AnchorIdsToLocate.Clear(); AnchorIdsToLocate.AddRange(anchorIds); anchorLocateCriteria.Identifiers = AnchorIdsToLocate.ToArray();
AnchorLocateCriteria.Identifiers
に取得したいアンカーの Key を設定する、
AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria(); CloudSpatialAnchorWatcher currentWatcher = cloudSpatialAnchorSession.CreateWatcher(anchorLocateCriteria);
AnchorLocateCriteria
を生成します。
その後、生成した AnchorLocateCriteria
から CloudSpatialAnchorWatcher
を生成します。
これで CloudSpatialAnchorWatcher
が AnchorLocateCriteria.Identifiers
に格納された Key に対応したアンカーの位置と向きの探索を開始します。
CloudSpatialAnchorWatcher.Stop()
で探索を終了できるので、生成した CloudSpatialAnchorWatcher
をメンバー変数に格納しておいた方が良いと思います。
cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
アンカーの位置と向きを特定した時に呼ばれる cloudSpatialAnchorSession.AnchorLocated
に関数を登録します。
private void CloudManager_OnAnchorLocated(object sender, AnchorLocatedEventArgs args) { if (args.Status == LocateAnchorStatus.Located) { QueueOnUpdate(new Action(() => { currentCloudAnchor = args.Anchor; Pose anchorPose = Pose.identity; #if UNITY_ANDROID || UNITY_IOS anchorPose = currentCloudAnchor.GetAnchorPose(); #endif GameObject newGameObject = GameObject.Instantiate(AnchoredObjectPrefab, anchorPose.position, anchorPose.rotation); newGameObject.AddARAnchor(); })); } }
cloudSpatialAnchorSession.AnchorLocated
に登録した関数の中身です。
取得したアンカーから、生成する GameObject の位置と向きを設定します。
動かしてみる
いろいろ写ってしまったのでカットしましたが、登録時の周辺情報の収集はそこそこ時間がかかります。
読み込み時の周辺情報の収集は、
- アンカー作成時と同じ視点から読み込みをすると、あまり時間がかからない。(少ない周辺情報だけで読み込める)
- アンカー作成時と違う視点から読み込みをすると、時間がかかる時がある。(より多くの周辺情報を必要とする)
ReadyForCreateProgress と RecommendedForCreateProgress の違い
ReadyForCreateProgress
と RecommendedForCreateProgress
を比較してみました。
最大値は、
ReadyForCreateProgress
: 2.5RecommendedForCreateProgress
: 2.0
で、同時に最大値に達しました。
ReadyForCreateProgress
の方が早く1を返すようになるので、 RecommendedForCreateProgress
の方が Spatial Anchor
作成に必要な周辺情報を多く要求し、精度が高いということだと思われます。
Anchor の削除
Anchor の削除機能の実装方法がわかりませんでした。
await cloudSpatialAnchorSession.DeleteAnchorAsync(currentCloudAnchor);
このコードを実行すると
- Azure 上の
Spatial Anchor Account
にAnchor Deleted
の命令は送られる CosmosDB
からは削除されない- アプリ側から Key のロードはできるが、そこから Anchor の特定ができなくなる(?)
おそらく
- Key 自体は CosmosDB 上に保存される
- Key に対応した Anchor の情報は別の場所に保存される
- 「
DeleteAnchorAsync
関数」と、「cloudSpatialAnchor.Expiration
に設定した期限は」別の場所に保存した Anchor の情報のみに影響を与えている
ということだと思います。
DeleteAnchorAsync
時に Spatial Anchor Account
に命令が送られているので、 Spatial Anchor Account
に保存されている?
おわりに
やっていることはシンプルなので、いろいろと遊ぶことができそうです。
まだまだ触れられていない機能もあるようなので、今後も積極的に使っていきたいです。
削除もなんとかしたいですね。