Koordinatensysteme in DirectX
Hinweis
Dieser Artikel bezieht sich auf die älteren winRT nativen APIs. Für neue native App-Projekte empfehlen wir die Verwendung der OpenXR-API.
Koordinatensysteme bilden die Grundlage für räumliches Verständnis von Windows Mixed Reality-APIs.
Die heute sitzenden 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 an, um das Wissen über die Räume des Benutzers kontinuierlich zu verbessern, führt jedoch zu Koordinatensystemen, die ihre Beziehung im Laufe der App-Lebensdauer zueinander ändern. Windows Mixed Reality unterstützt ein breites Spektrum an Geräten, von sitzenden immersiven Headsets bis hin zu weltweit angeschlossenen Referenzframes.
Hinweis
Die Codeausschnitte in diesem Artikel veranschaulichen derzeit die Verwendung von C++/CX anstelle von C++17-kompatiblen C++/WinRT, wie sie in der C++-Projektvorlage für holografische Grafiken verwendet werden. Die Konzepte sind gleichwertig für ein C++/WinRT-Projekt, sie müssen den Code jedoch übersetzen.
Räumliche Koordinatensysteme in Windows
Der Kerntyp, der zur Begründung von realen Koordinatensystemen in Windows verwendet wird, ist das SpatialCoordinateSystem. Eine Instanz dieses Typs stellt ein beliebiges Koordinatensystem dar und stellt eine Methode zum Abrufen von Transformationsmatrixdaten bereit, die Sie zum Transformieren zwischen zwei Koordinatensystemen verwenden können, ohne die Details der einzelnen Koordinaten zu verstehen.
Methoden, die räumliche Informationen zurückgeben, akzeptieren einen SpatialCoordinateSystem-Parameter, damit Sie das Koordinatensystem festlegen können, in dem es am nützlichsten ist, dass diese Koordinaten zurückgegeben werden. Räumliche Informationen werden als Punkte, Strahlen oder Volumes 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 derjenigen, die die Position des Geräts darstellen. An jedem Punkt kann das Gerät einige Koordinatensysteme und nicht andere finden. 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 – stattdessen sollten sie über die Wahrnehmungs-APIs genutzt werden. Es gibt drei primäre Quellen von Koordinatensystemen in den Wahrnehmungs-APIs, die jeweils einem Konzept zugeordnet sind, 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.
- Erstellen Sie zum Abrufen eines angefügten Referenzframes ein SpatialLocatorAttachedFrameOfReference-Objekt.
Alle von diesen Objekten zurückgegebenen Koordinatensysteme sind rechtshändig, mit +y nach oben, +x nach rechts und +z rückwärts. Sie können sich merken, welche Richtung die positiven Z-Achsenpunkte zeigen, indem Sie die Finger ihrer linken oder rechten Hand in die positive x-Richtung zeigen und sie in die positive Y-Richtung locken. Die Richtung, in der sich der Daumen in Richtung oder weg von Ihnen befindet, ist die Richtung, in der die positiven Z-Achsenpunkte für dieses Koordinatensystem liegen. Die folgende Abbildung zeigt diese beiden Koordinatensysteme.
Links- und Rechtskoordinatensysteme
Verwenden Sie die SpatialLocator-Klasse , um basierend auf der HoloLens-Position einen angefügten oder stationären Referenzrahmen 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 räumlichen Phase
Auf das Koordinatensystem für undurchsichtige immersive Windows Mixed Reality-Headsets wird mithilfe der statischen SpatialStageFrameOfReference::Current-Eigenschaft zugegriffen. Diese API bietet Folgendes:
- Ein Koordinatensystem
- Informationen darüber, ob der Spieler sitzt oder mobil ist
- Die Grenze eines sicheren Bereichs zum Gehen, wenn der Spieler mobil ist
- Ein Hinweis darauf, ob das Headset direktional ist.
- Ein Ereignishandler für Aktualisierungen der räumlichen Phase.
Zunächst erhalten wir die räumliche Phase und abonnieren Updates dafür:
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 prüfen und die Spielererfahrung aktualisieren. In diesem Beispiel stellen wir eine Visualisierung der Phasenbegrenzung und die startposition bereit, die vom Benutzer und dem Bereich der Ansicht und des Bereichs der Bewegungseigenschaften der Stufe angegeben wird. Wir greifen auch auf unser eigenes stationäres Koordinatensystem zurück, wenn eine Stufe nicht bereitgestellt werden kann.
Code für die Aktualisierung 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);
}
}
}
Der Satz von Scheitelpunkten, die die Stufenbegrenzung definieren, wird im Uhrzeigersinn bereitgestellt. Die Windows Mixed Reality-Shell zeichnet einen Zaun an der Grenze, wenn der Benutzer es nähert, aber Sie sollten den begehbaren Bereich zu Ihren eigenen Zwecken triangularisieren. Der folgende Algorithmus kann verwendet werden, um die Phase zu triangularisieren.
Code für räumliche Phasentriangularisierung
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 Referenzrahmens
Die SpatialStationaryFrameOfReference-Klasse stellt einen Bezugsrahmen dar, der relativ zur Umgebung des Benutzers bleibt, während der Benutzer sich bewegt. Dieser Referenzrahmen priorisiert, dass Koordinaten in der Nähe des Geräts stabil bleiben. Eine wichtige Verwendung eines SpatialStationaryFrameOfReference ist das zugrunde liegende Weltkoordinatensystem innerhalb eines Renderingmoduls beim Rendern von Hologrammen.
Verwenden Sie zum Abrufen eines SpatialStationaryFrameOfReference die SpatialLocator-Klasse und rufen Sie CreateStationaryFrameOfReferenceAtCurrentLocation auf.
Aus dem Vorlagencode der Windows Holographic-App:
// 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 - z. B. ein Punkt, den der Benutzer als besonders interessant angibt. Ankerpositionen driften nicht, können aber korrigiert werden; der Anker verwendet die korrigierte Position beginnend im nächsten Frame, nachdem die Korrektur erfolgt ist.
Platzieren von Hologrammen in der Welt mithilfe räumlicher Anker
Räumliche Anker 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 und Stelle 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 einer beliebigen Position und Ausrichtung innerhalb des SpatialCoordinateSystem Ihrer Wahl erstellen. Das Gerät muss in der Lage sein, dieses Koordinatensystem im Moment zu finden, und das System darf seine Grenze der räumlichen Verankerungen nicht erreicht haben.
Nach der Definition passt sich das Koordinatensystem eines SpatialAnchors kontinuierlich an, um die genaue Position und Ausrichtung der Ausgangsposition beizubehalten. Anschließend können Sie diesen SpatialAnchor verwenden, um Hologramme zu rendern, die an dieser genauen Position in der Umgebung des Benutzers fixiert angezeigt werden.
Die Auswirkungen der Anpassungen, die den Anker an Ort halten, werden vergrößert, wenn der Abstand zum Anker zunimmt. Sie sollten das Rendern von Inhalten im Verhältnis zu einem Anker vermeiden, 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 Beschleunigungen angewendet werden, 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.
Sie können einen SpatialAnchor lokal mithilfe der SpatialAnchorStore-Klasse beibehalten und dann in einer zukünftigen App-Sitzung auf demselben HoloLens-Gerät wieder abrufen.
Erstellen von SpatialAnchors für holografische Inhalte
Für dieses Codebeispiel haben wir die Windows Holographic-App-Vorlage so geändert, dass Anker erstellt werden, wenn die gedrückte Geste erkannt wird. Der Würfel 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 sind etwas, das Sie in Ihrer App steuern. In diesem Beispiel haben wir ein Benennungsschema erstellt, das sequenziell auf der Grundlage der Anzahl der Derzeit in der Ankersammlung der App gespeicherten Anker ist.
// 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 bei der Behandlung dieser Persistenz hilft, einschließlich:
- Speichern einer Sammlung von In-Memory-Ankern, indiziert von einem Platform::String-Schlüssel.
- Laden von Ankern aus dem SpatialAnchorStore des Systems, die von der lokalen Speicherauflistung getrennt gehalten wird.
- Speichern der lokalen Speicherauflistung von Ankern im SpatialAnchorStore, wenn die App dies zu tun hat.
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, da 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, den Sie zum Speichern der Anker verwenden können. Dies ist eine IMapView, die Schlüsselwerte zuordnet, die Zeichenfolgen sind, mit Datenwerten, die SpatialAnchors sind. In unserem Beispielcode speichern wir dies in einer privaten Membervariablen der Klasse, 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 Anhalte-/Fortsetzungsereignisse 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 angehalten, müssen Sie ihre räumlichen Verankerungen im Ankerspeicher speichern. Sie können auch Anker zu anderen Zeiten im Ankerspeicher speichern, da Sie feststellen, dass sie für die Implementierung Ihrer App erforderlich sind.
Wenn Sie bereit sind, die Speicheranker im SpatialAnchorStore zu speichern, können Sie ihre Sammlung durchlaufen und versuchen, die einzelnen Zusätze 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 von der IMapView des Ankerspeichers in Ihre eigene Speicherdatenbank von SpatialAnchors übertragen, wenn Ihre App fortgesetzt oder jederzeit fortgesetzt wird.
Wenn Sie Verankerungen aus dem SpatialAnchorStore wiederherstellen möchten, stellen Sie jeden, den Sie interessieren, in Ihrer eigenen Speichersammlung wieder her.
Sie benötigen eine eigene Speicherdatenbank von SpatialAnchors, um Zeichenfolgen den von Ihnen erstellten SpatialAnchors zuzuordnen. In unserem Beispielcode wählen wir die Verwendung eines Windows::Foundation::Collections::IMap zum Speichern der Anker aus, wodurch die Verwendung desselben Schlüssels und desselben Datenwerts für den SpatialAnchorStore einfach ist.
// 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 ist möglicherweise nicht sofort ablösbar. Beispielsweise kann es sich um einen Anker in einem separaten Raum oder in einem anderen Gebäude insgesamt handelt. Anchors, die aus dem AnchorStore abgerufen wurden, sollten vor der Verwendung auf Locatability getestet werden.
Hinweis
In diesem Beispielcode rufen wir alle Anker aus dem AnchorStore ab. Dies ist keine Voraussetzung; Ihre App kann auch eine bestimmte Teilmenge von Ankern auswählen und auswählen, indem Sie Zeichenfolgenschlüsselwerte verwenden, die für Ihre Implementierung aussagekräftig 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 des Ankerspeichers bei Bedarf
Manchmal müssen Sie den App-Zustand löschen und neue Daten schreiben. Hier erfahren Sie, wie Sie dies mit dem SpatialAnchorStore tun.
Bei Verwendung unserer Hilfsklasse ist es fast unnötig, die Clear-Funktion umzuschließen. Wir wählen dies in unserer Beispielimplementierung aus, da unsere Hilfsklasse der Besitzer der SpatialAnchorStore-Instanz ist.
// 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: Bezogene Ankerkoordinatensysteme auf stationäre Referenzrahmenkoordinatensysteme
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 den des stationären Referenzframes zu erhalten:
// 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:
- Es teilt Ihnen mit, ob die beiden Referenzframes relativ zueinander verstanden werden können;
- Wenn ja, bietet es Ihnen eine Transformation, die direkt von einem Koordinatensystem zum anderen wechselt.
Mit diesen Informationen haben Sie ein Verständnis der räumlichen Beziehung zwischen Objekten zwischen den beiden Referenzframes.
Beim Rendern können Sie häufig bessere Ergebnisse erzielen, indem Sie Objekte nach ihrem ursprünglichen Bezugsrahmen oder Anker gruppieren. Führen Sie einen separaten Zeichnungsdurchlauf für jede Gruppe aus. Die Ansichtsmatrizen sind für Objekte mit Modelltransformationen genauer, die anfänglich mit demselben Koordinatensystem erstellt werden.
Erstellen von Hologrammen mithilfe eines gerätegebundenen Referenzrahmens
Es gibt Zeiten, in denen Sie ein Hologramm rendern möchten, das an die Position des Geräts angefügt bleibt, z. B. ein Panel mit Debuginformationen oder eine Informationsmeldung, wenn das Gerät nur seine Ausrichtung und nicht seine Position im Raum bestimmen kann. Dazu verwenden wir einen angefügten Referenzrahmen.
Die SpatialLocatorAttachedFrameOfReference-Klasse definiert Koordinatensysteme, die relativ zum Gerät und nicht zur realen Welt sind. Dieser Frame hat eine feste Überschrift relativ zur Umgebung des Benutzers, die in die Richtung zeigt, in die der Benutzer beim Erstellen des Referenzframes gerichtet war. Von nun an sind alle Ausrichtungen in diesem Bezugsrahmen relativ zu dieser festen Überschrift, auch wenn der Benutzer das Gerät dreht.
Bei HoloLens befindet sich der Ursprung des Koordinatensystems dieses Frames im Mittelpunkt der Drehung des Kopfes des Benutzers, sodass seine Position nicht von der Kopfdrehung betroffen ist. Ihre App kann einen Offset relativ zu diesem Punkt angeben, um Hologramme vor dem Benutzer zu positionieren.
Verwenden Sie zum Abrufen eines SpatialLocatorAttachedFrameOfReference die SpatialLocator-Klasse, und rufen Sie CreateAttachedFrameOfReferenceAtCurrentHeading auf.
Dies gilt für die gesamte Palette von Windows Mixed Reality-Geräten.
Verwenden eines an das Gerät angeschlossenen Referenzrahmens
In diesen Abschnitten wird erläutert, was wir in der Windows Holographic-App-Vorlage geändert haben, um einen gerätegebundenen Referenzframe mithilfe dieser API zu aktivieren. Dieses "angefügte" Hologramm funktioniert zusammen mit stationären oder verankerten Hologrammen und kann auch verwendet werden, wenn das Gerät vorübergehend seine Position in der Welt nicht finden kann.
Zunächst haben wir die Vorlage so geändert, dass eine SpatialLocatorAttachedFrameOfReference anstelle eines SpatialStationaryFrameOfReference gespeichert wird:
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 des Updates erhalten wir nun das Koordinatensystem zum 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 Zeiger-Pose 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 dem gleichen Zeitstempel abrufen.
SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);
Dieser SpatialPointerPose verfügt über die erforderlichen Informationen, um das Hologramm gemäß der aktuellen Überschrift des Benutzers zu positionieren.
Für den Benutzerkomfort verwenden wir lineare Interpolation ("lerp"), um die Änderung der Position über einen bestimmten Zeitraum zu glätten. Dies ist für den Benutzer bequemer als das Hologramm an seinen Blick zu sperren. Durch das Lerping der Tag-along-Hologrammposition können wir das Hologramm auch durch Dämpfung der Bewegung stabilisieren. Wenn wir diese Dämpfung nicht durchgeführt haben, würde der Benutzer den Hologramm-Jitter sehen, weil es normalerweise als unverständliche Bewegungen des Kopfes des Benutzers betrachtet wird.
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 Debuggingpanels können Sie das Hologramm ein wenig an der Seite positionieren, damit die Ansicht nicht blockiert wird. Hier ist ein Beispiel dafür, 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 an die Kamera
Es reicht nicht aus, das Hologramm zu positionieren, was in diesem Fall ein Quad ist; wir müssen auch das Objekt drehen, um dem Benutzer gegenüber zu stehen. Diese Drehung erfolgt im Weltraum, da diese Art von Billboarding es ermöglicht, das Hologramm teil der Umgebung des Benutzers zu bleiben. Ansichtsraum-Billboarding ist nicht so komfortabel, da das Hologramm an der Anzeigeausrichtung gesperrt wird; In diesem Fall müssen Sie auch zwischen den Matrizen der linken und der rechten Ansicht interpolieren, um eine Ansichtsraum-Billboardtransformation zu erhalten, die das Stereorendering nicht stört. Hier drehen wir die X- und Z-Achse, 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 beschlossen hätten, ein anderes Koordinatensystem zu rendern, müssten wir eine Transformation vom Koordinatensystem des gerätegebundenen Referenzframes in dieses Koordinatensystem abrufen.)
Von 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" jetzt 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 finden kann, wird der "Tracking-Verlust" der App angezeigt. Windows Mixed Reality-Apps sollten solche Unterbrechungen im Positionsverfolgungssystem verarbeiten können. Diese Unterbrechungen können beobachtet und Antworten erstellt werden, indem das LocatabilityChanged-Ereignis auf dem Standardmäßigen SpatialLocator verwendet wird.
Von 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 Vorgang anhalten und ein Tag-entlang-Hologramm rendern, das eine Warnmeldung anzeigt.
Die Vorlage der Windows Holographic-App enthält einen bereits für Sie erstellten LocatabilityChanged-Handler. Standardmäßig wird in der Debugkonsole eine Warnung angezeigt, wenn die Positionsnachverfolgung nicht verfügbar ist. Sie können diesem Handler Code hinzufügen, um bei Bedarf in Ihrer App eine Antwort bereitzustellen.
Von 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 räumlichen Zuordnungs-APIs verwenden Koordinatensysteme, um Modelltransformationen für Oberflächengitter abzurufen.