Mapowanie przestrzenne w programie DirectX

Uwaga

Ten artykuł dotyczy starszych natywnych interfejsów API winRT. W przypadku nowych projektów aplikacji natywnych zalecamy używanie interfejsu API OpenXR.

W tym temacie opisano sposób implementowania mapowania przestrzennego w aplikacji DirectX, w tym szczegółowe wyjaśnienie przykładowej aplikacji mapowania przestrzennego spakowanej przy użyciu zestawu SDK platforma uniwersalna systemu Windows.

W tym temacie użyto kodu z przykładowego kodu platformy UWP HolographicSpatialMapping .

Uwaga

Fragmenty kodu w tym artykule pokazują obecnie użycie języka C++/CX, a nie C++17 zgodnego z językiem C++/WinRT używanego w szablonie projektu holograficznego języka C++. Pojęcia są równoważne projektowi C++/WinRT, chociaż trzeba będzie przetłumaczyć kod.

Obsługa urządzeń

Funkcja HoloLens (1. generacja) HoloLens 2 Immersyjne zestawy nagłowne
Mapowanie przestrzenne ✔️ ✔️

Omówienie programowania w usłudze DirectX

Programowanie aplikacji natywnej na potrzeby mapowania przestrzennego używa interfejsów API w przestrzeni nazw Windows.Perception.Spatial . Te interfejsy API zapewniają pełną kontrolę nad funkcjami mapowania przestrzennego w taki sam sposób, w jaki interfejsy API mapowania przestrzennego są udostępniane przez aparat Unity.

Interfejsy API percepcji

Typy podstawowe udostępniane na potrzeby tworzenia mapowania przestrzennego są następujące:

  • Obiekt SpatialSurfaceObserver zawiera informacje o powierzchniach w określonych przez aplikację regionach przestrzeni w pobliżu użytkownika w postaci obiektów SpatialSurfaceInfo.
  • SpatialSurfaceInfo opisuje pojedynczą powierzchnię przestrzenną, w tym unikatowy identyfikator, ilość ograniczenia i czas ostatniej zmiany. Zapewni asynchronicznie element SpatialSurfaceMesh na żądanie.
  • SpatialSurfaceMeshOptions zawiera parametry używane do dostosowywania obiektów SpatialSurfaceMesh żądanych z elementu SpatialSurfaceInfo.
  • SpatialSurfaceMesh reprezentuje dane siatki dla pojedynczej powierzchni przestrzennej. Dane dotyczące pozycji wierzchołków, normalnych wierzchołków i indeksów trójkątów są zawarte w obiektach SpatialSurfaceMeshBuffer.
  • SpatialSurfaceMeshBuffer opakowuje pojedynczy typ danych siatki.

Podczas tworzenia aplikacji przy użyciu tych interfejsów API podstawowy przepływ programu będzie wyglądać następująco (jak pokazano w przykładowej aplikacji opisanej poniżej):

  • Konfigurowanie serwera SpatialSurfaceObserver
    • Wywołaj metodę RequestAccessAsync, aby upewnić się, że użytkownik udzielił aplikacji uprawnień do korzystania z funkcji mapowania przestrzennego urządzenia.
    • Utwórz wystąpienie obiektu SpatialSurfaceObserver.
    • Wywołaj metodę SetBoundingVolumes , aby określić regiony przestrzeni, w których chcesz uzyskać informacje o powierzchniach przestrzennych. Te regiony można zmodyfikować w przyszłości, wywołując tę funkcję ponownie. Każdy region jest określany przy użyciu elementu SpatialBoundingVolume.
    • Zarejestruj się w przypadku zdarzenia ObservedSurfacesChanged , które będzie uruchamiane za każdym razem, gdy nowe informacje są dostępne na temat powierzchni przestrzennych w określonych regionach przestrzeni.
  • Obserwowane zdarzenia ProcesuSurfacesChanged
  • Siatka odbierania i przetwarzania
    • Każde wywołanie metody TryComputeLatestMeshAsync zwróci asynchronicznie jeden obiekt SpatialSurfaceMesh.
    • Z tego obiektu można uzyskać dostęp do zawartych obiektów SpatialSurfaceMeshBuffer, co zapewnia dostęp do indeksów trójkątów, pozycji wierzchołków i normalnych wierzchołków siatki w przypadku ich żądania. Te dane będą w formacie bezpośrednio zgodnym z interfejsami API Direct3D 11 używanymi do renderowania siatki.
    • W tym miejscu aplikacja może opcjonalnie analizować lub przetwarzać dane siatki i używać ich do renderowania i przetwarzania raycastingu fizyki oraz kolizji.
    • Należy pamiętać o tym, że należy zastosować skalę do pozycji wierzchołków siatki (na przykład w cieniatorze wierzchołków używanym do renderowania siatki), aby przekonwertować je z zoptymalizowanych jednostek całkowitych, w których są przechowywane w buforze, do mierników. Tę skalę można pobrać, wywołując element VertexPositionScale.

Rozwiązywanie problemów

Przewodnik po przykładowym kodzie mapowania przestrzennego

Przykładowy kod mapowania przestrzennego Holographic Zawiera kod, którego można użyć do rozpoczęcia ładowania siatki powierzchni do aplikacji, w tym infrastruktury do zarządzania siatkami powierzchni i renderowania ich.

Teraz omówimy sposób dodawania funkcji mapowania powierzchni do aplikacji DirectX. Możesz dodać ten kod do projektu szablonu aplikacji Systemu Windows Holographic . Możesz też przejść przez przeglądanie przykładu kodu wymienionego powyżej. Ten przykładowy kod jest oparty na szablonie aplikacji Windows Holographic.

Konfigurowanie aplikacji do korzystania z funkcji spatialPerception

Aplikacja może korzystać z możliwości mapowania przestrzennego. Jest to konieczne, ponieważ siatka przestrzenna jest reprezentacją środowiska użytkownika, które może być uznawane za dane prywatne. Zadeklaruj tę możliwość w pliku package.appxmanifest dla aplikacji. Oto przykład:

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

Możliwość pochodzi z przestrzeni nazw uap2 . Aby uzyskać dostęp do tej przestrzeni nazw w manifeście, dołącz go jako atrybut xlmns w elemecie <Package> . Oto przykład:

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

Sprawdzanie obsługi funkcji mapowania przestrzennego

Windows Mixed Reality obsługuje szeroką gamę urządzeń, w tym urządzeń, które nie obsługują mapowania przestrzennego. Jeśli aplikacja może używać mapowania przestrzennego lub musi używać mapowania przestrzennego, aby zapewnić funkcjonalność, przed podjęciem próby jej użycia należy sprawdzić, czy mapowanie przestrzenne jest obsługiwane. Jeśli na przykład mapowanie przestrzenne jest wymagane przez aplikację rzeczywistości mieszanej, powinno to spowodować wyświetlenie komunikatu, jeśli użytkownik spróbuje uruchomić go na urządzeniu bez mapowania przestrzennego. Możesz też renderować własne środowisko wirtualne zamiast środowiska użytkownika, zapewniając środowisko podobne do tego, co by się stało, gdyby mapowanie przestrzenne było dostępne. W każdym razie ten interfejs API umożliwia aplikacji zapoznanie się z tym, kiedy nie uzyska ona danych mapowania przestrzennego i odpowie w odpowiedni sposób.

Aby sprawdzić bieżące urządzenie pod kątem obsługi mapowania przestrzennego, najpierw upewnij się, że kontrakt platformy UWP jest na poziomie 4 lub większym, a następnie wywołaj metodę SpatialSurfaceObserver::IsSupported(). Oto jak to zrobić w kontekście przykładowego kodu holograficznego mapowania przestrzennego . Pomoc techniczna jest sprawdzana tuż przed zażądaniem dostępu.

Interfejs API SpatialSurfaceObserver::IsSupported() jest dostępny od wersji 15063 zestawu SDK. Jeśli to konieczne, przed rozpoczęciem korzystania z tego interfejsu API przekieruj projekt do platformy w wersji 15063.

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

Gdy kontrakt platformy UWP jest niższy niż poziom 4, aplikacja powinna kontynuować, tak jakby urządzenie mogło wykonywać mapowanie przestrzenne.

Żądanie dostępu do danych mapowania przestrzennego

Aplikacja musi zażądać uprawnień dostępu do danych mapowania przestrzennego przed próbą utworzenia obserwatorów powierzchni. Oto przykład na podstawie naszego przykładowego kodu mapowania powierzchni z bardziej szczegółowymi informacjami podanymi później na tej stronie:

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

Tworzenie obserwatora powierzchni

Przestrzeń nazw Windows::P erception::Spatial::Surface zawiera klasę SpatialSurfaceObserver , która obserwuje co najmniej jeden wolumin określony w elemecie SpatialCoordinateSystem. Użyj wystąpienia SpatialSurfaceObserver , aby uzyskać dostęp do danych siatki powierzchni w czasie rzeczywistym.

Z pliku 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;

Jak wspomniano w poprzedniej sekcji, musisz zażądać dostępu do danych mapowania przestrzennego, zanim aplikacja będzie mogła z niej korzystać. Ten dostęp jest udzielany automatycznie na urządzeniu 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();

Następnie należy skonfigurować obserwatora powierzchni w celu obserwowania określonego woluminu ograniczenia. Tutaj obserwujemy pole o wysokości 20 x 20 x 5 metrów, wyśrodkowane na początku układu współrzędnych.

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

Zamiast tego można ustawić wiele woluminów ograniczenia.

Jest to kod pseudokodowy:

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

Można również użyć innych kształtów ograniczenia , takich jak frustum widoku lub pole ograniczenia, które nie jest wyrównane.

Jest to kod pseudokodowy:

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

Jeśli aplikacja musi zrobić coś innego, gdy dane mapowania powierzchni nie są dostępne, możesz napisać kod, aby odpowiedzieć na wypadek, gdy parametr SpatialPerceptionAccessStatus nie jest dozwolony — na przykład nie będzie dozwolony na komputerach z urządzeniami immersywnymi dołączonymi, ponieważ te urządzenia nie mają sprzętu do mapowania przestrzennego. W przypadku tych urządzeń należy zamiast tego polegać na etapie przestrzennym w celu uzyskania informacji o środowisku użytkownika i konfiguracji urządzenia.

Inicjowanie i aktualizowanie kolekcji siatki powierzchni

Jeśli obserwator powierzchni został pomyślnie utworzony, możemy kontynuować inicjowanie kolekcji siatki powierzchni. W tym miejscu użyjemy interfejsu API modelu ściągania, aby od razu uzyskać bieżący zestaw obserwowanych powierzchni:

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

Dostępny jest również model wypychania umożliwiający uzyskanie danych siatki powierzchni. Możesz zaprojektować aplikację tak, aby korzystała tylko z modelu ściągania, jeśli wybierzesz, w takim przypadku będziesz sondować dane co tak często — powiedzmy, raz na ramkę — lub w określonym przedziale czasu, na przykład podczas konfigurowania gry. Jeśli tak, powyższy kod jest potrzebny.

W naszym przykładzie kodu wybraliśmy pokazanie użycia obu modeli do celów dydaktycznych. W tym miejscu subskrybujemy zdarzenie, aby otrzymywać aktualne dane siatki powierzchni za każdym razem, gdy system rozpoznaje zmianę.

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

Nasz przykładowy kod jest również skonfigurowany do reagowania na te zdarzenia. Przyjrzyjmy się temu, jak to robimy.

UWAGA: Może to nie być najbardziej efektywny sposób obsługi danych siatki przez aplikację. Ten kod jest napisany pod kątem przejrzystości i nie jest zoptymalizowany.

Dane siatki powierzchni są udostępniane na mapie tylko do odczytu, w której są przechowywane obiekty SpatialSurfaceInfo przy użyciu elementu Platform::Guids jako wartości klucza.

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

Aby przetworzyć te dane, najpierw szukamy wartości kluczy, które nie należą do naszej kolekcji. Szczegółowe informacje na temat sposobu przechowywania danych w naszej przykładowej aplikacji zostaną przedstawione w dalszej części tego tematu.

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

Musimy również usunąć siatki powierzchni, które znajdują się w naszej kolekcji siatki powierzchni, ale nie znajdują się już w kolekcji systemowej. Aby to zrobić, musimy zrobić coś podobnego do tego, co właśnie pokazaliśmy do dodawania i aktualizowania siatki; Przeprowadzamy pętlę w kolekcji aplikacji i sprawdzamy, czy identyfikator GUID znajduje się w kolekcji systemowej. Jeśli nie znajduje się ona w kolekcji systemowej, usuniemy ją z kolekcji.

Z poziomu programu obsługi zdarzeń w pliku AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

Implementacja oczyszczania siatki w pliku 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);
    }
}

Uzyskiwanie i używanie buforów danych siatki powierzchni

Pobieranie informacji o siatce powierzchni było tak proste, jak ściąganie danych i przetwarzanie aktualizacji do tej kolekcji. Teraz szczegółowo omówimy sposób używania danych.

W naszym przykładzie kodu wybraliśmy użycie siatki powierzchni do renderowania. Jest to typowy scenariusz dotyczący okludium hologramów za powierzchniami rzeczywistymi. Możesz również renderować siatki lub renderować przetworzone wersje, aby pokazać użytkownikowi, jakie obszary pomieszczenia są skanowane przed rozpoczęciem udostępniania funkcji aplikacji lub gier.

Przykładowy kod uruchamia proces po odebraniu aktualizacji siatki powierzchni z programu obsługi zdarzeń opisanego w poprzedniej sekcji. Ważnym wierszem kodu w tej funkcji jest wywołanie w celu zaktualizowania siatki powierzchni: do tej pory przetworzyliśmy już informacje o siatce i zamierzamy pobrać dane wierzchołka i indeksu do użycia w miarę dopasowania.

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

Nasz przykładowy kod został zaprojektowany tak, aby klasa danych SurfaceMesh obsługiwała przetwarzanie i renderowanie danych siatki. Te siatki są tym, co RealtimeSurfaceMeshRenderer rzeczywiście utrzymuje mapę. Każdy z nich ma odwołanie do elementu SpatialSurfaceMesh, z którego pochodzi, więc można go używać w dowolnym momencie, aby uzyskać dostęp do wierzchołka siatki lub buforów indeksu lub uzyskać przekształcenie dla siatki. Na razie flagujemy siatkę jako potrzebną aktualizację.

Z aplikacji SurfaceMesh.cpp:

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

Następnym razem, gdy siatka zostanie poproszona o narysowanie, najpierw sprawdzi flagę. Jeśli wymagana jest aktualizacja, wierzchołek i bufory indeksu zostaną zaktualizowane na procesorze GPU.

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

Najpierw uzyskujemy nieprzetworzone bufory danych:

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;

Następnie tworzymy bufory urządzeń Direct3D z danymi siatki dostarczonymi przez urządzenie 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;
}

UWAGA: Aby zapoznać się z funkcją pomocnika CreateDirectXBuffer używaną w poprzednim fragmencie kodu, zobacz przykładowy kod mapowania powierzchni: SurfaceMesh.cpp, GetDataFromIBuffer.h. Teraz tworzenie zasobów urządzenia zostało ukończone, a siatka jest uważana za załadowaną i gotową do aktualizacji i renderowania.

Aktualizowanie i renderowanie siatki powierzchni

Nasza klasa SurfaceMesh ma wyspecjalizowaną funkcję aktualizacji. Każdy element SpatialSurfaceMesh ma własną transformację, a nasz przykład używa bieżącego systemu współrzędnych dla elementu SpatialStationaryReferenceFrame w celu uzyskania przekształcenia. Następnie aktualizuje bufor stały modelu na procesorze 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
        );
}

Gdy nadszedł czas na renderowanie siatek powierzchni, przed renderowaniem kolekcji wykonamy pewne czynności przygotowawcze. Skonfigurowaliśmy potok cieniowania dla bieżącej konfiguracji renderowania i skonfigurowaliśmy etap asemblera wejściowego. Klasa pomocnika aparatu holograficznego CameraResources.cpp już skonfigurowała bufor stały widoku/projekcji.

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

Po wykonaniu tej czynności zapętlamy się na naszych siatkach i mówimy każdemu z nich, aby się narysował. UWAGA: Ten przykładowy kod nie jest zoptymalizowany pod kątem użycia jakiegokolwiek rodzaju frustum culling, ale należy uwzględnić tę funkcję w aplikacji.

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

Poszczególne siatki są odpowiedzialne za konfigurowanie buforu wierzchołka i bufora indeksu, kroku i buforu przekształcenia modelu. Podobnie jak w przypadku modułu wirującego w szablonie aplikacji Windows Holographic, renderujemy je w buforach steroskopowych przy użyciu tworzenia wystąpienia.

Z urządzenia 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.
    );

Opcje renderowania za pomocą mapowania powierzchni

Przykładowy kod mapowania powierzchni oferuje kod umożliwiający renderowanie danych siatki powierzchni tylko w przypadku renderowania na ekranie danych siatki powierzchni. Wybrana ścieżka — lub obie — zależy od aplikacji. Omówimy obie konfiguracje w tym dokumencie.

Renderowanie buforów okluzji dla efektu holograficznego

Rozpocznij od wyczyszczenia widoku docelowego renderowania dla bieżącej kamery wirtualnej.

W pliku AppMain.cpp:

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

Jest to przekazywanie "przed renderowaniem". W tym miejscu utworzymy bufor okluzji, prosząc moduł renderowany siatki o renderowanie tylko głębokości. W tej konfiguracji nie dołączamy widoku docelowego renderowania, a moduł renderujący siatki ustawia etap cieniowania pikseli na wartość nullptr , aby procesor GPU nie przeszkadzał w rysowaniu pikseli. Geometria zostanie rasteryzowana do bufora głębokości, a potok grafiki zatrzyma się tam.

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

Możemy narysować hologramy z dodatkowym testem głębokości w buforze okluzji mapowania powierzchni. W tym przykładzie kodu renderujemy piksele w module innym kolorem, jeśli znajdują się za powierzchnią.

W pliku 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()
    );

Na podstawie kodu z 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);
}

Uwaga: Aby zapoznać się z naszą procedurą GatherDepthLess , zobacz przykładowy kod mapowania powierzchni: SpecialEffectPixelShader.hlsl.

Renderowanie danych siatki powierzchni na ekranie

Możemy również po prostu narysować siatki powierzchni do buforów wyświetlacza stereo. Wybraliśmy rysowanie pełnych twarzy z oświetleniem, ale możesz narysować szkielet, przetwarzać siatki przed renderowaniem, stosować mapę tekstury itd.

W tym miejscu nasz przykładowy kod informuje moduł renderowania siatki o narysowaniu kolekcji. Tym razem nie określimy przebiegu tylko do głębokości, dołączymy cieniowanie pikseli i ukończymy potok renderowania przy użyciu elementów docelowych określonych dla bieżącej kamery wirtualnej.

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

Zobacz też