Ruimtelijke toewijzing in DirectX

Notitie

Dit artikel heeft betrekking op de verouderde Systeemeigen WinRT-API's. Voor nieuwe systeemeigen app-projecten raden we u aan de OpenXR-API te gebruiken.

In dit onderwerp wordt beschreven hoe u ruimtelijke toewijzing implementeert in uw DirectX-app, inclusief een gedetailleerde uitleg van de voorbeeldtoepassing voor ruimtelijke toewijzing die is verpakt met de Universeel Windows-platform SDK.

In dit onderwerp wordt code uit het CODEvoorbeeld HolographicSpatialMapping UWP gebruikt.

Notitie

De codefragmenten in dit artikel demonstreren momenteel het gebruik van C++/CX in plaats van C++17-compatibele C++/WinRT, zoals wordt gebruikt in de C++ holografische projectsjabloon. De concepten zijn gelijkwaardig voor een C++/WinRT-project, maar u moet de code vertalen.

Ondersteuning voor apparaten

Functie HoloLens (1e generatie) HoloLens 2 Insluitende headsets
Ruimtelijke toewijzing ✔️ ✔️

Overzicht van DirectX-ontwikkeling

Systeemeigen toepassingsontwikkeling voor ruimtelijke toewijzing maakt gebruik van de API's in de Naamruimte Windows.Perception.Spatial . Deze API's bieden u volledige controle over de functionaliteit van ruimtelijke toewijzing, op dezelfde manier als de API's voor ruimtelijke toewijzing worden weergegeven door Unity.

Perception-API's

De primaire typen voor ruimtelijke toewijzingsontwikkeling zijn als volgt:

  • SpatialSurfaceObserver biedt informatie over oppervlakken in door de toepassing opgegeven ruimtegebieden in de buurt van de gebruiker, in de vorm van SpatialSurfaceInfo-objecten.
  • SpatialSurfaceInfo beschrijft één extant ruimtelijk oppervlak, inclusief een unieke id, begrenzingsvolume en tijd van laatste wijziging. Het biedt een SpatialSurfaceMesh asynchroon op verzoek.
  • SpatialSurfaceMeshOptions bevat parameters die worden gebruikt om de SpatialSurfaceMesh-objecten aan te passen die zijn aangevraagd bij SpatialSurfaceInfo.
  • SpatialSurfaceMesh vertegenwoordigt de mesh-gegevens voor één ruimtelijk oppervlak. De gegevens voor hoekpuntposities, hoekpuntnormten en driehoekindexen bevinden zich in lid SpatialSurfaceMeshBuffer-objecten.
  • SpatialSurfaceMeshBuffer verpakt één type mesh-gegevens.

Wanneer u een toepassing ontwikkelt met behulp van deze API's, ziet uw basisprogrammastroom er als volgt uit (zoals in de voorbeeldtoepassing die hieronder wordt beschreven):

  • Uw SpatialSurfaceObserver instellen
    • Roep RequestAccessAsync aan om ervoor te zorgen dat de gebruiker toestemming heeft gegeven voor uw toepassing om de mogelijkheden voor ruimtelijke toewijzing van het apparaat te gebruiken.
    • Instantieer een SpatialSurfaceObserver-object.
    • Roep SetBoundingVolumes aan om de ruimtegebieden op te geven waarin u informatie wilt over ruimtelijke oppervlakken. U kunt deze regio's in de toekomst wijzigen door deze functie opnieuw aan te roepen. Elke regio wordt opgegeven met behulp van een SpatialBoundingVolume.
    • Registreer u voor de gebeurtenis ObservedSurfacesChanged , die wordt geactiveerd wanneer er nieuwe informatie beschikbaar is over de ruimtelijke oppervlakken in de gebieden van de ruimte die u hebt opgegeven.
  • Process ObservedSurfacesChanged-gebeurtenissen
    • Roep in uw gebeurtenis-handler GetObservedSurfaces aan om een kaart met SpatialSurfaceInfo-objecten te ontvangen. Met deze kaart kunt u uw records bijwerken van welke ruimtelijke oppervlakten er bestaan in de omgeving van de gebruiker.
    • Voor elk SpatialSurfaceInfo-object kunt u een query uitvoeren op TryGetBounds om de ruimtelijke gebieden van het oppervlak te bepalen, uitgedrukt in een ruimtelijk coördinaatsysteem van uw keuze.
    • Als u besluit om te vragen, roept u mesh aan voor een ruimtelijk oppervlak, roept u TryComputeLatestMeshAsync aan. U kunt opties opgeven voor de dichtheid van driehoeken en de indeling van de geretourneerde mesh-gegevens.
  • Mesh ontvangen en verwerken
    • Elke aanroep naar TryComputeLatestMeshAsync retourneert asynchroon één SpatialSurfaceMesh-object.
    • Vanuit dit object hebt u toegang tot de ingesloten SpatialSurfaceMeshBuffer-objecten, waarmee u toegang krijgt tot de driehoekindexen, hoekpuntposities en hoekpuntnormaten van het mesh als u deze aanvraagt. Deze gegevens hebben een indeling die rechtstreeks compatibel is met de Direct3D 11-API's die worden gebruikt voor rendering-meshes.
    • Vanaf hier kan uw toepassing eventueel de mesh-gegevens analyseren of verwerken en deze gebruiken voor het weergeven en fysica vanraycasting en botsingen.
    • Een belangrijk detail is dat u een schaal moet toepassen op de mesh-hoekpuntposities (bijvoorbeeld in de hoekpunt-shader die wordt gebruikt voor het weergeven van de meshes), om ze te converteren van de geoptimaliseerde gehele getallen waarin ze in de buffer worden opgeslagen, naar meters. U kunt deze schaal ophalen door VertexPositionScale aan te roepen.

Problemen oplossen

Overzicht van voorbeeld van code voor ruimtelijke toewijzing

Het codevoorbeeld Holographic Spatial Mapping bevat code die u kunt gebruiken om surface-meshes in uw app te laden, inclusief infrastructuur voor het beheren en weergeven van oppervlakte-meshes.

Nu wordt uitgelegd hoe u surface-mapping-mogelijkheden toevoegt aan uw DirectX-app. U kunt deze code toevoegen aan uw sjabloonproject voor de Windows Holographic-app of u kunt deze volgen door het bovenstaande codevoorbeeld te bekijken. Dit codevoorbeeld is gebaseerd op de sjabloon voor de Windows Holographic-app.

Uw app instellen voor het gebruik van de mogelijkheid spatialPerception

Uw app kan gebruikmaken van de mogelijkheid voor ruimtelijke toewijzing. Dit is nodig omdat het ruimtelijke mesh een weergave is van de omgeving van de gebruiker, die kan worden beschouwd als persoonlijke gegevens. Declareer deze mogelijkheid in het bestand package.appxmanifest voor uw app. Hier volgt een voorbeeld:

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

De mogelijkheid is afkomstig van de uap2-naamruimte . Als u toegang wilt krijgen tot deze naamruimte in uw manifest, neemt u deze op als een xlmns-kenmerk in het <element Pakket> . Hier volgt een voorbeeld:

<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"
    >

Controleren op ondersteuning voor functie voor ruimtelijke toewijzing

Windows Mixed Reality ondersteunt een breed scala aan apparaten, waaronder apparaten, die geen ruimtelijke toewijzing ondersteunen. Als uw app ruimtelijke toewijzing kan gebruiken of ruimtelijke toewijzing moet gebruiken om functionaliteit te bieden, moet deze controleren of ruimtelijke toewijzing wordt ondersteund voordat u deze probeert te gebruiken. Als bijvoorbeeld ruimtelijke toewijzing is vereist voor uw mixed reality-app, moet er een bericht worden weergegeven dat dit effect heeft als een gebruiker het probeert uit te voeren op een apparaat zonder ruimtelijke toewijzing. Of uw app kan een eigen virtuele omgeving weergeven in plaats van de omgeving van de gebruiker, wat een ervaring biedt die vergelijkbaar is met wat er zou gebeuren als ruimtelijke toewijzing beschikbaar zou zijn. In elk geval kan met deze API uw app op de hoogte zijn wanneer er geen ruimtelijke toewijzingsgegevens worden opgehaald en op de juiste manier worden gereageerd.

Als u het huidige apparaat wilt controleren op ondersteuning voor ruimtelijke toewijzing, moet u eerst controleren of het UWP-contract op niveau 4 of hoger is en vervolgens SpatialSurfaceObserver::IsSupported() aanroept. U doet dit als volgt in de context van het codevoorbeeld Holographic Spatial Mapping . Ondersteuning wordt vlak voordat u toegang aanvraagt, gecontroleerd.

De SpatialSurfaceObserver::IsSupported() API is beschikbaar vanaf SDK-versie 15063. Retarget uw project indien nodig naar platformversie 15063 voordat u deze API gebruikt.

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

Wanneer het UWP-contract kleiner is dan niveau 4, moet de app doorgaan alsof het apparaat ruimtelijk kan toewijzen.

Toegang tot ruimtelijke toewijzingsgegevens aanvragen

Uw app moet toestemming vragen voor toegang tot ruimtelijke toewijzingsgegevens voordat u eventuele surface-waarnemers probeert te maken. Hier volgt een voorbeeld op basis van ons codevoorbeeld voor Surface Mapping, met meer informatie die verderop op deze pagina wordt gegeven:

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.
    }
}

Een surface-waarnemer maken

De naamruimte Windows::P erception::Spatial::Surfaces bevat de klasse SpatialSurfaceObserver , die een of meer volumes observeert die u opgeeft in een SpatialCoordinateSystem. Gebruik een SpatialSurfaceObserver-exemplaar om in realtime toegang te krijgen tot surface mesh-gegevens.

Vanuit 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;

Zoals vermeld in de vorige sectie, moet u toegang tot ruimtelijke toewijzingsgegevens aanvragen voordat uw app deze kan gebruiken. Deze toegang wordt automatisch verleend op de HoloLens.

// 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();

Vervolgens moet u de surface-waarnemer configureren om een specifiek begrenzingsvolume te observeren. Hier observeren we een doos die 20x20x5 meter is, gecentreerd aan de oorsprong van het coördinaatsysteem.

// 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);

U kunt in plaats daarvan meerdere begrenzingsvolumes instellen.

Dit is pseudocode:

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

Het is ook mogelijk om andere begrenzingsshapes te gebruiken, zoals een weergave frustum of een begrenzingsvak dat niet is uitgelijnd.

Dit is pseudocode:

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

Als uw app iets anders moet doen wanneer surface mapping-gegevens niet beschikbaar zijn, kunt u code schrijven om te reageren op de situatie waarin SpatialPerceptionAccessStatus niet is toegestaan . Dit is bijvoorbeeld niet toegestaan op pc's met insluitende apparaten die zijn gekoppeld omdat deze apparaten geen hardware voor ruimtelijke toewijzing hebben. Voor deze apparaten moet u in plaats daarvan afhankelijk zijn van de ruimtelijke fase voor informatie over de omgeving en apparaatconfiguratie van de gebruiker.

De surface mesh-verzameling initialiseren en bijwerken

Als de surface-waarnemer is gemaakt, kunnen we onze surface mesh-verzameling blijven initialiseren. Hier gebruiken we de pull-model-API om de huidige set waargenomen oppervlakken direct op te halen:

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

Er is ook een pushmodel beschikbaar om surface mesh-gegevens op te halen. U kunt uw app ontwerpen om alleen het pull-model te gebruiken als u kiest. In dat geval pollt u zo vaak gegevens - bijvoorbeeld eenmaal per frame - of tijdens een specifieke periode, zoals tijdens het instellen van de game. Zo ja, dan is de bovenstaande code wat u nodig hebt.

In ons codevoorbeeld hebben we ervoor gekozen om het gebruik van beide modellen voor educatieve doeleinden te demonstreren. Hier abonneren we ons op een gebeurtenis om actuele surface mesh-gegevens te ontvangen wanneer het systeem een wijziging herkent.

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

Ons codevoorbeeld is ook geconfigureerd om te reageren op deze gebeurtenissen. Laten we eens doornemen hoe we dit doen.

NOTITIE: Dit is mogelijk niet de meest efficiënte manier voor uw app om mesh-gegevens te verwerken. Deze code is geschreven voor duidelijkheid en is niet geoptimaliseerd.

De surface mesh-gegevens worden verstrekt in een alleen-lezen kaart waarin SpatialSurfaceInfo-objecten worden opgeslagen met Platform::Guids als sleutelwaarden.

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

Om deze gegevens te verwerken, zoeken we eerst naar sleutelwaarden die niet in onze verzameling voorkomen. Verderop in dit onderwerp vindt u meer informatie over hoe de gegevens worden opgeslagen in onze voorbeeld-app.

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

We moeten ook oppervlaktenetten verwijderen die zich in onze surface mesh collectie bevinden, maar die zich niet meer in de systeemverzameling bevinden. Om dit te doen, moeten we iets doen dat lijkt op het tegenovergestelde van wat we net hebben laten zien voor het toevoegen en bijwerken van meshes; we herhalen de verzameling van onze app en controleren of de GUID zich in de systeemverzameling bevindt. Als het niet in de systeemverzameling staat, verwijderen we deze uit de onze.

Vanuit onze gebeurtenis-handler in AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

De implementatie van mesh-pruning 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);
    }
}

Surface Mesh-gegevensbuffers verkrijgen en gebruiken

Het ophalen van de surface mesh-informatie was net zo eenvoudig als het ophalen van gegevensverzameling en het verwerken van updates voor die verzameling. Nu gaan we dieper in op de wijze waarop u de gegevens kunt gebruiken.

In ons codevoorbeeld hebben we ervoor gekozen om de surface meshes te gebruiken voor rendering. Dit is een veelvoorkomend scenario voor het occluderen van hologrammen achter echte oppervlakken. U kunt ook de meshes weergeven of verwerkte versies ervan weergeven om de gebruiker te laten zien welke gebieden van de ruimte worden gescand voordat u app- of gamefunctionaliteit gaat bieden.

Het codevoorbeeld start het proces wanneer het surface mesh-updates ontvangt van de gebeurtenis-handler die we in de vorige sectie hebben beschreven. De belangrijke coderegel in deze functie is de aanroep om de surface mesh bij te werken: tegen deze tijd hebben we de mesh-informatie al verwerkt, en we staan op het punt om het hoekpunt en de indexgegevens op te halen die naar wens kunnen worden gebruikt.

Vanuit 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());
}

Onze voorbeeldcode is zo ontworpen dat een gegevensklasse, SurfaceMesh, mesh-gegevensverwerking en -rendering verwerkt. Deze meshes zijn waar de RealtimeSurfaceMeshRenderer eigenlijk een kaart van houdt. Elk exemplaar heeft een verwijzing naar de SpatialSurfaceMesh waaruit het afkomstig is, zodat u deze altijd kunt gebruiken wanneer u toegang nodig hebt tot het mesh-hoekpunt of de indexbuffers, of een transformatie voor het mesh krijgt. Voorlopig markeren we de mesh als er een update nodig is.

Van SurfaceMesh.cpp:

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

De volgende keer dat de mesh wordt gevraagd zichzelf te tekenen, wordt de vlag eerst gecontroleerd. Als er een update nodig is, worden het hoekpunt en de indexbuffers bijgewerkt op de GPU.

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

Eerst verkrijgen we de onbewerkte gegevensbuffers:

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;

Vervolgens maken we Direct3D-apparaatbuffers met de mesh-gegevens die worden geleverd door de HoloLens:

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

NOTITIE: Zie het codevoorbeeld voor Surface Mapping: SurfaceMesh.cpp, GetDataFromIBuffer.h voor de helperfunctie CreateDirectXBuffer die in het vorige codefragment wordt gebruikt. Het maken van de apparaatresource is nu voltooid en de mesh wordt beschouwd als geladen en gereed voor bijwerken en renderen.

Surface meshes bijwerken en weergeven

Onze SurfaceMesh-klasse heeft een gespecialiseerde updatefunctie. Elke SpatialSurfaceMesh heeft een eigen transformatie en ons voorbeeld maakt gebruik van het huidige coördinaatsysteem voor ons SpatialStationaryReferenceFrame om de transformatie te verkrijgen. Vervolgens wordt de modelconstante buffer op de GPU bijgewerkt.

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

Wanneer het tijd is om surface meshes weer te geven, doen we wat voorbereidingswerk voordat we de verzameling weergeven. We hebben de shader-pijplijn ingesteld voor de huidige renderingconfiguratie en we hebben de fase van de invoerassembly ingesteld. De holografische camerahulpklasse CameraResources.cpp heeft nu al de constante buffer voor weergave/projectie ingesteld.

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

Zodra dit is gebeurd, herhalen we onze meshes en vertellen we elk om zichzelf te tekenen. NOTITIE: Deze voorbeeldcode is niet geoptimaliseerd voor het gebruik van een soort frustum-ruiming, maar u moet deze functie opnemen in uw app.

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

De afzonderlijke meshes zijn verantwoordelijk voor het instellen van de hoekpunt- en indexbuffer, stride en modeltransformatieconstante buffer. Net als bij de draaiende kubus in de sjabloon van de Windows Holographic-app worden we weergegeven in stereoscopische buffers met behulp van instancing.

Van 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.
    );

Weergaveopties met Surface-toewijzing

Het codevoorbeeld voor Surface Mapping biedt code voor het weergeven van alleen-occlusie van surface mesh-gegevens en voor het weergeven op het scherm van surface mesh-gegevens. Welk pad u kiest, of beide, is afhankelijk van uw toepassing. We doorlopen beide configuraties in dit document.

Occlusiebuffers weergeven voor holografisch effect

Begin met het wissen van de weergavedoelweergave voor de huidige virtuele camera.

Vanuit AppMain.cpp:

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

Dit is een 'pre-rendering'-pas. Hier maken we een occlusiebuffer door de mesh-renderer te vragen om alleen diepte weer te geven. In deze configuratie koppelen we geen renderdoelweergave en stelt de mesh-renderer de pixel-shaderfase in op nullptr , zodat de GPU geen moeite heeft om pixels te tekenen. De geometrie wordt gerasterd naar de dieptebuffer en de grafische pijplijn stopt daar.

// 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);

We kunnen hologrammen tekenen met een extra dieptetest op basis van de occlusiebuffer van Surface Mapping. In dit codevoorbeeld geven we pixels op de kubus een andere kleur weer als ze zich achter een oppervlak bevinden.

Vanuit 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()
    );

Op basis van code van SpecialEffect PixelShader.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);
}

Notitie: Zie het codevoorbeeld voor Surface Mapping: SpecialEffect PixelShader.hlsl voor onze GatherDepthLess-routine .

Surface mesh-gegevens weergeven op het scherm

We kunnen ook gewoon de oppervlaktemesten tekenen op de stereoschermbuffers. We hebben ervoor gekozen om volledige gezichten te tekenen met verlichting, maar u bent vrij om draadmodel te tekenen, meshes te verwerken voordat ze worden weergegeven, een patroonkaart toe te passen, enzovoort.

Hier vertelt ons codevoorbeeld de mesh-renderer om de verzameling te tekenen. Deze keer geven we geen dieptepas op, wordt er een pixel-shader gekoppeld en wordt de renderingpijplijn voltooid met behulp van de doelen die we hebben opgegeven voor de huidige virtuele camera.

// 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);

Zie ook