DirectX の空間マッピング

注意

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

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

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

注意

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

デバイス サポート

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

DirectX 開発の概要

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

Perception 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 API と直接互換性のある形式になります。
    • ここから、アプリケーションは、必要に応じてメッシュ データを分析または処理し、レンダリングと物理のレイキャストと衝突に使用できます
    • 注意する必要がある重要な詳細の 1 つは、メッシュの頂点位置 (メッシュのレンダリングに使用される頂点シェーダーなど) にスケールを適用して、バッファーに格納されている最適化された整数単位からメートルに変換する必要がある点です。 このスケールを取得するには 、VertexPositionScale を呼び出します

トラブルシューティング

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

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

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

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

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 コード サンプルのコンテキストでこれを行う 方法を次 に示します。 アクセスを要求する直前にサポートが確認されます。

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

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

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

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::P ception::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 メートルのボックスを観察します。

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

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

これは擬似コードです。

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

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

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

サーフェス オブザーバーが正常に作成された場合は、引き続き Surface Mesh コレクションを初期化できます。 ここでは、プル モデル 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)
            );

このコードサンプルは、これらのイベントに応答するようにも構成されています。 では、その方法について説明します。

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

Surface メッシュデータは、key 値としてPlatform:: guidを使用して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);
    }
}

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

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

m_meshCollection->PruneMeshCollection(surfaceCollection);

RealtimeSurfaceMeshRenderer でのメッシュ排除の実装は次のとおりです。

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 メッシュのデータバッファーを取得して使用する

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

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

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

RealtimeSurfaceMeshRenderer から:

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

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 ヘルパー関数については、「Surface Mapping のコードサンプル: SurfaceMesh, 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
        );
}

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

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 アプリテンプレートの回転キューブと同様に、インスタンス化を使用してステレオスコピックバッファーにレンダリングします。

From SurfaceMesh::D raw:

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

サーフェイスマッピングでの選択肢の表示

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

Holographic effect のオクルージョンバッファーのレンダリング

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

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

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

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

// 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」を参照してください。

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

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

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

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

こちらもご覧ください