DirectX 中的空間對應

注意

本文與舊版 WinRT 原生 API 相關。 針對新的原生應用程式專案,我們建議使用 OpenXR API

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

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

注意

本文中的程式碼片段目前示範如何使用 C++/CX,而不是 C++17 相容的 C++/WinRT,如 C++ 全像攝影專案範本中所示。 雖然您需要翻譯程式碼,但概念相當於 C++/WinRT 專案。

裝置支援

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

DirectX 開發概觀

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

Perception API

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

  • SpatialSurfaceObserver 會以 SpatialSurfaceInfo 物件的形式,提供使用者附近應用程式指定空間區域中表面的相關資訊。
  • SpatialSurfaceInfo 描述單一外部空間表面,包括唯一識別碼、周框磁片區和上次變更的時間。 它會在要求時以非同步方式提供 SpatialSurfaceMesh。
  • SpatialSurfaceMeshOptions 包含用來自訂 SpatialSurfaceInfo 所要求的 SpatialSurfaceMesh 物件的參數。
  • SpatialSurfaceMesh 代表單一空間表面的網格資料。 頂點位置、頂點常態和三角形索引的資料包含在成員 SpatialSurfaceMeshBuffer 物件中。
  • SpatialSurfaceMeshBuffer 會包裝單一類型的網格資料。

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

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

疑難排解

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

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

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

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

您的應用程式可以使用空間對應功能。 這是必要的,因為空間網格是使用者環境的標記法,這可視為私人資料。 在 app 的 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支援各種不同的裝置,包括不支援空間對應的裝置。 如果您的 app 可以使用空間對應,或必須使用空間對應來提供功能,它應該先檢查是否支援空間對應,再嘗試使用它。 例如,如果您的混合實境應用程式需要空間對應,如果使用者嘗試在裝置上執行空間對應,它應該會顯示訊息給該效果。 或者,您的應用程式可以轉譯自己的虛擬環境來取代使用者的環境,並提供類似空間對應可用時會發生什麼情況的體驗。 在任何情況下,此 API 都可讓您的應用程式知道何時不會取得空間對應資料,並以適當的方式回應。

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

SpatialSurfaceObserver::IsSupported () API 可從 SDK 15063 版開始提供。 如有必要,請先將您的專案重定為平臺版本 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*/)
            );

如果您的 app 在無法使用表面對應資料時需要以不同的方式執行任何動作,您可以撰寫程式碼來回應 SpatialPerceptionAccessStatus 不是允許的情況,例如,因為這些裝置沒有空間對應的硬體,所以不會在附加沈浸式裝置的電腦上加以 允許 。 對於這些裝置,您應該改為依賴空間階段,以取得使用者環境和裝置設定的相關資訊。

初始化並更新表面網格集合

如果已成功建立表面觀察者,我們可以繼續初始化表面網格集合。 在這裡,我們會使用提取模型 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)
            );

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

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

表面網格資料是在唯讀地圖中提供,以使用 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
        );
}

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

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 對應遮蔽緩衝區繪製具有額外深度測試的全像投影。 在此程式碼範例中,如果立方體位於表面後方,我們會以不同的色彩呈現圖元。

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

另請參閱