本記事はAzure Spatial Anchorsの調査②の続きであり、最後の記事となります。
Azure Spatial Anchorsを使用して、複数のアンカーを配置したり、アンカーにリレーションをつけたり、アンカーの再現をしたりする検証アプリを作成しました。これらの実装を行う上でのポイントについて紹介していきます。
検証アプリの動画
実装のポイント
①セッションの開始
Azure Spatial Anchorsを使用するためには、セッションを生成しておく必要があります。
private async Task StartSessionAsync() { // セッションの作成 if (m_SpatialAnchorManager.Session == null) { await m_SpatialAnchorManager.CreateSessionAsync(); } // セッションの開始 if (!m_SpatialAnchorManager.IsSessionStarted) { await m_SpatialAnchorManager.StartSessionAsync(); } }
②アンカー情報の保存
アンカー情報を保存する上で、下記の2点がポイントとなります。
クラウドにアップロードしたCloudSpatialAnchor(アンカー情報)を再現するためには、CoundSpatialAnchor.Identifier(アンカー識別子)が必要となるため、サーバーorローカル問わずどこかに永続化しておく必要があります。
- CloudSpatialAnchorをクラウドにアップロード
- CoundSpatialAnchor.Identifierを永続化(今回はローカルに保存)
private async Task SaveAnchorAsync(GameObject anchorObject) { // CloudNativeAnchorコンポーネントを取得 CloudNativeAnchor cna = anchorObject.GetComponent<CloudNativeAnchor>(); // CloudAnchorのCloud Positionが未生成の場合、生成する if (cna.CloudAnchor == null) await cna.NativeToCloud(); CloudSpatialAnchor cloudAnchor = cna.CloudAnchor; // アンカーの有効期限を設定 cloudAnchor.Expiration = DateTimeOffset.Now.AddDays(7); // 現実空間の特徴点の収集が十分であるかの判定 while (!m_SpatialAnchorManager.IsReadyForCreate) { await Task.Delay(330); float createProgress = m_SpatialAnchorManager.SessionStatus.RecommendedForCreateProgress; Debug.Log($"Move your device to capture more environment data: {createProgress:0%}"); } try { // クラウドにアンカーを保存 await m_SpatialAnchorManager.CreateAnchorAsync(cloudAnchor); // ローカルにアンカーIdentifierを保存 SaveAnchorIdentifier(cloudAnchor.Identifier); Debug.Log($"Saved anchor. Idendifier is {cloudAnchor.Identifier}"); } catch (Exception exception) { Debug.LogException(exception); Debug.Log("Failed to save anchor " + exception.ToString()); } } private void SaveAnchorIdentifier(string identifier) { File.AppendAllText("任意のファイルパス", identifier + Environment.NewLine); }
③保存したアンカーの再現
保存したアンカーを再現するためには下記のような手順が必要となります。
1. ウォッチャーを生成
AnchorLocateCriteria.Identifiersに永続化しておいたCoundSpatialAnchor.Identifierを設定し、ウォッチャーを生成します。
private CloudSpatialAnchorWatcher CreateWatcher() { if ((m_SpatialAnchorManager != null) && (m_SpatialAnchorManager.Session != null)) { AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria(); // ローカルに永続化したアンカーIdentiferを取得 string[] identifiers = FileUtility.ReadFile(); // AnchorLocateCriteriaにアンカーIdentifierを設定 anchorLocateCriteria.Identifiers = identifiers; // ウォッチャーを生成 return m_SpatialAnchorManager.Session.CreateWatcher(anchorLocateCriteria); } else { Debug.Log("Wacher cannot be created."); return null; } } private string[] ReadFile() { if (!File.Exists("任意のファイルパス")) return null; string readText = File.ReadAllText("任意のファイルパス"); readText = readText.Replace(Environment.NewLine, "\r"); readText = readText.Trim('\r'); var result = readText.Split('\r'); if (result.Length == 1 && result[0].Equals(string.Empty)) result = null; return result; }
2. SpatialAnchorManager.AnchorLocatedイベントでアンカーを再現
SpatialAnchorManager.AnchorLocatedイベントは、ウォッチャーに登録したアンカーが探知されたときor探知できなかったときに呼び出されます。 AnchorLocatedEventArgs.Statusを判定し、アンカーが探知できた(=LocateAnchorStatus.Located)ときにアンカーを再現します。このとき、Unityの機能を使用する際は、UnityDispatcher.InvokeOnAppThread()を使用し、Unityのメインスレッドで処理を行う必要があります。
private void Start() { // 初期化の際にイベントを登録 m_SpatialAnchorManager.AnchorLocated += SpatialAnchorManager_AnchorLocated; } private void SpatialAnchorManager_AnchorLocated(object sender, AnchorLocatedEventArgs args) { Debug.LogFormat("Anchor recognized as a possible anchor {0} {1}", args.Identifier, args.Status); if (args.Status == LocateAnchorStatus.Located) { // 引数からCloudSpatialAnchorを取得 var cloudAnchor = args.Anchor; // Unityのメインスレッドで処理 UnityDispatcher.InvokeOnAppThread(() => { Pose anchorPose = Pose.identity; // アンカーの位置を取得 anchorPose = cloudAnchor.GetPose(); // アンカーを生成 var anchorObject = CreateAnchorObject(anchorPose.position, anchorPose.rotation); Debug.Log($"Reproduce anchor. Idendifier is {cloudAnchor.Identifier}"); }); } } private GameObject CreateAnchorObject(Vector3 worldPos, Quaternion worldRot) { // アンカー用のGameObjectを生成 GameObject anchorObject = Instantiate(m_AnchorPrefab.gameObject, worldPos, worldRot); // CloudNativeAnchorコンポーネントをアタッチ anchorObject.AddComponent<CloudNativeAnchor>(); // 色を設定 anchorObject.GetComponent<MeshRenderer>().material.color = Color.yellow; // コライダーを非アクティブ化 anchorObject.GetComponent<BoxCollider>().enabled = false; return anchorObject; }
3. SpatialAnchorManager.LocateAnchorsCompletedイベントで完了通知
SpatialAnchorManager.LocateAnchorsCompletedイベントはウォッチャーに登録したすべてのアンカーが処理された(探知されたかどうかを問わず)後に呼び出されます。 このとき、Unityの機能を使用する際は、UnityDispatcher.InvokeOnAppThread()を使用し、Unityのメインスレッドで処理を行う必要があります。
private void Start() { // 初期化の際にイベントを登録 m_SpatialAnchorManager.LocateAnchorsCompleted += SpatialAnchorManager_LocateAnchorsCompleted; } private void SpatialAnchorManager_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args) { Debug.Log($"アンカーの検索が完了し、{_existingCloudAnchors.Count}個のアンカーが見つかりました。"); UnityDispatcher.InvokeOnAppThread(() => { // do something... }); }
まとめ
HoloLens 2をマーカーレスでの使用を目指して、Azure Spatial Anchorsの調査を3本の記事に渡り行ってきました。
今まではQRコードを使った位置合わせが主流であったと思いますが、Azure Spatial Anchorsを使うことで現実空間の特徴点をマーカー代わりに使用することが可能となります。
Azure Spatial Anchorsをはじめとした、色々な技術やサービスを組み合わせることで、ユーザーが位置合わせをあまり意識しなくてもMR体験ができる未来もそう遠くないでしょう。