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

Примечание

Эта статья связана с устаревшими собственными API-интерфейсами WinRT.This article relates to the legacy WinRT native APIs. Для новых проектов собственных приложений рекомендуется использовать API опенкср.For new native app projects, we recommend using the OpenXR API.

В этом разделе описывается реализация пространственного сопоставления в приложении DirectX, включая подробное описание примера приложения пространственного сопоставления, упакованного с помощью пакета SDK для универсальная платформа Windows.This topic describes how to implement spatial mapping in your DirectX app, including a detailed explanation of the spatial mapping sample application packaged with the Universal Windows Platform SDK.

В этом разделе используется код из примера кода UWP для холографикспатиалмаппинг .This topic uses code from the HolographicSpatialMapping UWP code sample.

Примечание

Фрагменты кода в этой статье в настоящее время демонстрируют использование C++/CX вместо C + +17, совместимого с C++/WinRT, как используется в шаблоне C++ holographic.The code snippets in this article currently demonstrate use of C++/CX rather than C++17-compliant C++/WinRT as used in the C++ holographic project template. Понятия эквивалентны для проекта C++/WinRT, хотя код необходимо преобразовать.The concepts are equivalent for a C++/WinRT project, though you will need to translate the code.

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

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

Обзор разработки в DirectXDirectX development overview

При разработке собственного приложения для пространственного сопоставления используются API-интерфейсы в пространстве имен Windows. восприятие. пространственный.Native application development for spatial mapping uses the APIs in the Windows.Perception.Spatial namespace. Эти API предоставляют полный контроль над функциями пространственного сопоставления, точно так же, как интерфейсы API пространственного сопоставления предоставляются Unity.These APIs give you full control of spatial mapping functionality, in the same way that spatial mapping APIs are exposed by Unity.

API-интерфейсы восприятияPerception APIs

Ниже приведены основные типы, предоставляемые для разработки пространственных сопоставлений.The primary types provided for spatial mapping development are as follows:

  • Спатиалсурфацеобсервер предоставляет сведения о поверхностях в указанных в приложении областях пространства рядом с пользователем в виде объектов спатиалсурфацеинфо.SpatialSurfaceObserver provides information about surfaces in application-specified regions of space near the user, in the form of SpatialSurfaceInfo objects.
  • Спатиалсурфацеинфо описывает одну пространственно екстантную поверхность, включая уникальный идентификатор, связанный том и время последнего изменения.SpatialSurfaceInfo describes a single extant spatial surface, including a unique ID, bounding volume and time of last change. Он предоставит Спатиалсурфацемеш асинхронно после запроса.It will provide a SpatialSurfaceMesh asynchronously upon request.
  • Спатиалсурфацемешоптионс содержит параметры, используемые для настройки объектов спатиалсурфацемеш, запрошенных из спатиалсурфацеинфо.SpatialSurfaceMeshOptions contains parameters used to customize the SpatialSurfaceMesh objects requested from SpatialSurfaceInfo.
  • Спатиалсурфацемеш представляет данные сетки для отдельной пространственной поверхности.SpatialSurfaceMesh represents the mesh data for a single spatial surface. Данные для позиций вершин, нормалей вершин и индексов треугольников содержатся в объектах-членах Спатиалсурфацемешбуффер.The data for vertex positions, vertex normals, and triangle indices is contained in member SpatialSurfaceMeshBuffer objects.
  • Спатиалсурфацемешбуффер заключает в оболочку один тип данных сетки.SpatialSurfaceMeshBuffer wraps a single type of mesh data.

При разработке приложения с использованием этих API основная последовательность программ будет выглядеть следующим образом (как показано в примере приложения, описанном ниже):When developing an application using these APIs, your basic program flow will look like this (as demonstrated in the sample application described below):

  • Настройка СпатиалсурфацеобсерверSet up your SpatialSurfaceObserver
    • Вызовите рекуестакцессасинк, чтобы убедиться, что пользователь предоставил разрешение на использование возможностей пространственного сопоставления устройства.Call RequestAccessAsync, to ensure that the user has given permission for your application to use the device's spatial mapping capabilities.
    • Создайте экземпляр объекта Спатиалсурфацеобсервер.Instantiate a SpatialSurfaceObserver object.
    • Вызовите сетбаундингволумес , чтобы указать области пространства, в которых требуется получить сведения о пространственных поверхностях.Call SetBoundingVolumes to specify the regions of space in which you want information about spatial surfaces. Вы можете изменить эти регионы в будущем, вызвав эту функцию еще раз.You may modify these regions in the future by calling this function again. Каждый регион указывается с помощью спатиалбаундингволуме.Each region is specified using a SpatialBoundingVolume.
    • Зарегистрируйтесь на событие обсерведсурфацесчанжед , которое будет срабатывать каждый раз, когда станут доступны новые сведения о пространственных областях в указанных регионах.Register for the ObservedSurfacesChanged event, which will fire whenever new information is available about the spatial surfaces in the regions of space you've specified.
  • Обработка событий ОбсерведсурфацесчанжедProcess ObservedSurfacesChanged events
    • В обработчике событий вызовите жетобсерведсурфацес , чтобы получить карту объектов спатиалсурфацеинфо.In your event handler, call GetObservedSurfaces to receive a map of SpatialSurfaceInfo objects. С помощью этой схемы можно обновить записи, для которых существуют пространственные поверхности в среде пользователя.Using this map, you can update your records of which spatial surfaces exist in the user's environment.
    • Для каждого объекта Спатиалсурфацеинфо можно запросить трижетбаундс , чтобы определить пространственные экстенты поверхности, выраженные в пространственной системе координат по вашему выбору.For each SpatialSurfaceInfo object, you may query TryGetBounds to determine the spatial extents of the surface, expressed in a spatial coordinate system of your choosing.
    • Если вы решили запросить, сетку для пространственной поверхности, вызовите трикомпутелатестмешасинк.If you decide to request, mesh for a spatial surface, call TryComputeLatestMeshAsync. Вы можете указать параметры, задающие плотность треугольников, и формат возвращаемых данных сетки.You may provide options specifying the density of triangles, and the format of the returned mesh data.
  • Сетка приема и обработкиReceive and process mesh
    • Каждый вызов Трикомпутелатестмешасинк будет асинхронно возвращать один объект Спатиалсурфацемеш.Each call to TryComputeLatestMeshAsync will asynchronously return one SpatialSurfaceMesh object.
    • Из этого объекта можно получить доступ к содержащимся объектам Спатиалсурфацемешбуффер, которые предоставляют доступ к индексам треугольника, позициям вершин и нормалям вершин сетки, если вы запрашиваете их.From this object, you can access the contained SpatialSurfaceMeshBuffer objects, which gives you access to the triangle indices, vertex positions, and vertex normals of the mesh if you request them. Эти данные будут в формате, непосредственно совместимом с API-интерфейсами Direct3D 11 , используемыми для отрисовки сеток.This data will be in a format directly compatible with the Direct3D 11 APIs used for rendering meshes.
    • Здесь приложение может при необходимости анализировать или обрабатывать данные сетки, а также использовать их для визуализации и райкастинг и конфликта.From here your application can optionally analyze or process the mesh data, and use it for rendering and physics raycasting and collision.
    • Важно отметить, что необходимо применить шкалу к позициям вершин сетки (например, в шейдере вершин, используемом для отрисовки сеток), чтобы преобразовать их из оптимизированных целых единиц, в которых они хранятся в буфере, на метры.One important detail to note is that you must apply a scale to the mesh vertex positions (for example in the vertex shader used for rendering the meshes), to convert them from the optimized integer units in which they're stored in the buffer, to meters. Эту шкалу можно получить, вызвав вертекспоситионскале.You can retrieve this scale by calling VertexPositionScale.

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

Пример пошагового руководства по коду пространственного сопоставленияSpatial Mapping code sample walkthrough

Пример кода для пространственного сопоставления holographic включает в себя код, который можно использовать для начала загрузки сеток поверхности в приложение, включая инфраструктуру для управления и визуализации сеток поверхности.The Holographic Spatial Mapping code sample includes code that you can use to get started loading surface meshes into your app, including infrastructure for managing and rendering surface meshes.

Теперь мы пошаговым инструкциями по добавлению функции сопоставления поверхности в приложение DirectX.Now, we walk through how to add surface-mapping capability to your DirectX app. Этот код можно добавить в проект шаблона приложения Windows holographic . Кроме того, можно выполнить действия, просмотрев приведенный выше пример кода.You can add this code to your Windows Holographic app template project, or you can follow along by browsing through the code sample mentioned above. Этот пример кода основан на шаблоне приложения Windows Holographic.This code sample is based on the Windows Holographic app template.

Настройка приложения для использования возможности СпатиалперцептионSet up your app to use the spatialPerception capability

Приложение может использовать возможность пространственного сопоставления.Your app can use the spatial mapping capability. Это необходимо потому, что пространственный сетчатый объект представляет собой представление среды пользователя, которое может считаться частными данными.This is necessary because the spatial mesh is a representation of the user's environment, which may be considered private data. Объявите эту возможность в файле Package. appxmanifest для приложения.Declare this capability in the package.appxmanifest file for your app. Приведем пример:Here's an example:

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

Эта возможность поступает из пространства имен uap2 .The capability comes from the uap2 namespace. Чтобы получить доступ к этому пространству имен в манифесте, включите его в качестве атрибута кслмнс в < элемент Package>.To get access to this namespace in your manifest, include it as an xlmns attribute in the <Package> element. Приведем пример:Here's an example:

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

Проверка поддержки функции пространственного сопоставленияCheck for spatial mapping feature support

Windows Mixed Reality поддерживает широкий спектр устройств, включая устройства, которые не поддерживают пространственное сопоставление.Windows Mixed Reality supports a wide range of devices, including devices, which don't support spatial mapping. Если приложение может использовать пространственное сопоставление или должно использовать пространственное сопоставление для предоставления функциональных возможностей, перед попыткой его использования необходимо проверить, поддерживается ли пространственное сопоставление.If your app can use spatial mapping, or must use spatial mapping, to provide functionality, it should check to make sure spatial mapping is supported before trying to use it. Например, если для приложения смешанной реальности требуется пространственное сопоставление, оно должно отображать это сообщение, если пользователь пытается запустить его на устройстве без пространственного сопоставления.For example, if spatial mapping is required by your mixed reality app, it should display a message to that effect if a user tries running it on a device without spatial mapping. Кроме того, приложение может визуализировать собственную виртуальную среду вместо среды пользователя, предоставляя возможности, аналогичные тому, что произойдет, если было доступно пространственное сопоставление.Or, your app can render its own virtual environment in place of the user's environment, providing an experience that is similar to what would happen if spatial mapping were available. В любом событии этот API позволяет приложению знать, когда оно не получает данные пространственного сопоставления и отвечает соответствующим образом.In any event, this API allows your app to be aware of when it won't get spatial mapping data and respond in the appropriate way.

Чтобы проверить наличие поддержки пространственного сопоставления на текущем устройстве, убедитесь, что контракт UWP имеет уровень 4 или выше и затем вызовите Спатиалсурфацеобсервер:: support ().To check the current device for spatial mapping support, first make sure the UWP contract is at level 4 or greater and then call SpatialSurfaceObserver::IsSupported(). Вот как это сделать в контексте примера кода пространственного пространственного сопоставления .Here's how to do so in the context of the Holographic Spatial Mapping code sample. Поддержка проверяется непосредственно перед запросом доступа.Support is checked just before requesting access.

API Спатиалсурфацеобсервер:: support () доступен начиная с пакета SDK версии 15063.The SpatialSurfaceObserver::IsSupported() API is available starting in SDK version 15063. При необходимости перецелевой проект до версии платформы 15063 перед использованием этого API.If necessary, retarget your project to platform version 15063 before using this 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, приложение должно продолжать работу так, как будто устройство поддерживает пространственное сопоставление.When the UWP contract is less than level 4, the app should proceed as though the device is capable of doing spatial mapping.

Запрос доступа к данным пространственного сопоставленияRequest access to spatial mapping data

Приложение должно запросить разрешение на доступ к данным пространственного сопоставления, прежде чем пытаться создать любые наблюдатели Surface.Your app needs to request permission to access spatial mapping data before trying to create any surface observers. Ниже приведен пример, основанный на нашем примере кода сопоставления Surface, с дополнительными сведениями, приведенными далее на этой странице.Here's an example based upon our Surface Mapping code sample, with more details provided later on this page:

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

Создание наблюдателя SurfaceCreate a surface observer

Пространство имен Windows::P ерцептион:: spatial:: Surfaces включает класс спатиалсурфацеобсервер , который следит за одним или несколькими томами, указанными в спатиалкурдинатесистем.The Windows::Perception::Spatial::Surfaces namespace includes the SpatialSurfaceObserver class, which observes one or more volumes that you specify in a SpatialCoordinateSystem. Используйте экземпляр спатиалсурфацеобсервер для доступа к данным сетки Surface в режиме реального времени.Use a SpatialSurfaceObserver instance to access surface mesh data in real time.

Из аппмаин. h:From 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;

Как отмечалось в предыдущем разделе, необходимо запросить доступ к данным пространственного сопоставления, прежде чем приложение сможет его использовать.As noted in the previous section, you must request access to spatial mapping data before your app can use it. Этот доступ предоставляется автоматически на HoloLens.This access is granted automatically on the 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();

Далее необходимо настроить наблюдатель поверхности для наблюдения за конкретным ограничивающим томом.Next, you need to configure the surface observer to observe a specific bounding volume. Здесь мы наблюдаем поле, 20x20x5 измерительные приборы, центрированные по отношению к координатам системы координат.Here, we observe a box that is 20x20x5 meters, centered at the origin of the coordinate system.

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

Вместо этого можно задать несколько ограничивающих томов.You can set multiple bounding volumes instead.

Это псевдокод:This is pseudocode:

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

Также можно использовать другие ограничивающие фигуры, такие как представление фрустум, или ограничивающий прямоугольник, не выравниваемая по осям.It's also possible to use other bounding shapes - such as a view frustum, or a bounding box that isn't axis aligned.

Это псевдокод:This is pseudocode:

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

Если приложение должно выполнять какие-либо действия, если данные сопоставления поверхности недоступны, можно написать код для реагирования на случай, когда спатиалперцептионакцессстатус не разрешен — например, он не будет разрешен на компьютерах с подключенными устройствами, так как эти устройства не имеют оборудования для пространственного сопоставления.If your app needs to do anything differently when surface mapping data isn't available, you can write code to respond to the case where the SpatialPerceptionAccessStatus isn't Allowed - for example, it won't be allowed on PCs with immersive devices attached because those devices don't have hardware for spatial mapping. Для этих устройств следует использовать пространственный этап для получения сведений о среде пользователя и конфигурации устройства.For these devices, you should instead rely on the spatial stage for information about the user's environment and device configuration.

Инициализация и обновление коллекции сеток поверхностиInitialize and update the surface mesh collection

Если наблюдатель Surface успешно создан, мы можем продолжить инициализацию коллекции сеток Surface.If the surface observer was successfully created, we can continue to initialize our surface mesh collection. Здесь мы используем API модели извлечения для немедленного получения текущего набора наблюдаемых поверхностей:Here, we use the pull model API to get the current set of observed surfaces right away:

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

Есть также модель push-уведомлений, доступная для получения данных о сетке поверхности.There's also a push model available to get surface mesh data. Вы можете разрабатывать приложение для использования только модели извлечения, если выбрать, в этом случае вы будете опрашивать данные каждые часто, например, один раз в кадре или в течение определенного периода времени, как во время настройки игры.You're free to design your app to use only the pull model if you choose, in which case you'll poll for data every so often - say, once per frame - or during a specific time period, such as during game setup. В этом случае приведенный выше код является необходимым.If so, the above code is what you need.

В нашем примере кода мы решили продемонстрировать использование обеих моделей для воспитательный целей.In our code sample, we chose to demonstrate the use of both models for pedagogical purposes. Здесь мы подписались на событие, чтобы получить актуальные данные о сетке поверхности всякий раз, когда система распознает изменение.Here, we subscribe to an event to receive up-to-date surface mesh data whenever the system recognizes a change.

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

Наш пример кода также настроен для реагирования на эти события.Our code sample is also configured to respond to these events. Давайте подробно рассмотрим, как это сделать.Let's walk through how we do this.

Примечание. Это может быть не самым эффективным способом для работы приложения с данными сетки.NOTE: This might not be the most efficient way for your app to handle mesh data. Этот код написан для ясности и не оптимизирован.This code is written for clarity and isn't optimized.

Данные сетки поверхности предоставляются в карте только для чтения, в которой хранятся объекты спатиалсурфацеинфо с использованием Platform:: GUID в качестве значений ключа.The surface mesh data is provided in a read-only map that stores SpatialSurfaceInfo objects using Platform::Guids as key values.

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

Для обработки этих данных сначала мы рассмотрим значения ключей, которых нет в нашей коллекции.To process this data, we look first for key values that aren't in our collection. Сведения о том, как данные хранятся в нашем примере приложения, будут представлены далее в этом разделе.Details on how the data is stored in our sample app will be presented later in this topic.

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

Также необходимо удалить сети Surface, которые находятся в коллекции посетей, но больше нет в коллекции систем.We also have to remove surface meshes that are in our surface mesh collection, but that aren't in the system collection anymore. Для этого необходимо сделать что-то на противоположное, что было показано для добавления и обновления сеток; Мы выполним цикл по сбору нашего приложения и проверяем, есть ли идентификатор GUID в коллекции системы.To do so, we need to do something akin to the opposite of what we just showed for adding and updating meshes; we loop on our app's collection, and check to see if the Guid we have is in the system collection. Если он отсутствует в системной коллекции, мы удалим его из нашей.If it's not in the system collection, we remove it from ours.

Из нашего обработчика событий в Аппмаин. cpp:From our event handler in AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

Реализация удаления сетки в Реалтимесурфацемешрендерер. cpp:The implementation of mesh pruning in 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);
    }
}

Получение и использование буферов данных для сетки поверхностиAcquire and use surface mesh data buffers

Получение сведений о сетке поверхности было так же простым, как извлечение коллекции данных и обработка обновлений в этой коллекции.Getting the surface mesh information was as easy as pulling a data collection and processing updates to that collection. Теперь мы подробно рассмотрим, как можно использовать данные.Now, we'll go into detail on how you can use the data.

В нашем примере кода мы решили использовать сетки Surface для отрисовки.In our code example, we chose to use the surface meshes for rendering. Это распространенный сценарий для окклудингных голограмм, стоящих за реальными областями.This is a common scenario for occluding holograms behind real-world surfaces. Можно также визуализировать сетки или визуализировать обработанные версии, чтобы увидеть, какие области комнаты просматриваются, прежде чем приступить к работе с функциями приложения или игры.You can also render the meshes, or render processed versions of them, to show the user what areas of the room are scanned before you start providing app or game functionality.

Пример кода запускает процесс при получении обновлений сетки Surface из обработчика событий, описанного в предыдущем разделе.The code sample starts the process when it receives surface mesh updates from the event handler that we described in the previous section. Важной строкой кода в этой функции является вызов обновления сетки поверхности: на этот раз мы уже обработали сведения о сетке, и мы собираемся получить данные об вершине и индексе, которые будут использоваться, как можно увидеть.The important line of code in this function is the call to update the surface mesh: by this time we have already processed the mesh info, and we're about to get the vertex and index data for use as we see fit.

Из Реалтимесурфацемешрендерер. cpp:From 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());
}

Наш пример кода разработан таким образом, что класс данных сурфацемеш обрабатывает обработку и отрисовку данных в сети.Our sample code is designed so that a data class, SurfaceMesh, handles mesh data processing and rendering. Эти сетки — это то, что реалтимесурфацемешрендерер на самом деле сохраняет карту.These meshes are what the RealtimeSurfaceMeshRenderer actually keeps a map of. У каждой из них есть ссылка на Спатиалсурфацемеш, которую он поступил, поэтому вы можете использовать его в любое время, чтобы получить доступ к вершинной сетке или буферу индексов или получить преобразование для сетки.Each one has a reference to the SpatialSurfaceMesh it came from, so you can use it anytime you need to access the mesh vertex or index buffers, or get a transform for the mesh. Сейчас мы помечаем сетку как требующую обновления.For now, we flag the mesh as needing an update.

Из Сурфацемеш. cpp:From SurfaceMesh.cpp:

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

В следующий раз, когда сетка получит запрос на прорисовку, сначала будет проверяться флаг.Next time the mesh is asked to draw itself, it will check the flag first. Если требуется обновление, буферы вершин и индексов будут обновлены на GPU.If an update is needed, the vertex and index buffers will be updated on the GPU.

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

Сначала мы получаем необработанные буферы данных:First, we acquire the raw data buffers:

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:Then, we create Direct3D device buffers with the mesh data provided by the 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;
}

Примечание. Для вспомогательной функции Креатедиректксбуффер, используемой в предыдущем фрагменте, см. пример кода сопоставления Surface: Сурфацемеш. cpp, Жетдатафромибуффер. h.NOTE: For the CreateDirectXBuffer helper function used in the previous snippet, see the Surface Mapping code sample: SurfaceMesh.cpp, GetDataFromIBuffer.h. Теперь создание ресурса устройства завершено, и сетка считается загруженной и готова к обновлению и визуализации.Now the device resource creation is complete, and the mesh is considered to be loaded and ready for update and render.

Обновление и отрисовка сеток поверхностиUpdate and render surface meshes

Наш класс Сурфацемеш имеет специализированную функцию Update.Our SurfaceMesh class has a specialized update function. Каждый спатиалсурфацемеш имеет собственное преобразование, и в нашем примере используется текущая система координат для спатиалстатионариреференцефраме , чтобы получить преобразование.Each SpatialSurfaceMesh has its own transform, and our sample uses the current coordinate system for our SpatialStationaryReferenceFrame to acquire the transform. Затем он обновляет буфер констант модели на GPU.Then it updates the model constant buffer on the 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
        );
}

Когда вы намерены приступить к отрисовке сеток Surface, мы выполняем некоторые подготовительные действия перед отрисовкой коллекции.When it's time to render surface meshes, we do some prep work before rendering the collection. Мы настроили конвейер шейдера для текущей конфигурации подготовки к просмотру и настроили этап ассемблера входных данных.We set up the shader pipeline for the current rendering configuration, and we set up the input assembler stage. Класс модуля поддержки holographic Camera камераресаурцес. cpp уже настроил буфер констант представления/проекции прямо сейчас.The holographic camera helper class CameraResources.cpp already has set up the view/projection constant buffer by now.

Из реалтимесурфацемешрендерер:: Render:From 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
        );
}

По завершении этого цикла мы рассмотрим наши сети и поговорю, что каждый из них будет нарисован.Once this is done, we loop on our meshes and tell each one to draw itself. Примечание. Этот пример кода не оптимизирован для использования любого рода фрустумного отбора, но эту функцию следует включить в приложение.NOTE: This sample code isn't optimized to use any sort of frustum culling, but you should include this feature in your app.

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

Отдельные сети отвечают за настройку буферов вершин и индексов, а также буфера констант и преобразования модели.The individual meshes are responsible for setting up the vertex and index buffer, stride, and model transform constant buffer. Как и в случае вращающегося куба в шаблоне приложения Windows holographic, мы подготавливаем буферы стереоскопик с помощью создания экземпляров.As with the spinning cube in the Windows Holographic app template, we render to stereoscopic buffers using instancing.

Из сурфацемеш::D RAW:From SurfaceMesh::Draw:

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

Отрисовка вариантов с помощью сопоставления поверхностиRendering choices with Surface Mapping

Пример кода сопоставления Surface предлагает код для отрисовки данных в виде сетки поверхности перекрытия, а также для отображения данных в сетке поверхности на экране.The Surface Mapping code sample offers code for occlusion-only rendering of surface mesh data, and for on-screen rendering of surface mesh data. Выбор пути зависит от приложения.Which path you choose - or both - depends on your application. В этом документе мы рассмотрим обе конфигурации.We'll walk through both configurations in this document.

Отрисовка буферов перекрытия для holographic EffectRendering occlusion buffers for holographic effect

Начните с очистки представления целевого объекта прорисовки для текущей виртуальной камеры.Start by clearing the render target view for the current virtual camera.

Из Аппмаин. cpp:From AppMain.cpp:

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

Это этап предварительной отрисовки.This is a "pre-rendering" pass. Здесь мы создадим буфер перекрытия, запросив для модуля подготовки к сетке глубину прорисовки только глубины.Here, we create an occlusion buffer by asking the mesh renderer to render only depth. В этой конфигурации мы не подключим представление целевого объекта прорисовки, и модуль визуализации сетки задает для этапа Шейдер пикселей значение nullptr , чтобы GPU не проводился для рисования пикселей.In this configuration, we don't attach a render target view, and the mesh renderer sets the pixel shader stage to nullptr so that the GPU doesn't bother to draw pixels. Геометрия будет помещена в буфер глубины, а графический конвейер будет останавливаться.The geometry will be rasterized to the depth buffer, and the graphics pipeline will stop there.

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

Мы можем нарисовать голограммы с дополнительным тестом глубины по отношению к буферу сопоставления поверхности перекрытия.We can draw holograms with an extra depth test against the Surface Mapping occlusion buffer. В этом примере кода выполняется отрисовка пикселов в Кубе по другому цвету, если они находятся за поверхностью.In this code sample, we render pixels on the cube a different color if they are behind a surface.

Из Аппмаин. cpp:From 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()
    );

На основе кода из СпеЦиалеффектпикселшадер. HLSL:Based on code from 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);
}

Примечание. Для нашей подпрограммы гасердепслесс см. пример кода сопоставления Surface: спеЦиалеффектпикселшадер. HLSL.Note: For our GatherDepthLess routine, see the Surface Mapping code sample: SpecialEffectPixelShader.hlsl.

Отображение данных сетки поверхности для отображенияRendering surface mesh data to the display

Кроме того, можно просто нарисовать сетки Surface в экранных буферах вывода.We can also just draw the surface meshes to the stereo display buffers. Мы решили нарисовать полные лица с освещением, но вы можете рисовать каркасную схему, обработать сетки перед отрисовкой, применить текстурную карту и т. д.We chose to draw full faces with lighting, but you're free to draw wireframe, process meshes before rendering, apply a texture map, and so on.

Здесь наш пример кода указывает, что модуль подготовки сетки рисует коллекцию.Here, our code sample tells the mesh renderer to draw the collection. На этот раз не нужно указывать только глубину, он присоединяет построитель текстуры и завершает конвейер отрисовки, используя целевые объекты, которые мы указали для текущей виртуальной камеры.This time we don't specify a depth-only pass, it'll attach a pixel shader and complete the rendering pipeline using the targets that we specified for the current virtual camera.

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

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