共用方式為


DirectX 中的空間對應

注意事項

本文與舊版 WinRT 原生 API 相關。 對於新的原生應用程式項目,建議您使用 OpenXR API

本主題描述如何在 DirectX 應用程式中實作空間對應,包括使用 通用 Windows 平台 SDK 封裝的空間對應範例應用程式的詳細說明。

本主題使用 HolographicSpatialMapping UWP 程式代碼範例中的程式碼。

注意事項

本文中的代碼段目前示範如何使用C++/CX,而不是如C++ 全像攝影項目範本中所使用的C++17 相容C++/WinRT。 這些概念相當於C++/WinRT 專案,但您必須翻譯程序代碼。

裝置支援

功能 HoloLens (第 1 代) HoloLens 2 沉浸式頭戴裝置
空間對應 ✔️ ✔️

DirectX 開發概觀

空間對應的原生應用程式開發會使用 Windows.Perception.Spatial 命名空間中的 API。 這些 API 可讓您完全控制空間對應功能,其方式與 Unity 公開空間對應 API 的方式相同。

感知 API

為空間對應開發提供的主要類型如下:

  • SpatialSurfaceObserver 會以 SpatialSurfaceInfo 物件的形式,提供使用者附近應用程式指定空間區域中表面的相關信息。
  • SpatialSurfaceInfo 描述單一現存空間表面,包括唯一標識碼、周框磁碟區和上次變更的時間。 它會在要求時以異步方式提供 SpatialSurfaceMesh。
  • SpatialSurfaceMeshOptions 包含用來自定義從 SpatialSurfaceInfo 要求之 SpatialSurfaceMesh 物件的參數。
  • SpatialSurfaceMesh 代表單一空間表面的網格數據。 頂點位置、頂點標準和三角形索引的數據包含在成員 SpatialSurfaceMeshBuffer 物件中。
  • SpatialSurfaceMeshBuffer 會包裝單一類型的網格數據。

使用這些 API 開發應用程式時,您的基本程式流程看起來會像這樣 (,如下列範例應用程式所述) :

  • 設定 SpatialSurfaceObserver
    • 呼叫 RequestAccessAsync,以確保使用者已為您的應用程式授與許可權,以使用裝置的空間對應功能。
    • 具現化 SpatialSurfaceObserver 物件。
    • 呼叫 SetBoundingVolumes 來指定您想要空間表面相關信息的空間區域。 您可以在未來再次呼叫此函式來修改這些區域。 每個區域都是使用 SpatialBoundingVolume 來指定。
    • 註冊 ObservedSurfacesChanged 事件,每當有關於您所指定空間區域中空間表面的新資訊可用時,就會引發此事件。
  • 處理 ObservedSurfacesChanged 事件
    • 在事件處理程式中,呼叫 GetObservedSurfaces 以接收 SpatialSurfaceInfo 對象的對應。 使用此對應,您可以更新用戶 環境中存在哪些空間表面的記錄。
    • 針對每個 SpatialSurfaceInfo 物件,您可以查詢 TryGetBounds 來判斷表面的空間範圍,以您選擇 的空間座標系統 表示。
    • 如果您決定要求空間介面的網格,請呼叫 TryComputeLatestMeshAsync。 您可以提供選項來指定三角形的密度,以及所傳回網格數據的格式。
  • 接收和處理網格
    • 每次呼叫 TryComputeLatestMeshAsync 都會以異步方式傳回一個 SpatialSurfaceMesh 物件。
    • 您可以從這個物件存取包含的 SpatialSurfaceMeshBuffer 物件,這可讓您存取網格的三角形索引、頂點位置和頂點法。 此數據的格式會直接與用於轉譯網格的 Direct3D 11 API 相容。
    • 從這裡,您的應用程式可以選擇性地分析或 處理 網格數據,並將其用於 轉譯 和物理 光線傳播和碰撞
    • 要注意的一個重要詳細數據是,您必須將尺規套用至網格頂點位置 (例如,在用來轉譯網格) 的頂點著色器中,將它們從儲存在緩衝區中的優化整數單位轉換成公尺。 您可以呼叫 VertexPositionScale 來擷取此縮放比例。

疑難排解

空間對應程式代碼範例逐步解說

全像攝影空間對應程式代碼範例包含可用來開始將表面網格載入應用程式的程序代碼,包括用於管理和轉譯表面網格的基礎結構。

現在,我們將逐步解說如何將表面對應功能新增至您的 DirectX 應用程式。 您可以將此程式代碼新增至 您的 Windows 全像攝影應用程式範本 專案,也可以瀏覽上述程式代碼範例來跟著做。 此程式代碼範例是以 Windows 全像攝影應用程式範本為基礎。

設定您的應用程式以使用 spatialPerception 功能

您的應用程式可以使用空間對應功能。 這是必要的,因為空間網格是用戶環境的表示法,可能會被視為私人數據。 在您應用程式的 package.appxmanifest 檔案中宣告此功能。 以下為範例:

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

此功能來自 uap2 命名空間。 若要在指令清單中取得此命名空間的存取權,請將其包含為 Package> 元素中的 <xlmns 屬性。 以下為範例:

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

檢查空間對應功能支援

Windows Mixed Reality 支援各種裝置,包括不支援空間對應的裝置。 如果您的應用程式可以使用空間對應,或必須使用空間對應來提供功能,它應該先檢查以確定空間對應受到支援,然後再嘗試使用它。 例如,如果您的混合實境應用程式需要空間對應,當使用者嘗試在沒有空間對應的裝置上執行時,它應該會顯示該效果的訊息。 或者,您的應用程式可以呈現自己的虛擬環境來取代用戶的環境,提供類似空間對應可用時所發生的體驗。 在任何情況下,此 API 可讓您的應用程式知道何時不會取得空間對應數據,並以適當的方式回應。

若要檢查目前的裝置是否有空間對應支援,請先確定 UWP 合約位於層級 4 或更高,然後呼叫 SpatialSurfaceObserver::IsSupported () 。 以下說明如何在 全像攝影空間對 應程式代碼範例的內容中這麼做。 在要求存取之前,會先檢查支援。

從 SDK 15063 版開始,已提供 SpatialSurfaceObserver::IsSupported () API。 如有必要,請先將項目複位為平臺 15063 版,再使用此 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 ...

當UWP合約小於層級4時,應用程式應該會繼續進行,就像裝置能夠執行空間對應一樣。

要求存取空間對應數據

您的應用程式必須先要求存取空間對應數據的許可權,才能嘗試建立任何表面觀察者。 以下是以 Surface 對應程式碼範例為基礎的範例,稍後會在此頁面上提供更多詳細數據:

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

建立表面觀察者

Windows::P erception::Spatial::Surfaces 命名空間包含 SpatialSurfaceObserver 類別,它會觀察您在 SpatialCoordinateSystem 中指定的一或多個磁碟區。 使用 SpatialSurfaceObserver 實 例即時存取表面網格數據。

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;

如上一節所述,您必須先要求空間對應數據的存取權,您的應用程式才能使用它。 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();

接下來,您必須設定表面觀察者來觀察特定的周框磁碟區。 在這裡,我們觀察到一個 20x20x5 公尺的方塊,其位於座標系統的原點。

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

您可以改為設定多個周框磁碟區。

這是虛擬程式代碼:

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

您也可以使用其他周框圖形,例如檢視範圍或未對齊軸的周框方塊。

這是虛擬程式代碼:

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

如果您的應用程式在介面對應資料無法使用時,需要以不同的方式執行任何動作,您可以撰寫程式代碼來回應 SpatialPerceptionAccessStatus不允許的情況 -例如,在已連結沉浸式裝置的電腦上不允許它,因為這些裝置沒有空間對應的硬體。 針對這些裝置,您應該改為依賴空間階段來取得用戶環境和裝置設定的相關信息。

初始化和更新 Surface mesh 集合

如果成功建立表面觀察者,我們可以繼續初始化表面網格集合。 在這裡,我們會使用提取模型 API 立即取得一組目前觀察到的表面:

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

另外還有一個推送模型可用來取得表面網格數據。 如果您選擇,您可以自由地將應用程式設計為只使用提取模型,在此情況下,您會經常輪詢數據 ,例如,每個畫面一次,或在特定時間期間,例如在遊戲設定期間。 如果是,您需要上述程序代碼。

在我們的程式代碼範例中,我們選擇示範如何針對教學課程用途使用這兩個模型。 在這裡,我們會訂閱事件,以在系統辨識變更時接收最新的表面網格數據。

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

我們的程式代碼範例也會設定為回應這些事件。 讓我們逐步解說如何執行這項作。

注意: 這可能不是應用程式處理網格數據最有效率的方式。 為了清楚起見,撰寫此程式代碼並未優化。

Surface mesh 數據是在唯讀地圖中提供,以使用 Platform::Guids 作為索引鍵值來儲存 SpatialSurfaceInfo 物件。

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

若要處理此數據,我們會先尋找不在集合中的索引鍵值。 本主題稍後會提供如何將數據儲存在我們的範例應用程式中的詳細數據。

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

我們也必須移除表面網格集合中,但已不在系統集合中的表面網格。 若要這樣做,我們需要執行類似我們剛才在新增和更新網格時所示範的相反動作;我們會迴圈查看應用程式的集合,並檢查我們擁有的 Guid 是否在系統集合中。 如果它不在系統集合中,我們會從我們的中移除它。

從 AppMain.cpp 中的事件處理程式:

m_meshCollection->PruneMeshCollection(surfaceCollection);

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

取得和使用表面網格數據緩衝區

取得表面網格資訊就像提取數據收集和處理該集合的更新一樣簡單。 現在,我們將詳細說明如何使用數據。

在我們的程式代碼範例中,我們選擇使用表面網格進行轉譯。 這是在真實世界表面後方遮蔽全像投影的常見案例。 您也可以轉譯網格,或轉譯已處理的網格版本,向用戶顯示在開始提供應用程式或遊戲功能之前,會掃描會議室的哪些區域。

程式代碼範例會在從我們在上一節中所述的事件處理程式接收表面網格更新時啟動程式。 此函式中的重要程式碼行是更新介面 網格的呼叫:此時我們已經處理網格資訊,而且我們即將取得頂點和索引數據,以視情況使用。

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

我們的範例程式代碼是設計來讓數據類別 SurfaceMesh 處理網格數據處理和轉譯。 這些網格是 RealtimeSurfaceMeshRenderer 實際保存的對應。 每一個都有其來源 SpatialSurfaceMesh 的參考,因此您可以隨時使用它來存取網格頂點或索引緩衝區,或取得網格的轉換。 目前,我們會將網格標示為需要更新。

從 SurfaceMesh.cpp:

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

下次要求網格自行繪製時,它會先檢查旗標。 如果需要更新,GPU 上的頂點和索引緩衝區將會更新。

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

首先,我們會取得原始數據緩衝區:

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;

然後,我們會使用 HoloLens 所提供的網格數據來建立 Direct3D 裝置緩衝區:

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

注意: 如需上一個代碼段中使用的 CreateDirectXBuffer 協助程式函式,請參閱 Surface 對應程式碼範例:SurfaceMesh.cpp、GetDataFromIBuffer.h。 現在裝置資源建立已完成,且會將網格視為已載入並準備好進行更新和轉譯。

更新和轉譯表面網格

我們的 SurfaceMesh 類別具有特製化的更新函式。 每個 SpatialSurfaceMesh 都有自己的轉換,而我們的範例會使用 SpatialStationaryReferenceFrame 的目前座標系統來取得轉換。 然後它會更新 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
        );
}

當您要轉譯表面網格時,我們會先進行一些準備工作,再轉譯集合。 我們會設定目前轉譯組態的著色器管線,並設定輸入組合器階段。 全像攝影攝影機協助程序類別 CameraResources.cpp 目前已設定檢視/投影常數緩衝區。

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

完成此動作后,我們會在網格上執行迴圈,並告訴每個網格自行繪製。 注意: 此範例程式代碼未優化為使用任何種類的排除,但您應該在應用程式中包含此功能。

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

個別網格負責設定頂點和索引緩衝區、大步和模型轉換常數緩衝區。 如同 Windows 全像攝影應用程式範本中的旋轉立方體,我們會使用實例呈現到立體緩衝區。

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

使用 Surface 對應的轉譯選項

Surface 對應程式碼範例提供僅限遮蔽轉譯表面網狀結構數據,以及在螢幕上轉譯表面網格數據的程序代碼。 您選擇哪一個路徑或兩者皆相依於您的應用程式。 我們將在本檔中逐步解說這兩個設定。

呈現全像效果的遮蔽緩衝區

首先,清除目前虛擬相機的轉譯目標檢視。

從 AppMain.cpp:

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

這是「預先轉譯」階段。 在這裡,我們會藉由要求網格轉譯器只轉譯深度來建立遮蔽緩衝區。 在此組態中,我們不會附加轉譯目標檢視,而且網格轉譯器會將圖元著色器階段設定為 nullptr ,讓 GPU 不會干擾繪製圖元。 幾何會點陣化至深度緩衝區,而圖形管線會在該處停止。

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

我們可以針對 Surface Mapping 遮蔽緩衝區,使用額外的深度測試來繪製全像投影。 在此程式代碼範例中,如果立方體位於表面後方,我們會將立方體上的圖元呈現不同的色彩。

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

根據來自 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);
}

注意: 如需 GatherDepthLess 例程,請參閱 Surface 對應程式碼範例:SpecialEffectPixelShader.hlsl。

將表面網格數據轉譯到顯示器

我們也可以直接將表面網格繪製到立體聲顯示緩衝區。 我們選擇使用光源繪製完整的臉部,但您可以隨意繪製線框、在轉譯之前處理網格、套用紋理地圖等等。

在這裡,我們的程式代碼範例會告知網格轉譯器繪製集合。 這次我們不會指定僅限深度傳遞,它會附加圖元著色器,並使用我們為目前虛擬相機指定的目標來完成轉譯管線。

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

另請參閱