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

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 verpackt ist.

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

Hinweis

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

Geräteunterstützung

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

DirectX-Entwicklung – Übersicht

Die systemeigene Anwendungsentwicklung für räumliche Zuordnung verwendet die APIs im Windows.Perception.Spatial-Namespace . Diese APIs ermöglichen Ihnen die vollständige Kontrolle über die Funktionalität der räumlichen Zuordnung, und zwar auf die gleiche Weise, wie räumliche Zuordnungs-APIs von Unity verfügbar gemacht werden.

Wahrnehmungs-APIs

Die primären Typen für die räumliche Zuordnungsentwicklung sind wie folgt:

  • SpatialSurfaceObserver stellt Informationen zu Oberflächen in anwendungsspezifischen Bereichen des Raums in der Nähe des Benutzers in Form von SpatialSurfaceInfo-Objekten bereit.
  • SpatialSurfaceInfo beschreibt eine einzelne erweiterbare räumliche Oberfläche, einschließlich einer eindeutigen ID, dem Begrenzungsvolumen und der Uhrzeit der letzten Änderung. Es wird asynchron eine SpatialSurfaceMesh auf Anfrage bereitstellen.
  • SpatialSurfaceMeshOptions enthält Parameter, die zum Anpassen der von SpatialSurfaceInfo angeforderten SpatialSurfaceMesh-Objekte verwendet werden.
  • SpatialSurfaceMesh stellt die Gitterdaten für eine einzelne räumliche Oberfläche dar. Die Daten für Vertexpositionen, Vertexnormale und Dreiecksindizes sind in Member-SpatialSurfaceMeshBuffer-Objekten enthalten.
  • SpatialSurfaceMeshBuffer umschließt eine einzelne Art von Gitterdaten.

Beim Entwickeln einer Anwendung mit diesen APIs sieht der grundlegende Programmfluss wie folgt aus (wie in der unten beschriebenen Beispielanwendung gezeigt):

  • Einrichten ihres SpatialSurfaceObservers
    • Rufen Sie RequestAccessAsync auf, um sicherzustellen, dass der Benutzer die Berechtigung für die Verwendung der räumlichen Zuordnungsfunktionen des Geräts erteilt hat.
    • Instanziieren eines SpatialSurfaceObserver-Objekts.
    • Rufen Sie SetBoundingVolumes auf, um die Bereiche des Raums anzugeben, in denen Sie Informationen zu räumlichen Oberflächen wünschen. Sie können diese Regionen in Zukunft ändern, indem Sie diese Funktion erneut aufrufen. Jede Region wird mithilfe eines SpatialBoundingVolume angegeben.
    • Registrieren Sie sich für das Ereignis "ObservedSurfacesChanged" , das ausgelöst wird, wenn neue Informationen zu den räumlichen Oberflächen in den von Ihnen angegebenen Bereichen verfügbar sind.
  • Process ObservedSurfacesChanged-Ereignisse
    • Rufen Sie in Ihrem Ereignishandler GetObservedSurfaces auf, um eine Zuordnung von SpatialSurfaceInfo-Objekten zu erhalten. Mithilfe dieser Karte können Sie Ihre Datensätze aktualisieren, von denen räumliche Oberflächen in der Umgebung des Benutzers vorhanden sind.
    • Für jedes SpatialSurfaceInfo-Objekt können Sie TryGetBounds abfragen, um die räumlichen Ausmaße der Oberfläche zu bestimmen, die in einem räumlichen Koordinatensystem Ihrer Wahl ausgedrückt werden.
    • Wenn Sie sich für eine räumliche Oberfläche entscheiden, rufen Sie TryComputeLatestMeshAsync auf. Sie können Optionen angeben, die die Dichte von Dreiecken und das Format der zurückgegebenen Gitterdaten angeben.
  • Empfangen und Verarbeiten von Gittern
    • Jeder Aufruf von TryComputeLatestMeshAsync gibt asynchron ein SpatialSurfaceMesh-Objekt zurück.
    • Aus diesem Objekt können Sie auf die enthaltenen SpatialSurfaceMeshBuffer-Objekte zugreifen, die Ihnen Zugriff auf die Dreiecksindizes, Vertexpositionen und Vertexnormale des Gitters ermöglichen, 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 zum Rendernund Physik-Raycasting und -kollision verwenden.
    • Ein wichtiges Detail ist zu beachten, dass Sie eine Skalierung auf die Vertexpositionen des Gitters anwenden müssen (z. B. im Vertex-Shader, der zum Rendern der Gitter verwendet wird), um sie aus den optimierten ganzzahligen Einheiten zu konvertieren, in denen sie im Puffer gespeichert sind, in Meter. Sie können diese Skalierung abrufen, indem Sie VertexPositionScale aufrufen.

Problembehandlung

Beispiel für exemplarische Vorgehensweise für räumliche Zuordnung

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 wird erläutert, wie Sie Ihrer DirectX-App Surface-Zuordnungsfunktionen hinzufügen. Sie können diesen Code ihrem Windows Holographic-App-Vorlagenprojekt hinzufügen, oder Sie können folgen, indem Sie das oben genannte Codebeispiel durchlaufen. Dieses Codebeispiel basiert auf der Windows Holographic-App-Vorlage.

Einrichten Ihrer App für die Verwendung der spatialPerception-Funktion

Ihre App kann die Räumliche Zuordnungsfunktion 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 siehst du 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 siehst du 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 für räumliche Zuordnungen

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 oder räumliche Zuordnungen verwenden muss, um Funktionen bereitzustellen, sollte überprüft werden, ob die räumliche Zuordnung unterstützt wird, bevor Sie versuchen, sie zu verwenden. Wenn beispielsweise eine räumliche Zuordnung von Ihrer Mixed Reality-App erforderlich ist, sollte eine Meldung angezeigt werden, wenn ein Benutzer versucht, es 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 Erfahrung bereitstellen, die ähnlich ist wie bei der verfügbaren räumlichen Zuordnung. In jedem Fall kann diese API Ihre App beachten, wenn sie keine räumlichen Zuordnungsdaten erhält und auf die entsprechende Weise reagiert.

Um das aktuelle Gerät auf die Unterstützung für räumliche Zuordnungen zu überprüfen, stellen Sie zuerst sicher, dass der UWP-Vertrag auf Ebene 4 oder höher ist, und rufen Sie dann SpatialSurfaceObserver::IsSupported() auf. Hier erfahren Sie, wie Sie dies im Kontext des Codebeispiels "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. Wenn erforderlich, können Sie Ihr Projekt auf die Plattformversion 15063 zurücksetzen, 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 Stufe 4 ist, sollte die App fortfahren, als ob das Gerät in der Lage ist, räumliche Zuordnungen durchzuführen.

Anfordern des Zugriffs auf räumliche Zuordnungsdaten

Ihre App muss die Berechtigung zum Zugriff auf räumliche Zuordnungsdaten anfordern, bevor Sie versuchen, Oberflächenbeobachter zu erstellen. Nachfolgend finden Sie ein Beispiel, das auf unserem Surface Mapping-Codebeispiel basiert, und weitere Details finden Sie 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 eine oder mehrere Volumes beobachtet, die Sie in einem SpatialCoordinateSystem angeben. Verwenden Sie eine SpatialSurfaceObserver-Instanz , um auf Oberflächengitterdaten in Echtzeit 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 auf 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 Begrenzungsvolumen beobachtet wird. Hier beobachten wir ein Feld, das 20x20x5 Meter ist, zentriert am Ursprung des Koordinatensystems.

// 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 Begrenzungsvolumes festlegen.

Dies ist Pseudocode:

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

Es ist auch möglich, andere Begrenzungsformen zu verwenden , z. B. ein Ansichts frustum oder ein Begrenzungsfeld, das nicht achsenbündig ausgerichtet ist.

Dies ist Pseudocode:

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

Wenn Ihre App etwas anderes tun muss, wenn Oberflächenzuordnungsdaten nicht verfügbar sind, können Sie Code schreiben, um auf den Fall zu reagieren, in dem der SpatialPerceptionAccessStatus nicht zulässig ist – z. B. auf PCs mit immersiven Geräten, die angefügt sind, nicht zulässig, da diese Geräte keine Hardware für 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-Auflistung

Wenn der Oberflächenbeobachter erfolgreich erstellt wurde, können wir unsere Oberflächengittersammlung 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 gibt auch ein Pushmodell, das verfügbar ist, um Surface Mesh-Daten abzurufen. Sie können Ihre App kostenlos entwerfen, um nur das Pull-Modell zu verwenden, wenn Sie auswählen, in diesem Fall werden Sie alle so oft nach Daten abfragen , z. B. einmal pro Frame oder während eines bestimmten Zeitraums, z. B. während der Spieleinrichtung. Wenn ja, ist der obige Code das, was Sie benötigen.

In unserem Codebeispiel haben wir beschlossen, die Verwendung beider Modelle für pädagogische Zwecke zu veranschaulichen. Hier abonnieren wir ein Ereignis, um aktuelle Oberflächengitterdaten zu erhalten, 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 konfiguriert, um auf diese Ereignisse zu reagieren. Lassen Sie uns durchgehen, wie wir dies tun.

ANMERKUNG: Dies ist möglicherweise nicht die effiziente Möglichkeit für Ihre App, Gitterdaten zu behandeln. Dieser Code wird für Klarheit geschrieben und ist nicht optimiert.

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

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 später 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 Oberflächengittersammlung befinden, aber die nicht mehr in der Systemsammlung sind. Dazu müssen wir etwas tun, was wir gerade gezeigt haben, um Gitter hinzuzufügen und zu aktualisieren; wir führen eine Schleife in der App-Sammlung durch, und überprüfen Sie, ob die Guid , die wir haben, in der Systemsammlung vorhanden ist. Wenn es sich nicht in der Systemsammlung befindet, entfernen wir sie aus unseren.

Aus unserem Ereignishandler in AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

Die Implementierung der Gitterschneiden 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 Oberflächengitterdatenpuffern

Das Abrufen der Oberflächengitterinformationen war so einfach wie das Ziehen einer Datensammlung und der Verarbeitung von Updates zu dieser Sammlung. Jetzt werden wir ausführliche Informationen dazu erhalten, wie Sie die Daten verwenden können.

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

Im Codebeispiel wird der Prozess gestartet, wenn es Surface Mesh-Updates aus dem Ereignishandler empfängt, den wir im vorherigen Abschnitt beschrieben haben. Die wichtige Codezeile in dieser Funktion ist der Aufruf zum Aktualisieren des Oberflächengitters: Bis 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 sehen.

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, Gitterdatenverarbeitung und Rendering verarbeitet. Diese Gitter sind das, was der RealtimeSurfaceMeshRenderer tatsächlich eine Karte behält. Jeder hat einen Verweis auf das SpatialSurfaceMesh, von dem es stammt, sodass Sie es jederzeit verwenden können, um auf die Gittervertex- oder Indexpuffer zuzugreifen oder eine Transformation für das Gitter abzurufen. Jetzt kennzeichnen wir das Gitter als benötigt ein Update.

Von SurfaceMesh.cpp:

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

Beim nächsten Mal wird das Gitter aufgefordert, sich selbst zu zeichnen, wird das Flag zuerst überprüft. Wenn ein Update benötigt wird, werden die Vertex- 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 erwerben wir die rohen Datenpuffer:

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 der 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;
}

ANMERKUNG: Informationen zur in den vorherigen Codeausschnitten verwendeten CreateDirectXBuffer-Hilfsfunktion finden Sie im Beispiel zum Surface Mapping-Codebeispiel: SurfaceMesh.cpp, GetDataFromIBuffer.h. Jetzt ist die Erstellung der Geräteressource abgeschlossen, und das Gitter wird als geladen und bereit zum Aktualisieren und Rendern betrachtet.

Aktualisieren und Rendern von Oberflächengittern

Unsere SurfaceMesh-Klasse verfügt über eine spezielle Updatefunktion. Jede SpatialSurfaceMesh verfügt über eine eigene Transformation, und unser Beispiel verwendet das aktuelle Koordinatensystem für unsere SpatialStationaryReferenceFrame , um die Transformation zu erwerben. 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 Vorbereitungen vor dem Rendern der Auflistung aus. Wir richten die Shaderpipeline für die aktuelle Renderingkonfiguration ein, und wir richten die Eingabeassemierphase ein. Die Holographische Kamerahilfeklasse CameraResources.cpp hat bereits den Ansichts-/Projektionskonstantenpuffer jetzt eingerichtet.

Von RealtimeSurfaceMeshRenderer::Rendern:

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 getan ist, schleifen wir auf unsere Gitter und sagen jedem, sich selbst zu zeichnen. ANMERKUNG: Dieser Beispielcode ist nicht optimiert, um eine Art Frustum-Culling zu verwenden, 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 Strides und des Modelltransformationskonstantenpuffers verantwortlich. Wie bei der Drehwürfelvorlage in der Windows Holographic-App-Vorlage rendern wir in stereoskopischen Puffern mit Instance.

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 Beispiel für Surface Mapping bietet Code für das Nur-Rendern von Surface-Gitterdaten und für das Rendern von Surface Mesh-Daten auf dem Bildschirm. Welcher Pfad Sie auswählen – oder beides – hängt von Ihrer Anwendung ab. Wir durchlaufen beide Konfigurationen in diesem Dokument.

Rendern von Occlusionpuffern für holografische Effekte

Beginnen Sie mit dem Löschen der Renderzielansicht für die aktuelle virtuelle Kamera.

Von 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 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 nicht für das Zeichnen von Pixeln sorgt. 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 gegen den Surface-Zuordnungspuffer zeichnen. In diesem Codebeispiel rendern wir Pixel auf dem Würfel eine andere Farbe, wenn sie sich hinter einer Oberfläche befinden.

Von 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);
}

Anmerkung: Für unsere GatherDepthLess-Routine finden Sie im Beispiel für surface Mapping Code: SpecialEffectPixelShader.hlsl.

Rendern von Surface-Gitterdaten in die Anzeige

Wir können auch einfach die Oberflächengitter auf die Stereoanzeigepuffer zeichnen. Wir haben uns entschieden, vollständige Gesichter mit Beleuchtung zu zeichnen, aber Sie können Drahtrahmen zeichnen, Gitter vor dem Rendern verarbeiten, eine Texturzuordnung anwenden und so weiter.

Hier teilt unser Codebeispiel dem Gitterrenderer mit, die Auflistung zu zeichnen. Dieses Mal geben wir keinen Tiefenpass an, er fügt einen Pixel-Shader an und abgeschlossen die Renderingpipeline mithilfe der Ziele, 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);

Siehe auch