Mapeamento espacial em DirectX

Funcionalidade HoloLens (1ª geração) HoloLens 2 Auscultadores imersivos
Mapeamento espacial ✔️ ✔️

Visão geral do desenvolvimento do DirectX

O desenvolvimento de aplicações nativas para mapeamento espacial utiliza as APIs no Windows. Perception.Espaço de nome espacial. Estas APIs dão-lhe o controlo total da funcionalidade de mapeamento espacial, da mesma forma que as APIs de mapeamento espacial são expostas pela Unidade.

APIs de perceção

Os tipos primários previstos para o desenvolvimento do mapeamento espacial são os seguintes:

  • O SpatialSurfaceObserver fornece informações sobre superfícies em regiões de espaço especificadas pela aplicação perto do utilizador, sob a forma de objetos SpatialSurfaceInfo.
  • SpatialSurfaceInfo descreve uma única superfície espacial existente, incluindo um ID único, volume limitador e tempo da última mudança. Fornecerá um SpatialSurfaceMesh assíncronosamente a pedido.
  • SpatialSurfaceMeshOptions contém parâmetros usados para personalizar os objetos SpatialSurfaceMesh solicitados ao SpatialSurfaceInfo.
  • SpatialSurfaceMesh representa os dados de malha para uma única superfície espacial. Os dados relativos às posições dos vértices, normais de vértice e índices de triângulo estão contidos nos objetos SpatialSurfaceMeshBuffer.
  • SpatialSurfaceMeshBuffer envolve um único tipo de dados de malha.

Ao desenvolver uma aplicação utilizando estas APIs, o seu fluxo de programa básico será semelhante (como demonstrado na aplicação da amostra descrita abaixo):

  • Configurar o seu SpatialSurfaceObserver
    • Ligue para RequestAccessAsync,para garantir que o utilizador autorizou a sua aplicação a utilizar as capacidades de mapeamento espacial do dispositivo.
    • Instantiate um objeto EspacialSurfaceObserver.
    • Ligue para SetBoundingVolumes para especificar as regiões do espaço em que deseja informações sobre superfícies espaciais. Poderá modificar estas regiões no futuro, voltando a chamar esta função. Cada região é especificada utilizando um EspaçoialBoundingVolume.
    • Registe-se no evento ObserveedSurfacesChanged, que disparará sempre que houver novas informações sobre as superfícies espaciais nas regiões do espaço que especificou.
  • Processo ObservadoSsSurfaces Eventos alterados
    • No seu manipulador de eventos, ligue para GetObservedSurfaces para receber um mapa de objetos SpatialSurfaceInfo. Utilizando este mapa, pode atualizar os seus registos de quais superfícies espaciais existem no ambiente do utilizador.
    • Para cada objeto SpatialSurfaceInfo, pode consultar o TryGetBounds para determinar as extensões espaciais da superfície, expressas num sistema de coordenadas espaciais à sua escolha.
    • Se decidir solicitar, malha para uma superfície espacial, ligue para TryComputeLatestMeshAsync. Pode fornecer opções que especificam a densidade dos triângulos e o formato dos dados de malha devolvidos.
  • Receber e processar malha
    • Cada chamada para TryComputeLatestMeshAsync devolverá assincroticamente um objeto EspacialSurfaceMesh.
    • A partir deste objeto, pode aceder aos objetos SpatialSurfaceMeshBuffer contidos, o que lhe dá acesso aos índices do triângulo, posições de vértice e normais de vértice da malha se os solicitar. Estes dados serão num formato diretamente compatível com as APIs 11 3D 11 utilizadas para renderizar malhas.
    • A partir daqui a sua aplicação pode opcionalmente analisar ou processar os dados da malha, e usá-los para renderização e fisa física raycasting e colisão.
    • Um detalhe importante a notar é que deve aplicar uma balança nas posições vértuosas de malha (por exemplo, no shader vértice utilizado para renderizar as malhas), para convertê-las das unidades inteiros otimizadas nas quais estão armazenadas no tampão, em metros. Pode recuperar esta escala chamando VertexPositionScale.

Resolução de problemas

Mapear a amostra de código espacial walkthrough

A amostra de código de mapeamento espacial holográfico inclui código que você pode usar para começar a carregar malhas de superfície na sua aplicação, incluindo infraestruturas para gerir e renderizar malhas de superfície.

Agora, passamos por como adicionar capacidade de mapeamento de superfície à sua aplicação DirectX. Pode adicionar este código ao seu projeto de modelo de aplicações Holográficas Windows ou pode acompanhar navegando através da amostra de código acima mencionada. Esta amostra de código baseia-se no modelo de aplicação holográfica Windows.

Crie a sua app para utilizar a capacidade de perceção espacial

A sua aplicação pode usar a capacidade de mapeamento espacial. Isto é necessário porque a malha espacial é uma representação do ambiente do utilizador, que pode ser considerado dados privados. Declare esta capacidade no ficheiro package.appxmanifest para a sua aplicação. Eis um exemplo:

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

A capacidade vem do espaço de nome uap2. Para ter acesso a este espaço de nome no seu manifesto, inclua-o como um atributo xlmns no > elemento Pacote. Eis 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"
    >

Verifique se há suporte para funcionalidades de mapeamento espacial

Windows Mixed Reality suporta uma vasta gama de dispositivos, incluindo dispositivos, que não suportam mapeamento espacial. Se a sua aplicação pode usar mapeamento espacial, ou deve usar mapeamento espacial, para fornecer funcionalidades, deve verificar se o mapeamento espacial é suportado antes de tentar usá-lo. Por exemplo, se o mapeamento espacial for exigido pela sua aplicação de realidade mista, deve apresentar uma mensagem nesse sentido se um utilizador tentar executá-lo num dispositivo sem mapeamento espacial. Ou, a sua aplicação pode tornar o seu próprio ambiente virtual no lugar do ambiente do utilizador, proporcionando uma experiência semelhante ao que aconteceria se o mapeamento espacial estivesse disponível. Em qualquer caso, esta API permite que a sua app esteja ciente de quando não obtém dados de mapeamento espacial e responde da forma apropriada.

Para verificar se o dispositivo atual se encontra em suporte de mapeamento espacial, certifique-se primeiro de que o contrato de UWP está no nível 4 ou superior e, em seguida, ligue para o SpatialSurfaceObserver:::IsSupported(). Eis como fazê-lo no contexto da amostra de código de mapeamento espacial holográfico. O suporte é verificado pouco antes de solicitar o acesso.

A API EspacialSurface::IsSupported() API está disponível a partir da versão SDK 15063. Se necessário, reencaire o seu projeto para a versão 15063 da plataforma antes de utilizar esta 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 da UWP for inferior ao nível 4, a aplicação deve proceder como se o dispositivo fosse capaz de fazer mapeamento espacial.

Solicitar acesso a dados de mapeamento espacial

A sua aplicação necessita de solicitar permissão para aceder a dados de mapeamento espacial antes de tentar criar observadores de superfície. Aqui está um exemplo baseado na nossa amostra de código de Cartografia de Superfície, com mais detalhes fornecidos mais tarde 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 Windows::P erception::Espaço de nomes de superfícies inclui a classe SpatialSurfaceObserver, que observa um ou mais volumes que especifica num Sistema EspacialCoordinate. Utilize uma instância SpatialSurfaceObserver para aceder aos dados da malha de superfície em tempo real.

A partir de 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;

Como indicado na secção anterior, deve solicitar o acesso aos dados de mapeamento espacial antes de a sua aplicação poder usá-lo. Este 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, é necessário configurar o observador de superfície para observar um volume de delimitação específico. Aqui, observamos uma caixa que é de 20x20x5 metros, centrada 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, pode definir vários volumes de limitação.

Isto é pseudocódigo:

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

Também é possível usar outras formas de delimitação - como uma vista frustum, ou uma caixa de delimitação que não está alinhada com o eixo.

Isto é pseudocódigo:

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

Se a sua aplicação precisar de fazer algo diferente quando os dados de mapeamento de superfície não estiverem disponíveis, pode escrever código para responder ao caso em que o SpatialPerceptionAccessStatus não é permitido - por exemplo, não será permitido em Computadores com dispositivos imersivos ligados porque esses dispositivos não têm hardware para mapeamento espacial. Para estes dispositivos, deverá contar com a fase espacial para obter informações sobre o ambiente e a configuração do dispositivo.

Inicialize e atualize a coleção de malha de superfície

Se o observador de superfície foi criado com sucesso, podemos continuar a rubricar a nossa coleção de malha de superfície. Aqui, usamos o modelo de puxar API 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);
        }

Há também um modelo de push disponível para obter dados de malha de superfície. Você é livre de desenhar a sua app para usar apenas o modelo pull se você escolher, e nesse caso você vai pesquisar dados de vez em quando - digamos, uma vez por frame - ou durante um período de tempo específico, como durante a configuração do jogo. Se assim for, o código acima é o que precisa.

Na nossa amostra de código, optámos por demonstrar o uso de ambos os modelos para fins pedagógicos. Aqui, subscrevemos um evento para receber dados atualizados de malha de superfície sempre que o sistema reconheça uma alteração.

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

A nossa amostra de código também está configurada para responder a estes eventos. Vamos ver como fazemos isto.

NOTA: Esta pode não ser a forma mais eficiente de a sua aplicação lidar com dados de malha. Este código está escrito para clareza e não está otimizado.

Os dados de malha de superfície são fornecidos num mapa apenas de leitura que armazena objetos SpatialSurfaceInfo usando a Plataforma::Guids como valores-chave.

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

Para processar estes dados, procuramos primeiro valores-chave que não estão na nossa coleção. Os detalhes sobre como os dados são armazenados na nossa aplicação de amostra serão apresentados mais tarde 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 temos de remover as malhas superficiais que estão na nossa coleção de malha de superfície, mas que já não estão na recolha do sistema. Para tal, temos de fazer algo semelhante ao oposto do que acabámos de mostrar para adicionar e atualizar as malhas; fazemos um loop na coleção da nossa aplicação, e verificamos se o Guid que temos está na coleção do sistema. Se não estiver na coleção do sistema, removemo-la da nossa.

Do nosso manipulador de eventos em AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

A implementação da poda 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 utilizar tampões de dados de malha de superfície

Obter a informação sobre malha de superfície foi tão fácil como puxar uma recolha de dados e processar atualizações para essa recolha. Agora, vamos entrar em detalhes sobre como pode usar os dados.

No nosso exemplo de código, optámos por utilizar as malhas de superfície para renderização. Este é um cenário comum para ocluding hologramas atrás de superfícies do mundo real. Também pode renderizar as malhas, ou renderizar versões processadas das mesmos, para mostrar ao utilizador quais as áreas da sala que são digitalizadas antes de começar a fornecer aplicações ou funcionalidades de jogo.

A amostra de código inicia o processo quando recebe atualizações de malha de superfície do manipulador de eventos que descrevemos na secção anterior. A linha de código importante nesta função é a chamada para atualizar a malhade superfície : por esta altura já processamos a informação de malha, e estamos prestes a obter os dados do vértice e do índice para uso como entendermos.

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

O nosso código de amostra foi concebido para que uma classe de dados, a SurfaceMesh,lide com o processamento e renderização de dados de malha. Estas malhas são o que o RealtimeSurfaceMeshRenderer realmente mantém um mapa de. Cada um tem uma referência ao SpatialSurfaceMesh de onde veio, por isso pode usá-lo sempre que precisar de aceder ao vértice de malha ou aos amortecedores de índice, ou obter uma transformação para a malha. Por enquanto, sinalizamos a malha como precisando de uma atualização.

Da .cpp SurfaceMesh:

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

Da próxima vez que a malha for convidada a desenhar-se, verificará primeiro a bandeira. Se for necessária uma atualização, os amortecedores 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 amortecedores 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 tampão de dispositivo Direct3D com os dados de malha fornecidos pela 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 de ajudante CreateDirectXBuffer utilizada no corte anterior, consulte a amostra de código de mapeamento de superfície: SurfaceMesh.cpp, GetDataFromIBuffer.h. Agora a criação de recursos do dispositivo está completa, e a malha é considerada carregada e pronta para atualizar e renderizar.

Atualizar e renderizar malhas de superfície

A nossa classe SurfaceMesh tem uma função de atualização especializada. Cada SpatialSurfaceMesh tem a sua própria transformação, e a nossa amostra utiliza o sistema de coordenadas atual para o nosso SpatialStationaryReferenceFrame adquirir a transformação. Em seguida, atualiza o tampão 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 superficiais, fazemos alguns trabalhos de preparação antes de renderizar a coleção. Configuramos o pipeline shader para a configuração de renderização atual, e configuramos o estágio do conjuntor de entradas. A classe de ajudante de câmara holográfica CameraResources.cpp já montou o tampão constante de visualização/projeção.

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

Uma vez feito isto, damos a volta às nossas malhas e dizemos a cada um para se desenharem. NOTA: Este código de amostra não está otimizado para usar qualquer tipo de abate de frustum, mas você deve incluir esta funcionalidade na sua aplicação.

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 pela configuração do tampão vértice e índice, passo e tampão de transformação constante do modelo. Tal como acontece com o cubo de fiação no modelo de aplicação holográfica Windows, prestamos tampão estereoscópico usando a instancing.

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

Escolhas de renderização com mapeamento de superfície

A amostra de código de Cartografia de Superfície oferece código para a renderização apenas de dados de malha de superfície e para a renderização no ecrã de dados de malha de superfície. Qual o caminho que escolher - ou ambos - depende da sua aplicação. Vamos percorrer ambas as configurações neste documento.

Tampão de oclusão para efeitos holográficos

Comece por limpar a vista do alvo de renderização para a câmara virtual atual.

A partir de AppMain.cpp:

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

Este é um passe de "pré-renderização". Aqui, criamos um tampão de oclusão pedindo ao renderizador de malha para tornar apenas profundidade. Nesta configuração, não anexamos uma visão de alvo de renderização, e o renderizador de malha define o estágio de shader de pixel para anular para que a GPU não se dê ao trabalho de desenhar pixels. A geometria será rasterizada até ao tampão de profundidade, e o oleoduto gráfico vai parar 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 contra o tampão de oclusão deapeamento de superfície. Nesta amostra de código, tornamos os pixels no cubo uma cor diferente se estiverem atrás de uma superfície.

A partir de 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()
    );

Baseado no código da 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 a nossa rotina GatherDepthLess, consulte a amostra de código de Mapeamento de Superfície: SpecialEffectPixelShader.hlsl.

Renderização de dados de malha de superfície para o visor

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

Aqui, a nossa amostra de código diz ao renderizador de malha para desenhar a coleção. Desta vez não especificamos um passe apenas de profundidade, ele irá anexar um shader de pixels e completar o gasoduto de renderização usando os alvos que especificamos para a câmara 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);

Ver também

Nota

Este artigo diz respeito ao legado WinRT native APIs. Para novos projetos de aplicações nativas, recomendamos a utilização da API OpenXR.

Este tópico descreve como implementar mapeamento espacial na sua aplicação DirectX, incluindo uma explicação detalhada da aplicação de amostra de mapeamento espacial embalada com a Plataforma de Windows Universal SDK.

Este tópico utiliza código a partir da amostra de código UWP holográficaSpatialMapping.

Nota

Os fragmentos de código neste artigo demonstram atualmente a utilização de C++/CX em vez de C++17-compliant C++/WinRT, conforme ao C++17, conforme ao C+++/WinRT, utilizado no modelo de projeto holográfico C++. Os conceitos são equivalentes a um projeto C++/WinRT, embora tenha de traduzir o código.

Suporte de dispositivos