Mapowanie przestrzenne w directx

Uwaga

Ten artykuł dotyczy starszych interfejsów API natywnych 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ółowy opis przykładowej aplikacji mapowania przestrzennego spakowanej z zestawem SDK platformy Windows Platform.

W tym temacie jest używany kod z przykładu kodu holographicSpatialMapping platformy uniwersalnej systemu Windows.

Uwaga

Fragmenty kodu w tym artykule pokazują obecnie użycie języka C++/CX zamiast C++17 zgodnego z C++/WinRT, jak w szablonie projektu holograficznego języka C++. Pojęcia te są równoważne projektowi C++/WinRT, ale należy przetłumaczyć kod.

Obsługa urządzeń

Funkcja HoloLens (1. generacja) HoloLens 2 Immersywne zestawy nagłow
Mapowanie przestrzenne ✔️ ✔️

Omówienie tworzenia aplikacji DirectX

Tworzenie aplikacji natywnych dla mapowania przestrzennego używa interfejsów API w Windows. Perception.Spatial, przestrzeń nazw. 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 platformę Unity.

Interfejsy API percepcji

Podstawowe typy udostępniane do tworzenia map przestrzennych są następujące:

  • Obiekt SpatialSurfaceObserver zawiera informacje o powierzchniach w określonych przez aplikację regionach przestrzeni blisko użytkownika w postaci obiektów SpatialSurfaceInfo.
  • SpatialSurfaceInfo opisuje pojedynczą powierzchnię przestrzenną, w tym unikatowy identyfikator, ilość i czas ostatniej zmiany. Zapewni ona platformę SpatialSurfaceMesh asynchronicznie na żądanie.
  • Elementy SpatialSurfaceMeshOptions zawierają parametry służące do dostosowywania obiektów SpatialSurfaceMesh żądanych z obiektu SpatialSurfaceInfo.
  • SpatialSurfaceMesh reprezentuje dane siatki dla jednej powierzchni przestrzennej. Dane dotyczące pozycji wierzchołków, normalnych wierzchołków i indeksów trójkątów znajdują się w obiektach członkowskich SpatialSurfaceMeshBuffer.
  • SpatialSurfaceMeshBuffer opakowywuje pojedynczy typ danych siatki.

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

  • Konfigurowanie serwera SpatialSurfaceObserver
    • Wywołaj interfejs RequestAccessAsync,aby upewnić się, że użytkownik nadał aplikacji uprawnienia do korzystania z funkcji mapowania przestrzennego urządzenia.
    • Utworzyć wystąpienia obiektu SpatialSurfaceObserver.
    • Wywołaj wywołanie setBoundingVolumes, aby określić regiony przestrzeni, w których chcesz uzyskać informacje o powierzchniach przestrzennych. Te regiony można zmodyfikować w przyszłości, ponownie wywołując tę funkcję. Każdy region jest określony przy użyciu spatialboundingvolume.
    • Zarejestruj się w celu wystąpienia zdarzenia ObservedSurfacesChanged, które zostanie wyzrzucane za każdym razem, gdy są dostępne nowe informacje o powierzchniach przestrzennych w określonych regionach przestrzeni.
  • Zdarzenia Procesu ObservedSurfacesChanged
    • W programie obsługi zdarzeń wywołaj polecenie GetObservedSurfaces, aby otrzymać mapę obiektów SpatialSurfaceInfo. Za pomocą tej mapy można zaktualizować rekordy, które powierzchnie przestrzenne istnieją w środowisku użytkownika.
    • Dla każdego obiektu SpatialSurfaceInfo można wykonać zapytanie tryGetBounds w celu określenia zakresów przestrzennych powierzchni wyrażonych w systemie współrzędnych przestrzennych.
    • Jeśli zdecydujesz się zażądać siatki dla powierzchni przestrzennej, wywołaj wywołanie TryComputeLatestMeshAsync. Możesz podać opcje określające gęstość trójkątów i format zwracanych danych siatki.
  • Siatka odbierania i przetwarzania
    • Każde wywołanie metody TryComputeLatestMeshAsync asynchronicznie zwróci 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ąta, położenia wierzchołków i normalnych wartości wierzchołków siatki, jeśli są one zażądane. Te dane będą w formacie bezpośrednio zgodnym z interfejsami API Direct3D 11 używanymi do renderowania siatek.
    • W tym miejscu aplikacja może opcjonalnie analizować lub przetwarzać dane siatki oraz używać ich do renderowania i emisji fizyki oraz kolizji.
    • Jedną z ważnych informacji, na którą należy zwrócić uwagę, jest to, że należy zastosować skalę do pozycji wierzchołków siatki (na przykład w programie do cieniowania wierzchołków używanym do renderowania siatek), aby przekonwertować je ze zoptymalizowanych jednostek całkowitych, w których są przechowywane w buforze, na mierniki. Tę skalę można pobrać, wywołując vertexPositionScale.

Rozwiązywanie problemów

Przewodnik po przykładzie kodu mapowania przestrzennego

Przykład kodu holograficznego mapowania przestrzennego zawiera kod, który umożliwia rozpoczynanie ładowania siatek powierzchniowych do aplikacji, w tym infrastrukturę do zarządzania i renderowania siatek powierzchniowych.

Teraz opisano sposób dodawania możliwości mapowania powierzchni do aplikacji DirectX. Możesz dodać ten kod do swojego Windows aplikacji Holographic lub wykonać te czynności, przeglądając przykładowy kod wymieniony powyżej. Ten przykładowy kod jest oparty na Windows aplikacji Holographic.

Konfigurowanie aplikacji do korzystania z funkcji spatialPerception

Aplikacja może korzystać z funkcji 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 aplikacji. Oto przykład:

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

Ta możliwość pochodzi z przestrzeni nazw uap2. Aby uzyskać dostęp do tej przestrzeni nazw w manifeście, uwzględnij ją jako atrybut xlmns w < elemencie 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ądzenia, które nie obsługują mapowania przestrzennego. Jeśli aplikacja może używać mapowania przestrzennego lub musi używać mapowania przestrzennego w celu zapewnienia funkcjonalności, przed podjęciem próby użycia należy sprawdzić, czy mapowanie przestrzenne jest obsługiwane. Jeśli na przykład aplikacja rzeczywistości mieszanej wymaga mapowania przestrzennego, powinien zostać wyświetlony komunikat, jeśli użytkownik spróbuje go uruchamiać na urządzeniu bez mapowania przestrzennego. Alternatywnie aplikacja może renderować własne środowisko wirtualne w miejsce środowiska użytkownika, zapewniając środowisko podobne do tego, co by się stało, gdyby mapowanie przestrzenne było dostępne. W każdym przypadku ten interfejs API umożliwia aplikacji świadomość, kiedy nie będzie uzyskać danych mapowania przestrzennego i reagować w odpowiedni sposób.

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

Interfejs API SpatialSurfaceObserver::IsSupported() jest dostępny w zestawie SDK w wersji 15063. W razie potrzeby przekieruj projekt na platformę w wersji 15063 przed rozpoczęciem korzystania z tego interfejsu 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 ...

Jeśli kontrakt platformy uniwersalnej systemu Windows jest na poziomie mniejszym niż 4, aplikacja powinna kontynuować pracę tak, jakby urządzenie było w stanie mapowanie przestrzenne.

Żądanie dostępu do danych mapowania przestrzennego

Twoja aplikacja musi zażądać uprawnienia dostępu do danych mapowania przestrzennego przed próbą utworzenia jakichkolwiek obserwatorów powierzchni. Oto przykład oparty na przykładzie naszego kodu mapowania powierzchni z większej liczby szczegółów podanych w dalszej części tej strony:

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::Surfaces zawiera klasę SpatialSurfaceObserver, która obserwuje co najmniej jeden wolumin określony w systemie SpatialCoordinateSystem. Użyj wystąpienia SpatialSurfaceObserver, aby uzyskać dostęp do danych siatki powierzchniowej w czasie rzeczywistym.

Z 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, należy zażądać dostępu do danych mapowania przestrzennego, zanim aplikacja będzie z nich korzystać. Ten dostęp jest udzielany automatycznie na 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 ograniczonego. W tym miejscu obserwujemy pole o szerokości 20 x 20 x 5, 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 ograniczonych.

Jest to pseudokod:

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

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

Jest to pseudokod:

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

Jeśli aplikacja musi wykonać cokolwiek inaczej, gdy dane mapowania powierzchni nie są dostępne, możesz napisać kod, aby odpowiedzieć na przypadek, w którym statystyka SpatialPerceptionAccessStatus nie jest dozwolona — na przykład nie będzie ona dozwolona na komputerach z dołączonymi urządzeniami immersyjnymi, ponieważ te urządzenia nie mają sprzętu do mapowania przestrzennego. W przypadku tych urządzeń należy polegać na etapie przestrzennym, aby uzyskać informacje 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 pobrać 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 w celu uzyskania danych siatki powierzchniowej. Możesz zaprojektować aplikację tak, aby używać tylko modelu ściągania, jeśli wybierzesz, w takim przypadku sondowanie danych będzie co tak często — na przykład raz na ramkę — lub w określonym czasie, na przykład podczas konfigurowania gry. Jeśli tak, powyższy kod jest tym, czego potrzebujesz.

W naszym przykładzie kodu wybraliśmy sposób zademonstrowania użycia obu modeli do celów ycznych. W tym miejscu subskrybujemy zdarzenie, aby otrzymywać aktualne dane siatki powierzchni za każdym razem, gdy system rozpozna 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 zrobić.

UWAGA: Może to nie być najbardziej wydajny sposób obsługi danych siatki przez aplikację. Ten kod jest napisany w celu zapewnienia przejrzystości i nie jest zoptymalizowany.

Dane siatki powierzchniowej są udostępniane na mapie tylko do odczytu, która przechowuje obiekty SpatialSurfaceInfo przy użyciu właściwości Platform::Guids jako wartości kluczy.

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

Aby przetworzyć te dane, najpierw poszukamy wartości kluczy, które nie są w 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 powierzchniowe, które znajdują się w kolekcji siatki powierzchniowej, ale nie znajdują się już w kolekcji systemowej. Aby to zrobić, musimy zrobić coś podobnego do przeciwieńszego tego, co właśnie pokazaliśmy na potrzeby dodawania i aktualizowania siatek. Pętlę używamy w kolekcji naszej aplikacji i sprawdzamy, czy identyfikator GUID, który mamy, znajduje się w kolekcji systemowej. Jeśli nie ma jej w kolekcji systemowej, usuniemy ją z naszej kolekcji.

Z naszego programu obsługi zdarzeń w appMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

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

Pozyskiwanie i używanie buforów danych siatki powierzchni

Pobieranie informacji o siatce powierzchni było tak proste, jak ściągnięcie kolekcji danych i aktualizacje przetwarzania do tej kolekcji. Teraz szczegółowo opiszemy sposób korzystania z danych.

W naszym przykładzie kodu wybraliśmy użycie siatki powierzchni do renderowania. Jest to częsty scenariusz, w którym hologramy są okluste na rzeczywistych powierzchniach. Można również renderować siatki lub renderować przetworzone wersje tych siatek, aby pokazać użytkownikowi, jakie obszary pomieszczenia są skanowane przed rozpoczęciem dostarczania funkcji aplikacji lub gier.

Przykładowy kod uruchamia proces po otrzymaniu aktualizacji siatki powierzchni z procedury obsługi zdarzeń opisanej w poprzedniej sekcji. Ważnym wierszem kodu w tej funkcji jest wywołanie aktualizacji siatki powierzchni : do tego czasu przetworzyliśmy już informacje o siatce i chcemy uzyskać dane wierzchołka i indeksu do użycia zgodnie z potrzebami.

Z: 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ługiła przetwarzanie i renderowanie danych siatki. Te siatki są tym, co w rzeczywistości zachowuje mapa RealtimeSurfaceMeshRenderer. Każdy z nich ma odwołanie do obiektu SpatialSurfaceMesh, z który pochodzi, więc można go używać w dowolnym momencie, gdy trzeba uzyskać dostęp do wierzchołka siatki lub buforów indeksu albo uzyskać przekształcenie dla siatki. Na razie flagujemy siatkę jako potrzebną aktualizację.

Z surfaceMesh.cpp:

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

Następnym razem, gdy siatka zostanie poproszona o samo rysowanie, najpierw sprawdzi flagę. Jeśli jest potrzebna aktualizacja, bufory wierzchołków i indeksów 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 bufory danych pierwotnych:

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 utworzymy bufory urządzeń Direct3D z danymi siatki dostarczonymi przez 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 uzyskać informacje na temat funkcji pomocnika CreateDirectXBuffer użytej w poprzednim fragmencie kodu, zobacz przykładowy kod mapowania powierzchni: SurfaceMesh.cpp, GetDataFromIBuffer.h. Teraz tworzenie zasobu urządzenia jest ukończone, a siatka jest uważana za załadowaną i gotową do aktualizacji i renderowania.

Aktualizowanie i renderowanie siatek powierzchni

Nasza klasa SurfaceMesh ma wyspecjalizowaną funkcję aktualizacji. Każda struktura SpatialSurfaceMesh ma własną transformację, a nasz przykład używa bieżącego systemu współrzędnych dla ramki SpatialStationaryReferenceFrame w celu uzyskania przekształcenia. Następnie aktualizuje bufor stałej 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 nas czas renderowania siatek powierzchniowych, przed renderowaniem kolekcji należy wykonać pewne działania wstępne. Skonfigurujemy potok cieniowania dla bieżącej konfiguracji renderowania i skompletujemy etap asekompletu wejściowego. Klasa pomocnika aparatu holograficznego CameraResources.cpp ma już ustawiony bufor stałej widoku/projekcji.

Z: 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 zakończeniu pętlęujemy w naszych siatkach i mówimy każdemu z nich, aby się narysowały. UWAGA: Ten przykładowy kod nie jest zoptymalizowany pod kątem używania żadnego rodzaju cullinga frustum, 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 stałego wierzchołka i indeksu, kroku i przekształcenia modelu. Podobnie jak w przypadku obracającego się sześcianu w szablonie Windows holograficznego, jest renderowany do buforów stereoskopowych przy użyciu funkcji instancingu.

Z platformy 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ład kodu mapowania powierzchni oferuje kod do renderowania danych siatki powierzchniowej tylko w trybie oklulacji oraz do renderowania danych siatki powierzchniowej na ekranie. Wybór ścieżki lub obu tych opcji zależy od aplikacji. W tym dokumencie ominiemy obie konfiguracje.

Renderowanie buforów oklulacji dla efektu holograficznego

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

Z appMain.cpp:

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

Jest to przebieg "wstępnego renderowania". W tym miejscu utworzymy bufor oklulacji, prosząc program renderjący siatki o wyrenderowanie tylko głębokości. W tej konfiguracji nie dołączamy widoku docelowego renderowania, a moduł renderowania siatki ustawia etap cieniowania pikseli na nullptr, aby procesor GPU nie przeszkadzał w rysować pikseli. Geometria będzie rasteryzowana do bufora głębokości, a potok graficzny zatrzyma się w tym miejscu.

// 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 względem buforu oklulacji mapowania powierzchni. W tym przykładzie kodu renderowane są piksele modułu w innym kolorze, jeśli znajdują się za powierzchnią.

Z 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 uzyskać naszą procedurę GatherDepthLess, zobacz przykład kodu mapowania powierzchni: SpecialEffectPixelShader.hlsl.

Renderowanie danych siatki powierzchniowej na ekranie

Możemy również po prostu narysować siatki powierzchni do buforów wyświetlania stereo. Wybraliśmy rysowanie pełnych twarzy z oświetleniem, ale możesz narysować szkielet, przetworzyć siatki przed renderowaniem, zastosować mapę tekstury i tak dalej.

W tym miejscu nasz przykładowy kod nakazuje programowi renderowemu siatki narysowanie kolekcji. Tym razem nie określimy przejścia tylko do głębokości, ale dołączymy moduł cieniowania pikseli i ukończymy potok renderowania przy użyciu obiektó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ż