Getting your holograms to stay in place, move with you, or in some cases position themselves relative to other holograms is a big part of creating Mixed Reality applications. This article will take you through our recommended solution using World Locking Tools, but we'll also cover manually setting up spatial anchors in your Unity projects. Before we jump into any code, it's important to understand how Unity handles coordinate space and anchors in its own engine.
World-scale coordinate systems
Today, when writing games, data visualization apps, or virtual reality apps, the typical approach is to establish one absolute world coordinate system that all other coordinates can reliably map back to. In that environment, you can always find a stable transform that defines a relationship between any two objects in that world. If you didn't move those objects, their relative transforms would always remain the same. This kind of global coordinate system is easy to get right when rendering a purely virtual world where you know all of the geometry in advance. Room-scale VR apps today typically establish this kind of absolute room-scale coordinate system with its origin on the floor.
In contrast, an untethered mixed reality device such as HoloLens has a dynamic sensor-driven understanding of the world, continuously adjusting its knowledge over time of the user's surroundings as they walk many meters across an entire floor of a building. In a world-scale experience, if you placed all your holograms in a naive rigid coordinate system, those holograms would end up drifting over time, either based on the world or relative to each other.
For example, the headset may currently believe two locations in the world to be 4 meters apart, and then later refine that understanding, learning that the locations are in fact 3.9 meters apart. If those holograms had initially been placed 4 meters apart in a single rigid coordinate system, one of them would then always appear 0.1 meters off from the real world.
You can manually place spatial anchors in Unity to maintain a hologram's position in the physical world when the user is mobile. However, this sacrifices the self-consistency within the virtual world. Different anchors are constantly moving in relation to one another, and are also moving through the global coordinate space. In this scenario, simple tasks like layout become difficult. Physics simulation can also be problematic.
World Locking Tools (WLT) gets you the best of both worlds, stabilizing a single rigid coordinate system using an internal supply of spatial anchors spread throughout the virtual scene as the user moves around. WLT analyzes the coordinates of the camera and those spatial anchors every frame. Instead of changing the coordinates of everything in the world to compensate for the corrections in the coordinates of the user's head, WLT just fixes the head's coordinates instead.
Choosing your world locking approach
We recommend that you use World Locking Tools for all your hologram positioning needs.
World Locking Tools provides a stable coordinate system that minimizes the visible inconsistencies between virtual and real world markers. Put another way, it world-locks the entire scene with a shared pool of anchors, rather than locking each group of objects with the group's own individual anchor.
World Locking Tools automatically handles all creation and management of spatial anchors internally. You don't need to interact with ARAnchorManager or WorldAnchor to keep your holograms world-locked.
For Unity 2019/2020 using OpenXR or the Windows XR Plugin, you need to use ARAnchorManager
For older Unity versions or WSA projects, you need to use WorldAnchor
If you're ready to get started using the World Locking Tools, we recommend that you download the Mixed Reality Feature Tool. If you'd like to learn more about the basics, visit the main World Locking Tools documentation page. It contains links to our Overview and Quickstart and other topics that are useful for getting you started.
When your project is ready to go, run the configure scene utility from Mixed Reality > World Locking Tools:
Important
The Configure scene utility can be rerun at any time. For example, it should be rerun if the AR target has been changed from Legacy to XR SDK. If the scene is already properly configured, running the utility has no effect.
Visualizers
During early development, adding visualizers can be helpful to ensure WLT is setup and working properly. They can be removed for production performance, or if for any reason are no longer needed, using the Remove visualizers utility. More details on the visualizers can be found in the Tools documentation.
The Mixed Reality OpenXR Plugin supplies basic anchor functionality through an implementation of Unity’s ARFoundation ARAnchorManager. To learn the basics on ARAnchors in ARFoundation, visit the ARFoundation Manual for AR Anchor Manager.
Building a world-scale experience
Namespace:UnityEngine.XR.WSA Type:WorldAnchor
For true world-scale experiences on HoloLens that let users wander beyond 5 meters, you'll need new techniques beyond those used for room-scale experiences. One key technique you'll use is to create a spatial anchor to lock a cluster of holograms precisely in place in the physical world, no matter how far the user has roamed, and then find those holograms again in later sessions.
In Unity, you create a spatial anchor by adding the WorldAnchor Unity component to a GameObject.
Adding a World Anchor
To add a world anchor, call AddComponent<WorldAnchor>() on the game object with the transform you want to anchor in the real world.
That's it! This game object will now be anchored to its current location in the physical world - you may see its Unity world coordinates adjust slightly over time to ensure that physical alignment. Refer to loading a world anchor to find this anchored location again in a future app session.
Removing a World Anchor
If you no longer want the GameObject locked to a physical world location and don't intend on moving it this frame, then you can just call Destroy on the World Anchor component.
Destroy(gameObject.GetComponent<WorldAnchor>());
If you want to move the GameObject this frame, you need to call DestroyImmediate instead.
A WorldAnchor may not be locatable in the physical world at a point in time. If that occurs, Unity won't be updating the transform of the anchored object. This also can change while an app is running. Failure to handle the change in locatability will cause the object to not appear in the correct physical location in the world.
To be notified about locatability changes:
Subscribe to the OnTrackingChanged event
Handle the event
The OnTrackingChanged event will be called whenever the underlying spatial anchor changes between a state of being locatable vs. not being locatable.
private void Anchor_OnTrackingChanged(WorldAnchor self, bool located)
{
// This simply activates/deactivates this object and all children when tracking changes
self.gameObject.SetActiveRecursively(located);
}
Sometimes anchors are located immediately. In this case, this isLocated property of the anchor will be set to true when AddComponent<WorldAnchor>() returns. As a result, the OnTrackingChanged event won't be triggered. A clean pattern would be to call your OnTrackingChanged handler with the initial IsLocated state after attaching an anchor.
Spatial anchors save holograms in real-world space between application sessions. Once saved in the HoloLens' anchor store, they can be found and loaded in different sessions and are an ideal fallback when there's no internet connectivity.
Important
Local anchors are stored on device, while Azure Spatial Anchors are stored in the cloud. If you're looking to use Azure cloud services to store your anchors, we have a document that can walk you through integrating Azure Spatial Anchors. Note that you can have local and Azure anchors in the same project without conflict.
By default, World Locking Tools will restore Unity's coordinate system relative to the physical world across sessions on devices that support persistence of local spatial anchors. In order to have a hologram appear in the same place in the physical world after quitting and re-running the application, the application only needs to restore the same pose to the hologram.
If the application needs finer control, Auto-Save and Auto-Load may be disabled in the inspector, and persistence managed from a script as described in the persistence section of the documentation.
Local anchor persistence is currently only supported on the HoloLens family of devices. However, on Android and iOS, as well as HoloLens, persistence of coordinate spaces across sessions, as well as sharing coordinate spaces across devices, is supported via an integration with Azure Spatial Anchors. There is an abundance of further information and samples of using World Locking Tools in tandem with Azure Spatial Anchors.
An additional API called the XRAnchorStore enables anchors to be persisted between sessions. The XRAnchorStore is a representation of the saved anchors on a device. Anchors can be persisted from ARAnchors in the Unity scene, loaded from storage into new ARAnchors, or deleted from storage.
Note
These anchors are to be saved and loaded on the same device. Cross-device anchors are supported through Azure Spatial Anchors.
Namespaces
For Unity 2020 and OpenXR:
using Microsoft.MixedReality.ARSubsystems.XRAnchorStore
or Unity 2019/2020 + Windows XR Plugin:
using UnityEngine.XR.WindowsMR.XRAnchorStore
Public methods
{
// A list of all persisted anchors, which can be loaded.
public IReadOnlyList<string> PersistedAnchorNames { get; }
// Clear all persisted anchors
public void Clear();
// Load a single persisted anchor by name. The ARAnchorManager will create this new anchor and report it in
// the ARAnchorManager.anchorsChanged event. The TrackableId returned here is the same TrackableId the
// ARAnchor will have when it is instantiated.
public TrackableId LoadAnchor(string name);
// Attempts to persist an existing ARAnchor with the given TrackableId to the local store. Returns true if
// the storage is successful, false otherwise.
public bool TryPersistAnchor(TrackableId id, string name);
// Removes a single persisted anchor from the anchor store. This will not affect any ARAnchors in the Unity
// scene, only the anchors in storage.
public void UnpersistAnchor(string name);
}
Getting an anchor store reference
To load the XRAnchorStore with Unity 2020 and OpenXR, use extension method on the XRAnchorSubsystem, the subsystem of an ARAnchorManager:
public static Task<XRAnchorStore> LoadAnchorStoreAsync(this XRAnchorSubsystem anchorSubsystem)
To load the XRAnchorStore with Unity 2019/2020 and the Windows XR Plugin, use the extension method on the XRReferencePointSubsystem (Unity 2019) or XRAnchorSubsystem (Unity 2020), the subsystem of an ARReferencePointManager/ARAnchorManager:
// Unity 2019 + Windows XR Plugin
public static Task<XRAnchorStore> TryGetAnchorStoreAsync(this XRReferencePointSubsystem anchorSubsystem);
// Unity 2020 + Windows XR Plugin
public static Task<XRAnchorStore> TryGetAnchorStoreAsync(this XRAnchorSubsystem anchorSubsystem);
Loading an anchor store
To load an anchor store in Unity 2020 and OpenXR, access it from an ARAnchorManager's subsystem as follows:
To see a full example of persisting / unpersisting anchors, check out the Anchors -> Anchors Sample GameObject and AnchorsSample.cs script in the [Mixed Reality OpenXR Plugin Sample Scene]((https://github.com/microsoft/OpenXR-Unity-MixedReality-Samples):
The WorldAnchorStore is the key to creating holographic experiences where holograms stay in specific real world positions across instances of the application. Users can then pin individual holograms wherever they want, and find them later in the same spot over many uses of your app.
The WorldAnchorStore will allow you to persist the location of WorldAnchor's across sessions. To actually persist holograms across sessions, you'll need to separately keep track of your GameObjects that use a particular world anchor. It often makes sense to create a GameObject root with a world anchor and have children holograms anchored by it with a local position offset.
To load holograms from previous sessions:
Get the WorldAnchorStore
Load app data relating to the world anchor, which gives you the ID of the world anchor
Load a world anchor from its ID
To save holograms for future sessions:
Get the WorldAnchorStore
Save a world anchor specifying an ID
Save app data relating to the world anchor along with an ID
Getting the WorldAnchorStore
You'll want to keep a reference to the WorldAnchorStore so you know when it's ready to perform an operation. Since this is an async call, potentially as soon as start up, you want to call:
WorldAnchorStore.GetAsync(StoreLoaded);
StoreLoaded in this case is our handler for when the WorldAnchorStore has completed loading:
We now have a reference to the WorldAnchorStore, which we'll use to save and load specific World Anchors.
Saving a WorldAnchor
To save, we simply need to name what we are saving and pass it in the WorldAnchor we got before when we want to save. Note: trying to save two anchors to the same string will fail (store.Save will return false). Delete the previous save before saving the new one:
private void SaveGame()
{
// Save data about holograms positioned by this world anchor
if (!this.savedRoot) // Only Save the root once
{
this.savedRoot = this.store.Save("rootGameObject", anchor);
Assert(this.savedRoot);
}
}
Loading a WorldAnchor
And to load:
private void LoadGame()
{
// Save data about holograms positioned by this world anchor
this.savedRoot = this.store.Load("rootGameObject", rootGameObject);
if (!this.savedRoot)
{
s// We didn't actually have the game root saved! We have to re-place our objects or start over
}
}
We additionally can use store.Delete() to remove an anchor we previously saved and store.Clear() to remove all previously saved data.
Enumerating Existing Anchors
To discover previously stored anchors, call GetAllIds.
string[] ids = this.store.GetAllIds();
for (int index = 0; index < ids.Length; index++)
{
Debug.Log(ids[index]);
}
Persisting holograms for multiple devices
You can use Azure Spatial Anchors to create a durable cloud anchor from a local WorldAnchor, which your app can then locate across multiple HoloLens, iOS and Android devices, even if those devices aren't present together at the same time. Because cloud anchors are persistent, multiple devices over time can each see content rendered relative to that anchor in the same physical location.
If you're following the Unity development checkpoint journey we've laid out, you're in the midst of exploring the Mixed Reality core building blocks. From here, you can continue to the next building block: