DirectX의 공간 매핑

참고

이 문서는 레거시 WinRT 네이티브 API와 관련이 있습니다. 새 네이티브 앱 프로젝트의 경우 OpenXR API를 사용하는 것이 좋습니다.

이 항목에서는 유니버설 Windows 플랫폼 SDK로 패키지된 공간 매핑 샘플 애플리케이션에 대한 자세한 설명을 포함하여 DirectX 앱에서 공간 매핑을 구현하는 방법에 대해 설명합니다.

이 항목에서는 HolographicSpatialMapping UWP 코드 샘플의 코드를 사용합니다.

참고

이 문서의 코드 조각은 현재 C++ 홀로그램 프로젝트 템플릿에서 사용되는 C++17 규격 C++/WinRT 대신 C++/CX를 사용하는 방법을 보여 줍니다. 개념은 C++/WinRT 프로젝트에 해당하지만 코드를 번역해야 합니다.

디바이스 지원

기능 HoloLens(1세대) HoloLens 2 몰입형 헤드셋
공간 매핑 ✔️ ✔️

DirectX 개발 개요

공간 매핑을 위한 네이티브 애플리케이션 개발은 Windows.Perception.Spatial 네임스페이스의 API를 사용합니다. 이러한 API를 사용하면 공간 매핑 API가 Unity에서 노출되는 것과 동일한 방식으로 공간 매핑 기능을 완전히 제어할 수 있습니다.

Perception API

공간 매핑 개발을 위해 제공되는 기본 형식은 다음과 같습니다.

  • SpatialSurfaceObserver 는 사용자 근처 애플리케이션에서 지정한 공간 영역의 표면에 대한 정보를 SpatialSurfaceInfo 개체 형식으로 제공합니다.
  • SpatialSurfaceInfo 는 고유한 ID, 경계 볼륨 및 마지막 변경 시간을 포함하여 단일 기존 공간 표면을 설명합니다. 요청 시 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 Holographic 앱 템플릿 프로젝트에 추가하거나 위에서 언급한 코드 샘플을 탐색하여 따라갈 수 있습니다. 이 코드 샘플은 Windows Holographic 앱 템플릿을 기반으로 합니다.

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()를 호출합니다. 홀로그램 공간 매핑 코드 샘플의 컨텍스트에서 수행하는 방법은 다음과 같습니다. 액세스를 요청하기 직전에 지원이 확인됩니다.

SpatialSurfaceObserver::IsSupported() API는 SDK 버전 15063부터 사용할 수 있습니다. 필요한 경우 이 API를 사용하기 전에 프로젝트 대상을 플랫폼 버전 15063으로 다시 지정합니다.

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 Mapping 코드 샘플을 기반으로 하는 예제입니다. 자세한 내용은 이 페이지의 뒷부분에서 제공됩니다.

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 네임스페이스에는 SpatialCoordinateSystem에서 지정한 하나 이상의 볼륨을 관찰하는 SpatialSurfaceObserver 클래스가 포함됩니다. 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*/);

보기 frustum 또는 축이 정렬되지 않은 경계 상자와 같은 다른 경계 셰이프를 사용할 수도 있습니다.

의사 코드입니다.

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

표면 매핑 데이터를 사용할 수 없을 때 앱이 다르게 수행해야 하는 경우 SpatialPerceptionAccessStatus허용되지 않는 경우에 응답하는 코드를 작성할 수 있습니다. 예를 들어 해당 디바이스에 공간 매핑을 위한 하드웨어가 없기 때문에 몰입형 디바이스가 연결된 PC에서는 허용되지 않습니다. 이러한 디바이스의 경우 사용자 환경 및 디바이스 구성에 대한 정보를 위해 공간 단계를 대신 사용해야 합니다.

표면 메시 컬렉션 초기화 및 업데이트

표면 관찰자가 성공적으로 만들어진 경우 표면 메시 컬렉션을 계속 초기화할 수 있습니다. 여기서는 끌어오기 모델 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 도우미 함수는 SurfaceMesh.cpp GetDataFromIBuffer.h의 Surface 매핑 코드 샘플을 참조하세요. 이제 디바이스 리소스 만들기가 완료되었으며 메시가 로드되어 업데이트 및 렌더링할 준비가 된 것으로 간주됩니다.

표면 메시 업데이트 및 렌더링

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 Holographic 앱 템플릿의 회전 큐브와 마찬가지로 인스턴싱을 사용하여 스테레오스코픽 버퍼로 렌더링합니다.

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

추가 정보