はじめに
今回は、 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();
spawnObjectAR 空間に生成された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 に保存されている?
おわりに
やっていることはシンプルなので、いろいろと遊ぶことができそうです。
まだまだ触れられていない機能もあるようなので、今後も積極的に使っていきたいです。
削除もなんとかしたいですね。
