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, das von Windows Mixed Reality APIs angeboten wird.

Die heutigen 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 ausgelegt, wobei das Gerät seine Umgebung entdecken und lernen kann, 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 zueinander über die Lebensdauer der Apps ändern. Windows Mixed Reality unterstützt ein breites Spektrum von Geräten, die von sitzenden immersiven Headsets bis hin zu weltweit angefügten Referenzframes reichen.

Hinweis

Die Codeausschnitte in diesem Artikel veranschaulichen derzeit die Verwendung von C++/CX anstelle von C++17-kompatiblen C++/WinRT, die in der C++- Holographic-Projektvorlage verwendet werden. Die Konzepte entsprechen einem C++/WinRT-Projekt, obwohl Sie den Code übersetzen müssen.

Räumliche Koordinatensysteme in Windows

Der Kerntyp, der zum Grund für reale Koordinatensysteme in Windows verwendet wird, ist das SpatialCoordinateSystem. Eine Instanz dieses Typs stellt ein beliebiges Koordinatensystem dar, das eine Methode zum Abrufen von Transformationsmatrixdaten bereitstellt, die Sie zum Transformieren zwischen zwei Koordinatensystemen verwenden können, ohne die Details jeder zu verstehen.

Methoden, die räumliche Informationen zurückgeben, akzeptieren einen SpatialCoordinateSystem-Parameter, damit Sie das Koordinatensystem entscheiden können, in dem es für diese Koordinaten am nützlichsten ist, um zurückgegeben werden zu können. Räumliche Informationen werden als Punkte, Strahlen oder Volumen in der Umgebung des Benutzers dargestellt, und die Einheiten für diese Koordinaten werden immer in Meter sein.

Ein SpatialCoordinateSystem verfügt über eine dynamische Beziehung mit anderen Koordinatensystemen, einschließlich derer, 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 behandeln, in denen sie nicht gefunden werden können.

Ihre Anwendung sollte keine räumlichenCoordinateSystems 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 ist:

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 positive Z-Achse zeigt, indem Sie die Finger ihrer linken oder rechten Hand in die positive x-Richtung zeigen und sie in die positive y-Richtung ziehen. Die Richtung, die Ihre Daumenpunkte, entweder in Richtung oder weg von Ihnen, ist die Richtung, in der die positive Z-Achse für dieses Koordinatensystem verweist. Die folgende Abbildung zeigt diese beiden Koordinatensysteme.

Left-hand and right-hand coordinate systems
Links- und Rechtskoordinatensysteme

Verwenden Sie die SpatialLocator-Klasse, um einen angefügten oder stationären Referenzrahmen für bootstrap in ein SpatialCoordinateSystem basierend auf der HoloLens Position zu erstellen. Weiter zum nächsten Abschnitt, um mehr über diesen Prozess zu erfahren.

Platzieren Sie Hologramme in der Welt mithilfe einer räumlichen Phase

Das Koordinatensystem für undurchsichtige Windows Mixed Reality immersive Headsets wird mithilfe der statischen SpatialStageFrameOfReference::Current-Eigenschaft zugegriffen. Diese API stellt Folgendes bereit:

  • Ein Koordinatensystem
  • Informationen darüber, ob der Spieler sitzen oder mobil ist
  • Die Grenze eines sicheren Bereichs zum Wandern, wenn der Spieler mobil ist
  • Ein Hinweis darauf, ob das Headset richtungsiv ist.
  • Ein Ereignishandler für Updates in der räumlichen Phase.

Zunächst erhalten wir die räumliche Phase und abonnieren Updates dazu:

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 Spielererfahrung aktualisieren. In diesem Beispiel stellen wir eine Visualisierung der Phasengrenze und die startposition bereit, die vom Benutzer und dem Bereich der Ansicht und des Bewegungsbereichs der Stufe angegeben wird. Wir fallen auch auf unser eigenes stationäres Koordinatensystem zurück, wenn eine Phase 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);
        }
    }
}

Die Gruppe von Scheitelpunkten, die die Phasengrenze definieren, werden im Uhrzeigersinn bereitgestellt. Die Windows Mixed Reality-Shell zeichnet einen Zaun an der Grenze, wenn der Benutzer ihn angibt, aber Sie möchten den begehbaren Bereich möglicherweise 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 Sie Hologramme in der Welt mithilfe eines stationären Referenzrahmens

Die SpatialStationaryFrameOfReference-Klasse stellt einen Referenzrahmen dar, der relativ zur Umgebung des Benutzers bleibt, da der Benutzer sich bewegt. Dieser Referenzrahmen priorisiert, dass Koordinaten in der Nähe des Geräts stabil bleiben. Eine wichtige Verwendung eines SpatialStationaryFrameOfReference-Objekts besteht darin, als zugrunde liegendes Weltkoordinatensystem innerhalb eines Renderingmoduls zu handeln, wenn Hologramme gerendert werden.

Um ein SpatialStationaryFrameOfReference abzurufen, verwenden Sie 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 eine optimale Position relativ zum Gesamtraum bereitgestellt wird. Einzelne Positionen innerhalb dieses Referenzrahmens dürfen leicht driften. Dies ist normal, da das Gerät mehr über die Umgebung lernt.
  • Wenn die genaue Platzierung einzelner Hologramme erforderlich ist, sollte ein SpatialAnchor verwendet werden, um das einzelne Hologramm an eine Position in der realen Welt zu verankern - z. B. ein Punkt, den der Benutzer angibt, von besonderem Interesse zu sein. Verankerungspositionen lassen sich nicht driften, können aber korrigiert werden; der Anker verwendet die korrigierte Position ab dem nächsten Frame, nachdem die Korrektur aufgetreten ist.

Platzieren Sie Hologramme in der Welt mithilfe von räumlichen Ankern

Räumliche Anker sind eine großartige Möglichkeit, Hologramme an einem bestimmten Ort in der realen Welt zu platzieren, wobei das System sicherstellen kann, 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 einer beliebigen Position und Ausrichtung innerhalb des SpatialCoordinateSystem ihrer Auswahl erstellen. Das Gerät muss dieses Koordinatensystem derzeit suchen können, und das System darf seine Grenze für räumliche Anker nicht erreicht haben.

Nach der Definition passt sich das Koordinatensystem eines SpatialAnchors 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 dieser genauen Stelle behoben werden.

Die Auswirkungen der Anpassungen, die den Anker an Ort halten, werden vergrößert, da der Abstand vom Anker erhöht wird. Sie sollten das Rendern von Inhalten relativ zu einem Anker vermeiden, der mehr als 3 Meter vom Ursprung dieses Ankers entfernt ist.

Die Koordinatensystem-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 räumlichen Ankern

Sie können einen SpatialAnchor lokal mithilfe der SpatialAnchorStore-Klasse beibehalten und dann wieder in einer zukünftigen App-Sitzung auf demselben HoloLens Gerät abrufen.

Mithilfe von Azure Spatial Anchors können Sie einen dauerhaften Cloud-Anker aus einem lokalen SpatialAnchor erstellen, den Ihre App dann auf mehreren HoloLens, iOS- und Android-Geräten suchen kann. Durch Freigeben eines gemeinsamen räumlichen Ankers auf mehreren Geräten kann jeder Benutzer Inhalte sehen, die relativ zu diesem Anker an derselben physischen Stelle in Echtzeit gerendert werden.

Sie können auch Azure Spatial Anchors für asynchrone Hologramm-Peristenz in HoloLens, iOS- und Android-Geräten verwenden. Durch die Freigabe eines dauerhaften Cloud-Raumankers können mehrere Geräte das gleiche beibehaltene Hologramm im Laufe der Zeit beobachten, auch wenn diese Geräte gleichzeitig nicht vorhanden sind.

Um mit der Erstellung freigegebener Erfahrungen in Ihrer HoloLens-App zu beginnen, testen Sie die 5-minütige Azure Spatial Anchors HoloLens Schnellstart.

Sobald Sie mit Azure Spatial Anchors ausgeführt werden, können Sie dann Anker auf 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 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 auf der Grundlage der Anzahl der derzeit in der App-Auflistung von Ankern gespeicherten Anker sequenziell 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);
           }
       }
   }

Asynchron laden und zwischenspeichern, den SpatialAnchorStore

Sehen wir uns an, wie Sie eine SampleSpatialAnchorHelper-Klasse schreiben, die diese Persistenz behandelt, einschließlich:

  • Speichern einer Sammlung von Speicherankern, indiziert von einer Plattform::String-Schlüssel.
  • Laden von Ankern aus dem SpatialAnchorStore des Systems, das von der lokalen In-Memory-Auflistung getrennt wird.
  • Speichern sie die lokale Speichersammlung von Ankern in den SpatialAnchorStore, wenn die App dies tut.

Hier erfahren Sie, wie Sie SpatialAnchor-Objekte im SpatialAnchorStore speichern.

Wenn die Klasse gestartet wird, fordern wir asynchron den SpatialAnchorStore an. Dies umfasst System-I/O, da die API den Ankerspeicher lädt, und diese API wird asynchron gemacht, damit die I/O 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 zuordnen, die Zeichenfolgen sind, mit Datenwerten, die SpatialAnchors sind. In unserem Beispielcode speichern wir dies in einer privaten Klassenmitgliedsvariable, die über eine öffentliche Funktion unserer Hilfsklasse zugänglich ist.

   SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
   {
       m_anchorStore = anchorStore;
       m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
   }

Hinweis

Vergessen Sie nicht, die Ereignisse angehalten/fortsetzen, 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 hat, müssen Sie Ihre räumlichen Verankerungen im Ankerspeicher speichern. Sie können auch festlegen, dass Anker zum Ankerspeicher zu anderen Zeiten gespeichert werden, da Sie für die Implementierung Ihrer App erforderlich sind.

Wenn Sie bereit sind, die Speicheranker in den 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 von der IMapView des Ankerspeichers in Ihre eigene Speicherdatenbank von SpatialAnchors übertragen, wenn Ihre App jederzeit fortgesetzt oder jederzeit ausgeführt wird.

Um Anker aus dem SpatialAnchorStore wiederherzustellen, wiederherstellen Sie jede, die Sie an ihrer eigenen In-Memory-Auflistung interessiert sind.

Sie benötigen eine eigene Speicherdatenbank von SpatialAnchors, um Zeichenfolgen mit den von Ihnen erstellten SpatialAnchors zuzuordnen. In unserem Beispielcode verwenden wir einen Windows::Foundation::Collections::IMap zum Speichern der Anker, wodurch es einfach ist, denselben Schlüssel- und Datenwert für den SpatialAnchorStore zu verwenden.

   // 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 Anker, der wiederhergestellt wird, ist möglicherweise nicht sofort verfügbar. Beispielsweise kann es sich um einen Anker in einem separaten Raum oder in einem anderen Gebäude insgesamt handelt. Die aus dem AnchorStore abgerufenen Anker sollten vor der Verwendung für die 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 auswählen und eine bestimmte Teilmenge von Anker auswählen, indem Sie String-Schlüsselwerte verwenden, die für Ihre Implementierung sinnvoll 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-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 Funktion "Löschen" umzuschließen. Wir wählen dies in unserer Beispielimplementierung aus, da unsere Hilfsklasse die Verantwortung für die Besitzung der SpatialAnchorStore-Instanz erhält.

   // 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: Beziehen von Ankerkoordinatensystemen auf stationäre Referenzrahmenkoordinatensysteme

Sagen wir, Sie haben einen Anker, und Sie möchten etwas im Koordinatensystem Ihres Ankers mit dem SpatialStationaryReferenceFrame verknüpfen, das Sie bereits für Ihre anderen Inhalte verwenden. Sie können TryGetTransformTo verwenden, um eine Transformation von dem Koordinatensystem des Ankers in die des stationären Referenzrahmens 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 nützlich für Sie auf zwei Arten:

  1. Es teilt Ihnen mit, ob die beiden Referenzframes relativ zueinander verstanden werden können, und zwar;
  2. Wenn ja, bietet es Ihnen eine Transformation, um direkt von einem Koordinatensystem zu dem anderen zu wechseln.

Mit diesen Informationen haben Sie ein Verständnis der räumlichen Beziehung zwischen Objekten zwischen den beiden Referenzrahmen.

Beim Rendern können Sie häufig bessere Ergebnisse erhalten, indem Sie Objekte nach ihrem ursprünglichen Referenzrahmen oder Anker gruppieren. Führen Sie einen separaten Zeichnungspass für jede Gruppe aus. Die Ansichtsmatrizen sind für Objekte mit Modelltransformationen genauer, die zunächst mit demselben Koordinatensystem erstellt werden.

Erstellen von Hologrammen mithilfe eines geräteseitigen 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 Panel mit Debuginformationen oder eine Informationsnachricht, 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 sind, anstatt auf die reale Welt. Dieser Frame hat eine feste Überschrift relativ zur Umgebung des Benutzers, die auf die Richtung verweist, die der Benutzer beim Erstellen des Referenzrahmens angibt. Anschließend sind alle Ausrichtungen in diesem Referenzrahmen relativ zu dieser festen Überschrift, auch wenn der Benutzer das Gerät dreht.

Für HoloLens befindet sich der Ursprung des Koordinatensystems dieses Frames am 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.

Um einen SpatialLocatorAttachedFrameOfReference abzurufen, verwenden Sie die SpatialLocator-Klasse und rufen CreateAttachedFrameOfReferenceAtCurrentHeading auf.

Dies gilt für den gesamten Bereich von Windows Mixed Reality Geräten.

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äteseitigen Referenzrahmen mithilfe dieser API zu ermöglichen. 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 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 des Updates erhalten wir jetzt das Koordinatensystem zum Zeitpunkt des Zeitstempels, der aus der Rahmenvorhersage 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 folgen soll, ä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);

Dieses SpatialPointerPose verfügt über 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 die Änderung der Position über einen Zeitraum zu glätten. Dies ist bequemer für den Benutzer, als das Hologramm auf ihren Blick zu sperren. Die Position des Tag-entlang-Hologramms ermöglicht es uns auch, das Hologramm zu stabilisieren, indem sie die Bewegung dämpfen. Wenn wir diese Dämpfung nicht durchgeführt haben, würde der Benutzer den Hologramm-Jitter sehen, da dies normalerweise als nicht wahrnehmbare Bewegungen des Kopfes des Benutzers angesehen 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 Falle eines Debugbereichs können Sie das Hologramm möglicherweise auf die Seite zurückstellen, damit die Ansicht nicht blockiert wird. Hier finden Sie 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, um die Kamera zu sehen

Es ist nicht genug, das Hologramm zu positionieren, was in diesem Fall ein Quad ist; wir müssen das Objekt auch drehen, um dem Benutzer gegenüber zu stehen. Diese Drehung tritt im Weltraum auf, da diese Art von Billboarding das Hologramm einen Teil der Umgebung des Benutzers bleiben kann. Ansichtsraum-Billboards sind nicht so bequem, da das Hologramm auf die Anzeigeausrichtung gesperrt wird; In diesem Fall müssen Sie auch zwischen den Matrizen der linken und rechten Ansicht interpolieren, um eine Ansichtsraum-Billboardtransformation zu erwerben, die das Stereorendering nicht stören. Hier drehen wir uns auf die X- und Z-Achsen, um dem Benutzer gegenüber zu stehen.

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 wählen wir auch das Hologramm im Koordinatensystem des SpatialLocatorAttachedReferenceFrames aus, wo wir das Hologramm positioniert haben. (Wenn wir beschlossen haben, ein anderes Koordinatensystem zu rendern, müssen wir eine Transformation aus dem Koordinatensystem des Geräteanfügenden Referenzrahmens in dieses Koordinatensystem erwerben.)

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 wird nun eine Position verfolgen, 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 das Gerät sich nicht in der Welt finden kann, wird die App "Nachverfolgungsverlust" erleben. Windows Mixed Reality Apps sollten solche Unterbrechungen im Positionsverfolgungssystem verarbeiten können. Diese Unterbrechungen können beobachtet werden und antworten, indem sie das LocatabilityChanged-Ereignis auf dem Standard-SpatialLocator verwenden.

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 das Verhalten bei Bedarf geändert werden. Beispielsweise kann Ihre App im Status "PositionalTrackingInhibited" den normalen Vorgang anhalten und ein Tag-Entlang-Hologramm rendern, das eine Warnmeldung anzeigt.

Die Windows Holographic-App-Vorlage enthält bereits für Sie erstellte LocatabilityChanged-Handler. Standardmäßig wird eine Warnung in der Debugkonsole angezeigt, wenn die Positionsverfolgung nicht verfügbar ist. Sie können diesem Handler Code hinzufügen, um eine Antwort nach Bedarf aus Ihrer App 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

Mit den RÄUMLICHEn Zuordnungs-APIs werden Koordinatensysteme verwendet, um Modelltransformationen für Oberflächengitter abzurufen.

Weitere Informationen