Räumliche Abbildung 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.

In diesem Thema wird beschrieben, wie Sie räumliche Zuordnungen in Ihrer DirectX-App implementieren, einschließlich einer detaillierten Erläuterung der Beispielanwendung für räumliche Zuordnungen, die mit dem Universelle Windows-Plattform SDK gepackt ist.

In diesem Thema wird Code aus dem HolographicSpatialMapping-UWP-Codebeispiel verwendet.

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.

Geräteunterstützung

Feature HoloLens (1. Generation) HoloLens 2 Immersive Headsets
Räumliche Abbildung ✔️ ✔️

DirectX-Entwicklung – Übersicht

Bei der nativen Anwendungsentwicklung für räumliche Zuordnungen werden die APIs im Windows.Perception.Spatial-Namespace verwendet. Diese APIs bieten Ihnen die vollständige Kontrolle über die Funktionalität der räumlichen Zuordnung, auf die gleiche Weise wie raumbezogene Zuordnungs-APIs von Unity verfügbar gemacht werden.

Wahrnehmungs-APIs

Die primären Typen, die für die Entwicklung räumlicher Zuordnungen bereitgestellt werden, sind wie folgt:

  • SpatialSurfaceObserver stellt Informationen zu Oberflächen in anwendungsspezifischen Raumbereichen in der Nähe des Benutzers in Form von SpatialSurfaceInfo-Objekten bereit.
  • SpatialSurfaceInfo beschreibt eine einzelne noch existierende räumliche Oberfläche, einschließlich einer eindeutigen ID, eines begrenzungsfähigen Volumens und der Zeit der letzten Änderung. Auf Anforderung wird asynchron ein SpatialSurfaceMesh bereitgestellt.
  • SpatialSurfaceMeshOptions enthält Parameter, die zum Anpassen der SpatialSurfaceMesh-Objekte verwendet werden, die von SpatialSurfaceInfo angefordert werden.
  • SpatialSurfaceMesh stellt die Gitterdaten für eine einzelne räumliche Oberfläche dar. Die Daten für Vertexpositionen, Vertexnormale und Dreiecksindizes sind in SpatialSurfaceMeshBuffer-Objekten des Members enthalten.
  • SpatialSurfaceMeshBuffer umschließt einen einzelnen Typ von Gitterdaten.

Wenn Sie eine Anwendung mithilfe dieser APIs entwickeln, sieht Ihr grundlegender Programmablauf wie folgt aus (wie in der unten beschriebenen Beispielanwendung veranschaulicht):

  • Einrichten Ihres SpatialSurfaceObservers
    • Rufen Sie RequestAccessAsync auf, um sicherzustellen, dass der Benutzer Ihrer Anwendung die Berechtigung erteilt hat, die räumlichen Zuordnungsfunktionen des Geräts zu verwenden.
    • Instanziieren eines SpatialSurfaceObserver-Objekts.
    • Rufen Sie SetBoundingVolumes auf , um die Raumbereiche anzugeben, in denen Sie Informationen zu räumlichen Oberflächen benötigen. Sie können diese Regionen in Zukunft ändern, indem Sie diese Funktion erneut aufrufen. Jede Region wird mit einem SpatialBoundingVolume angegeben.
    • Registrieren Sie sich für das ObservedSurfacesChanged-Ereignis , das ausgelöst wird, wenn neue Informationen zu den räumlichen Oberflächen in den von Ihnen angegebenen Raumbereichen verfügbar sind.
  • Process ObservedSurfacesChanged-Ereignisse
    • Rufen Sie in Ihrem Ereignishandler GetObservedSurfaces auf, um eine Zuordnung von SpatialSurfaceInfo-Objekten zu erhalten. Mit dieser Karte können Sie Ihre Datensätze darüber aktualisieren, welche räumlichen Oberflächen in der Umgebung des Benutzers vorhanden sind.
    • Für jedes SpatialSurfaceInfo-Objekt können Sie TryGetBounds abfragen, um die räumlichen Ausdehnungen der Oberfläche zu bestimmen, die in einem räumlichen Koordinatensystem Ihrer Wahl ausgedrückt werden.
    • Wenn Sie sich entscheiden, für eine räumliche Oberfläche ein Mesh anzufordern, rufen Sie TryComputeLatestMeshAsync auf. Sie können Optionen bereitstellen, die die Dichte von Dreiecken und das Format der zurückgegebenen Gitterdaten angeben.
  • Empfangs- und Prozessgitter
    • Jeder Aufruf von TryComputeLatestMeshAsync gibt asynchron ein SpatialSurfaceMesh-Objekt zurück.
    • Von diesem Objekt aus können Sie auf die enthaltenen SpatialSurfaceMeshBuffer-Objekte zugreifen, sodass Sie auf die Dreiecksindizes, Vertexpositionen und Vertexnormale des Gitternetzes zugreifen können, wenn Sie sie anfordern. Diese Daten befinden sich in einem Format, das direkt mit den Direct3D 11-APIs kompatibel ist, die zum Rendern von Gittern verwendet werden.
    • Von hier aus kann Ihre Anwendung die Gitterdaten optional analysieren oder verarbeiten und sie zum Rendern und Physik-Raycasting und -kollision verwenden.
    • Ein wichtiges Detail ist, dass Sie eine Skalierung auf die Gittervertexpositionen anwenden müssen (z. B. im Vertex-Shader, der zum Rendern der Gitter verwendet wird), um sie von den optimierten ganzzahligen Einheiten, in denen sie im Puffer gespeichert sind, in Meter zu konvertieren. Sie können diese Skalierung abrufen, indem Sie VertexPositionScale aufrufen.

Problembehandlung

Codebeispiel für räumliche Zuordnungen exemplarische Vorgehensweise

Das Codebeispiel für holographic Spatial Mapping enthält Code, mit dem Sie mit dem Laden von Oberflächengittern in Ihre App beginnen können, einschließlich der Infrastruktur zum Verwalten und Rendern von Oberflächengittern.

Nun erfahren Sie, wie Sie Ihrer DirectX-App Oberflächenzuordnungsfunktionen hinzufügen. Sie können diesen Code Ihrem Windows Holographic-App-Vorlagenprojekt hinzufügen, oder Sie können das oben erwähnte Codebeispiel durchblättern. Dieses Codebeispiel basiert auf der Windows Holographic-App-Vorlage.

Einrichten Ihrer App für die Verwendung der Funktion spatialPerception

Ihre App kann die Funktion räumliche Zuordnung verwenden. Dies ist erforderlich, da das räumliche Gitter eine Darstellung der Umgebung des Benutzers ist, die als private Daten betrachtet werden kann. Deklarieren Sie diese Funktion in der Datei package.appxmanifest für Ihre App. Hier sehen Sie ein Beispiel:

<Capabilities>
  <uap2:Capability Name="spatialPerception" />
</Capabilities>

Die Funktion stammt aus dem uap2-Namespace . Um Zugriff auf diesen Namespace in Ihrem Manifest zu erhalten, fügen Sie ihn als xlmns-Attribut in das <Package-Element> ein. Hier sehen Sie ein Beispiel:

<Package
    xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
    xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
    xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
    xmlns:uap2="https://schemas.microsoft.com/appx/manifest/uap/windows10/2"
    IgnorableNamespaces="uap uap2 mp"
    >

Überprüfen der Unterstützung von Räumlichen Zuordnungsfeatures

Windows Mixed Reality unterstützt eine Vielzahl von Geräten, einschließlich Geräten, die keine räumliche Zuordnung unterstützen. Wenn Ihre App räumliche Zuordnungen verwenden kann oder räumliche Zuordnungen verwenden muss, um Funktionen bereitzustellen, sollte sie überprüfen, ob die räumliche Zuordnung unterstützt wird, bevor Sie versuchen, sie zu verwenden. Wenn beispielsweise eine räumliche Zuordnung für Ihre Mixed Reality-App erforderlich ist, sollte eine entsprechende Meldung angezeigt werden, wenn ein Benutzer versucht, sie auf einem Gerät ohne räumliche Zuordnung auszuführen. Oder Ihre App kann ihre eigene virtuelle Umgebung anstelle der Umgebung des Benutzers rendern und eine Umgebung bereitstellen, die ähnlich dem ist, was passieren würde, wenn räumliche Zuordnungen verfügbar wären. In jedem Fall ermöglicht diese API es Ihrer App, zu erkennen, wann sie keine räumlichen Zuordnungsdaten erhält und auf geeignete Weise reagiert.

Um das aktuelle Gerät auf Räumliche Zuordnungsunterstützung zu überprüfen, stellen Sie zunächst sicher, dass der UWP-Vertrag auf Ebene 4 oder höher liegt, und rufen Sie dann SpatialSurfaceObserver::IsSupported() auf. Hier erfahren Sie, wie Sie dies im Kontext des Codebeispiels für holographic Spatial Mapping tun. Der Support wird direkt vor dem Anfordern des Zugriffs überprüft.

Die SpatialSurfaceObserver::IsSupported()-API ist ab SDK-Version 15063 verfügbar. Führen Sie ihr Projekt bei Bedarf auf Plattformversion 15063 um, bevor Sie diese API verwenden.

if (m_surfaceObserver == nullptr)
   {
       using namespace Windows::Foundation::Metadata;
       if (ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 4))
       {
           if (!SpatialSurfaceObserver::IsSupported())
           {
               // The current system does not have spatial mapping capability.
               // Turn off spatial mapping.
               m_spatialPerceptionAccessRequested = true;
               m_surfaceAccessAllowed = false;
           }
       }

       if (!m_spatialPerceptionAccessRequested)
       {
           /// etc ...

Wenn der UWP-Vertrag kleiner als Ebene 4 ist, sollte die App so vorgehen, als wäre das Gerät in der Lage, räumliche Zuordnungen durchzuführen.

Anfordern des Zugriffs auf Räumliche Zuordnungsdaten

Ihre App muss die Berechtigung für den Zugriff auf räumliche Zuordnungsdaten anfordern, bevor Sie versuchen, Oberflächenbeobachter zu erstellen. Hier sehen Sie ein Beispiel, das auf unserem Surface Mapping-Codebeispiel basiert, mit weiteren Details weiter unten auf dieser Seite:

auto initSurfaceObserverTask = create_task(SpatialSurfaceObserver::RequestAccessAsync());
initSurfaceObserverTask.then([this, coordinateSystem](Windows::Perception::Spatial::SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Create a surface observer.
    }
    else
    {
        // Handle spatial mapping unavailable.
    }
}

Erstellen eines Oberflächenbeobachters

Der Windows::P erception::Spatial:Surfaces-Namespace enthält die SpatialSurfaceObserver-Klasse , die einen oder mehrere Volumes beobachtet, die Sie in einem SpatialCoordinateSystem angeben. Verwenden Sie eine SpatialSurfaceObserver-instance, um in Echtzeit auf Surface Mesh-Daten zuzugreifen.

Von AppMain.h:

// Obtains surface mapping data from the device in real time.
Windows::Perception::Spatial::Surfaces::SpatialSurfaceObserver^     m_surfaceObserver;
Windows::Perception::Spatial::Surfaces::SpatialSurfaceMeshOptions^  m_surfaceMeshOptions;

Wie im vorherigen Abschnitt erwähnt, müssen Sie den Zugriff auf Räumliche Zuordnungsdaten anfordern, bevor Ihre App sie verwenden kann. Dieser Zugriff wird automatisch für die HoloLens gewährt.

// The surface mapping API reads information about the user's environment. The user must
// grant permission to the app to use this capability of the Windows Mixed Reality device.
auto initSurfaceObserverTask = create_task(SpatialSurfaceObserver::RequestAccessAsync());
initSurfaceObserverTask.then([this, coordinateSystem](Windows::Perception::Spatial::SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // If status is allowed, we can create the surface observer.
        m_surfaceObserver = ref new SpatialSurfaceObserver();

Als Nächstes müssen Sie den Oberflächenbeobachter so konfigurieren, dass ein bestimmtes begrenzungsendes Volume beobachtet wird. Hier beobachten wir eine Box mit 20 x 20 x 5 Metern, die am Ursprung des Koordinatensystems zentriert ist.

// The surface observer can now be configured as needed.

        // In this example, we specify one area to be observed using an axis-aligned
        // bounding box 20 meters in width and 5 meters in height and centered at the
        // origin.
        SpatialBoundingBox aabb =
        {
            { 0.f,  0.f, 0.f },
            {20.f, 20.f, 5.f },
        };

        SpatialBoundingVolume^ bounds = SpatialBoundingVolume::FromBox(coordinateSystem, aabb);
        m_surfaceObserver->SetBoundingVolume(bounds);

Sie können stattdessen mehrere umschließende Volumes festlegen.

Dies ist Pseudocode:

m_surfaceObserver->SetBoundingVolumes(/* iterable collection of bounding volumes*/);

Es ist auch möglich, andere begrenzungsfähige Formen zu verwenden, z. B. ein Ansichtsfrstum oder ein begrenzungsfähiges Feld, das nicht achsenbündig ausgerichtet ist.

Dies ist Pseudocode:

m_surfaceObserver->SetBoundingVolume(
            SpatialBoundingVolume::FromFrustum(/*SpatialCoordinateSystem*/, /*SpatialBoundingFrustum*/)
            );

Wenn Ihre App etwas anderes tun muss, wenn keine Surface Mapping-Daten verfügbar sind, können Sie Code schreiben, um auf den Fall zu reagieren, in dem SpatialPerceptionAccessStatus nicht zulässig ist. Beispielsweise ist dies auf PCs mit angeschlossenen immersiven Geräten nicht zulässig, da diese Geräte keine Hardware für die räumliche Zuordnung haben. Für diese Geräte sollten Sie sich stattdessen auf die räumliche Phase verlassen, um Informationen zur Umgebung und Gerätekonfiguration des Benutzers zu finden.

Initialisieren und Aktualisieren der Surface Mesh-Sammlung

Wenn der Oberflächenbeobachter erfolgreich erstellt wurde, können wir unsere Surface Mesh-Sammlung weiterhin initialisieren. Hier verwenden wir die Pullmodell-API, um den aktuellen Satz beobachteter Oberflächen sofort abzurufen:

auto mapContainingSurfaceCollection = m_surfaceObserver->GetObservedSurfaces();
        for (auto& pair : mapContainingSurfaceCollection)
        {
            // Store the ID and metadata for each surface.
            auto const& id = pair->Key;
            auto const& surfaceInfo = pair->Value;
            m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
        }

Es ist auch ein Pushmodell verfügbar, um Surface Mesh-Daten abzurufen. Sie können Ihre App so entwerfen, dass sie nur das Pullmodell verwendet, wenn Sie sich dafür entscheiden. In diesem Fall werden Sie immer wieder nach Daten abfragen , z. B. einmal pro Frame, oder während eines bestimmten Zeitraums, z. B. während der Einrichtung des Spiels. Wenn ja, benötigen Sie den oben genannten Code.

In unserem Codebeispiel haben wir uns dafür entschieden, die Verwendung beider Modelle für pädagogische Zwecke zu veranschaulichen. Hier abonnieren wir ein Ereignis, um aktuelle Surface Mesh-Daten zu empfangen, wenn das System eine Änderung erkennt.

m_surfaceObserver->ObservedSurfacesChanged += ref new TypedEventHandler<SpatialSurfaceObserver^, Platform::Object^>(
            bind(&HolographicDesktopAppMain::OnSurfacesChanged, this, _1, _2)
            );

Unser Codebeispiel ist auch für die Reaktion auf diese Ereignisse konfiguriert. Lassen Sie uns durchgehen, wie wir dies tun.

HINWEIS: Dies ist möglicherweise nicht die effizienteste Möglichkeit für Ihre App, Gitterdaten zu verarbeiten. Dieser Code ist aus Gründen der Übersichtlichkeit geschrieben und nicht optimiert.

Die Surface Mesh-Daten werden in einer schreibgeschützten Karte bereitgestellt, in der SpatialSurfaceInfo-Objekte mithilfe von Platform::Guids als Schlüsselwerte gespeichert werden.

IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection = sender->GetObservedSurfaces();

Um diese Daten zu verarbeiten, suchen wir zuerst nach Schlüsselwerten, die sich nicht in unserer Sammlung befinden. Details dazu, wie die Daten in unserer Beispiel-App gespeichert werden, werden weiter unten in diesem Thema vorgestellt.

// Process surface adds and updates.
for (const auto& pair : surfaceCollection)
{
    auto id = pair->Key;
    auto surfaceInfo = pair->Value;

    if (m_meshCollection->HasSurface(id))
    {
        // Update existing surface.
        m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
    }
    else
    {
        // New surface.
        m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
    }
}

Wir müssen auch Oberflächengitter entfernen, die sich in unserer Surface Mesh-Sammlung befinden, die sich aber nicht mehr in der Systemsammlung befinden. Dazu müssen wir so etwas wie das Gegenteil von dem tun, was wir gerade beim Hinzufügen und Aktualisieren von Gittern gezeigt haben; Wir führen eine Schleife in der Sammlung unserer App durch und überprüfen, ob sich die guid in der Systemsammlung befindet. Wenn es sich nicht in der Systemsammlung befindet, entfernen wir es aus unserer.

Aus unserem Ereignishandler in AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

Die Implementierung des Gitterschnitts in RealtimeSurfaceMeshRenderer.cpp:

void RealtimeSurfaceMeshRenderer::PruneMeshCollection(IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection)
{
    std::lock_guard<std::mutex> guard(m_meshCollectionLock);
    std::vector<Guid> idsToRemove;

    // Remove surfaces that moved out of the culling frustum or no longer exist.
    for (const auto& pair : m_meshCollection)
    {
        const auto& id = pair.first;
        if (!surfaceCollection->HasKey(id))
        {
            idsToRemove.push_back(id);
        }
    }

    for (const auto& id : idsToRemove)
    {
        m_meshCollection.erase(id);
    }
}

Abrufen und Verwenden von Surface Mesh-Datenpuffern

Das Abrufen der Oberflächengitterinformationen war so einfach wie das Pullen einer Datensammlung und die Verarbeitung von Updates für diese Sammlung. Nun gehen wir ausführlich darauf ein, wie Sie die Daten verwenden können.

In unserem Codebeispiel haben wir uns für das Rendern der Oberflächengitter entschieden. Dies ist ein gängiges Szenario für das Verdecken von Hologrammen hinter realen Oberflächen. Sie können die Gitter auch rendern oder verarbeitete Versionen davon rendern, um dem Benutzer zu zeigen, welche Bereiche des Raums gescannt werden, bevor Sie mit der Bereitstellung von App- oder Spielfunktionen beginnen.

Das Codebeispiel startet den Prozess, wenn es Surface Mesh-Updates vom Ereignishandler empfängt, den wir im vorherigen Abschnitt beschrieben haben. Die wichtige Codezeile in dieser Funktion ist der Aufruf zum Aktualisieren des Oberflächengitters: Zu diesem Zeitpunkt haben wir die Gitterinformationen bereits verarbeitet, und wir sind dabei, die Vertex- und Indexdaten für die Verwendung zu erhalten, wie wir es für richtig halten.

Von RealtimeSurfaceMeshRenderer.cpp:

void RealtimeSurfaceMeshRenderer::AddOrUpdateSurface(Guid id, SpatialSurfaceInfo^ newSurface)
{
    auto options = ref new SpatialSurfaceMeshOptions();
    options->IncludeVertexNormals = true;

    auto createMeshTask = create_task(newSurface->TryComputeLatestMeshAsync(1000, options));
    createMeshTask.then([this, id](SpatialSurfaceMesh^ mesh)
    {
        if (mesh != nullptr)
        {
            std::lock_guard<std::mutex> guard(m_meshCollectionLock);
            '''m_meshCollection[id].UpdateSurface(mesh);'''
        }
    }, task_continuation_context::use_current());
}

Unser Beispielcode ist so konzipiert, dass eine Datenklasse, SurfaceMesh, die Verarbeitung und das Rendern von Gitterdaten übernimmt. Diese Gitter sind das, was der RealtimeSurfaceMeshRenderer tatsächlich eine Karte speichert. Jede instanz verfügt über einen Verweis auf die SpatialSurfaceMesh, von der sie stammt, sodass Sie sie jederzeit verwenden können, wenn Sie auf den Mesh-Scheitelpunkt oder indexpuffer zugreifen oder eine Transformation für das Gitter erhalten müssen. Derzeit kennzeichnen wir das Gitter, dass ein Update erforderlich ist.

Von SurfaceMesh.cpp:

void SurfaceMesh::UpdateSurface(SpatialSurfaceMesh^ surfaceMesh)
{
    m_surfaceMesh = surfaceMesh;
    m_updateNeeded = true;
}

Wenn das Gitter das nächste Mal aufgefordert wird, sich selbst zu zeichnen, wird zuerst das Flag überprüft. Wenn ein Update erforderlich ist, werden die Scheitelpunkt- und Indexpuffer auf der GPU aktualisiert.

void SurfaceMesh::CreateDeviceDependentResources(ID3D11Device* device)
{
    m_indexCount = m_surfaceMesh->TriangleIndices->ElementCount;
    if (m_indexCount < 3)
    {
        // Not enough indices to draw a triangle.
        return;
    }

Zunächst erfassen wir die Rohdatenpuffer:

Windows::Storage::Streams::IBuffer^ positions = m_surfaceMesh->VertexPositions->Data;
    Windows::Storage::Streams::IBuffer^ normals   = m_surfaceMesh->VertexNormals->Data;
    Windows::Storage::Streams::IBuffer^ indices   = m_surfaceMesh->TriangleIndices->Data;

Anschließend erstellen wir Direct3D-Gerätepuffer mit den Gitterdaten, die von holoLens bereitgestellt werden:

CreateDirectXBuffer(device, D3D11_BIND_VERTEX_BUFFER, positions, m_vertexPositions.GetAddressOf());
    CreateDirectXBuffer(device, D3D11_BIND_VERTEX_BUFFER, normals,   m_vertexNormals.GetAddressOf());
    CreateDirectXBuffer(device, D3D11_BIND_INDEX_BUFFER,  indices,   m_triangleIndices.GetAddressOf());

    // Create a constant buffer to control mesh position.
    CD3D11_BUFFER_DESC constantBufferDesc(sizeof(SurfaceTransforms), D3D11_BIND_CONSTANT_BUFFER);
    DX::ThrowIfFailed(
        device->CreateBuffer(
            &constantBufferDesc,
            nullptr,
            &m_modelTransformBuffer
            )
        );

    m_loadingComplete = true;
}

HINWEIS: Die im vorherigen Codeausschnitt verwendete CreateDirectXBuffer-Hilfsfunktion finden Sie im Surface Mapping-Codebeispiel: SurfaceMesh.cpp, GetDataFromIBuffer.h. Jetzt ist die Erstellung der Geräteressource abgeschlossen, und das Mesh gilt als geladen und bereit für das Update und Rendern.

Aktualisieren und Rendern von Oberflächengittern

Unsere SurfaceMesh-Klasse verfügt über eine spezielle Updatefunktion. Jede SpatialSurfaceMesh hat eine eigene Transformation, und unser Beispiel verwendet das aktuelle Koordinatensystem für den SpatialStationaryReferenceFrame , um die Transformation zu erfassen. Anschließend wird der Modellkonstantenpuffer auf der GPU aktualisiert.

void SurfaceMesh::UpdateTransform(
    ID3D11DeviceContext* context,
    SpatialCoordinateSystem^ baseCoordinateSystem
    )
{
    if (m_indexCount < 3)
    {
        // Not enough indices to draw a triangle.
        return;
    }

    XMMATRIX transform = XMMatrixIdentity();

    auto tryTransform = m_surfaceMesh->CoordinateSystem->TryGetTransformTo(baseCoordinateSystem);
    if (tryTransform != nullptr)
    {
        transform = XMLoadFloat4x4(&tryTransform->Value);
    }

    XMMATRIX scaleTransform = XMMatrixScalingFromVector(XMLoadFloat3(&m_surfaceMesh->VertexPositionScale));

    XMStoreFloat4x4(
        &m_constantBufferData.vertexWorldTransform,
        XMMatrixTranspose(
            scaleTransform * transform
            )
        );

    // Normals don't need to be translated.
    XMMATRIX normalTransform = transform;
    normalTransform.r[3] = XMVectorSet(0.f, 0.f, 0.f, XMVectorGetW(normalTransform.r[3]));
    XMStoreFloat4x4(
        &m_constantBufferData.normalWorldTransform,
        XMMatrixTranspose(
            normalTransform
        )
        );

    if (!m_loadingComplete)
    {
        return;
    }

    context->UpdateSubresource(
        m_modelTransformBuffer.Get(),
        0,
        NULL,
        &m_constantBufferData,
        0,
        0
        );
}

Wenn Es zeit ist, Oberflächengitter zu rendern, führen wir einige Vorbereitungsarbeiten durch, bevor wir die Auflistung rendern. Wir haben die Shaderpipeline für die aktuelle Renderingkonfiguration eingerichtet und die Eingabeassemierungsstufe eingerichtet. Die holografische Kamerahilfsklasse CameraResources.cpp hat bereits den Puffer für die Ansicht/Projektionskonstante eingerichtet.

Von RealtimeSurfaceMeshRenderer::Render:

auto context = m_deviceResources->GetD3DDeviceContext();

context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());

// Attach our vertex shader.
context->VSSetShader(
    m_vertexShader.Get(),
    nullptr,
    0
    );

// The constant buffer is per-mesh, and will be set as such.

if (depthOnly)
{
    // Explicitly detach the later shader stages.
    context->GSSetShader(nullptr, nullptr, 0);
    context->PSSetShader(nullptr, nullptr, 0);
}
else
{
    if (!m_usingVprtShaders)
    {
        // Attach the passthrough geometry shader.
        context->GSSetShader(
            m_geometryShader.Get(),
            nullptr,
            0
            );
    }

    // Attach our pixel shader.
    context->PSSetShader(
        m_pixelShader.Get(),
        nullptr,
        0
        );
}

Sobald dies geschehen ist, schleifen wir unsere Gitter und sagen jedem, sich selbst zu zeichnen. HINWEIS: Dieser Beispielcode ist nicht für die Verwendung von Frustum-Culling optimiert, aber Sie sollten dieses Feature in Ihre App einschließen.

std::lock_guard<std::mutex> guard(m_meshCollectionLock);

auto device = m_deviceResources->GetD3DDevice();

// Draw the meshes.
for (auto& pair : m_meshCollection)
{
    auto& id = pair.first;
    auto& surfaceMesh = pair.second;

    surfaceMesh.Draw(device, context, m_usingVprtShaders, isStereo);
}

Die einzelnen Gitter sind für das Einrichten des Vertex- und Indexpuffers, des Schritts und des Modelltransformationskonstantenpuffers verantwortlich. Wie beim sich drehenden Cube in der Windows Holographic-App-Vorlage rendern wir mithilfe von Instancing in stereoskopische Puffer.

Von SurfaceMesh::D raw:

// The vertices are provided in {vertex, normal} format

const auto& vertexStride = m_surfaceMesh->VertexPositions->Stride;
const auto& normalStride = m_surfaceMesh->VertexNormals->Stride;

UINT strides [] = { vertexStride, normalStride };
UINT offsets [] = { 0, 0 };
ID3D11Buffer* buffers [] = { m_vertexPositions.Get(), m_vertexNormals.Get() };

context->IASetVertexBuffers(
    0,
    ARRAYSIZE(buffers),
    buffers,
    strides,
    offsets
    );

const auto& indexFormat = static_cast<DXGI_FORMAT>(m_surfaceMesh->TriangleIndices->Format);

context->IASetIndexBuffer(
    m_triangleIndices.Get(),
    indexFormat,
    0
    );

context->VSSetConstantBuffers(
    0,
    1,
    m_modelTransformBuffer.GetAddressOf()
    );

if (!usingVprtShaders)
{
    context->GSSetConstantBuffers(
        0,
        1,
        m_modelTransformBuffer.GetAddressOf()
        );
}

context->PSSetConstantBuffers(
    0,
    1,
    m_modelTransformBuffer.GetAddressOf()
    );

context->DrawIndexedInstanced(
    m_indexCount,       // Index count per instance.
    isStereo ? 2 : 1,   // Instance count.
    0,                  // Start index location.
    0,                  // Base vertex location.
    0                   // Start instance location.
    );

Renderingoptionen mit Surface Mapping

Das Surface Mapping-Codebeispiel bietet Code für das reine Okklusionsrendering von Surface Mesh-Daten und für das Bildschirmrendering von Oberflächengitterdaten. Welchen Pfad Sie wählen – oder beides – hängt von Ihrer Anwendung ab. In diesem Dokument werden beide Konfigurationen beschrieben.

Rendern von Okklusionspuffern für holografische Effekte

Löschen Sie zunächst die Renderzielansicht für die aktuelle virtuelle Kamera.

Über AppMain.cpp:

context->ClearRenderTargetView(pCameraResources->GetBackBufferRenderTargetView(), DirectX::Colors::Transparent);

Dies ist ein "Pre-Rendering"-Pass. Hier erstellen wir einen Okklusionspuffer, indem wir den Gitterrenderer bitten, nur die Tiefe zu rendern. In dieser Konfiguration fügen wir keine Renderzielansicht an, und der Gitterrenderer legt die Pixel-Shaderstufe auf NULLptr fest, sodass die GPU keine Mühe hat, Pixel zu zeichnen. Die Geometrie wird auf den Tiefenpuffer gerastert, und die Grafikpipeline wird dort beendet.

// Pre-pass rendering: Create occlusion buffer from Surface Mapping data.
context->ClearDepthStencilView(pCameraResources->GetSurfaceDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

// Set the render target to null, and set the depth target occlusion buffer.
// We will use this same buffer as a shader resource when drawing holograms.
context->OMSetRenderTargets(0, nullptr, pCameraResources->GetSurfaceOcclusionDepthStencilView());

// The first pass is a depth-only pass that generates an occlusion buffer we can use to know which
// hologram pixels are hidden behind surfaces in the environment.
m_meshCollection->Render(pCameraResources->IsRenderingStereoscopic(), true);

Wir können Hologramme mit einem zusätzlichen Tiefentest für den Surface Mapping-Okklusionspuffer zeichnen. In diesem Codebeispiel rendern wir Pixel auf dem Cube in einer anderen Farbe, wenn sie sich hinter einer Oberfläche befinden.

Über AppMain.cpp:

// Hologram rendering pass: Draw holographic content.
context->ClearDepthStencilView(pCameraResources->GetHologramDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

// Set the render target, and set the depth target drawing buffer.
ID3D11RenderTargetView *const targets[1] = { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, pCameraResources->GetHologramDepthStencilView());

// Render the scene objects.
// In this example, we draw a special effect that uses the occlusion buffer we generated in the
// Pre-Pass step to render holograms using X-Ray Vision when they are behind physical objects.
m_xrayCubeRenderer->Render(
    pCameraResources->IsRenderingStereoscopic(),
    pCameraResources->GetSurfaceOcclusionShaderResourceView(),
    pCameraResources->GetHologramOcclusionShaderResourceView(),
    pCameraResources->GetDepthTextureSamplerState()
    );

Basierend auf Code von SpecialEffectPixelShader.hlsl:

// Draw boundaries
min16int surfaceSum = GatherDepthLess(envDepthTex, uniSamp, input.pos.xy, pixelDepth, input.idx.x);

if (surfaceSum <= -maxSum)
{
    // The pixel and its neighbors are behind the surface.
    // Return the occluded 'X-ray' color.
    return min16float4(0.67f, 0.f, 0.f, 1.0f);
}
else if (surfaceSum < maxSum)
{
    // The pixel and its neighbors are a mix of in front of and behind the surface.
    // Return the silhouette edge color.
    return min16float4(1.f, 1.f, 1.f, 1.0f);
}
else
{
    // The pixel and its neighbors are all in front of the surface.
    // Return the color of the hologram.
    return min16float4(input.color, 1.0f);
}

Hinweis: Informationen zu unserer GatherDepthLess-Routine finden Sie im Surface Mapping-Codebeispiel: SpecialEffectPixelShader.hlsl.

Rendern von Oberflächengitterdaten auf dem Display

Wir können auch einfach die Oberflächengitter in die Stereo-Anzeigepuffer zeichnen. Wir haben uns dafür entschieden, vollständige Gesichter mit Beleuchtung zu zeichnen, aber Sie können Drahtrahmen zeichnen, Gitter vor dem Rendern verarbeiten, eine Texturkarte anwenden usw.

In diesem Codebeispiel wird der Gitterrenderer aufgefordert, die Auflistung zu zeichnen. Dieses Mal geben wir keinen Tiefendurchlauf an, es wird ein Pixel-Shader angefügt und die Renderingpipeline mit den Zielen abgeschlossen, die wir für die aktuelle virtuelle Kamera angegeben haben.

// Spatial Mapping mesh rendering pass: Draw Spatial Mapping mesh over the world.
context->ClearDepthStencilView(pCameraResources->GetSurfaceOcclusionDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

// Set the render target to the current holographic camera's back buffer, and set the depth buffer.
ID3D11RenderTargetView *const targets[1] = { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, pCameraResources->GetSurfaceDepthStencilView());

// This drawing pass renders the surface meshes to the stereoscopic display. The user will be
// able to see them while wearing the device.
m_meshCollection->Render(pCameraResources->IsRenderingStereoscopic(), false);

Weitere Informationen