Koordinatensysteme in DirectX
Hinweis
Dieser Artikel bezieht sich auf die älteren nativen WinRT-APIs. Für neue native App-Projekte empfehlen wir die Verwendung der OpenXR-API.
Koordinatensysteme bilden die Basis für das räumliche Verständnis Windows Mixed Reality APIs.
Heutige VR- oder Einzelraum-VR-Geräte richten ein primäres Koordinatensystem für ihren nachverfolgten Raum ein. Mixed Reality Geräte wie HoloLens sind für große undefinierte Umgebungen konzipiert, wobei das Gerät seine Umgebung entdeckt und lernt, während der Benutzer herumläuft. Das Gerät passt sich der kontinuierlichen Verbesserung des Wissens über die Räume des Benutzers an, führt jedoch zu Koordinatensystemen, die ihre Beziehung zueinander im Laufe der Lebensdauer der Apps ändern. Windows Mixed Reality unterstützt ein breites Spektrum von Geräten, von sitzenden immersiven Headsets bis hin zu weltweit angeschlossenen Referenzrahmen.
Hinweis
Die Codeausschnitte in diesem Artikel veranschaulichen derzeit die Verwendung von C++/CX anstelle von C++17-kompatiblem C++/WinRT, wie sie in der holografischen C++-Projektvorlage verwendet wird. Die Konzepte sind für ein C++/WinRT-Projekt gleichwertig, obwohl Sie den Code übersetzen müssen.
Räumliche Koordinatensysteme in Windows
Der Kerntyp, der verwendet wird, um reale Koordinatensysteme in Windows zu begründen, ist das SpatialCoordinateSystem. Ein instance dieses Typs stellt ein beliebiges Koordinatensystem dar, das eine Methode zum Abrufen von Transformationsmatrixdaten bereitstellt, die Sie verwenden können, um zwischen zwei Koordinatensystemen zu transformieren, ohne die Details der einzelnen Systeme zu verstehen.
Methoden, die räumliche Informationen zurückgeben, akzeptieren einen SpatialCoordinateSystem-Parameter, mit dem Sie das Koordinatensystem bestimmen können, in dem diese Koordinaten zurückgegeben werden sollen. Räumliche Informationen werden als Punkte, Strahlen oder Volumen in der Umgebung des Benutzers dargestellt, und die Einheiten für diese Koordinaten befinden sich immer in Metern.
Ein SpatialCoordinateSystem verfügt über eine dynamische Beziehung zu anderen Koordinatensystemen, einschließlich denen, die die Position des Geräts darstellen. Zu jedem Zeitpunkt kann das Gerät einige Koordinatensysteme lokalisieren und andere nicht. Für die meisten Koordinatensysteme muss Ihre App bereit sein, Zeiträume zu verarbeiten, in denen sie nicht gefunden werden können.
Ihre Anwendung sollte SpatialCoordinateSystems nicht direkt erstellen, sondern über die Perception-APIs genutzt werden. Es gibt drei Primäre Quellen für Koordinatensysteme in den Wahrnehmungs-APIs, die jeweils einem konzept entsprechen, das auf der Seite Koordinatensysteme beschrieben wird:
- Um einen stationären Referenzrahmen abzurufen, erstellen Sie einen SpatialStationaryFrameOfReference , oder rufen Sie einen aus dem aktuellen SpatialStageFrameOfReference ab.
- Um einen Räumlichen Anker zu erhalten, erstellen Sie einen SpatialAnchor.
- Um einen angefügten Referenzrahmen abzurufen, erstellen Sie einen SpatialLocatorAttachedFrameOfReference.
Alle von diesen Objekten zurückgegebenen Koordinatensysteme sind rechtshändig, mit +y oben, +x nach rechts und +z rückwärts. Sie können sich daran erinnern, in welche Richtung die positive Z-Achse zeigt, indem Sie die Finger der linken oder rechten Hand in die positive x-Richtung zeigen und sie in die positive y-Richtung einschlagen. Die Richtung, in die Ihr Daumen zeigt, entweder auf Sie zu oder von Ihnen weg, ist die Richtung, die die positive Z-Achse für dieses Koordinatensystem zeigt. Die folgende Abbildung zeigt diese beiden Koordinatensysteme.
Linke und rechte Koordinatensysteme
Verwenden Sie die SpatialLocator-Klasse , um basierend auf der HoloLens-Position entweder einen angefügten oder einen stationären Bezugsrahmen für bootstrap in ein SpatialCoordinateSystem zu erstellen. Fahren Sie mit dem nächsten Abschnitt fort, um mehr über diesen Prozess zu erfahren.
Platzieren von Hologrammen in der Welt mithilfe einer Raumbühne
Auf das Koordinatensystem für undurchsichtige Windows Mixed Reality immersive Headsets wird mithilfe der statischen Eigenschaft SpatialStageFrameOfReference::Current zugegriffen. Diese API bietet Folgendes:
- Koordinatensystem
- Informationen darüber, ob der Spieler sitzend oder mobil ist
- Die Grenze eines sicheren Bereichs zum Herumlaufen, wenn der Spieler mobil ist
- Ein Hinweis darauf, ob das Headset richtungsweise ist.
- Ein Ereignishandler für Updates der räumlichen Phase.
Zunächst erhalten wir die raumbezogene Phase und abonnieren Updates für sie:
Code für die Initialisierung der räumlichen Phase
SpatialStageManager::SpatialStageManager(
const std::shared_ptr<DX::DeviceResources>& deviceResources,
const std::shared_ptr<SceneController>& sceneController)
: m_deviceResources(deviceResources), m_sceneController(sceneController)
{
// Get notified when the stage is updated.
m_spatialStageChangedEventToken = SpatialStageFrameOfReference::CurrentChanged +=
ref new EventHandler<Object^>(std::bind(&SpatialStageManager::OnCurrentChanged, this, _1));
// Make sure to get the current spatial stage.
OnCurrentChanged(nullptr);
}
In der OnCurrentChanged-Methode sollte Ihre App die räumliche Phase überprüfen und die Playererfahrung aktualisieren. In diesem Beispiel stellen wir eine Visualisierung der Phasenbegrenzung und der vom Benutzer angegebenen Startposition sowie des Sichtbereichs und des Bereichs der Bewegungseigenschaften der Stufe bereit. Wir greifen auch auf ein eigenes stationäres Koordinatensystem zurück, wenn eine Stufe nicht bereitgestellt werden kann.
Code für das Update der räumlichen Phase
void SpatialStageManager::OnCurrentChanged(Object^ /*o*/)
{
// The event notifies us that a new stage is available.
// Get the current stage.
m_currentStage = SpatialStageFrameOfReference::Current;
// Clear previous content.
m_sceneController->ClearSceneObjects();
if (m_currentStage != nullptr)
{
// Obtain stage geometry.
auto stageCoordinateSystem = m_currentStage->CoordinateSystem;
auto boundsVertexArray = m_currentStage->TryGetMovementBounds(stageCoordinateSystem);
// Visualize the area where the user can move around.
std::vector<float3> boundsVertices;
boundsVertices.resize(boundsVertexArray->Length);
memcpy(boundsVertices.data(), boundsVertexArray->Data, boundsVertexArray->Length * sizeof(float3));
std::vector<unsigned short> indices = TriangulatePoints(boundsVertices);
m_stageBoundsShape =
std::make_shared<SceneObject>(
m_deviceResources,
reinterpret_cast<std::vector<XMFLOAT3>&>(boundsVertices),
indices,
XMFLOAT3(DirectX::Colors::SeaGreen),
stageCoordinateSystem);
m_sceneController->AddSceneObject(m_stageBoundsShape);
// In this sample, we draw a visual indicator for some spatial stage properties.
// If the view is forward-only, the indicator is a half circle pointing forward - otherwise, it
// is a full circle.
// If the user can walk around, the indicator is blue. If the user is seated, it is red.
// The indicator is rendered at the origin - which is where the user declared the center of the
// stage to be during setup - above the plane of the stage bounds object.
float3 visibleAreaCenter = float3(0.f, 0.001f, 0.f);
// Its shape depends on the look direction range.
std::vector<float3> visibleAreaIndicatorVertices;
if (m_currentStage->LookDirectionRange == SpatialLookDirectionRange::ForwardOnly)
{
// Half circle for forward-only look direction range.
visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 9, XM_PI);
}
else
{
// Full circle for omnidirectional look direction range.
visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 16, XM_2PI);
}
// Its color depends on the movement range.
XMFLOAT3 visibleAreaColor;
if (m_currentStage->MovementRange == SpatialMovementRange::NoMovement)
{
visibleAreaColor = XMFLOAT3(DirectX::Colors::OrangeRed);
}
else
{
visibleAreaColor = XMFLOAT3(DirectX::Colors::Aqua);
}
std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);
// Visualize the look direction range.
m_stageVisibleAreaIndicatorShape =
std::make_shared<SceneObject>(
m_deviceResources,
reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
visibleAreaIndicatorIndices,
visibleAreaColor,
stageCoordinateSystem);
m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
}
else
{
// No spatial stage was found.
// Fall back to a stationary coordinate system.
auto locator = SpatialLocator::GetDefault();
if (locator)
{
m_stationaryFrameOfReference = locator->CreateStationaryFrameOfReferenceAtCurrentLocation();
// Render an indicator, so that we know we fell back to a mode without a stage.
std::vector<float3> visibleAreaIndicatorVertices;
float3 visibleAreaCenter = float3(0.f, -2.0f, 0.f);
visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.125f, 16, XM_2PI);
std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);
m_stageVisibleAreaIndicatorShape =
std::make_shared<SceneObject>(
m_deviceResources,
reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
visibleAreaIndicatorIndices,
XMFLOAT3(DirectX::Colors::LightSlateGray),
m_stationaryFrameOfReference->CoordinateSystem);
m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
}
}
}
Die Scheitelpunkte, die die Phasengrenze definieren, werden im Uhrzeigersinn bereitgestellt. Die Windows Mixed Reality Shell zeichnet einen Zaun an der Grenze, wenn sich der Benutzer ihr nähert, aber Sie können den begehbaren Bereich für Ihre eigenen Zwecke triangularisieren. Der folgende Algorithmus kann verwendet werden, um die Phase zu triangularisieren.
Code für die Triangularisierung der räumlichen Phase
std::vector<unsigned short> SpatialStageManager::TriangulatePoints(std::vector<float3> const& vertices)
{
size_t const& vertexCount = vertices.size();
// Segments of the shape are removed as they are triangularized.
std::vector<bool> vertexRemoved;
vertexRemoved.resize(vertexCount, false);
unsigned int vertexRemovedCount = 0;
// Indices are used to define triangles.
std::vector<unsigned short> indices;
// Decompose into convex segments.
unsigned short currentVertex = 0;
while (vertexRemovedCount < (vertexCount - 2))
{
// Get next triangle:
// Start with the current vertex.
unsigned short index1 = currentVertex;
// Get the next available vertex.
unsigned short index2 = index1 + 1;
// This cycles to the next available index.
auto CycleIndex = [=](unsigned short indexToCycle, unsigned short stopIndex)
{
// Make sure the index does not exceed bounds.
if (indexToCycle >= unsigned short(vertexCount))
{
indexToCycle -= unsigned short(vertexCount);
}
while (vertexRemoved[indexToCycle])
{
// If the vertex is removed, go to the next available one.
++indexToCycle;
// Make sure the index does not exceed bounds.
if (indexToCycle >= unsigned short(vertexCount))
{
indexToCycle -= unsigned short(vertexCount);
}
// Prevent cycling all the way around.
// Should not be needed, as we limit with the vertex count.
if (indexToCycle == stopIndex)
{
break;
}
}
return indexToCycle;
};
index2 = CycleIndex(index2, index1);
// Get the next available vertex after that.
unsigned short index3 = index2 + 1;
index3 = CycleIndex(index3, index1);
// Vertices that may define a triangle inside the 2D shape.
auto& v1 = vertices[index1];
auto& v2 = vertices[index2];
auto& v3 = vertices[index3];
// If the projection of the first segment (in clockwise order) onto the second segment is
// positive, we know that the clockwise angle is less than 180 degrees, which tells us
// that the triangle formed by the two segments is contained within the bounding shape.
auto v2ToV1 = v1 - v2;
auto v2ToV3 = v3 - v2;
float3 normalToV2ToV3 = { -v2ToV3.z, 0.f, v2ToV3.x };
float projectionOntoNormal = dot(v2ToV1, normalToV2ToV3);
if (projectionOntoNormal >= 0)
{
// Triangle is contained within the 2D shape.
// Remove peak vertex from the list.
vertexRemoved[index2] = true;
++vertexRemovedCount;
// Create the triangle.
indices.push_back(index1);
indices.push_back(index2);
indices.push_back(index3);
// Continue on to the next outer triangle.
currentVertex = index3;
}
else
{
// Triangle is a cavity in the 2D shape.
// The next triangle starts at the inside corner.
currentVertex = index2;
}
}
indices.shrink_to_fit();
return indices;
}
Platzieren von Hologrammen in der Welt mithilfe eines stationären Bezugsrahmens
Die SpatialStationaryFrameOfReference-Klasse stellt einen Bezugsrahmen dar, der relativ zur Umgebung des Benutzers stationär bleibt , wenn sich der Benutzer bewegt. Dieser Referenzrahmen priorisiert, dass die Koordinaten in der Nähe des Geräts stabil bleiben. Eine wichtige Verwendung eines SpatialStationaryFrameOfReference besteht darin, beim Rendern von Hologrammen als zugrunde liegendes Weltkoordinatensystem innerhalb einer Rendering-Engine zu fungieren.
Verwenden Sie zum Abrufen eines SpatialStationaryFrameOfReference die SpatialLocator-Klasse , und rufen Sie CreateStationaryFrameOfReferenceAtCurrentLocation auf.
Aus dem Code der Windows Holographic-App-Vorlage:
// The simplest way to render world-locked holograms is to create a stationary reference frame
// when the app is launched. This is roughly analogous to creating a "world" coordinate system
// with the origin placed at the device's position as the app is launched.
referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
- Stationäre Referenzrahmen sind so konzipiert, dass sie eine optimale Position relativ zum Gesamtraum bieten. Einzelne Positionen innerhalb dieses Referenzrahmens dürfen leicht driften. Dies ist normal, da das Gerät mehr über die Umgebung lernt.
- Wenn eine präzise Platzierung einzelner Hologramme erforderlich ist, sollte ein SpatialAnchor verwendet werden, um das einzelne Hologramm an einer Position in der realen Welt zu verankern – beispielsweise einen Punkt, den der Benutzer als von besonderem Interesse angibt. Ankerpositionen driften nicht ab, können aber korrigiert werden. der Anker verwendet die korrigierte Position ab dem nächsten Frame, nachdem die Korrektur erfolgt ist.
Platzieren von Hologrammen in der Welt mithilfe von Raumankern
Raumanker sind eine großartige Möglichkeit, Hologramme an einem bestimmten Ort in der realen Welt zu platzieren, wobei das System sicherstellt, dass der Anker im Laufe der Zeit an Ort bleibt. In diesem Thema wird erläutert, wie Sie einen Anker erstellen und verwenden und wie Sie mit Ankerdaten arbeiten.
Sie können einen SpatialAnchor an jeder Position und Ausrichtung innerhalb des SpatialCoordinateSystem Ihrer Wahl erstellen. Das Gerät muss in der Lage sein, dieses Koordinatensystem im Moment zu lokalisieren, und das System darf die Grenze der Raumanker nicht erreicht haben.
Nach der Definition passt sich das Koordinatensystem eines SpatialAnchor kontinuierlich an, um die genaue Position und Ausrichtung der ursprünglichen Position beizubehalten. Anschließend können Sie diesen SpatialAnchor verwenden, um Hologramme zu rendern, die in der Umgebung des Benutzers an genau dieser Stelle behoben angezeigt werden.
Die Auswirkungen der Anpassungen, die den Anker an Ort und Stelle halten, werden vergrößert, wenn die Entfernung vom Anker zunimmt. Sie sollten vermeiden, Inhalte relativ zu einem Anker zu rendern, der mehr als 3 Meter vom Ursprung dieses Ankers entfernt ist.
Die CoordinateSystem-Eigenschaft ruft ein Koordinatensystem ab, mit dem Sie Inhalte relativ zum Anker platzieren können, wobei die Beschleunigung angewendet wird, wenn das Gerät die genaue Position des Ankers anpasst.
Verwenden Sie die RawCoordinateSystem-Eigenschaft und das entsprechende RawCoordinateSystemAdjusted-Ereignis , um diese Anpassungen selbst zu verwalten.
Beibehalten und Freigeben von Raumankern
Sie können einen SpatialAnchor mit der SpatialAnchorStore-Klasse lokal beibehalten und dann in einer zukünftigen App-Sitzung auf demselben HoloLens-Gerät wieder abrufen.
Mithilfe von Azure Spatial Anchors können Sie einen dauerhaften Cloudanker aus einem lokalen SpatialAnchor erstellen, den Ihre App dann auf mehreren HoloLens-, iOS- und Android-Geräten finden kann. Durch die Gemeinsame Nutzung eines gemeinsamen Raumankers über mehrere Geräte hinweg kann jeder Benutzer Inhalte sehen, die relativ zu diesem Anker am gleichen physischen Ort in Echtzeit gerendert werden.
Sie können Azure Spatial Anchors auch für die asynchrone Hologrammpersistenz auf HoloLens-, iOS- und Android-Geräten verwenden. Durch die Gemeinsame Nutzung eines dauerhaften Raumankers in der Cloud können mehrere Geräte im Laufe der Zeit dasselbe persistente Hologramm beobachten, auch wenn diese Geräte nicht gleichzeitig vorhanden sind.
Testen Sie den 5-minütigen Azure Spatial Anchors HoloLens-Schnellstart, um mit dem Erstellen freigegebener Erfahrungen in Ihrer HoloLens-App zu beginnen.
Sobald Sie azure Spatial Anchors ausführen, können Sie Anker in HoloLens erstellen und suchen. Exemplarische Vorgehensweisen sind auch für Android und iOS verfügbar, sodass Sie dieselben Anker auf allen Geräten freigeben können.
Erstellen von SpatialAnchors für holografische Inhalte
Für dieses Codebeispiel haben wir die Windows Holographic-App-Vorlage geändert, um Anker zu erstellen, wenn die Geste gedrückt erkannt wird. Der Cube wird dann während des Renderdurchlaufs am Anker platziert.
Da mehrere Anker von der Hilfsklasse unterstützt werden, können wir so viele Cubes platzieren, wie wir dieses Codebeispiel verwenden möchten!
Hinweis
Die IDs für Anker steuern Sie in Ihrer App. In diesem Beispiel haben wir ein Benennungsschema erstellt, das sequenziell ist, basierend auf der Anzahl der Anker, die derzeit in der Ankersammlung der App gespeichert sind.
// Check for new input state since the last frame.
SpatialInteractionSourceState^ pointerState = m_spatialInputHandler->CheckForInput();
if (pointerState != nullptr)
{
// Try to get the pointer pose relative to the SpatialStationaryReferenceFrame.
SpatialPointerPose^ pointerPose = pointerState->TryGetPointerPose(currentCoordinateSystem);
if (pointerPose != nullptr)
{
// When a Pressed gesture is detected, the anchor will be created two meters in front of the user.
// Get the gaze direction relative to the given coordinate system.
const float3 headPosition = pointerPose->Head->Position;
const float3 headDirection = pointerPose->Head->ForwardDirection;
// The anchor position in the StationaryReferenceFrame.
static const float distanceFromUser = 2.0f; // meters
const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);
// Create the anchor at position.
SpatialAnchor^ anchor = SpatialAnchor::TryCreateRelativeTo(currentCoordinateSystem, gazeAtTwoMeters);
if ((anchor != nullptr) && (m_spatialAnchorHelper != nullptr))
{
// In this example, we store the anchor in an IMap.
auto anchorMap = m_spatialAnchorHelper->GetAnchorMap();
// Create an identifier for the anchor.
String^ id = ref new String(L"HolographicSpatialAnchorStoreSample_Anchor") + anchorMap->Size;
anchorMap->Insert(id->ToString(), anchor);
}
}
}
Asynchrones Laden und Zwischenspeichern des SpatialAnchorStore
Sehen Wir uns an, wie Sie eine SampleSpatialAnchorHelper-Klasse schreiben, die diese Persistenz behandelt, einschließlich:
- Speichern einer Sammlung von In-Memory-Ankern, die durch einen Platform::String-Schlüssel indiziert werden.
- Laden von Ankern aus dem SpatialAnchorStore des Systems, der von der lokalen In-Memory-Sammlung getrennt bleibt.
- Speichern Sie die lokale In-Memory-Sammlung von Ankern im SpatialAnchorStore, wenn die App dies wählt.
Hier erfahren Sie, wie Sie SpatialAnchor-Objekte im SpatialAnchorStore speichern.
Wenn die Klasse gestartet wird, fordern wir den SpatialAnchorStore asynchron an. Dies umfasst System-E/A, während die API den Ankerspeicher lädt, und diese API wird asynchron gemacht, sodass die E/A nicht blockiert wird.
// Request the spatial anchor store, which is the WinRT object that will accept the imported anchor data.
return create_task(SpatialAnchorManager::RequestStoreAsync())
.then([](task<SpatialAnchorStore^> previousTask)
{
std::shared_ptr<SampleSpatialAnchorHelper> newHelper = nullptr;
try
{
SpatialAnchorStore^ anchorStore = previousTask.get();
// Once the SpatialAnchorStore has been loaded by the system, we can create our helper class.
// Using "new" to access private constructor
newHelper = std::shared_ptr<SampleSpatialAnchorHelper>(new SampleSpatialAnchorHelper(anchorStore));
// Now we can load anchors from the store.
newHelper->LoadFromAnchorStore();
}
catch (Exception^ exception)
{
PrintWstringToDebugConsole(
std::wstring(L"Exception while loading the anchor store: ") +
exception->Message->Data() +
L"\n"
);
}
// Return the initialized class instance.
return newHelper;
});
Sie erhalten einen SpatialAnchorStore, mit dem Sie die Anker speichern können. Dies ist eine IMapView, die Schlüsselwerte Zeichenfolgen mit Datenwerten verknüpft, die SpatialAnchors sind. In unserem Beispielcode speichern wir dies in einer privaten Klassenmembervariable, auf die über eine öffentliche Funktion unserer Hilfsklasse zugegriffen werden kann.
SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
{
m_anchorStore = anchorStore;
m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
}
Hinweis
Vergessen Sie nicht, die Ereignisse zum Anhalten/Fortsetzen zu verbinden, um den Ankerspeicher zu speichern und zu laden.
void HolographicSpatialAnchorStoreSampleMain::SaveAppState()
{
// For example, store information in the SpatialAnchorStore.
if (m_spatialAnchorHelper != nullptr)
{
m_spatialAnchorHelper->TrySaveToAnchorStore();
}
}
void HolographicSpatialAnchorStoreSampleMain::LoadAppState()
{
// For example, load information from the SpatialAnchorStore.
LoadAnchorStore();
}
Speichern von Inhalten im Ankerspeicher
Wenn das System Ihre App anbricht, müssen Sie Ihre Raumanker im Ankerspeicher speichern. Sie können Anker auch zu anderen Zeiten im Ankerspeicher speichern, da Sie für die Implementierung Ihrer App erforderlich sind.
Wenn Sie versuchen möchten, die Speicheranker im SpatialAnchorStore zu speichern, können Sie ihre Sammlung durchlaufen und versuchen, jede zu speichern.
// TrySaveToAnchorStore: Stores all anchors from memory into the app's anchor store.
//
// For each anchor in memory, this function tries to store it in the app's AnchorStore. The operation will fail if
// the anchor store already has an anchor by that name.
//
bool SampleSpatialAnchorHelper::TrySaveToAnchorStore()
{
// This function returns true if all the anchors in the in-memory collection are saved to the anchor
// store. If zero anchors are in the in-memory collection, we will still return true because the
// condition has been met.
bool success = true;
// If access is denied, 'anchorStore' will not be obtained.
if (m_anchorStore != nullptr)
{
for each (auto& pair in m_anchorMap)
{
auto const& id = pair->Key;
auto const& anchor = pair->Value;
// Try to save the anchors.
if (!m_anchorStore->TrySave(id, anchor))
{
// This may indicate the anchor ID is taken, or the anchor limit is reached for the app.
success=false;
}
}
}
return success;
}
Laden von Inhalten aus dem Ankerspeicher, wenn die App fortgesetzt wird
Sie können gespeicherte Anker im AnchorStore wiederherstellen, indem Sie sie aus der IMapView des Ankerspeichers in Ihre eigene In-Memory-Datenbank von SpatialAnchors übertragen, wenn Ihre App fortgesetzt wird oder jederzeit.
Wenn Sie Anker aus dem SpatialAnchorStore wiederherstellen möchten, stellen Sie jeden, an dem Sie interessiert sind, in Ihrer eigenen In-Memory-Sammlung wieder her.
Sie benötigen Eine eigene In-Memory-Datenbank von SpatialAnchors, um Zeichenfolgen den von Ihnen erstellten SpatialAnchors zuzuordnen. In unserem Beispielcode wird ein Windows::Foundation::Collections::IMap zum Speichern der Anker verwendet, was die Verwendung desselben Schlüssels und Datenwerts für Den SpatialAnchorStore vereinfacht.
// This is an in-memory anchor list that is separate from the anchor store.
// These anchors may be used, reasoned about, and so on before committing the collection to the store.
Windows::Foundation::Collections::IMap<Platform::String^, Windows::Perception::Spatial::SpatialAnchor^>^ m_anchorMap;
Hinweis
Ein Wiederhergestellter Anker kann möglicherweise nicht sofort gefunden werden. Es kann beispielsweise ein Anker in einem separaten Raum oder in einem anderen Gebäude sein. Aus dem AnchorStore abgerufene Anker sollten vor der Verwendung auf Ihre Locatability getestet werden.
Hinweis
In diesem Beispielcode rufen wir alle Anker aus dem AnchorStore ab. Dies ist keine Anforderung; Ihre App könnte genauso gut eine bestimmte Teilmenge von Ankern auswählen und auswählen, indem Sie Zeichenfolgenschlüsselwerte verwenden, die für Ihre Implementierung von Bedeutung sind.
// LoadFromAnchorStore: Loads all anchors from the app's anchor store into memory.
//
// The anchors are stored in memory using an IMap, which stores anchors using a string identifier. Any string can be used as
// the identifier; it can have meaning to the app, such as "Game_Leve1_CouchAnchor," or it can be a GUID that is generated
// by the app.
//
void SampleSpatialAnchorHelper::LoadFromAnchorStore()
{
// If access is denied, 'anchorStore' will not be obtained.
if (m_anchorStore != nullptr)
{
// Get all saved anchors.
auto anchorMapView = m_anchorStore->GetAllSavedAnchors();
for each (auto const& pair in anchorMapView)
{
auto const& id = pair->Key;
auto const& anchor = pair->Value;
m_anchorMap->Insert(id, anchor);
}
}
}
Löschen Sie bei Bedarf den Ankerspeicher.
Manchmal müssen Sie den App-Status löschen und neue Daten schreiben. Hier erfahren Sie, wie Sie dies mit dem SpatialAnchorStore tun.
Mit unserer Hilfsklasse ist es fast unnötig, die Clear-Funktion umzuschließen. Wir entscheiden uns dafür in unserer Beispielimplementierung, da unsere Hilfsklasse die Verantwortung für den Besitz des SpatialAnchorStore-instance übernimmt.
// ClearAnchorStore: Clears the AnchorStore for the app.
//
// This function clears the AnchorStore. It has no effect on the anchors stored in memory.
//
void SampleSpatialAnchorHelper::ClearAnchorStore()
{
// If access is denied, 'anchorStore' will not be obtained.
if (m_anchorStore != nullptr)
{
// Clear all anchors from the store.
m_anchorStore->Clear();
}
}
Beispiel: Bezug von Ankerkoordinatensystemen zu stationären Referenzrahmenkoordinatensystemen
Angenommen, Sie haben einen Anker, und Sie möchten etwas im Koordinatensystem Ihres Ankers mit dem SpatialStationaryReferenceFrame verknüpfen, den Sie bereits für Ihre anderen Inhalte verwenden. Sie können TryGetTransformTo verwenden, um eine Transformation vom Koordinatensystem des Ankers in das des stationären Referenzrahmens abzurufen:
// In this code snippet, someAnchor is a SpatialAnchor^ that has been initialized and is valid in the current environment.
float4x4 anchorSpaceToCurrentCoordinateSystem;
SpatialCoordinateSystem^ anchorSpace = someAnchor->CoordinateSystem;
const auto tryTransform = anchorSpace->TryGetTransformTo(currentCoordinateSystem);
if (tryTransform != nullptr)
{
anchorSpaceToCurrentCoordinateSystem = tryTransform->Value;
}
Dieser Prozess ist für Sie auf zwei Arten nützlich:
- Sie teilt Ihnen mit, ob die beiden Bezugsrahmen relativ zueinander verstanden werden können, und;
- Wenn ja, erhalten Sie eine Transformation, um direkt von einem Koordinatensystem zum anderen zu wechseln.
Mit diesen Informationen haben Sie ein Verständnis der räumlichen Beziehung zwischen Objekten zwischen den beiden Verweisframes.
Beim Rendern können Sie häufig bessere Ergebnisse erzielen, indem Sie Objekte nach ihrem ursprünglichen Referenzrahmen oder Anker gruppieren. Führen Sie für jede Gruppe einen separaten Zeichnungsdurchlauf aus. Die Ansichtsmatrizen sind genauer für Objekte mit Modelltransformationen, die zunächst mit demselben Koordinatensystem erstellt werden.
Erstellen von Hologrammen mithilfe eines geräteseitig angefügten Referenzrahmens
Es gibt Zeiten, in denen Sie ein Hologramm rendern möchten, das an den Standort des Geräts angefügt bleibt , z. B. ein Bereich mit Debuginformationen oder eine Informationsmeldung, wenn das Gerät nur seine Ausrichtung und nicht seine Position im Raum bestimmen kann. Um dies zu erreichen, verwenden wir einen angefügten Referenzrahmen.
Die SpatialLocatorAttachedFrameOfReference-Klasse definiert Koordinatensysteme, die relativ zum Gerät und nicht zur realen Welt sind. Dieser Frame weist relativ zur Umgebung des Benutzers eine feste Überschrift auf, die in die Richtung zeigt, in die sich der Benutzer beim Erstellen des Referenzrahmens befand. Ab dann sind alle Ausrichtungen in diesem Bezugsrahmen relativ zu dieser festen Überschrift, auch wenn der Benutzer das Gerät rotiert.
Bei HoloLens befindet sich der Ursprung des Koordinatensystems dieses Frames in der Drehmitte des Kopfes des Benutzers, sodass seine Position nicht von der Kopfrotation beeinflusst wird. Ihre App kann einen Offset relativ zu diesem Punkt angeben, um Hologramme vor dem Benutzer zu positionieren.
Um einen SpatialLocatorAttachedFrameOfReference abzurufen, verwenden Sie die SpatialLocator-Klasse, und rufen Sie CreateAttachedFrameOfReferenceAtCurrentHeading auf.
Dies gilt für den gesamten Bereich Windows Mixed Reality Geräte.
Verwenden eines Referenzrahmens, der an das Gerät angefügt ist
In diesen Abschnitten wird erläutert, was wir in der Windows Holographic-App-Vorlage geändert haben, um einen geräteseitig angefügten Referenzrahmen mithilfe dieser API zu aktivieren. Dieses "angefügte" Hologramm funktioniert neben stationären oder verankerten Hologrammen und kann auch verwendet werden, wenn das Gerät vorübergehend nicht in der Lage ist, seine Position in der Welt zu finden.
Zunächst haben wir die Vorlage geändert, um einen SpatialLocatorAttachedFrameOfReference anstelle eines SpatialStationaryFrameOfReference zu speichern:
Von HolographicTagAlongSampleMain.h:
// A reference frame attached to the holographic camera.
Windows::Perception::Spatial::SpatialLocatorAttachedFrameOfReference^ m_referenceFrame;
Von HolographicTagAlongSampleMain.cpp:
// In this example, we create a reference frame attached to the device.
m_referenceFrame = m_locator->CreateAttachedFrameOfReferenceAtCurrentHeading();
Während der Aktualisierung erhalten wir nun das Koordinatensystem mit dem Zeitstempel, der mit der Framevorhersage abgerufen wurde.
// Next, we get a coordinate system from the attached frame of reference that is
// associated with the current frame. Later, this coordinate system is used for
// for creating the stereo view matrices when rendering the sample content.
SpatialCoordinateSystem^ currentCoordinateSystem =
m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp);
Rufen Sie eine räumliche Zeigerhaltung ab, und folgen Sie dem Blick des Benutzers.
Wir möchten, dass unser Beispiel-Hologramm dem Blick des Benutzers folgt, ähnlich wie die holografische Shell dem Blick des Benutzers folgen kann. Dazu müssen wir den SpatialPointerPose aus demselben Zeitstempel abrufen.
SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);
Diese SpatialPointerPose enthält die Informationen, die zum Positionieren des Hologramms gemäß der aktuellen Überschrift des Benutzers erforderlich sind.
Für den Benutzerkomfort verwenden wir lineare Interpolation ("lerp"), um den Positionswechsel über einen bestimmten Zeitraum zu glätten. Dies ist für den Benutzer bequemer, als das Hologramm für seinen Blick zu sperren. Durch Das Auslagern der Position des Tag-entlang-Hologramms können wir auch das Hologramm stabilisieren, indem wir die Bewegung dämpfen. Wenn wir diese Dämpfung nicht durchführen, würde der Benutzer sehen, dass das Hologramm aufgrund der normalerweise als nicht wahrnehmbare Bewegungen des Kopfes des Benutzers jittert.
Von StationaryQuadRenderer::P OsitionHologram:
const float& dtime = static_cast<float>(timer.GetElapsedSeconds());
if (pointerPose != nullptr)
{
// Get the gaze direction relative to the given coordinate system.
const float3 headPosition = pointerPose->Head->Position;
const float3 headDirection = pointerPose->Head->ForwardDirection;
// The tag-along hologram follows a point 2.0m in front of the user's gaze direction.
static const float distanceFromUser = 2.0f; // meters
const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);
// Lerp the position, to keep the hologram comfortably stable.
auto lerpedPosition = lerp(m_position, gazeAtTwoMeters, dtime * c_lerpRate);
// This will be used as the translation component of the hologram's
// model transform.
SetPosition(lerpedPosition);
}
Hinweis
Im Fall eines Debugbereichs können Sie das Hologramm ein wenig an der Seite positionieren, damit die Sicht nicht behindert wird. Hier sehen Sie ein Beispiel, wie Sie dies tun können.
Für StationaryQuadRenderer::P ositionHologram:
// If you're making a debug view, you might not want the tag-along to be directly in the
// center of your field of view. Use this code to position the hologram to the right of
// the user's gaze direction.
/*
const float3 offset = float3(0.13f, 0.0f, 0.f);
static const float distanceFromUser = 2.2f; // meters
const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * (headDirection + offset));
*/
Drehen des Hologramms, um sich der Kamera zu stellen
Es reicht nicht aus, das Hologramm zu positionieren, das in diesem Fall ein Quad ist; Wir müssen auch das Objekt rotieren, um dem Benutzer gegenüber zu stehen. Diese Rotation tritt im Weltraum auf, da diese Art von Billboarding es ermöglicht, dass das Hologramm Teil der Umgebung des Benutzers bleibt. Die Anzeige im Sichtraum ist nicht so komfortabel, da das Hologramm für die Anzeigeausrichtung gesperrt wird. in diesem Fall müssen Sie auch zwischen den linken und rechten Ansichtsmatrizen interpolieren, um eine Ansichtsraum-Billboardtransformation zu erhalten, die das Stereorendering nicht beeinträchtigt. Hier drehen wir uns auf den X- und Z-Achsen, um dem Benutzer zu begegnen.
Von StationaryQuadRenderer::Update:
// Seconds elapsed since previous frame.
const float& dTime = static_cast<float>(timer.GetElapsedSeconds());
// Create a direction normal from the hologram's position to the origin of person space.
// This is the z-axis rotation.
XMVECTOR facingNormal = XMVector3Normalize(-XMLoadFloat3(&m_position));
// Rotate the x-axis around the y-axis.
// This is a 90-degree angle from the normal, in the xz-plane.
// This is the x-axis rotation.
XMVECTOR xAxisRotation = XMVector3Normalize(XMVectorSet(XMVectorGetZ(facingNormal), 0.f, -XMVectorGetX(facingNormal), 0.f));
// Create a third normal to satisfy the conditions of a rotation matrix.
// The cross product of the other two normals is at a 90-degree angle to
// both normals. (Normalize the cross product to avoid floating-point math
// errors.)
// Note how the cross product will never be a zero-matrix because the two normals
// are always at a 90-degree angle from one another.
XMVECTOR yAxisRotation = XMVector3Normalize(XMVector3Cross(facingNormal, xAxisRotation));
// Construct the 4x4 rotation matrix.
// Rotate the quad to face the user.
XMMATRIX rotationMatrix = XMMATRIX(
xAxisRotation,
yAxisRotation,
facingNormal,
XMVectorSet(0.f, 0.f, 0.f, 1.f)
);
// Position the quad.
const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));
// The view and projection matrices are provided by the system; they are associated
// with holographic cameras, and updated on a per-camera basis.
// Here, we provide the model transform for the sample hologram. The model transform
// matrix is transposed to prepare it for the shader.
XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(rotationMatrix * modelTranslation));
Rendern des angefügten Hologramms
In diesem Beispiel wird auch das Hologramm im Koordinatensystem des SpatialLocatorAttachedReferenceFrame gerendert, wo wir das Hologramm positioniert haben. (Wenn wir uns für das Rendern mit einem anderen Koordinatensystem entschieden hätten, müssten wir eine Transformation aus dem Koordinatensystem des geräteseitig angefügten Referenzrahmens in dieses Koordinatensystem abrufen.)
Aus HolographicTagAlongSampleMain::Render:
// The view and projection matrices for each holographic camera will change
// every frame. This function refreshes the data in the constant buffer for
// the holographic camera indicated by cameraPose.
pCameraResources->UpdateViewProjectionBuffer(
m_deviceResources,
cameraPose,
m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp)
);
Das ist alles! Das Hologramm "jagt" nun eine Position, die 2 Meter vor der Blickrichtung des Benutzers liegt.
Hinweis
In diesem Beispiel werden auch zusätzliche Inhalte geladen– siehe StationaryQuadRenderer.cpp.
Behandeln von Nachverfolgungsverlusten
Wenn sich das Gerät nicht in der Welt befinden kann, tritt für die App "Verlustnachverfolgung" auf. Windows Mixed Reality-Apps sollten in der Lage sein, solche Störungen des Positionsverfolgungssystems zu bewältigen. Diese Unterbrechungen können beobachtet und Antworten mithilfe des LocatabilityChanged-Ereignisses auf dem Standardmäßigen SpatialLocator erstellt werden.
Aus AppMain::SetHolographicSpace:
// Be able to respond to changes in the positional tracking state.
m_locatabilityChangedToken =
m_locator->LocatabilityChanged +=
ref new Windows::Foundation::TypedEventHandler<SpatialLocator^, Object^>(
std::bind(&HolographicApp1Main::OnLocatabilityChanged, this, _1, _2)
);
Wenn Ihre App ein LocatabilityChanged-Ereignis empfängt, kann sie das Verhalten bei Bedarf ändern. Beispielsweise kann Ihre App im Zustand PositionalTrackingInhibited den normalen Betrieb anhalten und ein Tag-entlang-Hologramm rendern, das eine Warnmeldung anzeigt.
Die Windows Holographic-App-Vorlage enthält einen LocatabilityChanged-Handler, der bereits für Sie erstellt wurde. Standardmäßig wird eine Warnung in der Debugkonsole angezeigt, wenn die Positionsnachverfolgung nicht verfügbar ist. Sie können diesem Handler Code hinzufügen, um eine Antwort nach Bedarf aus Ihrer App bereitzustellen.
Über AppMain.cpp:
void HolographicApp1Main::OnLocatabilityChanged(SpatialLocator^ sender, Object^ args)
{
switch (sender->Locatability)
{
case SpatialLocatability::Unavailable:
// Holograms cannot be rendered.
{
String^ message = L"Warning! Positional tracking is " +
sender->Locatability.ToString() + L".\n";
OutputDebugStringW(message->Data());
}
break;
// In the following three cases, it is still possible to place holograms using a
// SpatialLocatorAttachedFrameOfReference.
case SpatialLocatability::PositionalTrackingActivating:
// The system is preparing to use positional tracking.
case SpatialLocatability::OrientationOnly:
// Positional tracking has not been activated.
case SpatialLocatability::PositionalTrackingInhibited:
// Positional tracking is temporarily inhibited. User action may be required
// in order to restore positional tracking.
break;
case SpatialLocatability::PositionalTrackingActive:
// Positional tracking is active. World-locked content can be rendered.
break;
}
}
Räumliche Abbildung
Die Raumzuordnungs-APIs verwenden Koordinatensysteme, um Modelltransformationen für Oberflächengitter abzurufen.