DirectX の空間マッピング

Note

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

このトピックでは、DirectX アプリで空間マッピングを実装する方法について説明します。これには、ユニバーサル Windows プラットフォーム SDK でパッケージ化された空間マッピング サンプル アプリケーションの詳細な説明が含まれます。

このトピックでは、HolographicSpatialMapping UWP コード サンプルのコードを使用します。

Note

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

デバイス サポート

機能 HoloLens (第 1 世代) HoloLens 2 イマーシブ ヘッドセット
空間マッピング ✔️ ✔️

DirectX 開発の概要

空間マッピング用のネイティブ アプリケーション開発では、Windows.Perception.Spatial 名前空間でこの API を使用します。 これらの API を使用すると、Unity によって空間マッピング API が公開されるのと同じ方法で、空間マッピング機能を完全に制御できます。

認知 API

空間マッピング開発用に提供される主な型は次のとおりです。

  • SpatialSurfaceObserver は、SpatialSurfaceInfo オブジェクトの形式で、ユーザーの近くのアプリケーションで指定された空間内のサーフェスに関する情報を提供します。
  • SpatialSurfaceInfo は、一意の ID、境界ボリューム、最終変更時刻など、1 つの外部空間サーフェスを表します。 要求に応じて SpatialSurfaceMesh が非同期的に提供されます。
  • SpatialSurfaceMeshOptions には、SpatialSurfaceInfo から要求された SpatialSurfaceMesh オブジェクトをカスタマイズするために使用されるパラメーターが含まれます。
  • SpatialSurfaceMesh は、1 つの空間サーフェスのメッシュ データを表します。 頂点位置、頂点法線、三角形インデックスのデータは、メンバー SpatialSurfaceMeshBuffer オブジェクトに含まれています。
  • SpatialSurfaceMeshBuffer は、1 種類のメッシュ データをラップします。

これらの API を使用してアプリケーションを開発する場合、基本的なプログラム フローは次のようになります (以下のサンプル アプリケーションで示します)。

  • SpatialSurfaceObserver を設定する
    • RequestAccessAsync を呼び出して、ユーザーがデバイスの空間マッピング機能を使用するためのアクセス許可をアプリケーションに付与したことを確実にします。
    • SpatialSurfaceObserver オブジェクトをインスタンス化します。
    • SetBoundingVolumes を呼び出して、空間サーフェスに関する情報が必要な領域を指定します。 この関数を再度呼び出すことによって、今後これらのリージョンを変更できます。 各リージョンは SpatialBoundingVolume を使用して指定されます。
    • ObservedSurfacesChanged イベントに登録します。このイベントは、指定した空間領域内の空間サーフェスに関する新しい情報が提供されるたびに発生します。
  • ObservedSurfacesChanged イベントを処理する
    • イベント ハンドラーで GetObservedSurfaces を呼び出して、SpatialSurfaceInfo オブジェクトのマップを受信します。 このマップを使用して、ユーザーの環境に存在する空間サーフェスのレコードを更新できます。
    • SpatialSurfaceInfo オブジェクトごとに、TryGetBounds に対してクエリを実行して、選択した空間座標系で表されるサーフェスの空間エクステントを決定できます。
    • 空間サーフェスのメッシュを要求する場合は、TryComputeLatestMeshAsync を呼び出します。 三角形の密度と返されるメッシュ データの形式を指定するオプションを指定できます。
  • メッシュを受信して処理する
    • TryComputeLatestMeshAsync を呼び出すごとに、1 つの SpatialSurfaceMesh オブジェクトが非同期的に返されます。
    • このオブジェクトから、含まれている SpatialSurfaceMeshBuffer オブジェクトにアクセスできます。これにより、メッシュの三角形インデックス、頂点位置、頂点法線を要求した場合にアクセスできます。 このデータは、メッシュのレンダリングに使用される Direct3D 11 APIs と直接互換性のある形式になります。
    • ここから、アプリケーションは、必要に応じてメッシュ データを分析または処理し、レンダリングと物理のレイキャストと衝突に使用できます。
    • 注意する必要がある重要な詳細の 1 つは、メッシュの頂点位置 (メッシュのレンダリングに使用される頂点シェーダーなど) にスケールを適用して、バッファーに格納されている最適化された整数単位から、メートルに変換する必要がある点です。 このスケールを取得するには、VertexPositionScale を呼び出します。

トラブルシューティング

  • SpatialSurfaceMesh.VertexPositionScale によって返されるスケールを使用して、頂点シェーダーでメッシュの頂点位置をスケーリングすることを忘れないでください

空間マッピングのコード サンプル チュートリアル

Holographic Spatial Mapping コード サンプルには、サーフェス メッシュを管理およびレンダリングするためのインフラストラクチャなど、アプリへのサーフェス メッシュの読み込みを開始するために使用できるコードが含まれています。

次に、DirectX アプリにサーフェス マッピング機能を追加する方法について確認します。 このコードを Windows Holographic アプリ テンプレート プロジェクトに追加するか、上記のコード サンプルを参照して確認できます。 このコード サンプルは、Windows Holographic アプリ テンプレートに基づいて作成されています。

spatialPerception 機能を使用するアプリを設定する

アプリでは、空間マッピング機能を使用できます。 空間メッシュは、プライベート データと見なされる可能性があるユーザーの環境の表現なので、これが必要です。 アプリの package.appxmanifest ファイルでこの機能を宣言します。 次に例を示します。

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

この機能は、uap2 名前空間から取得されます。 マニフェストでこの名前空間にアクセスするには、<Package> 要素に xlmns 属性として含める必要があります。 次に例を示します。

<Package
    xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
    xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
    xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
    xmlns:uap2="https://schemas.microsoft.com/appx/manifest/uap/windows10/2"
    IgnorableNamespaces="uap uap2 mp"
    >

空間マッピング機能のサポートを確認する

Windows Mixed Reality は、空間マッピングをサポートしないデバイスなど、さまざまなデバイスをサポートしています。 アプリで空間マッピングを使用できる、または空間マッピングを使用して機能を提供する必要がある場合、空間マッピングを使用する前に、空間マッピングがサポートされていないことを確認する必要があります。 たとえば、Mixed Reality アプリで空間マッピングが必要な場合、ユーザーが空間マッピングを使用せずにデバイスで実行しようとすると、その効果に対するメッセージが表示されます。 または、アプリはユーザーの環境の代用に独自の仮想環境をレンダリングし、空間マッピングが使用可能な場合に発生するエクスペリエンスと同様のエクスペリエンスを提供できます。 いずれにしても、この API を使用すると、アプリが空間マッピング データを取得し、適切な方法で応答しない状況を認識できます。

現在のデバイスで空間マッピングのサポートを確認するには、まず UWP コントラクトがレベル 4 以上であり、SpatialSurfaceObserver::IsSupported() を呼び出すことを確認します。 Holographic Spatial Mapping コード サンプルのコンテキストでこれを行う方法を次に示します。 アクセスを要求する直前にサポートが確認されます。

The SpatialSurfaceObserver::IsSupported() API は SDK バージョン 15063 から入手できます。 必要に応じて、この API を使用する前に、プロジェクトをプラットフォーム バージョン 15063 に再ターゲットします。

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 未満の場合、アプリは、デバイスが空間マッピングを実行できる場合と同様に続行する必要があります。

空間マッピング データへのアクセスを要求する

アプリは、サーフェス オブザーバーを作成する前に、空間マッピング データにアクセスするためのアクセス許可を要求する必要があります。 サーフェス マッピング コード サンプルに基づく例を次に示します。詳細については、このページで後で説明します。

auto initSurfaceObserverTask = create_task(SpatialSurfaceObserver::RequestAccessAsync());
initSurfaceObserverTask.then([this, coordinateSystem](Windows::Perception::Spatial::SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Create a surface observer.
    }
    else
    {
        // Handle spatial mapping unavailable.
    }
}

サーフェス オブザーバーを作成する

Windows::Perception::Spatial::Surfaces 名前空間には、SpatialCoordinateSystem で指定した 1 つ以上のボリュームを監視する SpatialSurfaceObserver クラスが含まれています。 SpatialSurfaceObserver インスタンスを使用して、サーフェス メッシュ データにリアルタイムでアクセスします。

AppMain.h から:

// Obtains surface mapping data from the device in real time.
Windows::Perception::Spatial::Surfaces::SpatialSurfaceObserver^     m_surfaceObserver;
Windows::Perception::Spatial::Surfaces::SpatialSurfaceMeshOptions^  m_surfaceMeshOptions;

前のセクションで説明した通り、アプリで使用する前に、空間マッピング データへのアクセスを要求する必要があります。 このアクセス権は、HoloLens で自動的に付与されます。

// The surface mapping API reads information about the user's environment. The user must
// grant permission to the app to use this capability of the Windows Mixed Reality device.
auto initSurfaceObserverTask = create_task(SpatialSurfaceObserver::RequestAccessAsync());
initSurfaceObserverTask.then([this, coordinateSystem](Windows::Perception::Spatial::SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // If status is allowed, we can create the surface observer.
        m_surfaceObserver = ref new SpatialSurfaceObserver();

次に、特定の境界ボリュームを観察するために、サーフェス オブザーバーを構成する必要があります。 ここでは、座標系の原点を中心に 20 x 20 x 5 m のボックスを観察します。

// The surface observer can now be configured as needed.

        // In this example, we specify one area to be observed using an axis-aligned
        // bounding box 20 meters in width and 5 meters in height and centered at the
        // origin.
        SpatialBoundingBox aabb =
        {
            { 0.f,  0.f, 0.f },
            {20.f, 20.f, 5.f },
        };

        SpatialBoundingVolume^ bounds = SpatialBoundingVolume::FromBox(coordinateSystem, aabb);
        m_surfaceObserver->SetBoundingVolume(bounds);

代わりに、複数の境界ボリュームを設定できます。

これは擬似コードです。

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

ビュー視錐台や軸が整列されていない境界ボックスなど、他の境界図形を使用することもできます。

これは擬似コードです。

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

サーフェス マッピング データが使用できないときにアプリで異なる操作を行う必要がある場合は、SpatialPerceptionAccessStatus許可されていない状況に応答するコードを記述できます。たとえば、イマーシブ デバイスが接続されている PC では、それらのデバイスに空間マッピング用のハードウェアが用意されていないため、許可されません。 これらのデバイスでは、ユーザーの環境とデバイスの構成に関する情報を空間ステージに依存する必要があります。

サーフェス メッシュ コレクションを初期化して更新する

サーフェス オブザーバーが正常に作成された場合は、引き続きサーフェス メッシュ コレクションを初期化できます。 ここでは、プル モデル API を使用して、現在の一連の観察されたサーフェスをすぐに取得します。

auto mapContainingSurfaceCollection = m_surfaceObserver->GetObservedSurfaces();
        for (auto& pair : mapContainingSurfaceCollection)
        {
            // Store the ID and metadata for each surface.
            auto const& id = pair->Key;
            auto const& surfaceInfo = pair->Value;
            m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
        }

また、サーフェス メッシュ データを取得するために使用できるプッシュ モデルもあります。 選択した場合は、プル モデルのみを使用するアプリを自由にデザインできます。その場合、データを頻繁にポーリングします (フレームごとに 1 回、ゲームのセットアップ中など、特定の期間中など)。 その場合は、上記のコードが必要です。

このコード サンプルでは、両方のモデルを教育目的で使用する方法を示しています。 ここでは、システムが変更を認識するたびに、最新のサーフェス メッシュ データを受信するイベントをサブスクライブします。

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

このコード サンプルは、これらのイベントに応答するように構成されています。 これを行う方法を見てみましょう。

注意: これは、アプリがメッシュ データを処理するための最も効率的な方法ではない可能性があります。 このコードはわかりやすいように記述されており、最適化されていません。

サーフェス メッシュ データは、キー値として Platform::Guids を使用して SpatialSurfaceInfo オブジェクトを格納する読み取り専用マップで提供されます。

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

このデータを処理するために、まずコレクションに含まれていないキー値を検索します。 サンプル アプリでのデータの格納方法の詳細については、このトピックの後半で説明します。

// Process surface adds and updates.
for (const auto& pair : surfaceCollection)
{
    auto id = pair->Key;
    auto surfaceInfo = pair->Value;

    if (m_meshCollection->HasSurface(id))
    {
        // Update existing surface.
        m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
    }
    else
    {
        // New surface.
        m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
    }
}

また、サーフェス メッシュ コレクションに含まれていても、システム コレクションには存在しないサーフェス メッシュを削除する必要があります。 これを行うには、メッシュを追加および更新するために説明したのとは逆の操作を行う必要があります。アプリのコレクションでループし、システム コレクションに含まれている Guid があるかどうかを確認します。 システム コレクションに含まれていない場合は、ここから削除します。

AppMain.cpp のイベント ハンドラーから、次のようにします。

m_meshCollection->PruneMeshCollection(surfaceCollection);

RealtimeSurfaceMeshRenderer.cpp でのメッシュ プルーニングの実装は次のとおりです。

void RealtimeSurfaceMeshRenderer::PruneMeshCollection(IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection)
{
    std::lock_guard<std::mutex> guard(m_meshCollectionLock);
    std::vector<Guid> idsToRemove;

    // Remove surfaces that moved out of the culling frustum or no longer exist.
    for (const auto& pair : m_meshCollection)
    {
        const auto& id = pair.first;
        if (!surfaceCollection->HasKey(id))
        {
            idsToRemove.push_back(id);
        }
    }

    for (const auto& id : idsToRemove)
    {
        m_meshCollection.erase(id);
    }
}

サーフェス メッシュのデータ バッファーを取得して使用する

サーフェス メッシュ情報の取得は、データ コレクションをプルし、そのコレクションに更新を処理するのと同じように簡単でした。 ここでは、データの使用方法について詳しく説明します。

このコード例では、レンダリングにサーフェス メッシュを使用することを選択しました。 これは、実世界のサーフェスの背景にあるオクルード ホログラムの一般的なシナリオです。 また、メッシュをレンダリングしたり、処理したバージョンをレンダリングして、アプリまたはゲームの機能の提供を開始する前に、どの領域がスキャンされるかをユーザーに示すこともできます。

このコード サンプルでは、前のセクションで説明したイベント ハンドラーからサーフェス メッシュの更新を受信したときにプロセスを開始します。 この関数の重要なコード行は、サーフェス メッシュを更新するための呼び出しです。今回はメッシュ情報を既に処理したため、必要に応じて頂点とインデックスのデータを取得しようとしています。

RealtimeSurfaceMeshRenderer.cpp から:

void RealtimeSurfaceMeshRenderer::AddOrUpdateSurface(Guid id, SpatialSurfaceInfo^ newSurface)
{
    auto options = ref new SpatialSurfaceMeshOptions();
    options->IncludeVertexNormals = true;

    auto createMeshTask = create_task(newSurface->TryComputeLatestMeshAsync(1000, options));
    createMeshTask.then([this, id](SpatialSurfaceMesh^ mesh)
    {
        if (mesh != nullptr)
        {
            std::lock_guard<std::mutex> guard(m_meshCollectionLock);
            '''m_meshCollection[id].UpdateSurface(mesh);'''
        }
    }, task_continuation_context::use_current());
}

このサンプル コードは、データ クラス SurfaceMesh がメッシュ データの処理とレンダリングを処理するように設計されています。 これらのメッシュは、RealtimeSurfaceMeshRenderer が実際にマップを保持しているものです。 それぞれに SpatialSurfaceMesh の送信元への参照があるため、メッシュの頂点またはインデックス バッファーにアクセスしたり、メッシュの変換を取得したりする必要があるときに、いつでも使用できます。 ここでは、更新が必要であるとしてメッシュにフラグを付けます。

SurfaceMesh.cpp から:

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

次にメッシュがそれ自体を描画するように要求されたときに、最初にフラグをチェックします。 更新が必要な場合は、頂点バッファーとインデックス バッファーが GPU 上で更新されます。

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

まず、生データ バッファーを取得します。

Windows::Storage::Streams::IBuffer^ positions = m_surfaceMesh->VertexPositions->Data;
    Windows::Storage::Streams::IBuffer^ normals   = m_surfaceMesh->VertexNormals->Data;
    Windows::Storage::Streams::IBuffer^ indices   = m_surfaceMesh->TriangleIndices->Data;

次に、HoloLens によって提供されるメッシュ データを使用して、Direct3D デバイスバッファーを作成します。

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 ヘルパー関数については、サーフェス マッピングのコード サンプルである SurfaceMesh.cpp、GetDataFromIBuffer.h を参照してください。 これで、デバイス リソースの作成が完了し、メッシュが読み込まれ、更新とレンダリングの準備が整っていると見なされます。

サーフェス メッシュを更新してレンダリングする

SurfaceMesh クラスには、特殊な更新関数があります。 各 SpatialSurfaceMesh には独自の変換があり、このサンプルでは、SpatialStationaryReferenceFrame の現在の座標系を使用して変換を取得します。 次に、GPU のモデル定数バッファーを更新します。

void SurfaceMesh::UpdateTransform(
    ID3D11DeviceContext* context,
    SpatialCoordinateSystem^ baseCoordinateSystem
    )
{
    if (m_indexCount < 3)
    {
        // Not enough indices to draw a triangle.
        return;
    }

    XMMATRIX transform = XMMatrixIdentity();

    auto tryTransform = m_surfaceMesh->CoordinateSystem->TryGetTransformTo(baseCoordinateSystem);
    if (tryTransform != nullptr)
    {
        transform = XMLoadFloat4x4(&tryTransform->Value);
    }

    XMMATRIX scaleTransform = XMMatrixScalingFromVector(XMLoadFloat3(&m_surfaceMesh->VertexPositionScale));

    XMStoreFloat4x4(
        &m_constantBufferData.vertexWorldTransform,
        XMMatrixTranspose(
            scaleTransform * transform
            )
        );

    // Normals don't need to be translated.
    XMMATRIX normalTransform = transform;
    normalTransform.r[3] = XMVectorSet(0.f, 0.f, 0.f, XMVectorGetW(normalTransform.r[3]));
    XMStoreFloat4x4(
        &m_constantBufferData.normalWorldTransform,
        XMMatrixTranspose(
            normalTransform
        )
        );

    if (!m_loadingComplete)
    {
        return;
    }

    context->UpdateSubresource(
        m_modelTransformBuffer.Get(),
        0,
        NULL,
        &m_constantBufferData,
        0,
        0
        );
}

サーフェス メッシュをレンダリングする時間があれば、コレクションをレンダリングする前にいくつかの準備作業を行います。 現在の表示構成にシェーダー パイプラインを設定し、入力アセンブラー ステージを設定します。 ホログラフィック カメラ ヘルパー クラス CameraResources.cpp は、既にビューまたはプロジェクション定数バッファーを設定しています。

RealtimeSurfaceMeshRenderer::Render から:

auto context = m_deviceResources->GetD3DDeviceContext();

context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());

// Attach our vertex shader.
context->VSSetShader(
    m_vertexShader.Get(),
    nullptr,
    0
    );

// The constant buffer is per-mesh, and will be set as such.

if (depthOnly)
{
    // Explicitly detach the later shader stages.
    context->GSSetShader(nullptr, nullptr, 0);
    context->PSSetShader(nullptr, nullptr, 0);
}
else
{
    if (!m_usingVprtShaders)
    {
        // Attach the passthrough geometry shader.
        context->GSSetShader(
            m_geometryShader.Get(),
            nullptr,
            0
            );
    }

    // Attach our pixel shader.
    context->PSSetShader(
        m_pixelShader.Get(),
        nullptr,
        0
        );
}

この処理が完了したら、メッシュをループし、それぞれを描画するように指示します。 注意: このサンプル コードは、任意の種類の視錐台カリングを使用するようには最適化されていませんが、アプリにこの機能を含める必要があります。

std::lock_guard<std::mutex> guard(m_meshCollectionLock);

auto device = m_deviceResources->GetD3DDevice();

// Draw the meshes.
for (auto& pair : m_meshCollection)
{
    auto& id = pair.first;
    auto& surfaceMesh = pair.second;

    surfaceMesh.Draw(device, context, m_usingVprtShaders, isStereo);
}

個々のメッシュは、頂点とインデックス バッファー、ストライド、およびモデル変換の定数バッファーを設定します。 Windows Holographic アプリ テンプレートの回転キューブと同様に、インスタンス化を使用してステレオスコピック バッファーにレンダリングします。

SurfaceMesh::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.
    );

サーフェス マッピングでのレンダリングの選択肢

このサーフェス マッピング コード サンプルでは、サーフェス メッシュ データのオクルージョンのみのレンダリング、サーフェス メッシュ データの画面表示のためのコードを提供します。 どちらのパス、または両方とも選択するかは、アプリケーションによって異なります。 このドキュメントでは、両方の構成について説明します。

ホログラフィック効果のオクルージョン バッファーのレンダリング

まず、現在の仮想カメラのレンダリング ターゲット ビューをクリアします。

AppMain.cpp から:

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

これは "事前レンダリング" パスです。 ここでは、メッシュ レンダラーに深度だけをレンダリングするように求めることによって、オクルージョン バッファーを作成します。 この構成ではレンダリング ターゲット ビューをアタッチせず、メッシュ レンダラーはピクセル シェーダー ステージを nullptr に設定して、GPU がピクセルを描画しないようにします。 ジオメトリが深度バッファーにラスタライズされ、グラフィックス パイプラインがそこで停止します。

// Pre-pass rendering: Create occlusion buffer from Surface Mapping data.
context->ClearDepthStencilView(pCameraResources->GetSurfaceDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

// Set the render target to null, and set the depth target occlusion buffer.
// We will use this same buffer as a shader resource when drawing holograms.
context->OMSetRenderTargets(0, nullptr, pCameraResources->GetSurfaceOcclusionDepthStencilView());

// The first pass is a depth-only pass that generates an occlusion buffer we can use to know which
// hologram pixels are hidden behind surfaces in the environment.
m_meshCollection->Render(pCameraResources->IsRenderingStereoscopic(), true);

サーフェス マッピングのオクルージョン バッファーに対する追加の深度テストを使用して、ホログラムを描画できます。 このコード サンプルでは、画面の背後にある場合は、キューブのピクセルは異なる色でレンダリングされます。

AppMain.cpp から:

// Hologram rendering pass: Draw holographic content.
context->ClearDepthStencilView(pCameraResources->GetHologramDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

// Set the render target, and set the depth target drawing buffer.
ID3D11RenderTargetView *const targets[1] = { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, pCameraResources->GetHologramDepthStencilView());

// Render the scene objects.
// In this example, we draw a special effect that uses the occlusion buffer we generated in the
// Pre-Pass step to render holograms using X-Ray Vision when they are behind physical objects.
m_xrayCubeRenderer->Render(
    pCameraResources->IsRenderingStereoscopic(),
    pCameraResources->GetSurfaceOcclusionShaderResourceView(),
    pCameraResources->GetHologramOcclusionShaderResourceView(),
    pCameraResources->GetDepthTextureSamplerState()
    );

SpecialEffectPixelShader.hlsl のコードに基づいて次のようにします。

// Draw boundaries
min16int surfaceSum = GatherDepthLess(envDepthTex, uniSamp, input.pos.xy, pixelDepth, input.idx.x);

if (surfaceSum <= -maxSum)
{
    // The pixel and its neighbors are behind the surface.
    // Return the occluded 'X-ray' color.
    return min16float4(0.67f, 0.f, 0.f, 1.0f);
}
else if (surfaceSum < maxSum)
{
    // The pixel and its neighbors are a mix of in front of and behind the surface.
    // Return the silhouette edge color.
    return min16float4(1.f, 1.f, 1.f, 1.0f);
}
else
{
    // The pixel and its neighbors are all in front of the surface.
    // Return the color of the hologram.
    return min16float4(input.color, 1.0f);
}

注意:GatherDepthLess ルーチンについては、サーフェス マッピングのコード サンプルである SpecialEffectPixelShader.hlsl を参照してください。

画面にサーフェス メッシュ データをレンダリングする

また、単にサーフェス メッシュをステレオ ディスプレイ バッファーに描画することもできます。 全面を照明付きで描画することを選択しましたが、ワイヤーフレームの描画、レンダリング前のメッシュの処理、テクスチャ マップの適用などを自由に行うことができます。

ここでは、このコード サンプルでは、コレクションを描画するようにメッシュ レンダラーに指示しています。 ここでは、深度のみのパスを指定していません。ピクセル シェーダーをアタッチし、現在の仮想カメラに指定したターゲットを使用してレンダリング パイプラインを完成させます。

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

関連項目