Térbeli leképezés a DirectX-ben

Megjegyzés

Ez a cikk az örökölt WinRT natív API-kkal kapcsolatos. Új natív alkalmazásprojektek esetén az OpenXR API használatát javasoljuk.

Ez a témakör azt ismerteti, hogyan implementálhat térbeli leképezést a DirectX-alkalmazásban, beleértve a Univerzális Windows-platform SDK-val csomagolt térbeli leképezési mintaalkalmazás részletes magyarázatát.

Ez a témakör a HolographicSpatialMapping UWP-kódmintából származó kódot használja.

Megjegyzés

A jelen cikkben szereplő kódrészletek jelenleg a C++/CX használatát mutatják be a C++17-kompatibilis C++/WinRT helyett, a C++ holografikus projektsablonban használt módon. A fogalmak egyenértékűek egy C++/WinRT-projekt esetében, bár a kódot le kell fordítania.

Eszköztámogatás

Szolgáltatás HoloLens (1. generációs) HoloLens 2 Modern headsetek
Térbeli leképezés ✔️ ✔️

A DirectX-fejlesztés áttekintése

A térbeli leképezés natív alkalmazásfejlesztése a Windows.Perception.Spatial névtér API-jait használja. Ezek az API-k teljes körű ellenőrzést biztosítanak a térbeli leképezési funkciók felett, ugyanúgy, ahogyan a Unity a térbeli leképezési API-kat is elérhetővé teszi.

Percepciós API-k

A térbeli leképezés fejlesztéséhez biztosított elsődleges típusok a következők:

  • A SpatialSurfaceObserver információt nyújt a felhasználó közelében lévő alkalmazás által megadott térterületek felületeiről SpatialSurfaceInfo objektumok formájában.
  • A SpatialSurfaceInfo egyetlen külső térbeli felületet ír le, beleértve az egyedi azonosítót, a határoló kötetet és az utolsó módosítás időpontját. Kérésre aszinkron módon biztosítja a SpatialSurfaceMesh-t.
  • A SpatialSurfaceMeshOptions a SpatialSurfaceInfo által kért SpatialSurfaceMesh objektumok testreszabásához használt paramétereket tartalmaz.
  • A SpatialSurfaceMesh egyetlen térbeli felület hálóadatait jelöli. A csúcspontpozíciók, a csúcspontok normaljai és a háromszögindexek adatait a tag SpatialSurfaceMeshBuffer objektumok tartalmazzák.
  • A SpatialSurfaceMeshBuffer egyetlen típusú hálóadatokat burkol.

Amikor alkalmazásokat fejleszt ezen API-k használatával, az alapszintű programfolyamat így fog kinézni (ahogy az alább ismertetett mintaalkalmazásban is látható):

  • A SpatialSurfaceObserver beállítása
    • Hívja meg a RequestAccessAsync parancsot, és győződjön meg arról, hogy a felhasználó engedélyt adott az alkalmazásnak az eszköz térbeli leképezési képességeinek használatára.
    • Egy SpatialSurfaceObserver objektum példányosítása.
    • Hívja meg a SetBoundingVolumes parancsot a térbeli felületekkel kapcsolatos információk megadásához. A jövőben módosíthatja ezeket a régiókat a függvény ismételt meghívásával. Minden régió egy SpatialBoundingVolume használatával van megadva.
    • Regisztráljon a ObservedSurfacesChanged eseményre, amely akkor aktiválódik, ha új információ áll rendelkezésre a megadott térterületek térbeli felületeiről.
  • Process ObservedSurfacesChanged events
  • Háló fogadása és feldolgozása
    • A TryComputeLatestMeshAsync minden hívása aszinkron módon egy SpatialSurfaceMesh objektumot ad vissza.
    • Ebből az objektumból elérheti a tartalmazott SpatialSurfaceMeshBuffer objektumokat, amelyek hozzáférést biztosítanak a háló háromszögindexeihez, csúcspontpozícióihoz és csúcspontjaihoz, ha kéri őket. Ezek az adatok közvetlenül kompatibilisek lesznek a hálók renderelésére használt Direct3D 11 API-kkal .
    • Innen az alkalmazás opcionálisan elemezheti vagy feldolgozhatja a hálóadatokat, és felhasználhatja a rendereléshez és a fizikai sugárszóráshoz és az ütközéshez.
    • Fontos megjegyezni, hogy skálát kell alkalmaznia a háló csúcspontpozícióira (például a hálók rendereléséhez használt csúcspontárnyékolóban), hogy azokat a pufferben tárolt optimalizált egész számegységekből mérőkké alakítsa. Ezt a skálát a VertexPositionScale meghívásával lehet lekérni.

Hibaelhárítás

Térbeli leképezési kódminta – útmutató

A holografikus térbeli leképezési kódminta olyan kódot tartalmaz, amellyel megkezdheti a felületi hálók alkalmazásba való betöltését, beleértve a felületi hálók kezelésére és renderelésére szolgáló infrastruktúrát is.

Most bemutatjuk, hogyan adhat felületleképezési képességet a DirectX-alkalmazáshoz. Ezt a kódot hozzáadhatja a Windows Holographic-alkalmazássablon projektjéhez, vagy a fent említett kódmintát böngészve követheti. Ez a kódminta a Windows Holographic alkalmazássablonon alapul.

Az alkalmazás beállítása a spatialPerception képesség használatára

Az alkalmazás használhatja a térbeli leképezési képességet. Erre azért van szükség, mert a térbeli háló a felhasználó környezetének reprezentációja, amely privát adatnak tekinthető. Deklarálja ezt a képességet az alkalmazás package.appxmanifest fájljában. Bemutatunk egy példát:

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

A képesség az uap2 névtérből származik. Ha hozzá szeretne férni ehhez a névtérhez a jegyzékben, vegye fel xlmns attribútumként a <Csomag> elembe. Bemutatunk egy példát:

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

Térbeli leképezési funkció támogatásának ellenőrzése

Windows Mixed Reality számos olyan eszközt támogat, beleértve az eszközöket is, amelyek nem támogatják a térbeli leképezést. Ha az alkalmazás használhat térbeli leképezést, vagy térbeli leképezést kell használnia a funkciók biztosításához, a használat előtt ellenőriznie kell, hogy támogatott-e a térbeli leképezés. Ha például a térbeli leképezésre van szükség a vegyes valóság alkalmazásához, akkor egy erre vonatkozó üzenetet kell megjelenítenie, ha egy felhasználó térbeli leképezés nélkül próbálja futtatni egy eszközön. Vagy az alkalmazás saját virtuális környezetét is megjelenítheti a felhasználó környezete helyett, és olyan élményt nyújt, amely hasonló ahhoz, ami akkor történne, ha a térbeli leképezés elérhető lenne. Ez az API mindenesetre lehetővé teszi, hogy az alkalmazás tisztában legyen azzal, hogy mikor nem kap térbeli leképezési adatokat, és mikor válaszol a megfelelő módon.

Az aktuális eszköz térbeli leképezési támogatásának ellenőrzéséhez először győződjön meg arról, hogy az UWP-szerződés 4. vagy annál magasabb szinten van, majd hívja meg a SpatialSurfaceObserver::IsSupported() parancsot. Ezt a holografikus térbeli leképezési kódminta kontextusában teheti meg. A támogatás ellenőrzése közvetlenül a hozzáférés kérése előtt történik.

A SpatialSurfaceObserver::IsSupported() API az SDK 15063-es verziójától kezdve érhető el. Ha szükséges, az API használata előtt indítsa újra a projektet az 15063-es platformra.

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

Ha az UWP-szerződés kisebb, mint a 4. szint, az alkalmazásnak úgy kell haladnia, mintha az eszköz képes volna térbeli leképezést végezni.

Hozzáférés kérése térbeli leképezési adatokhoz

Az alkalmazásnak engedélyt kell kérnie a térbeli leképezési adatokhoz való hozzáféréshez, mielőtt felszíni megfigyelőket próbálna létrehozni. Íme egy példa a Surface Mapping-kódminta alapján, amelyről később további részleteket talál ezen az oldalon:

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

Felszíni megfigyelő létrehozása

A Windows::P erception::Spatial::Surface névtér tartalmazza a SpatialSurfaceObserver osztályt, amely a SpatialCoordinateSystemben megadott egy vagy több kötetet figyeli meg. Egy SpatialSurfaceObserver-példány használatával valós időben érheti el a surface mesh-adatokat.

Az AppMain.h fájlból:

// Obtains surface mapping data from the device in real time.
Windows::Perception::Spatial::Surfaces::SpatialSurfaceObserver^     m_surfaceObserver;
Windows::Perception::Spatial::Surfaces::SpatialSurfaceMeshOptions^  m_surfaceMeshOptions;

Ahogy az előző szakaszban is említettük, a térbeli leképezési adatokhoz való hozzáférést kell kérnie ahhoz, hogy az alkalmazás használni tudja azokat. Ez a hozzáférés automatikusan meg van adva a HoloLensben.

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

Ezután konfigurálnia kell a felszíni megfigyelőt egy adott határoló kötet megfigyeléséhez. Itt egy 20x20x5 méteres dobozt figyelünk meg, amely a koordinátarendszer forrásánál van.

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

Ehelyett több határoló kötetet is beállíthat.

Ez a pseudocode:

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

Más határoló alakzatokat is használhat – például nézet frustumot vagy nem tengelyre igazított határolókeretet.

Ez a pseudocode:

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

Ha az alkalmazásnak másképpen kell tennie, ha a felületleképezési adatok nem érhetők el, írhat kódot arra az esetre, ha a SpatialPerceptionAccessStatus nem engedélyezett – például nem lesz engedélyezve olyan számítógépeken, amelyekhez modern eszközök vannak csatolva, mert ezek az eszközök nem rendelkeznek térbeli leképezéshez szükséges hardverrel. Ezeknél az eszközöknél inkább a térbeli szakaszra kell támaszkodnia a felhasználó környezetével és eszközkonfigurációjával kapcsolatos információkhoz.

A Surface Mesh-gyűjtemény inicializálása és frissítése

Ha a felszíni megfigyelő sikeresen létrejött, folytathatjuk a surface mesh-gyűjtemény inicializálását. Itt a lekéréses modell API-val azonnal lekérjük a megfigyelt felületek aktuális készletét:

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

A surface mesh-adatok lekéréséhez egy leküldéses modell is rendelkezésre áll. Az alkalmazást szabadon megtervezheti úgy, hogy csak a lekéréses modellt használja, ha úgy dönt, ebben az esetben minden alkalommal – például keretenként egyszer – vagy egy adott időszakban, például a játék beállítása során kérdezi le az adatokat. Ha igen, a fenti kódra van szüksége.

A kódmintánkban mindkét modell pedagógiai célokra való használatát mutattuk be. Itt előfizetünk egy eseményre, hogy naprakész surface mesh-adatokat fogadjunk, amikor a rendszer felismer egy változást.

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

A kódmintánk úgy is konfigurálva van, hogy reagáljon ezekre az eseményekre. Nézzük meg, hogyan csináljuk ezt.

MEGJEGYZÉS: Előfordulhat, hogy nem ez a leghatékonyabb módja annak, hogy az alkalmazás kezelje a hálóadatokat. Ez a kód az egyértelműség kedvéért van megírva, és nincs optimalizálva.

A felületi háló adatai egy csak olvasható térképen találhatók, amely a Platform::Guids kulcsértékek használatával tárolja a SpatialSurfaceInfo objektumokat.

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

Az adatok feldolgozásához először olyan kulcsértékeket keresünk, amelyek nem szerepelnek a gyűjteményünkben. Az adatok mintaalkalmazásban való tárolásának részleteit a jelen témakör későbbi részében ismertetjük.

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

El kell távolítanunk a felületi hálógyűjteményben lévő, de a rendszergyűjteményben már nem szereplő felületi hálószemeket is. Ehhez a hálók hozzáadásához és frissítéséhez az imént bemutatott ellenkezőjét kell tennünk; hurkot hozunk létre az alkalmazás gyűjteményén, és ellenőrizzük, hogy a guid azonosító szerepel-e a rendszergyűjteményben. Ha nem szerepel a rendszergyűjteményben, eltávolítjuk a miénkből.

Az AppMain.cpp-ben található eseménykezelőből:

m_meshCollection->PruneMeshCollection(surfaceCollection);

A hálómetszet implementálása a RealtimeSurfaceMeshRenderer.cpp fájlban:

void RealtimeSurfaceMeshRenderer::PruneMeshCollection(IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection)
{
    std::lock_guard<std::mutex> guard(m_meshCollectionLock);
    std::vector<Guid> idsToRemove;

    // Remove surfaces that moved out of the culling frustum or no longer exist.
    for (const auto& pair : m_meshCollection)
    {
        const auto& id = pair.first;
        if (!surfaceCollection->HasKey(id))
        {
            idsToRemove.push_back(id);
        }
    }

    for (const auto& id : idsToRemove)
    {
        m_meshCollection.erase(id);
    }
}

Surface Mesh-adatpufferek beszerzése és használata

A Surface Mesh információinak lekérése olyan egyszerű volt, mint egy adatgyűjtés lekérése és a gyűjtemény frissítéseinek feldolgozása. Most részletesen bemutatjuk, hogyan használhatja fel az adatokat.

A kód példájában úgy döntöttünk, hogy a felületi hálókat használjuk a rendereléshez. Ez gyakori forgatókönyv a hologramok valós felületek mögötti elzárásához. Renderelheti a hálókat, vagy renderelheti azok feldolgozott verzióit is, hogy megmutassa a felhasználónak, hogy a szoba mely területeit vizsgálja meg, mielőtt elkezdené az alkalmazás- vagy játékfunkciók biztosítását.

A kódminta akkor indítja el a folyamatot, amikor surface mesh-frissítéseket kap az előző szakaszban ismertetett eseménykezelőtől. Ebben a függvényben a kód fontos sora a felületi háló frissítésének hívása: ekkorra már feldolgoztuk a háló adatait, és a csúcspont és az index adatainak lekérésére készülünk, hogy a megfelelőnek látsszuk.

Forrás: 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());
}

A mintakód úgy lett kialakítva, hogy a SurfaceMesh adatosztály kezelje a hálós adatfeldolgozást és -renderelést. Ezek a hálók azok, amelyekről a RealtimeSurfaceMeshRenderer valójában térképet vezet. Mindegyik rendelkezik hivatkozással a kapott SpatialSurfaceMesh fájlra, így bármikor használhatja, amikor hozzá kell férnie a háló csúcspontjaihoz vagy indexpuffereihez, vagy átalakíthatja a hálót. Egyelőre frissítésre szorulóként jelöljük meg a hálót.

A SurfaceMesh.cpp fájlból:

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

Amikor legközelebb a hálót arra kérik, hogy rajzolja meg magát, először a jelzőt fogja ellenőrizni. Ha frissítésre van szükség, a csúcspont és az indexpufferek frissülnek a 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;
    }

Először a nyers adatpuffereket szerezzük be:

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;

Ezután direct3D-eszközpuffereket hozunk létre a HoloLens által biztosított hálóadatokkal:

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

MEGJEGYZÉS: Az előző kódrészletben használt CreateDirectXBuffer segédfüggvényhez tekintse meg a Surface-leképezési kódmintát: SurfaceMesh.cpp, GetDataFromIBuffer.h. Az eszközerőforrás létrehozása befejeződött, és a háló be van töltve, és készen áll a frissítésre és a renderelésre.

Felületi hálók frissítése és renderelése

A SurfaceMesh osztály egy speciális frissítési függvénnyel rendelkezik. Minden SpatialSurfaceMesh saját átalakítást használ, és a minta a SpatialStationaryReferenceFrame aktuális koordinátarendszerét használja az átalakítás beszerzéséhez. Ezután frissíti a modellállandó puffert a 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
        );
}

Amikor itt az ideje a felületi hálók renderelésének, a gyűjtemény renderelése előtt el kell végeznünk néhány előkészítő munkát. Beállítjuk az árnyékoló folyamatot az aktuális renderelési konfigurációhoz, és beállítjuk a bemeneti összeszerelő fázist. A CameraResources.cpp holografikus kamera segédosztálya már beállította a nézet-/vetítési állandó puffert.

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

Ha ez megtörtént, hurkot vonunk a hálóinkra, és elmondjuk mindegyiknek, hogy rajzolja meg magát. MEGJEGYZÉS: Ez a mintakód nem úgy van optimalizálva, hogy bármilyen frustum-selejtezést használjon, de ezt a funkciót fel kell vennie az alkalmazásba.

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

Az egyes hálók felelősek a csúcs- és indexpuffer, a stride és a modellátalakítás konstanspufferének beállításáért. A Windows Holographic alkalmazássablon forgó kockáihoz hasonlóan a sztereoszkopikus pufferekbe is instancing használatával renderelünk.

A SurfaceMesh::D raw-ból:

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

Megjelenítési lehetőségek Surface-leképezéssel

A Surface Mapping-kódminta kódot kínál a surface mesh-adatok csak occlusion-rendereléséhez és a surface mesh-adatok képernyőn történő rendereléséhez. A választott elérési út – vagy mindkettő – az alkalmazástól függ. Ebben a dokumentumban mindkét konfigurációt áttekintjük.

Elzáródási pufferek renderelése holografikus effektushoz

Először törölje az aktuális virtuális kamera renderelési célnézetét.

Az AppMain.cpp fájlból:

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

Ez egy "előmegjelenítési" passz. Itt létrehozunk egy elzáródási puffert úgy, hogy megkérjük a háló renderelőt, hogy csak a mélységet renderelje. Ebben a konfigurációban nem csatolunk renderelési célnézetet, és a háló renderelője nullptrre állítja a képpontárnyékoló fázisát, hogy a GPU ne fáradjon a képpontok rajzolására. A geometria a mélységi pufferbe lesz raszterizálva, és a grafikus folyamat ott fog leállni.

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

A Surface Mapping elzáródási pufferének további mélységi vizsgálatával hologramokat rajzolhatunk. Ebben a kódmintában a kockán lévő képpontokat más színnel jelenítjük meg, ha azok egy felület mögött találhatók.

Az AppMain.cpp fájlból:

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

A SpecialEffectPixelShader.hlsl kód alapján:

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

Megjegyzés:A GatherDepthLess rutinhoz tekintse meg a Surface Mapping kódmintáját: SpecialEffectPixelShader.hlsl.

Surface Mesh-adatok megjelenítése a kijelzőn

A felületi hálókat a sztereó kijelzőpufferekhez is megrajzolhatjuk. Úgy döntöttünk, hogy teljes arcokat rajzolunk világítással, de szabadon rajzolhat drótvázvázokat, feldolgozhatja a hálókat a renderelés előtt, textúratérképet alkalmazhat stb.

Itt a kódminta arra utasítja a mesh renderelőt, hogy rajzolja meg a gyűjteményt. Ezúttal nem ad meg csak mélységi átengedést, hanem egy képpontárnyékolót csatol, és befejezi a renderelési folyamatot az aktuális virtuális kamerához megadott célokkal.

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

Lásd még