Rumslig mappning i DirectX

Anteckning

Den här artikeln handlar om äldre inbyggda WinRT-API:er. För nya interna appprojekt rekommenderar vi att du använder OpenXR-API:et.

Det här avsnittet beskriver hur du implementerar rumslig mappning i Din DirectX-app, inklusive en detaljerad förklaring av exempelprogrammet för rumslig mappning som paketeras med Universell Windows-plattform SDK.

Det här avsnittet använder kod från UWP-kodexemplet HolographicSpatialMapping .

Anteckning

Kodfragmenten i den här artikeln visar för närvarande användningen av C++/CX i stället för C++17-kompatibel C++/WinRT som används i C++-holografisk projektmall. Begreppen motsvarar ett C++/WinRT-projekt, men du måste översätta koden.

Stöd för enheter

Drag HoloLens (första generationen) HoloLens 2 Integrerande headset
Rumslig mappning ✔️ ✔️

Översikt över DirectX-utveckling

Intern programutveckling för rumslig mappning använder API:erna i namnområdet Windows.Perception.Spatial . Dessa API:er ger dig fullständig kontroll över funktionerna för rumslig mappning, på samma sätt som API:er för rumslig mappning exponeras av Unity.

API:er för uppfattning

De primära typerna för utveckling av rumslig mappning är följande:

  • SpatialSurfaceObserver innehåller information om ytor i programspecifika områden av utrymme nära användaren, i form av SpatialSurfaceInfo-objekt.
  • SpatialSurfaceInfo beskriver en enda extant rumslig yta, inklusive ett unikt ID, avgränsningsvolym och tidpunkt för den senaste ändringen. Den tillhandahåller en SpatialSurfaceMesh asynkront på begäran.
  • SpatialSurfaceMeshOptions innehåller parametrar som används för att anpassa SpatialSurfaceMesh-objekt som begärs från SpatialSurfaceInfo.
  • SpatialSurfaceMesh representerar nätdata för en enda rumslig yta. Data för hörnpositioner, hörnnormaler och triangelindex finns i medlemmen SpatialSurfaceMeshBuffer-objekt.
  • SpatialSurfaceMeshBuffer omsluter en enda typ av nätdata.

När du utvecklar ett program med dessa API:er ser det grundläggande programflödet ut så här (vilket visas i exempelprogrammet som beskrivs nedan):

  • Konfigurera SpatialSurfaceObserver
    • Anropa RequestAccessAsync för att säkerställa att användaren har gett behörighet för ditt program att använda enhetens funktioner för rumslig mappning.
    • Instansiera ett SpatialSurfaceObserver-objekt.
    • Anropa SetBoundingVolumes för att ange de områden i utrymmet där du vill ha information om rumsliga ytor. Du kan ändra dessa regioner i framtiden genom att anropa den här funktionen igen. Varje region anges med hjälp av SpatialBoundingVolume.
    • Registrera dig för händelsen ObservedSurfacesChanged , som utlöses när ny information är tillgänglig om de rumsliga ytorna i de områden i utrymmet som du har angett.
  • Process ObservedSurfacesChanged-händelser
    • I händelsehanteraren anropar du GetObservedSurfaces för att ta emot en karta över SpatialSurfaceInfo-objekt. Med den här kartan kan du uppdatera dina poster för vilka rumsliga ytor som finns i användarens miljö.
    • För varje SpatialSurfaceInfo-objekt kan du fråga TryGetBounds för att fastställa ytans rumsliga utrymmen, uttryckt i ett rumsligt koordinatsystem som du väljer.
    • Om du bestämmer dig för att begära mesh för en rumslig yta anropar du TryComputeLatestMeshAsync. Du kan ange alternativ för trianglars densitet och formatet för returnerade nätdata.
  • Ta emot och bearbeta nät
    • Varje anrop till TryComputeLatestMeshAsync returnerar asynkront ett SpatialSurfaceMesh-objekt.
    • Från det här objektet kan du komma åt inneslutna SpatialSurfaceMeshBuffer-objekt, vilket ger dig åtkomst till triangelindex, hörnpositioner och hörnnormaler för nätet om du begär dem. Dessa data kommer att vara i ett format som är direkt kompatibelt med Direct3D 11-API:er som används för återgivning av nät.
    • Härifrån kan ditt program eventuellt analysera eller bearbeta nätdata och använda dem för återgivning och fysikens raycasting och kollision.
    • En viktig detalj att tänka på är att du måste tillämpa en skala på näthörnpositionerna (till exempel i hörnskuggan som används för att återge näten) för att konvertera dem från de optimerade heltalsenheterna som de lagras i bufferten i till mätare. Du kan hämta den här skalan genom att anropa VertexPositionScale.

Felsökning

Genomgång av kodexempel för spatial mappning

Kodexemplet Holographic Spatial Mapping innehåller kod som du kan använda för att komma igång med att läsa in ytnät i din app, inklusive infrastruktur för hantering och återgivning av ytnät.

Nu går vi igenom hur du lägger till funktioner för ytmappning i din DirectX-app. Du kan lägga till den här koden i mallprojektet för Windows Holographic-appen , eller så kan du följa med genom att bläddra igenom kodexemplet som nämns ovan. Det här kodexemplet baseras på Windows Holographic-appmallen.

Konfigurera appen så att den använder spatialPerception-funktionen

Din app kan använda funktionen för rumslig mappning. Detta är nödvändigt eftersom det rumsliga nätet är en representation av användarens miljö, som kan betraktas som privata data. Deklarera den här funktionen i filen package.appxmanifest för din app. Här är ett exempel:

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

Funktionen kommer från uap2-namnområdet . Om du vill få åtkomst till det här namnområdet i manifestet inkluderar du det som ett xlmns-attribut i <package-elementet> . Här är ett exempel:

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

Sök efter stöd för funktioner för rumslig mappning

Windows Mixed Reality stöder en mängd olika enheter, inklusive enheter som inte stöder rumslig mappning. Om din app kan använda rumslig mappning, eller om den måste använda rumslig mappning, för att tillhandahålla funktioner, bör den kontrollera att rumslig mappning stöds innan den används. Om rumslig mappning till exempel krävs av din mixed reality-app bör den visa ett meddelande om detta om en användare försöker köra den på en enhet utan rumslig mappning. Eller så kan din app återge sin egen virtuella miljö i stället för användarens miljö, vilket ger en upplevelse som liknar vad som skulle hända om rumslig mappning var tillgänglig. I vilket fall som helst gör det här API:et att din app kan vara medveten om när den inte får spatial mappningsdata och svara på lämpligt sätt.

Om du vill kontrollera om den aktuella enheten har stöd för spatial mappning kontrollerar du först att UWP-kontraktet är på nivå 4 eller högre och anropar sedan SpatialSurfaceObserver::IsSupported(). Så här gör du i kontexten för kodexemplet Holographic Spatial Mapping . Supporten kontrolleras precis innan du begär åtkomst.

SpatialSurfaceObserver::IsSupported()-API:et är tillgängligt från och med SDK version 15063. Om det behövs kan du omdirigera projektet till plattformsversion 15063 innan du använder det här API:et.

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

När UWP-kontraktet är mindre än nivå 4 bör appen fortsätta som om enheten kan utföra rumslig mappning.

Begär åtkomst till spatial mappningsdata

Din app måste begära behörighet att komma åt spatial mappningsdata innan den försöker skapa några ytobservatörer. Här är ett exempel som baseras på vårt kodexempel för Surface Mapping, med mer information längre fram på den här sidan:

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

Skapa en ytobservatör

Namnområdet Windows::P erception::Spatial::Surfaces innehåller klassen SpatialSurfaceObserver, som observerar en eller flera volymer som du anger i ett SpatialCoordinateSystem. Använd en SpatialSurfaceObserver-instans för att komma åt surface mesh-data i realtid.

Från 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;

Som du antecknade i föregående avsnitt måste du begära åtkomst till spatial mappningsdata innan din app kan använda dem. Den här åtkomsten beviljas automatiskt på 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();

Därefter måste du konfigurera ytövervakaren för att observera en specifik avgränsningsvolym. Här ser vi en ruta som är 20x20x5 meter, centrerad efter koordinatsystemets ursprung.

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

Du kan ange flera avgränsningsvolymer i stället.

Det här är pseudokod:

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

Det är också möjligt att använda andra avgränsningsformer , till exempel en vy frustum eller en avgränsningsruta som inte är axeljusterad.

Det här är pseudokod:

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

Om din app behöver göra något annorlunda när data för ytmappning inte är tillgängliga kan du skriva kod för att svara på det fall där SpatialPerceptionAccessStatus inte är Tillåten . Den tillåts till exempel inte på datorer med integrerande enheter som är anslutna eftersom enheterna inte har någon maskinvara för rumslig mappning. För dessa enheter bör du i stället förlita dig på den rumsliga fasen för information om användarens miljö och enhetskonfiguration.

Initiera och uppdatera samlingen med ytnät

Om ytobservatören har skapats kan vi fortsätta att initiera samlingen med ytnät. Här använder vi pull-modell-API:et för att hämta den aktuella uppsättningen observerade ytor direkt:

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

Det finns också en push-modell för att hämta surface mesh-data. Du kan utforma din app så att den endast använder pull-modellen om du väljer det, i så fall kommer du att söka efter data så ofta – till exempel en gång per bildruta – eller under en viss tidsperiod, till exempel under spelkonfigurationen. I så fall är koden ovan vad du behöver.

I vårt kodexempel valde vi att demonstrera användningen av båda modellerna i pedagogiska syften. Här prenumererar vi på en händelse för att få aktuella surface mesh-data när systemet känner igen en ändring.

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

Vårt kodexempel är också konfigurerat för att svara på dessa händelser. Låt oss gå igenom hur vi gör detta.

NOT: Det här kanske inte är det mest effektiva sättet för din app att hantera nätdata. Den här koden är skriven för tydlighetens skull och är inte optimerad.

Surface Mesh-data tillhandahålls i en skrivskyddad karta som lagrar SpatialSurfaceInfo-objekt med Platform::Guids som nyckelvärden.

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

För att bearbeta dessa data letar vi först efter nyckelvärden som inte finns i vår samling. Information om hur data lagras i vår exempelapp visas senare i det här avsnittet.

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

Vi måste också ta bort ytnät som finns i vår surface mesh-samling, men som inte finns i systemsamlingen längre. För att göra det måste vi göra något som liknar motsatsen till det vi just visade för att lägga till och uppdatera nät; Vi loopar i appens samling och kontrollerar om det GUID vi har finns i systemsamlingen. Om den inte finns i systemsamlingen tar vi bort den från vår.

Från vår händelsehanterare i AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

Implementeringen av nätrensning i 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);
    }
}

Hämta och använda databuffertar för surface mesh

Det var lika enkelt att hämta information om surface mesh som att hämta en datainsamling och bearbeta uppdateringar av samlingen. Nu ska vi gå in närmare på hur du kan använda data.

I vårt kodexempel valde vi att använda ytnäten för återgivning. Det här är ett vanligt scenario för att occludera hologram bakom verkliga ytor. Du kan också rendera näten, eller rendera bearbetade versioner av dem, för att visa användaren vilka områden i rummet som genomsöks innan du börjar tillhandahålla app- eller spelfunktioner.

Kodexemplet startar processen när den tar emot surface mesh-uppdateringar från händelsehanteraren som vi beskrev i föregående avsnitt. Den viktiga kodraden i den här funktionen är anropet för att uppdatera ytnätet: vid den här tiden har vi redan bearbetat nätinformationen och vi är på väg att hämta hörn- och indexdata för användning som vi anser lämpligt.

Från 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());
}

Vår exempelkod är utformad så att en dataklass, SurfaceMesh, hanterar nätdatabearbetning och återgivning. Dessa nät är vad RealtimeSurfaceMeshRenderer faktiskt håller en karta över. Var och en har en referens till SpatialSurfaceMesh den kom från, så du kan använda den när som helst du behöver komma åt näthörnet eller indexbuffertarna, eller få en transformering för nätet. För tillfället flaggar vi nätet som behöver en uppdatering.

Från SurfaceMesh.cpp:

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

Nästa gång nätet uppmanas att rita sig själv kontrollerar det flaggan först. Om en uppdatering behövs uppdateras hörn- och indexbuffertarna på GPU:n.

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

Först hämtar vi rådatabuffertar:

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;

Sedan skapar vi Direct3D-enhetsbuffertar med de nätdata som tillhandahålls av 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;
}

NOT: Hjälpfunktionen CreateDirectXBuffer som användes i föregående kodfragment finns i kodexemplet Surface Mapping: SurfaceMesh.cpp, GetDataFromIBuffer.h. Nu har enhetsresursen skapats och nätet anses vara inläst och redo för uppdatering och återgivning.

Uppdatera och rendera ytnät

Vår SurfaceMesh-klass har en specialiserad uppdateringsfunktion. Varje SpatialSurfaceMesh har en egen transformering och vårt exempel använder det aktuella koordinatsystemet för vår SpatialStationaryReferenceFrame för att hämta transformeringen. Sedan uppdateras modellkonstantbufferten på GPU:n.

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

När det är dags att rendera ytnät utför vi lite förberedelsearbete innan vi återger samlingen. Vi konfigurerade skuggningspipelinen för den aktuella renderingskonfigurationen och konfigurerade indatamonterarens fas. Den holografiska kamerahjälpklassen CameraResources.cpp har redan konfigurerat konstantbufferten för visning/projektion vid det här laget.

Från 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
        );
}

När detta är klart loopar vi på våra nät och säger åt var och en att rita sig själv. NOT: Den här exempelkoden är inte optimerad för att använda någon form av frustum-utslaktning, men du bör inkludera den här funktionen i din 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 enskilda näten ansvarar för att konfigurera hörn och indexbuffert, steg och modelltransformeringskonstantbuffert. Precis som med den snurrande kuben i Windows Holographic-appmallen renderas vi till stereoskopiska buffertar med hjälp av instancing.

Från 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.
    );

Renderingsalternativ med Surface Mapping

Kodexemplet för Surface Mapping innehåller kod för återgivning endast av surface mesh-data och för återgivning på skärmen av surface mesh-data. Vilken sökväg du väljer – eller båda – beror på ditt program. Vi går igenom båda konfigurationerna i det här dokumentet.

Återgivning av ocklusionsbuffertar för holografisk effekt

Börja med att rensa återgivningsmålvyn för den aktuella virtuella kameran.

Från AppMain.cpp:

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

Det här är ett "förrenderingspass". Här skapar vi en ocklusionsbuffert genom att be nätåtergivningen att endast återge djup. I den här konfigurationen kopplar vi inte en återgivningsmålvy och mesh-renderaren anger pixelskuggningssteget till nullptr så att GPU:n inte bryr sig om att rita pixlar. Geometrin rastreras till djupbufferten och grafikpipelinen stoppas där.

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

Vi kan rita hologram med ett extra djuptest mot ocklusionsbufferten surface mapping. I det här kodexemplet renderar vi bildpunkter på kuben en annan färg om de finns bakom en yta.

Från 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()
    );

Baserat på kod från 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);
}

Not: Vår GatherDepthLess-rutin finns i kodexemplet Surface Mapping: SpecialEffectPixelShader.hlsl.

Återgivning av surface mesh-data till skärmen

Vi kan också rita ytnäten till stereodisplaybuffertarna. Vi valde att rita hela ansikten med belysning, men du kan rita trådram, bearbeta nät före återgivning, tillämpa en strukturkarta och så vidare.

Här instruerar vårt kodexempel mesh-renderaren att rita samlingen. Den här gången anger vi inte ett pass med endast djup, utan kopplar en pixelskuggare och slutför renderingspipelinen med hjälp av de mål som vi har angett för den aktuella virtuella kameran.

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

Se även