Пространственное сопоставление в DirectX

Примечание

Эта статья относится к устаревшим собственным API WinRT. Для новых проектов собственных приложений рекомендуется использовать API OpenXR.

В этом разделе описывается, как реализовать пространственное сопоставление в приложении DirectX, включая подробное описание примера приложения пространственного сопоставления, упаковаемого с помощью пакета SDK для универсальная платформа Windows.

В этом разделе используется код из примера кода UWP HolographicSpatialMapping .

Примечание

Фрагменты кода в этой статье в настоящее время демонстрируют использование C++/CX, а не C++17-совместимых C++/WinRT, как используется в шаблоне голографического проекта C++. Эти понятия эквивалентны для проекта C++/WinRT, хотя вам потребуется преобразовать код.

Поддержка устройств

Возможность HoloLens (1-го поколения) HoloLens 2 Иммерсивные гарнитуры
пространственное сопоставление ✔️ ✔️

Обзор разработки в DirectX

Разработка собственных приложений для пространственного сопоставления использует API в пространстве имен Windows.Perception.Spatial . Эти API предоставляют полный контроль над функциями пространственного сопоставления таким же образом, как api пространственного сопоставления предоставляются Unity.

API распознавания

Основными типами, предоставляемыми для разработки пространственного сопоставления, являются следующие:

  • SpatialSurfaceObserver предоставляет сведения о поверхностях в областях, указанных приложением, рядом с пользователем, в виде объектов SpatialSurfaceInfo.
  • SpatialSurfaceInfo описывает одну экстентную пространственную поверхность, включая уникальный идентификатор, ограничивающий том и время последнего изменения. Он будет предоставлять SpatialSurfaceMesh асинхронно по запросу.
  • SpatialSurfaceMeshOptions содержит параметры, используемые для настройки объектов SpatialSurfaceMesh, запрошенных из SpatialSurfaceInfo.
  • SpatialSurfaceMesh представляет данные сетки для одной пространственной поверхности. Данные для позиций вершин, норм вершин и индексов треугольников содержатся в объектах SpatialSurfaceMeshBuffer.
  • SpatialSurfaceMeshBuffer упаковывает один тип данных сетки.

При разработке приложения с помощью этих API базовый поток программы будет выглядеть следующим образом (как показано в примере приложения, описанном ниже):

  • Настройка объекта SpatialSurfaceObserver
    • ВызовИте RequestAccessAsync, чтобы убедиться, что пользователь предоставил приложению разрешение на использование возможностей пространственного сопоставления устройства.
    • Создайте экземпляр объекта SpatialSurfaceObserver.
    • ВызовИте SetBoundingVolumes, чтобы указать области пространства, в которых требуется информация о пространственных поверхностях. Вы можете изменить эти регионы в будущем, снова вызвав эту функцию. Каждый регион задается с помощью SpatialBoundingVolume.
    • Зарегистрируйтесь для события ObservedSurfacesChanged , которое будет возникать при появлении новых сведений о пространственных поверхностях в указанных регионах пространства.
  • События Process ObservedSurfacesChanged
    • В обработчике событий вызовите Метод GetObservedSurfaces для получения карты объектов SpatialSurfaceInfo. С помощью этой карты можно обновить записи о том, какие пространственные поверхности существуют в среде пользователя.
    • Для каждого объекта SpatialSurfaceInfo можно запросить TryGetBounds , чтобы определить пространственные экстенты поверхности, выраженные в выбранной пространственной системе координат .
    • Если вы решите запросить, сетка для пространственной поверхности вызовите TryComputeLatestMeshAsync. Вы можете указать параметры, определяющие плотность треугольников и формат возвращаемых данных сетки.
  • Сетка получения и обработки
    • Каждый вызов TryComputeLatestMeshAsync будет асинхронно возвращать один объект SpatialSurfaceMesh.
    • Из этого объекта можно получить доступ к содержащимся объектам SpatialSurfaceMeshBuffer, которые предоставляют доступ к индексам треугольников, позициям вершин и нормали вершин сетки при запросе. Эти данные будут иметь формат, совместимый напрямую с API Direct3D 11 , используемыми для отрисовки сеток.
    • Здесь приложение может дополнительно анализировать или обрабатывать данные сетки, а также использовать его для отрисовки и физики лучей и столкновения.
    • Важно отметить, что необходимо применить масштаб к позициям вершин сетки (например, в вершинном шейдере, используемом для отрисовки сетки), чтобы преобразовать их из оптимизированных целых чисел, в которых они хранятся в буфере, к счетчикам. Чтобы получить этот масштаб, вызовите VertexPositionScale.

Устранение неполадок

  • Не забудьте масштабировать позиции вершин сетки в вершинном шейдере, используя масштаб, возвращаемый SpatialSurfaceMesh.VertexPositionScale

Пошаговое руководство по примеру кода пространственного сопоставления

Пример кода голографического пространственного сопоставления содержит код, который можно использовать для начала загрузки сеток поверхности в приложение, включая инфраструктуру для управления сетками поверхности и отрисовки.

Теперь мы рассмотрим, как добавить возможность сопоставления поверхностей в приложение DirectX. Этот код можно добавить в проект шаблона приложения Windows Holographic или просмотреть приведенный выше пример кода. Этот пример кода основан на шаблоне приложения Windows Holographic.

Настройка приложения для использования возможности spatialPerception

Приложение может использовать функцию пространственного сопоставления. Это необходимо, так как пространственная сетка является представлением среды пользователя, которая может считаться частными данными. Объявите эту возможность в файле package.appxmanifest для приложения. Ниже приведен пример:

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

Возможность поступает из пространства имен uap2 . Чтобы получить доступ к этому пространству имен в манифесте, добавьте его в качестве атрибута xlmns в <элемент Package> . Ниже приведен пример:

<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(). Вот как это сделать в контексте примера кода голографического пространственного сопоставления . Поддержка проверяется непосредственно перед запросом доступа.

API SpatialSurfaceObserver::IsSupported() доступен начиная с пакета 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 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 включает класс 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*/);

Кроме того, можно использовать другие ограничивающие фигуры, такие как вид frustum или ограничивающий прямоугольник, не выровненный по оси.

Это псевдокод:

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

Если приложению нужно сделать что-либо по-другому, если данные сопоставления поверхностей недоступны, можно написать код, чтобы ответить на ситуацию, когда SpatialPerceptionAccessStatus не допускается , например, на компьютерах с иммерсивными устройствами, подключенными, так как эти устройства не имеют оборудования для пространственного сопоставления. Для этих устройств следует использовать пространственный этап для получения сведений о среде пользователя и конфигурации устройства.

Инициализация и обновление коллекции сетки surface

Если наблюдатель поверхности успешно создан, мы можем продолжать инициализировать коллекцию сетки поверхности. Здесь мы используем 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)
            );

Наш пример кода также настроен для реагирования на эти события. Давайте рассмотрим, как это сделать.

ЗАМЕТКА: Это может быть не самым эффективным способом обработки данных сетки в приложении. Этот код написан для ясности и не оптимизирован.

Данные сетки поверхности предоставляются на карте только для чтения, в которой объекты SpatialSurfaceInfo хранятся с помощью Platform::Guids в качестве ключевых значений.

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

Получение и использование буферов данных сетки поверхности

Получение сведений о сетке поверхности было так же просто, как извлечение сбора и обработки обновлений для этой коллекции. Теперь мы подробно рассмотрим, как можно использовать данные.

В нашем примере кода мы решили использовать сетки поверхности для отрисовки. Это распространенный сценарий для occluding голограмм за реальными поверхностями. Вы также можете отобразить сетки или отрисовку обработанных версий, чтобы показать пользователю, какие области комнаты сканируются перед началом предоставления функциональных возможностей приложения или игры.

Пример кода запускает процесс при получении обновлений сетки поверхности от обработчика событий, описанного в предыдущем разделе. Важной строкой кода в этой функции является вызов обновления сетки поверхности: к этому времени мы уже обработали сведения о сетке, и мы собираемся получить вершины и индексные данные для использования, как мы видим.

Из 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;

Затем мы создадим буферы устройств Direct3D с данными сетки, предоставляемыми 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;
}

ЗАМЕТКА: Вспомогательные функции 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 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);

Мы можем нарисовать голограммы с дополнительным тестом глубины для буфера окклюзии сопоставления поверхностей. В этом примере кода мы отрисовываем пиксели куба другим цветом, если они находятся за поверхностью.

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

Отрисовка данных сетки поверхности для отображения

Мы также можем просто рисовать сетки поверхности в буферы стерео display. Мы решили нарисовать полные лица с освещением, но вы можете рисовать проволочные рамки, обрабатывать сетки перед отрисовкой, применять карту текстур и т. д.

В нашем примере кода показан отрисовщик сетки для рисования коллекции. На этот раз мы не указываем проход только для глубины, он прикрепит шейдер пикселей и завершит конвейер отрисовки с помощью целевых объектов, указанных для текущей виртуальной камеры.

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

См. также раздел