How to create and locate anchors using Azure Spatial Anchors in Unity
Azure Spatial Anchors allow you to share anchors in the world between different devices. It supports several different development environments. In this article, we'll dive into how to use the Azure Spatial Anchors SDK, in Unity, to:
- Correctly set up and manage an Azure Spatial Anchors session.
- Create and set properties on local anchors.
- Upload them to the cloud.
- Locate and delete cloud spatial anchors.
Prerequisites
To complete this guide, make sure you have:
- Read through the Azure Spatial Anchors overview.
- Completed one of the 5-minute Quickstarts.
- Basic knowledge on C# and Unity.
- Basic knowledge on ARCore if you want to use Android, or ARKit if you want to use iOS.
Initialize the session
The main entry point for the SDK is the class representing your session. Typically you'll declare a field in the class that manages your view and native AR session.
Learn more about the CloudSpatialAnchorSession class.
CloudSpatialAnchorSession cloudSession;
// In your view handler
this.cloudSession = new CloudSpatialAnchorSession();
Set up authentication
To access the service, you need to provide an account key, access token, or Microsoft Entra auth token. You can also read more about this in the Authentication concept page.
Account Keys
Account Keys are a credential that allows your application to authenticate with the Azure Spatial Anchors service. The intended purpose of Account Keys is to help you get started quickly. Especially during the development phase of your application's integration with Azure Spatial Anchors. As such, you can use Account Keys by embedding them in your client applications during development. As you progress beyond development, it's highly recommended to move to an authentication mechanism that is production-level, supported by Access Tokens, or Microsoft Entra user authentication. To get an Account Key for development, visit your Azure Spatial Anchors account, and navigate to the "Keys" tab.
Learn more about the SessionConfiguration class.
this.cloudSession.Configuration.AccountKey = @"MyAccountKey";
Access Tokens
Access Tokens are a more robust method to authenticate with Azure Spatial Anchors. Especially as you prepare your application for a production deployment. The summary of this approach is to set up a back-end service that your client application can securely authenticate with. Your back-end service interfaces with AAD at runtime and with the Azure Spatial Anchors Secure Token Service to request an Access Token. This token is then delivered to the client application and used in the SDK to authenticate with Azure Spatial Anchors.
this.cloudSession.Configuration.AccessToken = @"MyAccessToken";
If an access token isn't set, you must handle the TokenRequired
event, or implement the tokenRequired
method on the delegate protocol.
You can handle the event synchronously by setting the property on the event arguments.
Learn more about the TokenRequiredDelegate delegate.
this.cloudSession.TokenRequired += (object sender, TokenRequiredEventArgs args) =>
{
args.AccessToken = @"MyAccessToken";
};
If you need to execute asynchronous work in your handler, you can defer setting the token by requesting a deferral
object and then completing it, as in the following example.
this.cloudSession.TokenRequired += async (object sender, TokenRequiredEventArgs args) =>
{
var deferral = args.GetDeferral();
string myToken = await MyGetTokenAsync();
if (myToken != null) args.AccessToken = myToken;
deferral.Complete();
};
Microsoft Entra authentication
Azure Spatial Anchors also allows applications to authenticate with user Microsoft Entra ID (Active Directory) tokens. For example, you can use Microsoft Entra tokens to integrate with Azure Spatial Anchors. If an Enterprise maintains users in Microsoft Entra ID, you can supply a user Microsoft Entra token in the Azure Spatial Anchors SDK. Doing so allows you to authenticate directly to the Azure Spatial Anchors service for an account that's part of the same Microsoft Entra tenant.
this.cloudSession.Configuration.AuthenticationToken = @"MyAuthenticationToken";
Like with access tokens, if a Microsoft Entra token isn't set, you must handle the TokenRequired event, or implement the tokenRequired method on the delegate protocol.
You can handle the event synchronously by setting the property on the event arguments.
this.cloudSession.TokenRequired += (object sender, TokenRequiredEventArgs args) =>
{
args.AuthenticationToken = @"MyAuthenticationToken";
};
If you need to execute asynchronous work in your handler, you can defer setting the token by requesting a deferral
object and then completing it, as in the following example.
this.cloudSession.TokenRequired += async (object sender, TokenRequiredEventArgs args) =>
{
var deferral = args.GetDeferral();
string myToken = await MyGetTokenAsync();
if (myToken != null) args.AuthenticationToken = myToken;
deferral.Complete();
};
Set up the session
Invoke Start()
to enable your session to process environment data.
To handle events raised by your session, attach an event handler.
Learn more about the Start method.
#if UNITY_ANDROID || UNITY_IOS
this.cloudSession.Session = aRSession.subsystem.nativePtr.GetPlatformPointer();
#elif UNITY_WSA || WINDOWS_UWP
// No need to set a native session pointer for HoloLens.
#else
throw new NotSupportedException("The platform is not supported.");
#endif
this.cloudSession.Start();
Provide frames to the session
The spatial anchor session works by mapping the space around the user. Doing so helps to determine where anchors are located. Mobile platforms (iOS & Android) require a native call to the camera feed to obtain frames from your platform's AR library. In contrast, HoloLens is constantly scanning the environment, so there's no need for a specific call like on mobile platforms.
Learn more about the ProcessFrame method.
#if UNITY_ANDROID || UNITY_IOS
XRCameraFrame xRCameraFrame;
if (aRCameraManager.subsystem.TryGetLatestFrame(cameraParams, out xRCameraFrame))
{
long latestFrameTimeStamp = xRCameraFrame.timestampNs;
bool newFrameToProcess = latestFrameTimeStamp > lastFrameProcessedTimeStamp;
if (newFrameToProcess)
{
session.ProcessFrame(xRCameraFrame.nativePtr.GetPlatformPointer());
lastFrameProcessedTimeStamp = latestFrameTimeStamp;
}
}
#endif
Provide feedback to the user
You can write code to handle the session updated event. This event fires every time the session improves its understanding of your surroundings. Doing so, allows you to:
- Use the
UserFeedback
class to provide feedback to the user as the device moves and the session updates its environment understanding. To do this, - Determine at what point there's enough tracked spatial data to create spatial anchors. You determine this with either
ReadyForCreateProgress
orRecommendedForCreateProgress
. OnceReadyForCreateProgress
is above 1, we have enough data to save a cloud spatial anchor, though we recommend you wait untilRecommendedForCreateProgress
is above 1 to do so.
Learn more about the SessionUpdatedDelegate delegate.
this.cloudSession.SessionUpdated += (object sender, SessionUpdatedEventArgs args) =>
{
var status = args.Status;
if (status.UserFeedback == SessionUserFeedback.None) return;
this.feedback = $"Feedback: {Enum.GetName(typeof(SessionUserFeedback), status.UserFeedback)} -" +
$" Recommend Create={status.RecommendedForCreateProgress: 0.#%}";
};
Create a cloud spatial anchor
To create a cloud spatial anchor, you first create an anchor in your platform's AR system, and then create a cloud counterpart. You use the CreateAnchorAsync()
method.
Learn more about the CloudSpatialAnchor class.
// Create a local anchor, perhaps by hit-testing and spawning an object within the scene
Vector3 hitPosition = new Vector3();
#if UNITY_ANDROID || UNITY_IOS
Vector2 screenCenter = new Vector2(0.5f, 0.5f);
List<ARRaycastHit> aRRaycastHits = new List<ARRaycastHit>();
if(arRaycastManager.Raycast(screenCenter, aRRaycastHits) && aRRaycastHits.Count > 0)
{
ARRaycastHit hit = aRRaycastHits[0];
hitPosition = hit.pose.position;
}
#elif WINDOWS_UWP || UNITY_WSA
RaycastHit hit;
if (this.TryGazeHitTest(out hit))
{
hitPosition = hit.point;
}
#endif
Quaternion rotation = Quaternion.AngleAxis(0, Vector3.up);
this.localAnchor = GameObject.Instantiate(/* some prefab */, hitPosition, rotation);
this.localAnchor.AddComponent<CloudNativeAnchor>();
// If the user is placing some application content in their environment,
// you might show content at this anchor for a while, then save when
// the user confirms placement.
CloudNativeAnchor cloudNativeAnchor = this.localAnchor.GetComponent<CloudNativeAnchor>();
if (cloudNativeAnchor.CloudAnchor == null) { await cloudNativeAnchor.NativeToCloud(); }
CloudSpatialAnchor cloudAnchor = cloudNativeAnchor.CloudAnchor;
await this.cloudSession.CreateAnchorAsync(cloudAnchor);
this.feedback = $"Created a cloud anchor with ID={cloudAnchor.Identifier}");
As described earlier, you need sufficient environment data captured before trying to create a new cloud spatial anchor. That means ReadyForCreateProgress
has to be above 1, though we recommend you wait until RecommendedForCreateProgress
is above 1 to do so.
Learn more about the GetSessionStatusAsync method.
SessionStatus value = await this.cloudSession.GetSessionStatusAsync();
if (value.RecommendedForCreateProgress < 1.0f) return;
// Issue the creation request ...
Set properties
You may choose to add some properties when saving your cloud spatial anchors. Like the type of object being saved, or basic properties like whether it should be enabled for interaction. Doing so can be useful upon discovery: you can immediately render the object for the user, for example a picture frame with blank content. Then, a different download in the background gets additional state details, for example, the picture to display in the frame.
Learn more about the AppProperties property.
CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor() { LocalAnchor = localAnchor };
cloudAnchor.AppProperties[@"model-type"] = @"frame";
cloudAnchor.AppProperties[@"label"] = @"my latest picture";
await this.cloudSession.CreateAnchorAsync(cloudAnchor);
Update properties
To update the properties on an anchor, you use the UpdateAnchorProperties()
method. If two or more devices try to update properties for the same anchor at the same time, we use an optimistic concurrency model. Which means that the first write will win. All other writes will get a "Concurrency" error: a refresh of the properties would be needed before trying again.
Learn more about the UpdateAnchorPropertiesAsync method.
CloudSpatialAnchor anchor = /* locate your anchor */;
anchor.AppProperties[@"last-user-access"] = @"just now";
await this.cloudSession.UpdateAnchorPropertiesAsync(anchor);
You can't update the location of an anchor once it has been created on the service - you must create a new anchor and delete the old one to track a new position.
If you don't need to locate an anchor to update its properties, you can use the GetAnchorPropertiesAsync()
method, which returns a CloudSpatialAnchor
object with properties.
Learn more about the GetAnchorPropertiesAsync method.
var anchor = await cloudSession.GetAnchorPropertiesAsync(@"anchorId");
if (anchor != null)
{
anchor.AppProperties[@"last-user-access"] = @"just now";
await this.cloudSession.UpdateAnchorPropertiesAsync(anchor);
}
Set expiration
It's also possible to configure your anchor to expire automatically at a given date in the future. When an anchor expires, it will no longer be located or updated. Expiration can only be set when the anchor is created, before saving it to the cloud. Updating expiration afterwards isn't possible. If no expiration is set during anchor creation, the anchor will only expire when deleted manually.
Learn more about the Expiration property.
cloudAnchor.Expiration = DateTimeOffset.Now.AddDays(7);
Locate a cloud spatial anchor
Being able to locate a previously saved cloud spatial anchor is one of the main reasons for using Azure Spatial Anchors. For this, we're using "Watchers". You can only use one Watcher at a time; multiple Watchers are not supported. There are several different ways (also known as Anchor Locate Strategies) a Watcher can locate a cloud spatial anchor. You can use one strategy on a watcher at a time.
- Locate anchors by identifier.
- Locate anchors connected to a previously located anchor. You can learn about anchor relationships here.
- Locate anchor using Coarse relocalization.
Note
Each time you locate an anchor, Azure Spatial Anchors will attempt to use the environment data collected to augment the visual information on the anchor. If you are having trouble locating an anchor, it can be useful to create an anchor, then locate it several times from different angles and lighting conditions.
If you're locating cloud spatial anchors by identifier, you can store the cloud spatial anchor identifier in your application's back-end service, and make it accessible to all devices that can properly authenticate to it. For an example of this, see Tutorial: Share Spatial Anchors across devices.
Instantiate an AnchorLocateCriteria
object, set the identifiers you're looking for, and invoke the CreateWatcher
method on the session by providing your AnchorLocateCriteria
.
Learn more about the CreateWatcher method.
AnchorLocateCriteria criteria = new AnchorLocateCriteria();
criteria.Identifiers = new string[] { @"id1", @"id2", @"id3" };
this.cloudSession.CreateWatcher(criteria);
After your watcher is created, the AnchorLocated
event will fire for every anchor requested. This event fires when an anchor is located, or if the anchor can't be located. If this situation happens, the reason will be stated in the status. After all anchors for a watcher are processed, found or not found, then the LocateAnchorsCompleted
event will fire. There is a limit of 35 identifiers per watcher.
Learn more about the AnchorLocatedDelegate delegate.
this.cloudSession.AnchorLocated += (object sender, AnchorLocatedEventArgs args) =>
{
switch (args.Status)
{
case LocateAnchorStatus.Located:
CloudSpatialAnchor foundAnchor = args.Anchor;
// Go add your anchor to the scene...
break;
case LocateAnchorStatus.AlreadyTracked:
// This anchor has already been reported and is being tracked
break;
case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
// The anchor was deleted or never existed in the first place
// Drop it, or show UI to ask user to anchor the content anew
break;
case LocateAnchorStatus.NotLocated:
// The anchor hasn't been found given the location data
// The user might in the wrong location, or maybe more data will help
// Show UI to tell user to keep looking around
break;
}
}
Delete anchors
Deleting anchors when no longer used is a good practice to include early on in your development process and practices, to keep your Azure resources cleaned up.
Learn more about the DeleteAnchorAsync method.
Delete anchor after locating (recommended)
await this.cloudSession.DeleteAnchorAsync(cloudAnchor);
// Perform any processing you may want when delete finishes
Delete anchor without locating
If you are unable to locate an anchor but would still like to delete it you can use the GetAnchorPropertiesAsync
API which takes an anchorId as input to get the CloudSpatialAnchor
object. You can then pass this object into DeleteAnchorAsync
to delete it.
var anchor = await cloudSession.GetAnchorPropertiesAsync(@"anchorId");
await this.cloudSession.DeleteAnchorAsync(anchor);
Pause, reset, or stop the session
To stop the session temporarily, you can invoke Stop()
. Doing so will stop any watchers and environment processing, even if you invoke ProcessFrame()
. You can then invoke Start()
to resume processing. When resuming, environment data already captured in the session is maintained.
Learn more about the Stop method.
this.cloudSession.Stop();
To reset the environment data that has been captured in your session, you can invoke Reset()
.
Learn more about the Reset method.
this.cloudSession.Reset();
To clean up properly after a session, invoke Dispose()
.
Learn more about the Dispose method.
this.cloudSession.Dispose();
Next steps
In this guide, you learned about how to create and locate anchors using the Azure Spatial Anchors SDK. To learn more about anchor relationships, continue to the next guide.