DirectX の座標系

Note

この記事は、従来のWinRTネイティブAPIに関連します。 新しいネイティブ アプリ プロジェクトでは、OpenXR API を使用することをお勧めします。

座標系は、Windows Mixed Reality API によって提供される空間を理解するための基礎となります。

現在のシーテッド VR デバイスまたはシングル ルーム VR デバイスを使用すると、追跡空間の 1 つの主要な座標系が確立されます。 HoloLens のような Mixed Reality デバイスは、大規模な未定義の環境向けに設計されています。ユーザーが歩き回ると、そのデバイスによって、周囲の検出と学習が行われます。 このデバイスは、向上を続けるユーザーの部屋についての情報に適応しますが、座標系では、アプリの有効期間にわたって、相互の関係が変化します。 Windows Mixed Reality は、シーテッド イマーシブ ヘッドセットから、世界中にアタッチされた参照フレームまで、幅広いデバイスをサポートしています。

Note

この記事のコード スニペットでは、現在、C++ holographic プロジェクト テンプレートで使用される C++17 準拠の C++/WinRT ではなく、C++/CX の使用を示しています。 概念は、C++/WinRT プロジェクトの場合と同じですが、コードは変換する必要があります。

Windows の空間座標系

Windows で現実世界の座標系を推論するために使用されるコアの種類は、SpatialCoordinateSystem です。 この種類のインスタンスは、任意の座標系を表し、変換マトリックス データを取得するためのメソッドを備えています。この変換マトリックス データを使用すると、2 つの座標系を詳しく把握していなくても、相互の変換を行うことができます。

空間情報を返すメソッドでは、SpatialCoordinateSystem パラメーターを受け取り、返される座標に最も適した座標系を決定できるようになります。 空間情報は、ユーザーの周囲のポイント、レイ、またはボリュームとして表され、これらの座標の単位は常にメートル単位です。

SpatialCoordinateSystem と他の座標系とは、デバイスの位置を表すものも含め、動的な関係にあります。 どの時点でも、デバイスにとって、識別可能な座標系と、識別できない座標系があります。 お使いのアプリは、ほとんどの座標系に対して、座標系を特定できない期間を処理できる状態となっている必要があります。

SpatialCoordinateSystems は、アプリケーションで直接作成するのではなく、Perception API を介して使用する必要があります。 Perception API の座標系には、3 つの主要なソースがあり、それぞれが「座標系」のページで説明されている概念に対応しています。

これらのオブジェクトによって返されるすべての座標系は右手系であり、+y が上方向、+x が右方向、+z が後ろ方向です。 どちらの方向を z 軸の正の向きが指しているかは、左手または右手の指で x 軸の正の向きを指し、指を y 軸の正の向きに曲げるとわかります。 親指が指している方向は、自身に向かうかまたは離れる方向のいずれかとなり、その座標系の z 軸の正の向きが指す方向となります。 次の図は、これらの 2 つの座標系を示しています。

左側および右側の座標系
"左手系と右手系の座標系"

SpatialLocator クラスを使用して、ブートストラップにアタッチされた参照フレームまたは静止した参照フレームを、HoloLens の位置に基づいて、SpatialCoordinateSystem に作成します。 このプロセスの詳細を確認するには、次のセクションに進んでください。

空間ステージを使用して現実世界にホログラムを配置する

不透明 Windows Mixed Reality イマーシブ ヘッドセットの座標系には、静的な 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 Mixed Reality シェルの場合、ユーザーが境界に近づくと、そこにフェンスが描画されますが、徒歩可能な範囲を、自分の目的に合わせて三角形にすることもできます。 次のアルゴリズムを使用すると、ステージを三角形にすることができます。

空間ステージの三角形化のコード

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 クラスは、ユーザーが動き回っているときのユーザーの周囲を基準とする、静止したままの参照フレームを表します。 この参照フレームでは、デバイスの近くで座標が安定していることが優先されます。 SpatialStationaryFrameOfReference の主な用途の 1 つには、ホログラムをレンダリングするときに、レンダリング エンジン内で基になるワールド座標系として機能を果たすというものがあります。

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();
  • 静止参照フレームは、全体スペースに対して最適な位置となるように設計されています。 その参照フレーム内の個々の位置は、多少移動させることができます。 これは、デバイスが環境について学習を進めていく中で、通常行うことです。
  • 個々のホログラムが正確に配置されている必要がある場合は、SpatialAnchor を使用して、個々のホログラムを現実世界の位置に固定する必要があります。たとえば、ユーザーが特別に関心を持っているポイントなどです。 アンカーの位置は変わりませんが、修正することはできます。アンカーは、修正が行われた後、次のフレームから、修正された位置を使用します。

空間アンカーを使用して現実世界にホログラムを配置する

空間アンカーは、現実世界の特定の場所にホログラムを配置するための優れた方法です。この方法を使用するシステムでは、時間が経っても、アンカーの場所は変わりません。 このトピックでは、アンカーを作成して使用する方法と、アンカー データを操作する方法について説明します。

選択した SpatialCoordinateSystem 内に任意の位置と向きで、SpatialAnchor を作成できます。 デバイスは、その時点のその座標系を特定できる機能を持っている必要があり、システムは、空間アンカーの制限に達していない必要があります。

定義すると、SpatialAnchor の座標系は、最初の場所の正確な位置と向きを維持するように継続的に適応します。 その後、この SpatialAnchor を使用してホログラムをレンダリングすると、ホログラムは、ユーザーの周囲で、まさしくその場所に固定されているかのように現れます。

この調整には、アンカーを所定の場所に維持する効果があり、アンカーから離れれば離れるほど、その効果は増大します。 アンカーの基点から約 3 メートルを超えるアンカーに対しては、コンテンツをレンダリングしないようにする必要があります。

CoordinateSystem プロパティでは、アンカーを基準としてコンテンツを配置できる座標系を取得します。デバイスでアンカーの正確な場所を調整する場合は、イージングが適用されます。

RawCoordinateSystem プロパティと、それに対応するRawCoordinateSystemAdjusted イベントを使用して、これらの調整を自分で管理します。

空間アンカーを永続化して共有する

SpatialAnchorStore クラスを使用してローカルで SpatialAnchor を永続化することで、同じ HoloLens デバイスでの今後のアプリ セッションに戻すことができます。

Azure Spatial Anchors を使用して、ローカル SpatialAnchor から持続性のあるクラウド アンカーを作成すると、複数の HoloLens、iOS、および Android デバイスで、お使いのアプリから見つけることができます。 複数のデバイスで共通の空間アンカーを共有することにより、各ユーザーは、同じ物理的場所のそのアンカーを基準としてレンダリングされたコンテンツをリアルタイムで見ることができます。

また、HoloLens、iOS、および Android デバイス間の非同期ホログラム永続化にも、Azure Spatial Anchors を使用できます。 永続的なクラウド空間アンカーを共有すると、永続化した同じホログラムを長時間にわたって複数のデバイスに表示できます。これらのデバイスが同じ時間に一緒に存在しなくても可能です。

HoloLens アプリの共有エクスペリエンスの構築を開始するには、5 分間の Azure Spatial Anchors HoloLens クイックスタートをお試しください。

Azure Spatial Anchors が稼働状態になったら、HoloLens でアンカーを作成して配置できるようになります。 Android と iOS 用のチュートリアルも用意されています。これにより、すべてのデバイスで同じアンカーを共有できるようになります。

ホログラフィック コンテンツの SpatialAnchors を作成する

このコード サンプルでは、押すジェスチャが検出されたらアンカーが作成されるように、Windows Holographic アプリ テンプレートを変更しました。 次に、レンダー パス中にキューブがアンカーに配置されます。

ヘルパー クラスで複数のアンカーがサポートされているため、このコード サンプルを使用するときには、キューブを必要な数だけ配置できます。

Note

アンカーの ID は、アプリで制御します。 この例では、アプリのアンカーのコレクションに現在格納されているアンカーの数に基づいて、シーケンシャルな名前付けスキームを作成しました。

   // 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 に保存する方法を次に示します。

クラスの開始時に、非同期で SpatialAnchorStore を要求します。 これには、API がアンカー ストアを読み込むときのシステム I/O が含まれます。この API は、I/O がブロックされないように、非同期になります。

   // 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 が指定されます。これは、アンカーを保存するために使用できます。 これは、文字列であるキー値と SpatialAnchors であるデータ値を関連付ける IMapView です。 このサンプル コードでは、ヘルパー クラスのパブリック関数を介してアクセスできるプライベート クラス メンバー変数にそれを格納します。

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

Note

アンカー ストアの保存と読み込みを行うには、中断/再開イベントを必ずフックしてください。

   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 から SpatialAnchors の独自のメモリ内データベースに転送することで、アプリの再開時、またはいつでも復元できます。

SpatialAnchorStore からアンカーを復元するには、目的とする各アンカーを独自のメモリ内コレクションに復元します。

作成する SpatialAnchors に文字列を関連付けるには、SpatialAnchors の独自のメモリ内データベースが必要です。 このサンプル コードでは、アンカーを格納するために 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;

Note

復元されたアンカーがすぐに見つからない可能性があります。 たとえば、まったく別の部屋やまったく異なる建物の中にあるアンカーということもありえます。 AnchorStore から取得したアンカーは、使用する前に、見つけやすさをテストする必要があります。


Note

このコード例では、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();
       }
   }

例: アンカー座標系を静止参照フレーム座標系に関連付ける

たとえば、アンカーがあり、アンカーの座標系にあるものを、既に他のコンテンツに使用している SpatialStationaryReferenceFrame に関連付けたいとします。 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;
   }

このプロセスは、次の 2 つの点で役立ちます。

  1. 2 つの参照フレームを相互を基準として解釈できるかどうかが示されます。
  2. その場合は、一方の座標系からもう一方の座標系に直接移動するための変換が提供されます。

この情報を使用して、2 つの参照フレーム間のオブジェクト間の空間関係を解釈できます。

レンダリングについては、多くの場合、元の参照フレームまたはアンカーに従ってオブジェクトをグループ化することで、よりよい結果を得ることができます。 グループごとに別個の描画パスを実行します。 ビュー マトリックスとしては、同じ座標系を使用して最初に作成されたモデル変換を使用するオブジェクトの方が正確です。

デバイスにアタッチされている参照フレームを使用してホログラムを作成する

デバイスの場所にアタッチされたままのホログラムをレンダリングしたい場合もあります。たとえば、デバッグ情報を含むパネルや、デバイスが空間内の自らの位置ではなく、その向きだけを判別できる場合の情報メッセージです。 これを行うには、アタッチされた参照フレームを使用します。

SpatialLocatorAttachedFrameOfReference クラスは、現実世界ではなくデバイスを基準とした座標系を定義します。 このフレームには、参照フレームの作成時にユーザーが向いていた方向を示す、ユーザーの周囲を基準とした固定見出しがあります。 それ以降、この参照フレーム内のすべての向きは、ユーザーがデバイスを回転させても、その固定見出しが基準となります。

HoloLens の場合、このフレームの座標系の基点は、ユーザーの頭部の回転中心にあるため、その位置は頭の回転の影響を受けません。 アプリでは、そのポイントを基準としてオフセットを指定することで、ユーザーの前にホログラムを配置できます。

SpatialLocatorAttachedFrameOfReference を取得するには、SpatialLocator クラスを使用し、CreateAttachedFrameOfReferenceAtCurrentHeading を呼び出します。

これは、Windows Mixed Reality デバイスの全範囲に適用されます。

デバイスにアタッチされている参照フレームを使用する

これらのセクションでは、この API を使用してデバイスにアタッチされた参照フレームを有効にするために Windows Holographic アプリ テンプレートで変更した内容について説明します。 この "アタッチされた" ホログラムは、静止した、または固定されたホログラムと共に動作し、デバイスが現実世界での自らの位置を一時的に検出できない場合にも使用できます。

最初に、SpatialStationaryFrameOfReference ではなく、SpatialLocatorAttachedFrameOfReference を格納するようにテンプレートを変更しました。

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") を使用しています。 ホログラムをユーザーの視線入力にロックするよりも、こちらの方がユーザーにとっては快適です。 Tag-along ホログラムの位置を lerp すると、動きが減衰するので、ホログラムを安定させることもできます。 この減衰を行わなかった場合、通常は、ユーザーの頭部の動きが感知できない程度のものとみなされるため、ホログラム ジッターが発生します。

StationaryQuadRenderer::PositionHologram から:

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

Note

デバッグ パネルの場合は、ビューが妨げられないように、ホログラムの位置を変えて、サイドから少し離すようにすることもできます。 その方法の例を次に示します。

StationaryQuadRenderer::PositionHologram の場合:

       // 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 メートルの位置を "追跡" するようになりました。

Note

この例では、追加のコンテンツも読み込まれます。StationaryQuadRenderer.cpp を参照してください。

追跡損失の処理

デバイスが自らを現実世界で見つけられないと、アプリで "追跡損失" が発生します。 Windows Mixed Reality アプリには、位置追跡システムに対するこのような中断を処理する機能があります。 これらの中断を観察し、応答を作成するには、既定の SpatialLocator で LocatabilityChanged イベントを使用します。

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 状態では、アプリは通常の操作を一時停止し、警告メッセージを表示する tag-along ホログラムをレンダリングできます。

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 では、サーフェス メッシュのモデル変換を取得するために、座標系を使用します。

関連項目