DirectX の空間マッピングSpatial mapping in DirectX

注意

この記事は、従来の WinRT ネイティブ Api に関連しています。This article relates to the legacy WinRT native APIs. 新しいネイティブアプリプロジェクトの場合は、 OPENXR API を使用することをお勧めします。For new native app projects, we recommend using the OpenXR API .

このトピックでは、DirectX アプリで 空間マッピング を実装する方法について説明します。This topic describes how to implement spatial mapping in your DirectX app. これには、ユニバーサル Windows プラットフォーム SDK に含まれる空間マッピングサンプルアプリケーションの詳細な説明が含まれます。This includes a detailed explanation of the spatial mapping sample application that is included with the Universal Windows Platform SDK.

このトピックでは、 HolographicSpatialMapping UWP コードサンプルのコードを使用します。This topic uses code from the HolographicSpatialMapping UWP code sample.

注意

この記事のコードスニペットでは、 C++ holographic プロジェクトテンプレートで使用される c + c++ 17 準拠の c++/WinRT ではなく、c++/cx の使用方法を現在説明しています。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 ✔️✔️ ✔️✔️

DirectX 開発の概要DirectX development overview

空間マッピングのネイティブアプリケーション開発では、 Windows のような名前空間の api を使用します。Native application development for spatial mapping uses the APIs under the Windows.Perception.Spatial namespace. これらの Api は、 Unityによって公開される空間マッピング api に直接似た方法で空間マッピング機能を完全に制御します。These APIs provide full control of spatial mapping functionality, in a manner directly analogous to the spatial mapping APIs exposed by Unity.

認識 ApiPerception APIs

空間マッピング開発の主な種類は次のとおりです。The primary types provided for spatial mapping development are as follows:

  • SpatialSurfaceObserver は、SpatialSurfaceInfo オブジェクトの形式で、ユーザーの近くにある、アプリケーションで指定された領域内のサーフェイスに関する情報を提供します。SpatialSurfaceObserver provides information about surfaces in application-specified regions of space near the user, in the form of SpatialSurfaceInfo objects.
  • SpatialSurfaceInfo は、一意の ID、境界ボリューム、最後の変更の時間など、1つの既存空間サーフェスを表します。SpatialSurfaceInfo describes a single extant spatial surface, including a unique ID, bounding volume and time of last change. 要求に応じて非同期的に SpatialSurfaceMesh を提供します。It will provide a SpatialSurfaceMesh asynchronously upon request.
  • SpatialSurfaceMeshOptions には、SpatialSurfaceInfo から要求された SpatialSurfaceMesh オブジェクトをカスタマイズするために使用されるパラメーターが含まれています。SpatialSurfaceMeshOptions contains parameters used to customize the SpatialSurfaceMesh objects requested from SpatialSurfaceInfo.
  • SpatialSurfaceMesh は、1つの空間サーフェスのメッシュデータを表します。SpatialSurfaceMesh represents the mesh data for a single spatial surface. 頂点の位置、頂点の法線、三角形のインデックスのデータは、メンバーの SpatialSurfaceMeshBuffer オブジェクトに含まれています。The data for vertex positions, vertex normals and triangle indices is contained in member SpatialSurfaceMeshBuffer objects.
  • SpatialSurfaceMeshBuffer は、単一の種類のメッシュデータをラップします。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):

  • SpatialSurfaceObserver を設定するSet up your SpatialSurfaceObserver
    • Requestaccessasyncを呼び出して、ユーザーがアプリケーションにデバイスの空間マッピング機能を使用するためのアクセス許可を与えられていることを確認します。Call RequestAccessAsync, to ensure that the user has given permission for your application to use the device's spatial mapping capabilities.
    • SpatialSurfaceObserver オブジェクトをインスタンス化します。Instantiate a SpatialSurfaceObserver object.
    • 空間サーフェスに関する情報が必要な領域を指定するには、 SetBoundingVolumes を呼び出します。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 simply calling this function again. 各リージョンは、 SpatialBoundingVolumeを使用して指定されます。Each region is specified using a SpatialBoundingVolume.
    • ObservedSurfacesChangedイベントに登録します。これは、指定した領域の空間サーフェスに関する新しい情報が利用可能になるたびに起動されます。Register for the ObservedSurfacesChanged event, which will fire whenever new information is available about the spatial surfaces in the regions of space you have specified.
  • ObservedSurfacesChanged イベントの処理Process ObservedSurfacesChanged events
    • イベントハンドラーで、 GetObservedSurfaces を呼び出して、SpatialSurfaceInfo オブジェクトのマップを受信します。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.
    • 各 SpatialSurfaceInfo オブジェクトに対して、 Trygetbounds に対してクエリを実行し、選択した 空間座標系 で表現されるサーフェスの空間範囲を決定することができます。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.
    • 空間サーフェスにメッシュを要求する場合は、 TryComputeLatestMeshAsyncを呼び出します。If you decide to request mesh for a spatial surface, call TryComputeLatestMeshAsync. 三角形の目的の密度と、返されるメッシュデータの形式を指定するオプションを指定できます。You may provide options specifying the desired density of triangles, and the format of the returned mesh data.
  • メッシュの受信と処理Receive and process mesh
    • TryComputeLatestMeshAsync を呼び出すたびに、1つの SpatialSurfaceMesh オブジェクトが aysnchronously 返されます。Each call to TryComputeLatestMeshAsync will aysnchronously return one SpatialSurfaceMesh object.
    • このオブジェクトから、含まれている SpatialSurfaceMeshBuffer オブジェクトにアクセスして、三角形のインデックス、頂点位置、および (要求された場合) メッシュの頂点法線にアクセスできます。From this object you can access the contained SpatialSurfaceMeshBuffer objects in order to access the triangle indices, vertex positions and (if requested) vertex normals of the mesh. このデータは、メッシュのレンダリングに使用される Direct3D 11 api と直接互換性がある形式になります。This data will be in a format directly compatible with the Direct3D 11 APIs used for rendering meshes.
    • ここから、アプリケーションでメッシュデータの分析または 処理 を必要に応じて実行し、 レンダリング や物理的な raycasting と競合に使用することができます。From here your application can optionally perform analysis or processing of the mesh data, and use it for rendering and physics raycasting and collision.
    • 注意すべき重要な点の1つは、メッシュの頂点位置 (メッシュのレンダリングに使用される頂点シェーダーなど) にスケールを適用して、バッファーに格納されている最適化された整数単位からメーターに変換する必要があることです。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 are stored in the buffer, to meters. このスケールを取得するには、 Vertexpositionscaleを呼び出します。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 アプリに surface マッピング機能を追加する方法について説明します。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.

SpatialPerception 機能を使用するようにアプリを設定するSet up your app to use the spatialPerception capability

アプリで空間マッピング機能を使用できる必要があります。Your app must be able to 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. マニフェスト内のこの名前空間へのアクセスを取得するには、パッケージの> 要素に xlmns 属性として含め < ます。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 do not support spatial mapping. アプリで空間マッピングを使用できる場合、または空間マッピングを使用して機能を提供する場合は、使用する前に空間マッピングがサポートされていることを確認する必要があります。If your app can use spatial mapping, or must use spatial mapping, to provide functionality, it should check to make sure that 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 may be able to 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 will not get spatial mapping data and respond in the appropriate way.

現在のデバイスでの空間マッピングのサポートを確認するには、最初に UWP コントラクトがレベル4以上であることを確認してから、SpatialSurfaceObserver:: IsSupported () を呼び出します。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(). Holographic 空間マッピングコードサンプルのコンテキストでこれを行う方法を次に示します。Here's how to do so in the context of the Holographic Spatial Mapping code sample. アクセスを要求する直前にサポートが確認されます。Support is checked just before requesting access.

SpatialSurfaceObserver:: IsSupported () API は、SDK バージョン15063以降で使用できます。The SpatialSurfaceObserver::IsSupported() API is available starting in SDK version 15063. 必要に応じて、この API を使用する前に、プロジェクトをプラットフォームバージョン15063に再ターゲットします。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より小さい場合は、デバイスが空間マッピングを実行できるかのようにアプリを続行する必要があることに注意してください。Note that 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.
    }
}

Surface オブザーバーを作成するCreate a surface observer

Windows::P erception:: 空間:: surface 名前空間には、 SpatialSurfaceObserverクラスが含まれています。このクラスは、 SpatialCoordinateSystemで指定した1つ以上のボリュームを監視します。The Windows::Perception::Spatial::Surfaces namespace includes the SpatialSurfaceObserver class, which observes one or more volumes that you specify in a SpatialCoordinateSystem. SpatialSurfaceObserverインスタンスを使用して、リアルタイムでサーフェイスメッシュデータにアクセスします。Use a SpatialSurfaceObserver instance to access surface mesh data in real time.

Appmain から: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();

次に、特定の境界ボリュームを観察するように surface オブザーバーを構成する必要があります。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);

複数の境界ボリュームを設定できることに注意してください。Note that you can set multiple bounding volumes instead.

これは擬似コードです。This is pseudocode:

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

また、ビューを視するビューや、軸に沿っていない境界ボックスなど、他の境界図形を使用することもできます。It is also possible to use other bounding shapes - such as a view frustum, or a bounding box that is not axis aligned.

これは擬似コードです。This is pseudocode:

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

Surface マッピングのデータを使用できない場合にアプリの動作を変える必要がある場合は、 SpatialPerceptionAccessStatus許可 されていない場合に応答するコードを記述できます。たとえば、デバイスにデバイスを接続している pc では、空間マッピングのハードウェアが搭載されていないため、このコードを使用することはできません。If your app needs to do anything differently when surface mapping data is not available, you can write code to respond to the case where the SpatialPerceptionAccessStatus is not Allowed - for example, it will not 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.

Surface メッシュコレクションの初期化と更新Initialize and update the surface mesh collection

Surface オブザーバーが正常に作成された場合、surface メッシュコレクションの初期化に進むことができます。If the surface observer was successfully created, we can proceed 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);
        }

Surface メッシュデータを取得するために使用できるプッシュモデルもあります。There is also a push model available to get surface mesh data. プルモデルのみを使用するようにアプリを設計することもできます。選択した場合は、(たとえば、フレームごとに1回、またはゲームのセットアップ中など、特定の期間に) データをポーリングします。You are 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 is not optimized.

Surface メッシュデータは、key 値としてPlatform:: guidを使用してSpatialSurfaceInfoオブジェクトを格納する読み取り専用マップで提供されます。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.

AppMain のイベントハンドラーから、次のようにします。From our event handler in AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

RealtimeSurfaceMeshRenderer でのメッシュ排除の実装は次のとおりです。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);
    }
}

Surface メッシュのデータバッファーを取得して使用するAcquire and use surface mesh data buffers

Surface メッシュ情報の取得は、データコレクションをプルし、そのコレクションに更新を処理するのと同じように簡単でした。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.

このコード例では、レンダリングにサーフェイスメッシュを使用することを選択しました。In our code example, we chose to use the surface meshes for rendering. これは、実際のサーフェイスの背後にある occluding ホログラムの一般的なシナリオです。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.

このコードサンプルでは、前のセクションで説明したイベントハンドラーから表面メッシュの更新を受信したときにプロセスを開始します。The code sample starts the process when it receives surface mesh updates from the event handler that we described in the previous section. この関数の重要なコード行は、surface メッシュ を更新するための呼び出しです。今回はメッシュ情報を既に処理したため、必要に応じて頂点とインデックスのデータを取得しようとしています。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 are about to get the vertex and index data for use as we see fit.

RealtimeSurfaceMeshRenderer から: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());
}

このサンプルコードは、データクラス SurfaceMesh がメッシュデータの処理とレンダリングを処理するように設計されています。Our sample code is designed so that a data class, SurfaceMesh , handles mesh data processing and rendering. これらのメッシュは、 RealtimeSurfaceMeshRenderer が実際にマップを保持しているものです。These meshes are what the RealtimeSurfaceMeshRenderer actually keeps a map of. 各ファイルには、SpatialSurfaceMesh の元のものへの参照があり、メッシュの頂点またはインデックスバッファーにアクセスしたり、メッシュの変換を取得したりする必要があるときはいつでも使用します。Each one has a reference to the SpatialSurfaceMesh it came from, and we use it any time that we 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.

SurfaceMesh から: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;

次に、HoloLens によって提供されるメッシュデータを使用して、Direct3D デバイスバッファーを作成します。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;
}

注: 前のスニペットで使用した CreateDirectXBuffer ヘルパー関数については、「Surface Mapping のコードサンプル: SurfaceMesh, GetDataFromIBuffer. 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

SurfaceMesh クラスには、特殊な更新関数があります。Our SurfaceMesh class has a specialized update function. SpatialSurfaceMesh には独自の変換があり、このサンプルでは、 SpatialStationaryReferenceFrame の現在の座標系を使用して変換を取得します。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
        );
}

サーフェイスメッシュをレンダリングする時間があれば、コレクションをレンダリングする前にいくつかの準備作業を行います。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 カメラヘルパークラス CameraResources は、既にビュー/プロジェクション定数バッファーを設定していることに注意してください。Note that the holographic camera helper class CameraResources.cpp already has set up the view/projection constant buffer by now.

RealtimeSurfaceMeshRenderer:: 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 is not 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.

From SurfaceMesh::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 Mapping のコードサンプルでは、表面メッシュデータのオクルーリングのみのレンダリング、および表面メッシュデータの画面表示のためのコードを提供します。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 effect のオクルージョンバッファーのレンダリングRendering occlusion buffers for holographic effect

まず、現在の仮想カメラのレンダーターゲットビューをクリアします。Start by clearing the render target view for the current virtual camera.

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

Surface マッピングのオクルーバッファーに対する追加の深度テストを使用して、ホログラムを描画できます。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.

AppMain .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()
    );

SpecialEffectPixelShader のコードに基づいて次のようにします。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);
}

注:****GatherDepthLess ルーチンについては、「Surface Mapping のコードサンプル: SpecialEffectPixelShader」を参照してください。Note: For our GatherDepthLess routine, see the Surface Mapping code sample: SpecialEffectPixelShader.hlsl.

画面にサーフェイスメッシュデータをレンダリングするRendering surface mesh data to the display

また、表面メッシュをステレオディスプレイバッファーに描画するだけでもかまいません。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, so it will 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