Mapeamento espacial no DirectX

Observação

Este artigo refere-se às APIs nativas do WinRT herdadas. Para novos projetos de aplicativos nativos, recomendamos usar a API OpenXR.

Este tópico descreve como implementar o mapeamento espacial em seu aplicativo DirectX, incluindo uma explicação detalhada do aplicativo de exemplo de mapeamento espacial empacotado com o SDK do Plataforma Universal do Windows.

Este tópico usa código do exemplo de código UWP HolographicSpatialMapping .

Observação

Os snippets de código neste artigo demonstram atualmente o uso do C++/CX em vez do C++17 compatível com C++/WinRT, conforme usado no modelo de projeto holográfico do C++. Os conceitos são equivalentes a um projeto C++/WinRT, embora você precise traduzir o código.

Suporte a dispositivos

Recurso HoloLens (1ª geração) HoloLens 2 Headsets imersivos
mapeamento espacial ✔️ ✔️

Visão geral de desenvolvimento do DirectX

O desenvolvimento de aplicativos nativos para mapeamento espacial usa as APIs no namespace Windows.Perception.Spatial . Essas APIs fornecem controle total da funcionalidade de mapeamento espacial, da mesma forma que as APIs de mapeamento espacial são expostas pelo Unity.

APIs de percepção

Os principais tipos fornecidos para o desenvolvimento de mapeamento espacial são os seguintes:

  • SpatialSurfaceObserver fornece informações sobre superfícies em regiões de espaço especificadas pelo aplicativo perto do usuário, na forma de objetos SpatialSurfaceInfo.
  • SpatialSurfaceInfo descreve uma única superfície espacial extant, incluindo uma ID exclusiva, volume delimitador e hora da última alteração. Ele fornecerá um SpatialSurfaceMesh de forma assíncrona mediante solicitação.
  • SpatialSurfaceMeshOptions contém parâmetros usados para personalizar os objetos SpatialSurfaceMesh solicitados de SpatialSurfaceInfo.
  • SpatialSurfaceMesh representa os dados de malha de uma única superfície espacial. Os dados para posições de vértice, normais de vértice e índices de triângulo estão contidos em objetos SpatialSurfaceMeshBuffer membros.
  • SpatialSurfaceMeshBuffer encapsula um único tipo de dados de malha.

Ao desenvolver um aplicativo usando essas APIs, seu fluxo de programa básico terá esta aparência (conforme demonstrado no aplicativo de exemplo descrito abaixo):

  • Configurar seu SpatialSurfaceObserver
    • Chame RequestAccessAsync para garantir que o usuário tenha dado permissão para seu aplicativo usar os recursos de mapeamento espacial do dispositivo.
    • Instancie um objeto SpatialSurfaceObserver.
    • Chame SetBoundingVolumes para especificar as regiões de espaço nas quais você deseja obter informações sobre superfícies espaciais. Você pode modificar essas regiões no futuro chamando essa função novamente. Cada região é especificada usando spatialBoundingVolume.
    • Registre-se para o evento ObservedSurfacesChanged , que será acionado sempre que novas informações estiverem disponíveis sobre as superfícies espaciais nas regiões de espaço especificadas.
  • Eventos Process ObservedSurfacesChanged
    • No manipulador de eventos, chame GetObservedSurfaces para receber um mapa de objetos SpatialSurfaceInfo. Usando esse mapa, você pode atualizar seus registros de quais superfícies espaciais existem no ambiente do usuário.
    • Para cada objeto SpatialSurfaceInfo, você pode consultar TryGetBounds para determinar as extensões espaciais da superfície, expressas em um sistema de coordenadas espaciais de sua escolha.
    • Se você decidir solicitar uma malha para uma superfície espacial, chame TryComputeLatestMeshAsync. Você pode fornecer opções que especificam a densidade de triângulos e o formato dos dados de malha retornados.
  • Tela de recebimento e processo
    • Cada chamada para TryComputeLatestMeshAsync retornará de forma assíncrona um objeto SpatialSurfaceMesh.
    • Nesse objeto, você pode acessar os objetos SpatialSurfaceMeshBuffer contidos, o que lhe dá acesso aos índices de triângulo, posições de vértice e normais de vértice da malha se você solicitá-los. Esses dados estarão em um formato diretamente compatível com as APIs do Direct3D 11 usadas para renderizar malhas.
    • A partir daqui, o aplicativo pode, opcionalmente, analisar ou processar os dados de malha e usá-los para renderização e raycasting físico e colisão.
    • Um detalhe importante a ser observado é que você deve aplicar uma escala às posições de vértice de malha (por exemplo, no sombreador de vértice usado para renderizar as malhas), para convertê-las das unidades de inteiro otimizadas nas quais elas são armazenadas no buffer, em medidores. Você pode recuperar essa escala chamando VertexPositionScale.

Solução de problemas

Passo a passo de exemplo de código de mapeamento espacial

O exemplo de código de Mapeamento Espacial Holográfico inclui o código que você pode usar para começar a carregar malhas de superfície em seu aplicativo, incluindo infraestrutura para gerenciar e renderizar malhas de superfície.

Agora, explicamos como adicionar a funcionalidade de mapeamento de superfície ao seu aplicativo DirectX. Você pode adicionar esse código ao seu projeto de modelo de aplicativo Holographic do Windows ou acompanhar navegando pelo exemplo de código mencionado acima. Este exemplo de código é baseado no modelo de aplicativo Holographic do Windows.

Configurar seu aplicativo para usar a funcionalidade spatialPerception

Seu aplicativo pode usar a funcionalidade de mapeamento espacial. Isso é necessário porque a malha espacial é uma representação do ambiente do usuário, que pode ser considerada dados privados. Declare essa funcionalidade no arquivo package.appxmanifest para seu aplicativo. Veja um exemplo:

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

A funcionalidade vem do namespace uap2 . Para obter acesso a esse namespace em seu manifesto, inclua-o como um atributo xlmns no <elemento Package> . Veja um exemplo:

<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"
    >

Verificar se há suporte a recursos de mapeamento espacial

Windows Mixed Reality dá suporte a uma ampla variedade de dispositivos, incluindo dispositivos, que não dão suporte ao mapeamento espacial. Se o aplicativo puder usar o mapeamento espacial ou precisar usar o mapeamento espacial para fornecer funcionalidade, ele deverá marcar para garantir que o mapeamento espacial tenha suporte antes de tentar usá-lo. Por exemplo, se o mapeamento espacial for exigido pelo aplicativo de realidade misturada, ele deverá exibir uma mensagem para esse efeito se um usuário tentar executá-lo em um dispositivo sem mapeamento espacial. Ou, seu aplicativo pode renderizar seu próprio ambiente virtual no lugar do ambiente do usuário, fornecendo uma experiência semelhante ao que aconteceria se o mapeamento espacial estivesse disponível. De qualquer forma, essa API permite que seu aplicativo esteja ciente de quando não obterá dados de mapeamento espacial e responderá da maneira apropriada.

Para marcar o dispositivo atual para suporte ao mapeamento espacial, primeiro verifique se o contrato UWP está no nível 4 ou superior e, em seguida, chame SpatialSurfaceObserver::IsSupported(). Veja como fazer isso no contexto do exemplo de código de Mapeamento Espacial Holográfico . O suporte é verificado pouco antes de solicitar acesso.

A API SpatialSurfaceObserver::IsSupported() está disponível a partir do SDK versão 15063. Se necessário, redirecione seu projeto para a plataforma versão 15063 antes de usar essa 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 ...

Quando o contrato UWP for menor que o nível 4, o aplicativo deverá continuar como se o dispositivo fosse capaz de fazer mapeamento espacial.

Solicitar acesso a dados de mapeamento espacial

Seu aplicativo precisa solicitar permissão para acessar dados de mapeamento espacial antes de tentar criar observadores de superfície. Aqui está um exemplo baseado em nosso exemplo de código de Mapeamento de Superfície, com mais detalhes fornecidos posteriormente nesta página:

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.
    }
}

Criar um observador de superfície

O namespace Windows::P erception::Spatial::Surfaces inclui a classe SpatialSurfaceObserver , que observa um ou mais volumes especificados em um SpatialCoordinateSystem. Use uma instância SpatialSurfaceObserver para acessar dados de malha de superfície em tempo real.

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

Conforme observado na seção anterior, você deve solicitar acesso aos dados de mapeamento espacial antes que seu aplicativo possa usá-los. Esse acesso é concedido automaticamente no 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();

Em seguida, você precisa configurar o observador de superfície para observar um volume delimitador específico. Aqui, observamos uma caixa de 20x20x5 metros, centralizada na origem do sistema de coordenadas.

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

Em vez disso, você pode definir vários volumes delimitadores.

Isso é pseudocódigo:

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

Também é possível usar outras formas delimitadoras , como um frusto de exibição ou uma caixa delimitadora que não está alinhada ao eixo.

Isso é pseudocódigo:

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

Se o aplicativo precisar fazer algo diferente quando os dados de mapeamento de superfície não estiverem disponíveis, você poderá escrever código para responder ao caso em que SpatialPerceptionAccessStatus não é permitido – por exemplo, ele não será permitido em computadores com dispositivos imersivos anexados porque esses dispositivos não têm hardware para mapeamento espacial. Para esses dispositivos, você deve, em vez disso, contar com o estágio espacial para obter informações sobre o ambiente do usuário e a configuração do dispositivo.

Inicializar e atualizar a coleção de malha de superfície

Se o observador de superfície foi criado com êxito, podemos continuar a inicializar nossa coleção de malha de superfície. Aqui, usamos a API do modelo de pull para obter o conjunto atual de superfícies observadas imediatamente:

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

Também há um modelo de push disponível para obter dados de malha de superfície. Você está livre para criar seu aplicativo para usar apenas o modelo de pull se escolher, nesse caso, você sondará dados de vez em quando - digamos, uma vez por quadro - ou durante um período específico, como durante a instalação do jogo. Nesse caso, o código acima é o que você precisa.

Em nosso exemplo de código, escolhemos demonstrar o uso de ambos os modelos para fins pedagógicos. Aqui, assinamos um evento para receber dados atualizados da malha de superfície sempre que o sistema reconhecer uma alteração.

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

Nosso exemplo de código também está configurado para responder a esses eventos. Vamos explicar como fazemos isso.

NOTA: Essa pode não ser a maneira mais eficiente para seu aplicativo lidar com dados de malha. Esse código é escrito para maior clareza e não é otimizado.

Os dados de malha de superfície são fornecidos em um mapa somente leitura que armazena objetos SpatialSurfaceInfo usando Platform::Guids como valores principais.

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

Para processar esses dados, procuramos primeiro os principais valores que não estão em nossa coleção. Detalhes sobre como os dados são armazenados em nosso aplicativo de exemplo serão apresentados posteriormente neste tópico.

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

Também precisamos remover malhas de superfície que estão em nossa coleção de malha de superfície, mas que não estão mais na coleção do sistema. Para fazer isso, precisamos fazer algo semelhante ao que acabamos de mostrar para adicionar e atualizar malhas; fazemos um loop na coleção do aplicativo e marcar para ver se o Guid que temos está na coleção do sistema. Se não estiver na coleção do sistema, vamos removê-la da nossa.

Em nosso manipulador de eventos no AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

A implementação da remoção de malha em 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);
    }
}

Adquirir e usar buffers de dados de malha surface

Obter as informações da malha de superfície foi tão fácil quanto extrair uma coleta de dados e processar atualizações para essa coleção. Agora, vamos entrar em detalhes sobre como você pode usar os dados.

Em nosso exemplo de código, escolhemos usar as malhas de superfície para renderização. Esse é um cenário comum para ocluir hologramas por trás de superfícies do mundo real. Você também pode renderizar as malhas ou renderizar versões processadas delas para mostrar ao usuário quais áreas da sala são verificadas antes de começar a fornecer funcionalidade de aplicativo ou jogo.

O exemplo de código inicia o processo quando recebe atualizações de malha de superfície do manipulador de eventos que descrevemos na seção anterior. A linha de código importante nessa função é a chamada para atualizar a malha de superfície: nesse momento, já processamos as informações de malha e estamos prestes a obter os dados de vértice e índice para uso como acharmos adequado.

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

Nosso código de exemplo foi projetado para que uma classe de dados, SurfaceMesh, manipule o processamento e a renderização de dados de malha. Essas malhas são o que o RealtimeSurfaceMeshRenderer realmente mantém um mapa. Cada um tem uma referência ao SpatialSurfaceMesh de onde veio, para que você possa usá-lo sempre que precisar acessar o vértice de malha ou buffers de índice ou obter uma transformação para a malha. Por enquanto, sinalizamos a malha como precisando de uma atualização.

No SurfaceMesh.cpp:

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

Na próxima vez que a malha for solicitada a desenhar a si mesma, ela marcar o sinalizador primeiro. Se uma atualização for necessária, os buffers de vértice e índice serão atualizados na GPU.

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

Primeiro, adquirimos os buffers de dados brutos:

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;

Em seguida, criamos buffers de dispositivo Direct3D com os dados de malha fornecidos pelo 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;
}

NOTA: Para a função auxiliar CreateDirectXBuffer usada no snippet anterior, consulte o exemplo de código do Mapeamento de Superfície: SurfaceMesh.cpp, GetDataFromIBuffer.h. Agora, a criação do recurso do dispositivo está concluída e a malha é considerada carregada e pronta para atualização e renderização.

Atualizar e renderizar malhas de superfície

Nossa classe SurfaceMesh tem uma função de atualização especializada. Cada SpatialSurfaceMesh tem sua própria transformação, e nosso exemplo usa o sistema de coordenadas atual para nosso SpatialStationaryReferenceFrame para adquirir a transformação. Em seguida, ele atualiza o buffer constante do modelo na 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
        );
}

Quando é hora de renderizar malhas de superfície, fazemos algum trabalho de preparação antes de renderizar a coleção. Configuramos o pipeline de sombreador para a configuração de renderização atual e configuramos o estágio do assembler de entrada. A classe auxiliar de câmera holográfica CameraResources.cpp já configurou o buffer constante de exibição/projeção agora.

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

Depois que isso for feito, vamos fazer loop em nossas malhas e dizer a cada um para desenhar a si mesmo. NOTA: Este código de exemplo não é otimizado para usar qualquer tipo de abate de frutos, mas você deve incluir esse recurso em seu aplicativo.

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

As malhas individuais são responsáveis por configurar o buffer de vértice e índice, o passo a passo e o buffer constante de transformação de modelo. Assim como acontece com o cubo giratório no modelo de aplicativo Do Windows Holographic, renderizamos em buffers estereoscópicos usando instanciação.

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

Opções de renderização com o Surface Mapping

O exemplo de código do Surface Mapping oferece código para renderização somente de oclusão de dados de malha de superfície e para renderização na tela de dados de malha de superfície. O caminho escolhido – ou ambos – depende do aplicativo. Examinaremos as duas configurações neste documento.

Renderizando buffers de oclusão para efeito holográfico

Comece desmarcando a exibição de destino de renderização para a câmera virtual atual.

Em AppMain.cpp:

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

Este é um passe de "pré-renderização". Aqui, criamos um buffer de oclusão solicitando que o renderizador de malha renderize apenas a profundidade. Nessa configuração, não anexamos uma exibição de destino de renderização e o renderizador de malha define o estágio do sombreador de pixel como nullptr para que a GPU não se incomode em desenhar pixels. A geometria será rasterizada para o buffer de profundidade e o pipeline de gráficos será interrompido por aí.

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

Podemos desenhar hologramas com um teste de profundidade extra no buffer de oclusão do Mapeamento de Superfície. Neste exemplo de código, renderizaremos pixels no cubo de uma cor diferente se eles estiverem atrás de uma superfície.

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

Com base no código de 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);
}

Nota: Para nossa rotina GatherDepthLess , consulte o exemplo de código do Mapeamento de Superfície: SpecialEffectPixelShader.hlsl.

Renderizando dados de malha de superfície para a exibição

Também podemos apenas desenhar as malhas de superfície para os buffers de exibição estéreo. Optamos por desenhar rostos cheios com iluminação, mas você está livre para desenhar fio, processar malhas antes de renderizar, aplicar um mapa de textura e assim por diante.

Aqui, nosso exemplo de código informa ao renderizador de malha para desenhar a coleção. Desta vez, não especificamos uma passagem somente de profundidade, ela anexará um sombreador de pixel e concluirá o pipeline de renderização usando os destinos especificados para a câmera virtual atual.

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

Confira também