Поделиться через


Системы координат в DirectX

Примечание.

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

Системы координат формируют основу для пространственного понимания, предлагаемых API-интерфейсами Windows Смешанная реальность.

Сегодняшние устройства VR или однокомнатные виртуальные реальности устанавливают одну основную систему координат для их отслеживаемого пространства. Смешанная реальность устройствах, таких как HoloLens, предназначены для больших неопределенных сред, при этом устройство обнаруживает и изучает его окружающую среду по мере того, как пользователь проходит вокруг. Устройство адаптируется к постоянному улучшению знаний о комнатах пользователя, но приводит к системам координат, которые изменяют их связь друг с другом в течение всего времени существования приложений. Windows Смешанная реальность поддерживает широкий спектр устройств, начиная от иммерсивных гарнитур через подключенные к миру эталонные кадры.

Примечание.

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

Системы пространственной координаты в Windows

Основной тип, используемый для определения реальных систем координат в Windows, является SpatialCoordinateSystem. Экземпляр этого типа представляет произвольную систему координат, предоставляя метод получения данных матрицы преобразования, которые можно использовать для преобразования между двумя системами координат без понимания деталей каждого.

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

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

Приложение не должно напрямую создавать ПространственныеCoordinateSystems - скорее они должны использоваться через API распознавания. Существует три основных источника систем координат в API распознавания, каждый из которых сопоставляет концепцию, описанную на странице систем координат:

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

Системы координат слева и правой руки
Системы координат слева и правой руки

Используйте класс SpatialLocator, чтобы создать присоединенный или стационарный кадр ссылки для начальной загрузки в SpatialCoordinateSystem на основе позиции HoloLens. Перейдите к следующему разделу, чтобы узнать больше об этом процессе.

Размещение голограмм в мире с помощью пространственной стадии

Доступ к системе координат для непрозрачных гарнитур Windows Смешанная реальность иммерсивным гарнитурам осуществляется с помощью статического свойства SpatialStageFrameOfReference::Current. Этот API предоставляет следующие возможности:

  • Система координат
  • Сведения о том, находится ли игрок на месте или мобильном устройстве
  • Граница безопасной области для прогулки, если игрок является мобильным
  • Указание направления гарнитуры.
  • Обработчик событий для обновлений пространственного этапа.

Сначала мы получаем пространственный этап и подписываемся на обновления:

Код для инициализации пространственного этапа

SpatialStageManager::SpatialStageManager(
    const std::shared_ptr<DX::DeviceResources>& deviceResources, 
    const std::shared_ptr<SceneController>& sceneController)
    : m_deviceResources(deviceResources), m_sceneController(sceneController)
{
    // Get notified when the stage is updated.
    m_spatialStageChangedEventToken = SpatialStageFrameOfReference::CurrentChanged +=
        ref new EventHandler<Object^>(std::bind(&SpatialStageManager::OnCurrentChanged, this, _1));

    // Make sure to get the current spatial stage.
    OnCurrentChanged(nullptr);
}

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

Код для обновления пространственного этапа

void SpatialStageManager::OnCurrentChanged(Object^ /*o*/)
{
    // The event notifies us that a new stage is available.
    // Get the current stage.
    m_currentStage = SpatialStageFrameOfReference::Current;

    // Clear previous content.
    m_sceneController->ClearSceneObjects();

    if (m_currentStage != nullptr)
    {
        // Obtain stage geometry.
        auto stageCoordinateSystem = m_currentStage->CoordinateSystem;
        auto boundsVertexArray = m_currentStage->TryGetMovementBounds(stageCoordinateSystem);

        // Visualize the area where the user can move around.
        std::vector<float3> boundsVertices;
        boundsVertices.resize(boundsVertexArray->Length);
        memcpy(boundsVertices.data(), boundsVertexArray->Data, boundsVertexArray->Length * sizeof(float3));
        std::vector<unsigned short> indices = TriangulatePoints(boundsVertices);
        m_stageBoundsShape =
            std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(boundsVertices),
                    indices,
                    XMFLOAT3(DirectX::Colors::SeaGreen),
                    stageCoordinateSystem);
        m_sceneController->AddSceneObject(m_stageBoundsShape);

        // In this sample, we draw a visual indicator for some spatial stage properties.
        // If the view is forward-only, the indicator is a half circle pointing forward - otherwise, it
        // is a full circle.
        // If the user can walk around, the indicator is blue. If the user is seated, it is red.

        // The indicator is rendered at the origin - which is where the user declared the center of the
        // stage to be during setup - above the plane of the stage bounds object.
        float3 visibleAreaCenter = float3(0.f, 0.001f, 0.f);

        // Its shape depends on the look direction range.
        std::vector<float3> visibleAreaIndicatorVertices;
        if (m_currentStage->LookDirectionRange == SpatialLookDirectionRange::ForwardOnly)
        {
            // Half circle for forward-only look direction range.
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 9, XM_PI);
        }
        else
        {
            // Full circle for omnidirectional look direction range.
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 16, XM_2PI);
        }

        // Its color depends on the movement range.
        XMFLOAT3 visibleAreaColor;
        if (m_currentStage->MovementRange == SpatialMovementRange::NoMovement)
        {
            visibleAreaColor = XMFLOAT3(DirectX::Colors::OrangeRed);
        }
        else
        {
            visibleAreaColor = XMFLOAT3(DirectX::Colors::Aqua);
        }

        std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);

        // Visualize the look direction range.
        m_stageVisibleAreaIndicatorShape =
            std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
                    visibleAreaIndicatorIndices,
                    visibleAreaColor,
                    stageCoordinateSystem);
        m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
    }
    else
    {
        // No spatial stage was found.
        // Fall back to a stationary coordinate system.
        auto locator = SpatialLocator::GetDefault();
        if (locator)
        {
            m_stationaryFrameOfReference = locator->CreateStationaryFrameOfReferenceAtCurrentLocation();

            // Render an indicator, so that we know we fell back to a mode without a stage.
            std::vector<float3> visibleAreaIndicatorVertices;
            float3 visibleAreaCenter = float3(0.f, -2.0f, 0.f);
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.125f, 16, XM_2PI);
            std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);
            m_stageVisibleAreaIndicatorShape =
                std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
                    visibleAreaIndicatorIndices,
                    XMFLOAT3(DirectX::Colors::LightSlateGray),
                    m_stationaryFrameOfReference->CoordinateSystem);
            m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
        }
    }
}

Набор вершин, определяющих границу этапа, предоставляется по часовой стрелке. Оболочка Windows Смешанная реальность рисует забор на границе, когда пользователь приближается к нему, но может потребоваться треугольную область для своих целей. Следующий алгоритм можно использовать для треугольной обработки этапа.

Код для триангуляризации пространственного этапа

std::vector<unsigned short> SpatialStageManager::TriangulatePoints(std::vector<float3> const& vertices)
{
    size_t const& vertexCount = vertices.size();

    // Segments of the shape are removed as they are triangularized.
    std::vector<bool> vertexRemoved;
    vertexRemoved.resize(vertexCount, false);
    unsigned int vertexRemovedCount = 0;

    // Indices are used to define triangles.
    std::vector<unsigned short> indices;

    // Decompose into convex segments.
    unsigned short currentVertex = 0;
    while (vertexRemovedCount < (vertexCount - 2))
    {
        // Get next triangle:
        // Start with the current vertex.
        unsigned short index1 = currentVertex;

        // Get the next available vertex.
        unsigned short index2 = index1 + 1;

        // This cycles to the next available index.
        auto CycleIndex = [=](unsigned short indexToCycle, unsigned short stopIndex)
        {
            // Make sure the index does not exceed bounds.
            if (indexToCycle >= unsigned short(vertexCount))
            {
                indexToCycle -= unsigned short(vertexCount);
            }

            while (vertexRemoved[indexToCycle])
            {
                // If the vertex is removed, go to the next available one.
                ++indexToCycle;

                // Make sure the index does not exceed bounds.
                if (indexToCycle >= unsigned short(vertexCount))
                {
                    indexToCycle -= unsigned short(vertexCount);
                }

                // Prevent cycling all the way around.
                // Should not be needed, as we limit with the vertex count.
                if (indexToCycle == stopIndex)
                {
                    break;
                }
            }

            return indexToCycle;
        };
        index2 = CycleIndex(index2, index1);

        // Get the next available vertex after that.
        unsigned short index3 = index2 + 1;
        index3 = CycleIndex(index3, index1);

        // Vertices that may define a triangle inside the 2D shape.
        auto& v1 = vertices[index1];
        auto& v2 = vertices[index2];
        auto& v3 = vertices[index3];

        // If the projection of the first segment (in clockwise order) onto the second segment is 
        // positive, we know that the clockwise angle is less than 180 degrees, which tells us 
        // that the triangle formed by the two segments is contained within the bounding shape.
        auto v2ToV1 = v1 - v2;
        auto v2ToV3 = v3 - v2;
        float3 normalToV2ToV3 = { -v2ToV3.z, 0.f, v2ToV3.x };
        float projectionOntoNormal = dot(v2ToV1, normalToV2ToV3);
        if (projectionOntoNormal >= 0)
        {
            // Triangle is contained within the 2D shape.

            // Remove peak vertex from the list.
            vertexRemoved[index2] = true;
            ++vertexRemovedCount;

            // Create the triangle.
            indices.push_back(index1);
            indices.push_back(index2);
            indices.push_back(index3);

            // Continue on to the next outer triangle.
            currentVertex = index3;
        }
        else
        {
            // Triangle is a cavity in the 2D shape.
            // The next triangle starts at the inside corner.
            currentVertex = index2;
        }
    }

    indices.shrink_to_fit();
    return indices;
}

Размещение голограмм в мире с помощью стационарного кадра ссылок

Класс SpatialStationaryFrameOfReference представляет кадр ссылок, который остается непостояным относительно окружения пользователя по мере перемещения пользователя. Этот кадр ссылок определяет стабильность координат рядом с устройством. Одним из основных способов использования пространственной аттестацииFrameOfReference является роль базовой системы координат мира в обработчике отрисовки при отрисовке голограмм.

Чтобы получить объект SpatialStationaryFrameOfReference, используйте класс SpatialLocator и вызов CreateStationaryFrameOfReferenceAtCurrentLocation.

Из кода шаблона приложения Windows Holographic:

           // The simplest way to render world-locked holograms is to create a stationary reference frame
           // when the app is launched. This is roughly analogous to creating a "world" coordinate system
           // with the origin placed at the device's position as the app is launched.
           referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
  • Стационарные эталонные кадры предназначены для обеспечения оптимальной позиции относительно общего пространства. Отдельные позиции в этом эталонном кадре могут немного смеяться. Это нормально, так как устройство узнает больше о среде.
  • Если требуется точное размещение отдельных голограмм, следует использовать ПространственныйAnchor для привязки отдельной голограммы к позиции в реальном мире, например, то, что пользователь указывает, что он имеет особый интерес. Позиции привязки не смеются, но могут быть исправлены; Привязка будет использовать исправленную позицию, начиная с следующего кадра после изменения.

Размещение голограмм в мире с помощью пространственных привязок

Пространственные привязки — отличный способ разместить голограммы в определенном месте в реальном мире, при этом система гарантирует, что привязка остается на месте с течением времени. В этом разделе объясняется, как создать и использовать привязку, а также как работать с данными привязки.

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

После определения система координат пространственногоanchor постоянно настраивается, чтобы сохранить точное положение и ориентацию исходного расположения. Затем этот объект SpatialAnchor можно использовать для отрисовки голограмм, которые будут отображаться в окружениях пользователя в этом точном расположении.

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

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

Используйте свойство RawCoordinateSystem и соответствующее событие RawCoordinateSystemAdjusted для самостоятельного управления этими корректировками.

Вы можете сохранить ПространственныйAnchor локально с помощью класса SpatialAnchorStore , а затем вернуть его в будущий сеанс приложения на том же устройстве HoloLens.

Создание пространственныхanchors для голографического содержимого

В этом примере кода мы изменили шаблон приложения Windows Holographic, чтобы создать привязки при обнаружении жеста нажатия. Затем куб помещается в привязку во время передачи отрисовки.

Так как несколько привязок поддерживаются вспомогательным классом, мы можем разместить столько кубов, сколько мы хотим использовать этот пример кода!

Примечание.

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

   // Check for new input state since the last frame.
   SpatialInteractionSourceState^ pointerState = m_spatialInputHandler->CheckForInput();
   if (pointerState != nullptr)
   {
       // Try to get the pointer pose relative to the SpatialStationaryReferenceFrame.
       SpatialPointerPose^ pointerPose = pointerState->TryGetPointerPose(currentCoordinateSystem);
       if (pointerPose != nullptr)
       {
           // When a Pressed gesture is detected, the anchor will be created two meters in front of the user.

           // Get the gaze direction relative to the given coordinate system.
           const float3 headPosition = pointerPose->Head->Position;
           const float3 headDirection = pointerPose->Head->ForwardDirection;

           // The anchor position in the StationaryReferenceFrame.
           static const float distanceFromUser = 2.0f; // meters
           const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

           // Create the anchor at position.
           SpatialAnchor^ anchor = SpatialAnchor::TryCreateRelativeTo(currentCoordinateSystem, gazeAtTwoMeters);

           if ((anchor != nullptr) && (m_spatialAnchorHelper != nullptr))
           {
               // In this example, we store the anchor in an IMap.
               auto anchorMap = m_spatialAnchorHelper->GetAnchorMap();

               // Create an identifier for the anchor.
               String^ id = ref new String(L"HolographicSpatialAnchorStoreSample_Anchor") + anchorMap->Size;

               anchorMap->Insert(id->ToString(), anchor);
           }
       }
   }

Асинхронная загрузка и кэширование, SpatialAnchorStore

Давайте посмотрим, как написать класс SampleSpatialAnchorHelper, который помогает обрабатывать эту сохраняемость, в том числе:

  • Хранение коллекции привязок в памяти, индексированных ключом Platform::String.
  • Загрузка якорей из хранилища SpatialAnchorStore системы, которая хранится отдельно от локальной коллекции в памяти.
  • Сохранение локальной коллекции привязок в памяти в SpatialAnchorStore при выборе этого приложения.

Вот как сохранить объекты SpatialAnchor в SpatialAnchorStore.

При запуске класса мы запрашиваем ПространственныйAnchorStore асинхронно. Это включает в себя системный ввод-вывод, так как API загружает хранилище привязки, и этот API выполняется асинхронно, чтобы ввод-вывод не блокировался.

   // Request the spatial anchor store, which is the WinRT object that will accept the imported anchor data.
   return create_task(SpatialAnchorManager::RequestStoreAsync())
       .then([](task<SpatialAnchorStore^> previousTask)
   {
       std::shared_ptr<SampleSpatialAnchorHelper> newHelper = nullptr;

       try
       {
           SpatialAnchorStore^ anchorStore = previousTask.get();

           // Once the SpatialAnchorStore has been loaded by the system, we can create our helper class.

           // Using "new" to access private constructor
           newHelper = std::shared_ptr<SampleSpatialAnchorHelper>(new SampleSpatialAnchorHelper(anchorStore));

           // Now we can load anchors from the store.
           newHelper->LoadFromAnchorStore();
       }
       catch (Exception^ exception)
       {
           PrintWstringToDebugConsole(
               std::wstring(L"Exception while loading the anchor store: ") +
               exception->Message->Data() +
               L"\n"
               );
       }

       // Return the initialized class instance.
       return newHelper;
   });

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

   SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
   {
       m_anchorStore = anchorStore;
       m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
   }

Примечание.

Не забудьте подключить события приостановки и возобновления, чтобы сохранить и загрузить хранилище привязки.

   void HolographicSpatialAnchorStoreSampleMain::SaveAppState()
   {
       // For example, store information in the SpatialAnchorStore.
       if (m_spatialAnchorHelper != nullptr)
       {
           m_spatialAnchorHelper->TrySaveToAnchorStore();
       }
   }
   void HolographicSpatialAnchorStoreSampleMain::LoadAppState()
   {
       // For example, load information from the SpatialAnchorStore.
       LoadAnchorStore();
   }

Сохранение содержимого в хранилище привязки

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

Когда вы будете готовы сохранить привязки в памяти в SpatialAnchorStore, можно выполнить цикл по коллекции и попытаться сохранить каждую из них.

   // TrySaveToAnchorStore: Stores all anchors from memory into the app's anchor store.
   //
   // For each anchor in memory, this function tries to store it in the app's AnchorStore. The operation will fail if
   // the anchor store already has an anchor by that name.
   //
   bool SampleSpatialAnchorHelper::TrySaveToAnchorStore()
   {
       // This function returns true if all the anchors in the in-memory collection are saved to the anchor
       // store. If zero anchors are in the in-memory collection, we will still return true because the
       // condition has been met.
       bool success = true;

       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           for each (auto& pair in m_anchorMap)
           {
               auto const& id = pair->Key;
               auto const& anchor = pair->Value;

               // Try to save the anchors.
               if (!m_anchorStore->TrySave(id, anchor))
               {
                   // This may indicate the anchor ID is taken, or the anchor limit is reached for the app.
                   success=false;
               }
           }
       }

       return success;
   }

Загрузка содержимого из хранилища привязки при возобновлении работы приложения

Вы можете восстановить сохраненные привязки в AnchorStore, передав их из IMapView хранилища привязки в собственную базу данных в памяти ПространственныхAnchors, когда приложение возобновляется или в любое время.

Чтобы восстановить привязки из SpatialAnchorStore, восстановите каждую из них, которая вам нужна, в собственную коллекцию в памяти.

Вам нужна собственная база данных в памяти ПространственныхAnchors для связывания строк с создаваемыми пространственнымиAnchors. В нашем примере кода мы используем Windows::Foundation::Collections::IMap для хранения привязок, что упрощает использование одного и того же ключа и значения данных для SpatialAnchorStore.

   // This is an in-memory anchor list that is separate from the anchor store.
   // These anchors may be used, reasoned about, and so on before committing the collection to the store.
   Windows::Foundation::Collections::IMap<Platform::String^, Windows::Perception::Spatial::SpatialAnchor^>^ m_anchorMap;

Примечание.

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


Примечание.

В этом примере кода мы извлекаем все привязки из AnchorStore. Это не обязательно; приложение может также выбрать и выбрать определенное подмножество привязок с помощью строковых значений ключей, значимых для реализации.

   // LoadFromAnchorStore: Loads all anchors from the app's anchor store into memory.
   //
   // The anchors are stored in memory using an IMap, which stores anchors using a string identifier. Any string can be used as
   // the identifier; it can have meaning to the app, such as "Game_Leve1_CouchAnchor," or it can be a GUID that is generated
   // by the app.
   //
   void SampleSpatialAnchorHelper::LoadFromAnchorStore()
   {
       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           // Get all saved anchors.
           auto anchorMapView = m_anchorStore->GetAllSavedAnchors();
           for each (auto const& pair in anchorMapView)
           {
               auto const& id = pair->Key;
               auto const& anchor = pair->Value;
               m_anchorMap->Insert(id, anchor);
           }
       }
   }

Очистка хранилища привязки при необходимости

Иногда необходимо очистить состояние приложения и записать новые данные. Вот как это сделать с помощью SpatialAnchorStore.

Используя вспомогательный класс, почти не требуется упаковать функцию Clear. Мы решили сделать это в нашем примере реализации, так как вспомогательный класс отвечает за владение экземпляром SpatialAnchorStore.

   // ClearAnchorStore: Clears the AnchorStore for the app.
   //
   // This function clears the AnchorStore. It has no effect on the anchors stored in memory.
   //
   void SampleSpatialAnchorHelper::ClearAnchorStore()
   {
       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           // Clear all anchors from the store.
           m_anchorStore->Clear();
       }
   }

Пример. Связывание систем координат привязки с стационарными системами координат ссылочного кадра

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

   // In this code snippet, someAnchor is a SpatialAnchor^ that has been initialized and is valid in the current environment.
   float4x4 anchorSpaceToCurrentCoordinateSystem;
   SpatialCoordinateSystem^ anchorSpace = someAnchor->CoordinateSystem;
   const auto tryTransform = anchorSpace->TryGetTransformTo(currentCoordinateSystem);
   if (tryTransform != nullptr)
   {
       anchorSpaceToCurrentCoordinateSystem = tryTransform->Value;
   }

Этот процесс полезен для вас двумя способами:

  1. Он сообщает вам, можно ли понять два эталонных кадра относительно друг друга, и;
  2. В этом случае он предоставляет преобразование для перехода непосредственно из одной системы координат в другую.

С помощью этой информации вы понимаете пространственное отношение между объектами между двумя эталонными кадрами.

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

Создание голограмм с помощью присоединенного к устройству кадра ссылки

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

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

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

Чтобы получить объект SpatialLocatorAttachedFrameOfReference, используйте класс SpatialLocator и вызов createAttachedFrameOfReferenceAtCurrentHeading.

Это относится ко всему диапазону устройств Windows Смешанная реальность.

Использование эталонного кадра, подключенного к устройству

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

Сначала мы изменили шаблон для хранения объекта SpatialLocatorAttachedFrameOfReference вместо spatialStationaryFrameOfReference:

Из HolographicTagAlongSampleMain.h:

   // A reference frame attached to the holographic camera.
   Windows::Perception::Spatial::SpatialLocatorAttachedFrameOfReference^   m_referenceFrame;

Из HolographicTagAlongSampleMain.cpp:

   // In this example, we create a reference frame attached to the device.
   m_referenceFrame = m_locator->CreateAttachedFrameOfReferenceAtCurrentHeading();

Во время обновления теперь мы получаем систему координат в метке времени, полученной с прогнозом кадра.

   // Next, we get a coordinate system from the attached frame of reference that is
   // associated with the current frame. Later, this coordinate system is used for
   // for creating the stereo view matrices when rendering the sample content.
   SpatialCoordinateSystem^ currentCoordinateSystem =
       m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp);

Получение поз пространственного указателя и отслеживание взгляда пользователя

Мы хотим, чтобы наш пример голограммы следует взгляду пользователя, аналогично тому, как голографическая оболочка может следовать взгляду пользователя. Для этого нам нужно получить пространственный объект SpatialPointerPose из той же метки времени.

SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);

Эта функция SpatialPointerPose содержит сведения, необходимые для размещения голограммы в соответствии с текущим заголовком пользователя.

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

Из stationaryQuadRenderer::P ositionHologram:

   const float& dtime = static_cast<float>(timer.GetElapsedSeconds());

   if (pointerPose != nullptr)
   {
       // Get the gaze direction relative to the given coordinate system.
       const float3 headPosition  = pointerPose->Head->Position;
       const float3 headDirection = pointerPose->Head->ForwardDirection;

       // The tag-along hologram follows a point 2.0m in front of the user's gaze direction.
       static const float distanceFromUser = 2.0f; // meters
       const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

       // Lerp the position, to keep the hologram comfortably stable.
       auto lerpedPosition = lerp(m_position, gazeAtTwoMeters, dtime * c_lerpRate);

       // This will be used as the translation component of the hologram's
       // model transform.
       SetPosition(lerpedPosition);
   }

Примечание.

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

Для stationaryQuadRenderer::P ositionHologram:

       // If you're making a debug view, you might not want the tag-along to be directly in the
       // center of your field of view. Use this code to position the hologram to the right of
       // the user's gaze direction.
       /*
       const float3 offset = float3(0.13f, 0.0f, 0.f);
       static const float distanceFromUser = 2.2f; // meters
       const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * (headDirection + offset));
       */

Поворот голограммы для лица камеры

Для размещения голограммы недостаточно, что в данном случае — квадрограмма; Необходимо также повернуть объект, чтобы столкнуться с пользователем. Эта смена происходит в мировом пространстве, так как этот тип рекламных щитов позволяет голограмме оставаться частью среды пользователя. Рекламные щиты в пространстве просмотра не так удобны, так как голограмма становится заблокированной на ориентацию дисплея; В этом случае вам также придется интерполировать матрицы левого и правого представлений, чтобы получить преобразование рекламных щитов представления, которое не нарушает стереорисовку. Здесь мы поворачиваем оси X и Z, чтобы столкнуться с пользователем.

Из StationaryQuadRenderer::Update:

   // Seconds elapsed since previous frame.
   const float& dTime = static_cast<float>(timer.GetElapsedSeconds());

   // Create a direction normal from the hologram's position to the origin of person space.
   // This is the z-axis rotation.
   XMVECTOR facingNormal = XMVector3Normalize(-XMLoadFloat3(&m_position));

   // Rotate the x-axis around the y-axis.
   // This is a 90-degree angle from the normal, in the xz-plane.
   // This is the x-axis rotation.
   XMVECTOR xAxisRotation = XMVector3Normalize(XMVectorSet(XMVectorGetZ(facingNormal), 0.f, -XMVectorGetX(facingNormal), 0.f));

   // Create a third normal to satisfy the conditions of a rotation matrix.
   // The cross product  of the other two normals is at a 90-degree angle to
   // both normals. (Normalize the cross product to avoid floating-point math
   // errors.)
   // Note how the cross product will never be a zero-matrix because the two normals
   // are always at a 90-degree angle from one another.
   XMVECTOR yAxisRotation = XMVector3Normalize(XMVector3Cross(facingNormal, xAxisRotation));

   // Construct the 4x4 rotation matrix.

   // Rotate the quad to face the user.
   XMMATRIX rotationMatrix = XMMATRIX(
       xAxisRotation,
       yAxisRotation,
       facingNormal,
       XMVectorSet(0.f, 0.f, 0.f, 1.f)
       );

   // Position the quad.
   const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));

   // The view and projection matrices are provided by the system; they are associated
   // with holographic cameras, and updated on a per-camera basis.
   // Here, we provide the model transform for the sample hologram. The model transform
   // matrix is transposed to prepare it for the shader.
   XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(rotationMatrix * modelTranslation));

Отрисовка присоединенной голограммы

В этом примере мы также выбираем отрисовку голограммы в системе координат объекта SpatialLocatorAttachedReferenceFrame, где мы размещали голограмму. (Если бы мы решили выполнить отрисовку с помощью другой системы координат, нам потребуется получить преобразование из системы координат, подключенной к устройству, в такую систему координат.)

Из HolographicTagAlongSampleMain::Render:

   // The view and projection matrices for each holographic camera will change
   // every frame. This function refreshes the data in the constant buffer for
   // the holographic camera indicated by cameraPose.
   pCameraResources->UpdateViewProjectionBuffer(
       m_deviceResources,
       cameraPose,
       m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp)
       );

Вот и все! Голограмма теперь будет "преследовать" позицию, которая составляет 2 метра перед взглядом пользователя.

Примечание.

В этом примере также загружается дополнительное содержимое. См. StationaryQuadRenderer.cpp.

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

Когда устройство не может найти себя в мире, приложение испытывает "отслеживание потери". Приложения Windows Смешанная реальность должны иметь возможность обрабатывать такие нарушения в системе отслеживания позиций. Эти нарушения можно наблюдать, а ответы создаются с помощью события LocatabilityChanged в объекте SpatialLocator по умолчанию.

Из AppMain::SetHolographicSpace:

   // Be able to respond to changes in the positional tracking state.
   m_locatabilityChangedToken =
       m_locator->LocatabilityChanged +=
           ref new Windows::Foundation::TypedEventHandler<SpatialLocator^, Object^>(
               std::bind(&HolographicApp1Main::OnLocatabilityChanged, this, _1, _2)
               );

Когда приложение получает событие LocatabilityChanged, оно может изменить поведение по мере необходимости. Например, в состоянии PositionalTrackingInhibited приложение может приостановить нормальную работу и отобразить голограмму наряду с тегами, отображающую предупреждающее сообщение.

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

Из AppMain.cpp:

   void HolographicApp1Main::OnLocatabilityChanged(SpatialLocator^ sender, Object^ args)
   {
       switch (sender->Locatability)
       {
       case SpatialLocatability::Unavailable:
           // Holograms cannot be rendered.
           {
               String^ message = L"Warning! Positional tracking is " +
                                           sender->Locatability.ToString() + L".\n";
               OutputDebugStringW(message->Data());
           }
           break;

       // In the following three cases, it is still possible to place holograms using a
       // SpatialLocatorAttachedFrameOfReference.
       case SpatialLocatability::PositionalTrackingActivating:
           // The system is preparing to use positional tracking.

       case SpatialLocatability::OrientationOnly:
           // Positional tracking has not been activated.

       case SpatialLocatability::PositionalTrackingInhibited:
           // Positional tracking is temporarily inhibited. User action may be required
           // in order to restore positional tracking.
           break;

       case SpatialLocatability::PositionalTrackingActive:
           // Positional tracking is active. World-locked content can be rendered.
           break;
       }
   }

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

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

См. также