Bagikan melalui


Pemetaan spasial di DirectX

Catatan

Artikel ini berkaitan dengan API asli WinRT warisan. Untuk proyek aplikasi asli baru, sebaiknya gunakan OpenXR API.

Topik ini menjelaskan cara menerapkan pemetaan spasial di aplikasi DirectX Anda, termasuk penjelasan terperinci tentang aplikasi sampel pemetaan spasial yang dipaketkan dengan SDK Platform Windows Universal.

Topik ini menggunakan kode dari sampel kode UWP HolographicSpatialMapping .

Catatan

Cuplikan kode dalam artikel ini saat ini menunjukkan penggunaan C++/CX daripada C++17-compliant C++/WinRT seperti yang digunakan dalam templat proyek holografik C++. Konsepnya setara untuk proyek C++/WinRT, meskipun Anda harus menerjemahkan kode.

Dukungan perangkat

Fitur HoloLens (generasi ke-1) HoloLens 2 Headset imersif
Pemetaan spasial ✔️ ✔️

Gambaran umum pengembangan DirectX

Pengembangan aplikasi asli untuk pemetaan spasial menggunakan API di namespace Windows.Perception.Spatial . API ini memberi Anda kontrol penuh atas fungsionalitas pemetaan spasial, dengan cara yang sama seperti API pemetaan spasial diekspos oleh Unity.

API Persepsi

Jenis utama yang disediakan untuk pengembangan pemetaan spasial adalah sebagai berikut:

  • SpatialSurfaceObserver menyediakan informasi tentang permukaan di wilayah ruang yang ditentukan aplikasi di dekat pengguna, dalam bentuk objek SpatialSurfaceInfo.
  • SpatialSurfaceInfo menjelaskan satu permukaan spasial yang ada, termasuk ID unik, volume pembatas, dan waktu perubahan terakhir. Ini akan menyediakan SpatialSurfaceMesh secara asinkron berdasarkan permintaan.
  • SpatialSurfaceMeshOptions berisi parameter yang digunakan untuk menyesuaikan objek SpatialSurfaceMesh yang diminta dari SpatialSurfaceInfo.
  • SpatialSurfaceMesh mewakili data jala untuk satu permukaan spasial. Data untuk posisi puncak, vertex normal, dan indeks segitiga terkandung dalam objek SpatialSurfaceMeshBuffer anggota.
  • SpatialSurfaceMeshBuffer membungkus satu jenis data jala.

Saat mengembangkan aplikasi menggunakan API ini, alur program dasar Anda akan terlihat seperti ini (seperti yang ditunjukkan dalam aplikasi sampel yang dijelaskan di bawah):

  • Menyiapkan SpatialSurfaceObserver Anda
    • Panggil RequestAccessAsync, untuk memastikan bahwa pengguna telah memberikan izin bagi aplikasi Anda untuk menggunakan kemampuan pemetaan spasial perangkat.
    • Membuat instans objek SpatialSurfaceObserver.
    • Panggil SetBoundingVolumes untuk menentukan wilayah ruang tempat Anda menginginkan informasi tentang permukaan spasial. Anda dapat mengubah wilayah ini di masa mendatang dengan memanggil fungsi ini lagi. Setiap wilayah ditentukan menggunakan SpatialBoundingVolume.
    • Daftar untuk peristiwa ObservedSurfacesChanged , yang akan diaktifkan setiap kali informasi baru tersedia tentang permukaan spasial di wilayah ruang yang telah Anda tentukan.
  • Proses peristiwa ObservedSurfacesChanged
    • Di penanganan aktivitas Anda, panggil GetObservedSurfaces untuk menerima peta objek SpatialSurfaceInfo. Dengan menggunakan peta ini, Anda dapat memperbarui catatan Anda tentang permukaan spasial mana yang ada di lingkungan pengguna.
    • Untuk setiap objek SpatialSurfaceInfo, Anda dapat meminta TryGetBounds untuk menentukan tingkat spasial permukaan, yang dinyatakan dalam sistem koordinat spasial yang Anda pilih.
    • Jika Anda memutuskan untuk meminta, jala untuk permukaan spasial, panggil TryComputeLatestMeshAsync. Anda dapat memberikan opsi yang menentukan kepadatan segitiga, dan format data jala yang dikembalikan.
  • Menerima dan memproses jala
    • Setiap panggilan ke TryComputeLatestMeshAsync akan mengembalikan satu objek SpatialSurfaceMesh secara asinkron.
    • Dari objek ini, Anda dapat mengakses objek SpatialSurfaceMeshBuffer yang terkandung, yang memberi Anda akses ke indeks segitiga, posisi puncak, dan vertex normal jala jika Anda memintanya. Data ini akan dalam format yang kompatibel langsung dengan API Direct3D 11 yang digunakan untuk merender jala.
    • Dari sini aplikasi Anda dapat secara opsional menganalisis atau memproses data jala, dan menggunakannya untuk penyajian dan raycasting fisika dan tabrakan.
    • Salah satu detail penting yang perlu diperhatikan adalah Anda harus menerapkan skala ke posisi puncak jala (misalnya dalam shader vertex yang digunakan untuk merender jala), untuk mengonversinya dari unit bilangan bulat yang dioptimalkan tempat mereka disimpan di buffer, menjadi meter. Anda dapat mengambil skala ini dengan memanggil VertexPositionScale.

Pemecahan Masalah

Panduan sampel kode Pemetaan Spasial

Sampel kode Pemetaan Spasial Holografik menyertakan kode yang dapat Anda gunakan untuk mulai memuat jala permukaan ke dalam aplikasi Anda, termasuk infrastruktur untuk mengelola dan merender jala permukaan.

Sekarang, kita berjalan melalui cara menambahkan kemampuan pemetaan permukaan ke aplikasi DirectX Anda. Anda dapat menambahkan kode ini ke proyek templat aplikasi Holografik Windows , atau Anda dapat mengikutinya dengan menelusuri sampel kode yang disebutkan di atas. Sampel kode ini didasarkan pada templat aplikasi Holografik Windows.

Menyiapkan aplikasi Anda untuk menggunakan kemampuan spatialPerception

Aplikasi Anda dapat menggunakan kemampuan pemetaan spasial. Ini diperlukan karena jala spasial adalah representasi lingkungan pengguna, yang mungkin dianggap sebagai data privat. Nyatakan kemampuan ini dalam file package.appxmanifest untuk aplikasi Anda. Berikut contohnya:

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

Kemampuan berasal dari namespace uap2 . Untuk mendapatkan akses ke namespace layanan ini dalam manifes Anda, sertakan sebagai atribut xlmns dalam <elemen Paket> . Berikut contohnya:

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

Periksa dukungan fitur pemetaan spasial

Windows Mixed Reality mendukung berbagai perangkat, termasuk perangkat, yang tidak mendukung pemetaan spasial. Jika aplikasi Anda dapat menggunakan pemetaan spasial, atau harus menggunakan pemetaan spasial, untuk menyediakan fungsionalitas, aplikasi harus memeriksa untuk memastikan pemetaan spasial didukung sebelum mencoba menggunakannya. Misalnya, jika pemetaan spasial diperlukan oleh aplikasi realitas campuran Anda, itu harus menampilkan pesan ke efek tersebut jika pengguna mencoba menjalankannya di perangkat tanpa pemetaan spasial. Atau, aplikasi Anda dapat merender lingkungan virtualnya sendiri sebagai pengganti lingkungan pengguna, memberikan pengalaman yang mirip dengan apa yang akan terjadi jika pemetaan spasial tersedia. Dalam hal apa pun, API ini memungkinkan aplikasi Anda untuk menyadari kapan aplikasi tidak akan mendapatkan data pemetaan spasial dan merespons dengan cara yang sesuai.

Untuk memeriksa perangkat saat ini untuk dukungan pemetaan spasial, pertama-tama pastikan kontrak UWP berada di tingkat 4 atau lebih besar lalu panggil SpatialSurfaceObserver::IsSupported(). Berikut cara melakukannya dalam konteks sampel kode Pemetaan Spasial Holografik . Dukungan diperiksa tepat sebelum meminta akses.

API SpatialSurfaceObserver::IsSupported() tersedia mulai dari SDK versi 15063. Jika perlu, targetkan ulang proyek Anda ke platform versi 15063 sebelum menggunakan API ini.

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

Ketika kontrak UWP kurang dari tingkat 4, aplikasi harus melanjutkan seolah-olah perangkat mampu melakukan pemetaan spasial.

Meminta akses ke data pemetaan spasial

Aplikasi Anda perlu meminta izin untuk mengakses data pemetaan spasial sebelum mencoba membuat pengamat permukaan apa pun. Berikut adalah contoh berdasarkan sampel kode Pemetaan Permukaan kami, dengan detail lebih lanjut yang disediakan nanti di halaman ini:

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

Membuat pengamat permukaan

Namespace layanan Windows::P erception::Spatial::Surfaces mencakup kelas SpatialSurfaceObserver , yang mengamati satu atau beberapa volume yang Anda tentukan dalam SpatialCoordinateSystem. Gunakan instans SpatialSurfaceObserver untuk mengakses data surface mesh secara real time.

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

Seperti yang disebutkan di bagian sebelumnya, Anda harus meminta akses ke data pemetaan spasial sebelum aplikasi Anda dapat menggunakannya. Akses ini diberikan secara otomatis pada 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();

Selanjutnya, Anda perlu mengonfigurasi pengamat permukaan untuk mengamati volume pembatas tertentu. Di sini, kami mengamati kotak yang berukuran 20x20x5 meter, berpusat di asal sistem koordinat.

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

Anda dapat mengatur beberapa volume pembatas sebagai gantinya.

Ini adalah pseudocode:

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

Anda juga dapat menggunakan bentuk pembatas lainnya - seperti frustum tampilan, atau kotak pembatas yang tidak diratakan sumbu.

Ini adalah pseudocode:

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

Jika aplikasi Anda perlu melakukan sesuatu yang berbeda saat data pemetaan permukaan tidak tersedia, Anda dapat menulis kode untuk menanggapi kasus di mana SpatialPerceptionAccessStatus tidak Diizinkan - misalnya, itu tidak akan diizinkan pada PC dengan perangkat imersif yang terpasang karena perangkat tersebut tidak memiliki perangkat keras untuk pemetaan spasial. Untuk perangkat ini, Anda harus mengandalkan tahap spasial untuk informasi tentang lingkungan pengguna dan konfigurasi perangkat.

Menginisialisasi dan memperbarui koleksi surface mesh

Jika pengamat permukaan berhasil dibuat, kita dapat terus menginisialisasi koleksi surface mesh kita. Di sini, kita menggunakan API model penarikan untuk mendapatkan set permukaan yang diamati saat ini segera:

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

Ada juga model pendorongan yang tersedia untuk mendapatkan data surface mesh. Anda bebas merancang aplikasi untuk hanya menggunakan model penarikan jika Anda memilih, dalam hal ini Anda akan sering melakukan polling untuk data - katakanlah, sekali per bingkai - atau selama periode waktu tertentu, seperti selama penyiapan game. Jika demikian, kode di atas adalah apa yang Anda butuhkan.

Dalam sampel kode kami, kami memilih untuk menunjukkan penggunaan kedua model untuk tujuan pedagogis. Di sini, kami berlangganan peristiwa untuk menerima data surface mesh terbaru setiap kali sistem mengenali perubahan.

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

Sampel kode kami juga dikonfigurasi untuk menanggapi peristiwa ini. Mari kita berjalan melalui bagaimana kita melakukan hal ini.

CATATAN: Ini mungkin bukan cara paling efisien bagi aplikasi Anda untuk menangani data jala. Kode ini ditulis untuk kejelasan dan tidak dioptimalkan.

Data surface mesh disediakan dalam peta baca-saja yang menyimpan objek SpatialSurfaceInfo menggunakan Platform::Guids sebagai nilai kunci.

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

Untuk memproses data ini, kami mencari nilai kunci pertama yang tidak ada dalam koleksi kami. Detail tentang bagaimana data disimpan di aplikasi sampel kami akan disajikan nanti dalam topik ini.

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

Kami juga harus menghapus jala permukaan yang ada di koleksi surface mesh kami, tetapi itu tidak ada dalam koleksi sistem lagi. Untuk melakukannya, kita perlu melakukan sesuatu yang mirip dengan kebalikan dari apa yang baru saja kita tunjukkan untuk menambahkan dan memperbarui jala; kami mengulang koleksi aplikasi kami, dan memeriksa untuk melihat apakah Guid yang kami miliki ada di koleksi sistem. Jika tidak ada dalam koleksi sistem, kami menghapusnya dari kami.

Dari penanganan aktivitas kami di AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

Implementasi pemangkasan jala di 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);
    }
}

Memperoleh dan menggunakan buffer data surface mesh

Mendapatkan informasi surface mesh sem mudahnya menarik pengumpulan data dan memproses pembaruan ke koleksi tersebut. Sekarang, kita akan menjelaskan secara rinci tentang bagaimana Anda dapat menggunakan data.

Dalam contoh kode kami, kami memilih untuk menggunakan jala permukaan untuk penyajian. Ini adalah skenario umum untuk menempati hologram di belakang permukaan dunia nyata. Anda juga dapat merender jala, atau merender versi yang diproses, untuk menunjukkan kepada pengguna area ruangan apa yang dipindai sebelum Anda mulai menyediakan fungsionalitas aplikasi atau game.

Sampel kode memulai proses ketika menerima pembaruan surface mesh dari penanganan aktivitas yang kami jelaskan di bagian sebelumnya. Baris kode penting dalam fungsi ini adalah panggilan untuk memperbarui jala permukaan: pada saat ini kita telah memproses info jala, dan kita akan mendapatkan data puncak dan indeks untuk digunakan sesuai keinginan.

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

Kode sampel kami dirancang agar kelas data, SurfaceMesh, menangani pemrosesan dan penyajian data jala. Jala inilah yang sebenarnya disimpan oleh RealtimeSurfaceMeshRenderer . Masing-masing memiliki referensi ke SpatialSurfaceMesh asalnya, sehingga Anda dapat menggunakannya kapan saja Anda perlu mengakses puncak jala atau buffer indeks, atau mendapatkan transformasi untuk jala. Untuk saat ini, kami menandai jala sebagai memerlukan pembaruan.

Dari SurfaceMesh.cpp:

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

Lain kali jala diminta untuk menggambar sendiri, itu akan memeriksa bendera terlebih dahulu. Jika pembaruan diperlukan, puncak dan buffer indeks akan diperbarui pada GPU.

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

Pertama, kami memperoleh buffer data mentah:

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;

Kemudian, kami membuat buffer perangkat Direct3D dengan data jala yang disediakan oleh 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;
}

CATATAN: Untuk fungsi pembantu CreateDirectXBuffer yang digunakan dalam cuplikan sebelumnya, lihat sampel kode Pemetaan Permukaan: SurfaceMesh.cpp, GetDataFromIBuffer.h. Sekarang pembuatan sumber daya perangkat selesai, dan jala dianggap dimuat dan siap untuk diperbarui dan dirender.

Memperbarui dan merender jala permukaan

Kelas SurfaceMesh kami memiliki fungsi pembaruan khusus. Setiap SpatialSurfaceMesh memiliki transformasinya sendiri, dan sampel kami menggunakan sistem koordinat saat ini untuk SpatialStationaryReferenceFrame kami untuk memperoleh transformasi. Kemudian memperbarui buffer konstan model pada 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
        );
}

Ketika saatnya untuk merender jala permukaan, kita melakukan beberapa pekerjaan persiapan sebelum merender koleksi. Kami menyiapkan alur shader untuk konfigurasi penyajian saat ini, dan kami menyiapkan tahap perakit input. Kelas pembantu kamera holografik CameraResources.cpp sudah menyiapkan buffer konstanta tampilan/proyeksi sekarang.

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

Setelah ini selesai, kita mengulangi jala kita dan menyuruh masing-masing untuk menggambar dirinya sendiri. CATATAN: Kode sampel ini tidak dioptimalkan untuk menggunakan segala jenis pemusnahan frustum, tetapi Anda harus menyertakan fitur ini di aplikasi Anda.

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

Jala individu bertanggung jawab untuk menyiapkan buffer verteks dan indeks, langkah, dan model mengubah buffer konstanta. Seperti halnya kubus berputar di templat aplikasi Holografik Windows, kami merender ke buffer stereoskopis menggunakan instancing.

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

Pilihan penyajian dengan Pemetaan Permukaan

Sampel kode Pemetaan Permukaan menawarkan kode untuk penyajian data surface mesh khusus oklusi, dan untuk penyajian data surface mesh di layar. Jalur mana yang Anda pilih - atau keduanya - tergantung pada aplikasi Anda. Kita akan menelusuri kedua konfigurasi dalam dokumen ini.

Penyajian buffer oklusi untuk efek holografik

Mulailah dengan menghapus tampilan target render untuk kamera virtual saat ini.

Dari AppMain.cpp:

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

Ini adalah pass "pra-rendering". Di sini, kami membuat buffer oklusi dengan meminta perender jala untuk merender hanya kedalaman. Dalam konfigurasi ini, kami tidak melampirkan tampilan target render, dan perender jala mengatur tahap shader piksel ke nullptr sehingga GPU tidak repot-repot menggambar piksel. Geometri akan dirasterisasi ke buffer kedalaman, dan alur grafis akan berhenti di sana.

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

Kita dapat menggambar hologram dengan uji kedalaman ekstra terhadap buffer oklusi Pemetaan Permukaan. Dalam sampel kode ini, kami merender piksel pada kubus warna yang berbeda jika berada di belakang permukaan.

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

Berdasarkan kode dari 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);
}

Catatan: Untuk rutinitas GatherDepthLess kami, lihat sampel kode Pemetaan Permukaan: SpecialEffectPixelShader.hlsl.

Merender data surface mesh ke tampilan

Kita juga bisa menggambar jala permukaan ke buffer layar stereo. Kami memilih untuk menggambar wajah penuh dengan pencahayaan, tetapi Anda bebas menggambar wireframe, memproses jala sebelum merender, menerapkan peta tekstur, dan sebagainya.

Di sini, sampel kode kami memberi tahu perender jala untuk menggambar koleksi. Kali ini kami tidak menentukan pass kedalaman saja, itu akan melampirkan shader piksel dan menyelesaikan alur penyajian menggunakan target yang kami tentukan untuk kamera virtual saat ini.

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

Lihat juga