Mapping spaziale in DirectX

Nota

Questo articolo riguarda le API native WinRT legacy. Per nuovi progetti di app native, è consigliabile usare l'API OpenXR.

Questo argomento descrive come implementare il mapping spaziale nell'app DirectX, inclusa una spiegazione dettagliata dell'applicazione di esempio di mapping spaziale in pacchetto con l'SDK di piattaforma UWP (Universal Windows Platform).

Questo argomento usa il codice dell'esempio di codice UWP HolographicSpatialMapping .

Nota

I frammenti di codice in questo articolo illustrano attualmente l'uso di C++/CX anziché C++17 conforme a C++/WinRT come usato nel modello di progetto olografico C++. I concetti sono equivalenti per un progetto C++/WinRT, anche se sarà necessario tradurre il codice.

Supporto di dispositivi

Funzionalità HoloLens (prima generazione) HoloLens 2 Visori VR immersive
Mapping spaziale ✔️ ✔️

Panoramica dello sviluppo DirectX

Lo sviluppo di applicazioni native per il mapping spaziale usa le API nello spazio dei nomi Windows.Perception.Spatial . Queste API offrono il controllo completo della funzionalità di mapping spaziale, nello stesso modo in cui le API di mapping spaziale vengono esposte da Unity.

API di percezione

I tipi principali forniti per lo sviluppo di mapping spaziale sono i seguenti:

  • SpatialSurfaceObserver fornisce informazioni sulle superfici nelle aree specificate dall'applicazione dello spazio vicino all'utente, sotto forma di oggetti SpatialSurfaceInfo.
  • SpatialSurfaceInfo descrive una singola superficie spaziale esistente, tra cui un ID univoco, un volume di associazione e un'ora dell'ultima modifica. Fornisce un oggetto SpatialSurfaceMesh in modo asincrono su richiesta.
  • SpatialSurfaceMeshOptions contiene parametri usati per personalizzare gli oggetti SpatialSurfaceMesh richiesti da SpatialSurfaceInfo.
  • SpatialSurfaceMesh rappresenta i dati mesh per una singola superficie spaziale. I dati per le posizioni dei vertici, le normali dei vertici e gli indici triangoli sono contenuti negli oggetti SpatialSurfaceMeshBuffer.
  • SpatialSurfaceMeshBuffer esegue il wrapping di un singolo tipo di dati mesh.

Quando si sviluppa un'applicazione usando queste API, il flusso di programma di base sarà simile al seguente (come illustrato nell'applicazione di esempio descritta di seguito):

  • Configurare il server SpatialSurfaceOb
    • Chiamare RequestAccessAsync per assicurarsi che l'utente abbia concesso l'autorizzazione per l'applicazione per usare le funzionalità di mapping spaziale del dispositivo.
    • Creare un'istanza di un oggetto SpatialSurfaceObserver.
    • Chiamare SetBoundingVolumes per specificare le aree dello spazio in cui si desiderano informazioni sulle superfici spaziali. È possibile modificare queste aree in futuro chiamando nuovamente questa funzione. Ogni area viene specificata usando un oggetto SpatialBoundingVolume.
    • Registrare l'evento ObservedSurfacesChanged , che verrà generato ogni volta che sono disponibili nuove informazioni sulle superfici spaziali nelle aree dello spazio specificato.
  • Processi osservatiSurfacesChanged eventi
    • Nel gestore eventi chiamare GetObservedSurfaces per ricevere una mappa di oggetti SpatialSurfaceInfo. Usando questa mappa, è possibile aggiornare i record delle superfici spaziali presenti nell'ambiente dell'utente.
    • Per ogni oggetto SpatialSurfaceInfo, è possibile eseguire query su TryGetBounds per determinare gli extent spaziali della superficie, espressi in un sistema di coordinate spaziali della scelta.
    • Se si decide di richiedere, mesh per una superficie spaziale, chiamare TryComputeLatestMeshAsync. È possibile fornire opzioni che specificano la densità dei triangoli e il formato dei dati mesh restituiti.
  • Mesh di ricezione e elaborazione
    • Ogni chiamata a TryComputeLatestMeshAsync restituirà in modo asincrono un oggetto SpatialSurfaceMesh.
    • Da questo oggetto è possibile accedere agli oggetti SpatialSurfaceMeshBuffer contenuti, che consentono di accedere agli indici triangoli, alle posizioni dei vertici e ai normali vertici della mesh se richiesti. Questi dati saranno in un formato direttamente compatibile con le API Direct3D 11 usate per il rendering di mesh.
    • Da qui l'applicazione può analizzare o elaborare facoltativamente i dati mesh e usarli per il rendering e il raycasting e la collisione fisica.
    • Un dettaglio importante da notare è che è necessario applicare una scala alle posizioni del vertice mesh (ad esempio nel vertex shader usato per il rendering delle mesh), per convertirle dalle unità integer ottimizzate in cui vengono archiviate nel buffer, ai metri. È possibile recuperare questa scala chiamando VertexPositionScale.

Risoluzione dei problemi

Procedura dettagliata dell'esempio di codice mapping spaziale

L'esempio di codice Di mapping spaziale holographic include il codice che è possibile usare per iniziare a caricare le mesh di superficie nell'app, tra cui l'infrastruttura per la gestione e il rendering delle mesh di superficie.

Ora viene illustrato come aggiungere funzionalità di mapping della superficie all'app DirectX. È possibile aggiungere questo codice al progetto di modello di app Holographic di Windows oppure seguire la procedura passando attraverso l'esempio di codice indicato in precedenza. Questo esempio di codice si basa sul modello di app Windows Holographic.

Configurare l'app per usare la funzionalità spatialPerception

L'app può usare la funzionalità di mapping spaziale. Questa operazione è necessaria perché la mesh spaziale è una rappresentazione dell'ambiente dell'utente, che può essere considerata dati privati. Dichiarare questa funzionalità nel file package.appxmanifest per l'app. Ecco un esempio:

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

La funzionalità proviene dallo spazio dei nomi uap2 . Per ottenere l'accesso a questo spazio dei nomi nel manifesto, includerlo come attributo xlmns nell'elemento <Package> . Ecco un esempio:

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

Verificare il supporto delle funzionalità di mapping spaziale

Windows Mixed Reality supporta un'ampia gamma di dispositivi, inclusi i dispositivi, che non supportano il mapping spaziale. Se l'app può usare il mapping spaziale o deve usare il mapping spaziale, per fornire funzionalità, deve verificare che il mapping spaziale sia supportato prima di provare a usarlo. Ad esempio, se il mapping spaziale è richiesto dall'app di realtà mista, deve visualizzare un messaggio a tale effetto se un utente prova a eseguirlo in un dispositivo senza mapping spaziale. In alternativa, l'app può eseguire il rendering del proprio ambiente virtuale al posto dell'ambiente dell'utente, fornendo un'esperienza simile a quella che accadrebbe se il mapping spaziale fosse disponibile. In qualsiasi caso, questa API consente all'app di essere consapevoli quando non otterrà i dati di mapping spaziale e risponderà in modo appropriato.

Per controllare il supporto del mapping spaziale del dispositivo corrente, assicurarsi che il contratto UWP sia a livello 4 o superiore e quindi chiamare SpatialSurfaceObserver::IsSupported(). Ecco come eseguire questa operazione nel contesto dell'esempio di codice mapping spaziale Holographic . Il supporto viene controllato appena prima di richiedere l'accesso.

L'API SpatialSurfaceObserver::IsSupported() è disponibile a partire da SDK versione 15063. Se necessario, ritargete il progetto alla versione 15063 della piattaforma prima di usare questa API.

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

Quando il contratto UWP è inferiore al livello 4, l'app deve procedere come se il dispositivo sia in grado di eseguire il mapping spaziale.

Richiedere l'accesso ai dati del mapping spaziale

L'app deve richiedere l'autorizzazione per accedere ai dati di mapping spaziale prima di provare a creare gli osservatori di superficie. Ecco un esempio basato sull'esempio di codice Surface Mapping, con altri dettagli forniti più avanti in questa pagina:

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

Creare un osservatore di superficie

Lo spazio dei nomi Windows::P erception::Spatial:: Surface include la classe SpatialSurfaceObserver , che osserva uno o più volumi specificati in Un sistema SpatialCoordinate. Usare un'istanza di SpatialSurfaceObserver per accedere ai dati mesh di superficie in tempo reale.

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

Come indicato nella sezione precedente, è necessario richiedere l'accesso ai dati di mapping spaziale prima che l'app possa usarla. Questo accesso viene concesso automaticamente in 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();

È quindi necessario configurare l'osservatore della superficie per osservare un volume di delimitamento specifico. Qui si osserva una scatola che è 20x20x5 metri, centrata all'origine del sistema di coordinate.

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

È invece possibile impostare più volumi di limite.

Si tratta di pseudocodice:

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

È anche possibile usare altre forme di delimitazione, ad esempio un frustum di visualizzazione o un rettangolo di selezione che non è allineato all'asse.

Si tratta di pseudocodice:

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

Se l'app deve eseguire operazioni diverse quando i dati di mapping della superficie non sono disponibili, è possibile scrivere codice per rispondere al caso in cui spatialPerceptionAccessStatus non sia consentito , ad esempio non sarà consentito nei PC con dispositivi immersivi collegati perché tali dispositivi non dispongono di hardware per il mapping spaziale. Per questi dispositivi, è consigliabile basarsi sulla fase spaziale per informazioni sull'ambiente e sulla configurazione del dispositivo dell'utente.

Inizializzare e aggiornare la raccolta mesh di superficie

Se l'osservatore della superficie è stato creato correttamente, è possibile continuare a inizializzare la raccolta mesh di superficie. In questo caso viene usata l'API del modello pull per ottenere immediatamente il set corrente di superfici osservate:

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

È disponibile anche un modello push per ottenere dati mesh di superficie. Si è liberi di progettare l'app per usare solo il modello pull se si sceglie, in questo caso si eseguirà il polling dei dati ogni tanto, ad esempio una volta per fotogramma, o durante un periodo di tempo specifico, ad esempio durante l'installazione del gioco. In tal caso, il codice precedente è ciò che è necessario.

Nell'esempio di codice abbiamo scelto di illustrare l'uso di entrambi i modelli a scopo pedagogico. In questo caso si sottoscrive un evento per ricevere dati mesh di superficie aggiornati ogni volta che il sistema riconosce una modifica.

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

L'esempio di codice è configurato anche per rispondere a questi eventi. Esaminiamo come facciamo questa operazione.

NOTA: Questo potrebbe non essere il modo più efficiente per l'app per gestire i dati mesh. Questo codice viene scritto per chiarezza e non è ottimizzato.

I dati della mesh di superficie vengono forniti in una mappa di sola lettura che archivia gli oggetti SpatialSurfaceInfo usando Platform::Guids come valori chiave.

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

Per elaborare questi dati, cercare prima di tutto i valori chiave che non si trovano nella raccolta. Informazioni dettagliate sul modo in cui i dati vengono archiviati nell'app di esempio verranno presentati più avanti in questo argomento.

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

È anche necessario rimuovere le mesh di superficie presenti nella raccolta mesh di superficie, ma che non si trovano più nella raccolta di sistemi. A tale scopo, dobbiamo fare qualcosa di simile all'opposto di ciò che abbiamo appena mostrato per l'aggiunta e l'aggiornamento di mesh; si esegue il ciclo sulla raccolta dell'app e si verifica se il Guid è presente nella raccolta di sistema. Se non si trova nella raccolta di sistema, la rimuoviamo dai nostri.

Dal gestore eventi in AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

L'implementazione dell'eliminazione della mesh 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);
    }
}

Acquisire e usare buffer dati mesh di superficie

Ottenere le informazioni sulla mesh di superficie è stato semplice come eseguire il pull di una raccolta dati e l'elaborazione degli aggiornamenti a tale raccolta. A questo punto, verrà illustrato in dettaglio come usare i dati.

Nell'esempio di codice è stato scelto di usare le mesh di superficie per il rendering. Si tratta di uno scenario comune per l'occlusione degli ologrammi dietro le superfici reali. È anche possibile eseguire il rendering delle mesh o eseguire il rendering delle versioni elaborate di loro, per mostrare all'utente quali aree della stanza vengono analizzate prima di iniziare a fornire funzionalità dell'app o del gioco.

L'esempio di codice avvia il processo quando riceve gli aggiornamenti della mesh di superficie dal gestore eventi descritto nella sezione precedente. La riga importante di codice in questa funzione è la chiamata per aggiornare la mesh di superficie: in questo momento abbiamo già elaborato le informazioni della mesh e stiamo per ottenere i dati del vertice e dell'indice per l'uso come si vede.

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

Il codice di esempio è progettato in modo che una classe di dati, SurfaceMesh, gestisce l'elaborazione e il rendering dei dati mesh. Queste mesh sono ciò che realtimeSurfaceMeshRenderer mantiene effettivamente una mappa di. Ognuno di essi ha un riferimento al buffer spazialeSurfaceMesh, quindi è possibile usarlo in qualsiasi momento necessario per accedere al vertice della mesh o ai buffer di indice oppure ottenere una trasformazione per la mesh. Per il momento, contrassegniamo la mesh in base alla necessità di un aggiornamento.

Da SurfaceMesh.cpp:

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

La volta successiva che la mesh viene chiesto di disegnare se stessa, verificherà prima il flag. Se è necessario un aggiornamento, i vertici e i buffer di indice verranno aggiornati nella GPU.

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

In primo luogo, si acquisiscono i buffer di dati non elaborati:

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;

Creare quindi buffer di dispositivi Direct3D con i dati mesh forniti da 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;
}

NOTA: Per la funzione helper CreateDirectXBuffer usata nel frammento precedente, vedere l'esempio di codice di Mapping di Surface: SurfaceMesh.cpp, GetDataFromIBuffer.h. La creazione della risorsa del dispositivo è stata completata e la mesh viene considerata caricata e pronta per l'aggiornamento e il rendering.

Aggiornare e eseguire il rendering delle mesh di superficie

La classe SurfaceMesh ha una funzione di aggiornamento specializzata. Ogni SpatialSurfaceMesh ha una propria trasformazione e l'esempio usa il sistema di coordinate corrente per il nostro SpatialStationaryReferenceFrame per acquisire la trasformazione. Aggiorna quindi il buffer costante del modello sulla GPU.

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

Quando è il momento di eseguire il rendering delle mesh di superficie, è possibile eseguire alcune operazioni di preparazione prima di eseguire il rendering della raccolta. È stata configurata la pipeline shader per la configurazione di rendering corrente e è stata configurata la fase dell'assembler di input. La classe helper della fotocamera olografica CameraResources.cpp ha già configurato il buffer costante di visualizzazione/proiezione.

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

Una volta fatto questo, si esegue il ciclo sulle reti e si dice a ognuno di disegnare se stesso. NOTA: Questo codice di esempio non è ottimizzato per l'uso di alcun tipo di stristum frustum, ma è consigliabile includere questa funzionalità nell'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);
}

Le singole mesh sono responsabili della configurazione del buffer del vertice e dell'indice, dello stride e del buffer costante di trasformazione del modello. Come per il cubo di rotazione nel modello di app Windows Holographic, viene eseguito il rendering dei buffer stereoscopici usando instancing.

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

Opzioni di rendering con Surface Mapping

L'esempio di codice Surface Mapping offre codice per il rendering solo dell'occlusione dei dati della mesh di superficie e per il rendering su schermo dei dati della mesh di superficie. Quale percorso si sceglie, o entrambi, dipende dall'applicazione. In questo documento verranno dettagliate entrambe le configurazioni.

Rendering dei buffer di occlusion per l'effetto olografico

Iniziare cancellando la visualizzazione di destinazione di rendering per la fotocamera virtuale corrente.

Da AppMain.cpp:

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

Si tratta di un passaggio "pre-rendering". In questo caso viene creato un buffer di occlusione chiedendo al renderer mesh di eseguire il rendering solo della profondità. In questa configurazione non si associa una visualizzazione di destinazione di rendering e il renderer mesh imposta la fase pixel shader su nullptr in modo che la GPU non si preoccupa di disegnare pixel. La geometria verrà rasterizzata nel buffer di profondità e la pipeline grafica si arresterà.

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

È possibile disegnare ologrammi con un test di profondità aggiuntivo rispetto al buffer di occlusione del mapping di surface. In questo esempio di codice viene eseguito il rendering dei pixel nel cubo un colore diverso se si trovano dietro una superficie.

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

Basato sul codice di 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);
}

Nota: Per la routine GatherDepthLess , vedere l'esempio di codice di mapping di Surface: SpecialEffectPixelShader.hlsl.

Rendering dei dati della mesh di superficie nella visualizzazione

È anche possibile disegnare le mesh di superficie nei buffer di visualizzazione stereo. Abbiamo scelto di disegnare visi completi con illuminazione, ma sei libero di disegnare wireframe, elaborare mesh prima del rendering, applicare una mappa di trama e così via.

In questo caso, l'esempio di codice indica al renderer mesh di disegnare la raccolta. Questa volta non viene specificato un passaggio di profondità, verrà collegato un pixel shader e completato la pipeline di rendering usando le destinazioni specificate per la fotocamera virtuale corrente.

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

Vedi anche