Partilhar via


Mapeamento espacial no DirectX

Nota

Este artigo está relacionado com as APIs nativas do WinRT legadas. Para novos projetos de aplicações nativas, recomendamos a utilização da API OpenXR.

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

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

Nota

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

Suporte de dispositivos

Funcionalidade HoloLens (1.ª geração) HoloLens 2 Headsets envolventes
Mapeamento espacial ✔️ ✔️

Descrição geral do desenvolvimento do DirectX

O desenvolvimento de aplicações nativas para mapeamento espacial utiliza as APIs no espaço de nomes Windows.Perception.Spatial . Estas APIs dão-lhe controlo total da funcionalidade de mapeamento espacial, da mesma forma que as APIs de mapeamento espacial são expostas pelo Unity.

APIs de Perceção

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

  • 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 exclusivo, volume delimitador e hora da última alteração. Fornecerá um SpatialSurfaceMesh de forma assíncrona mediante pedido.
  • SpatialSurfaceMeshOptions contém parâmetros utilizados para personalizar os objetos SpatialSurfaceMesh pedidos ao SpatialSurfaceInfo.
  • SpatialSurfaceMesh representa os dados de malha para 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 uma aplicação com estas APIs, o fluxo de programa básico terá este aspeto (conforme demonstrado na aplicação de exemplo descrita abaixo):

  • Configurar o SpatialSurfaceObserver
    • Chame RequestAccessAsync para garantir que o utilizador concedeu permissão para a sua aplicação utilizar as capacidades de mapeamento espacial do dispositivo.
    • Instanciar um objeto SpatialSurfaceObserver.
    • Chame SetBoundingVolumes para especificar as regiões de espaço em que pretende obter informações sobre superfícies espaciais. Poderá modificar estas regiões no futuro ao chamar esta função novamente. Cada região é especificada com um SpatialBoundingVolume.
    • Registe-se no evento ObservedSurfacesChanged , que será acionado sempre que estiverem disponíveis novas informações sobre as superfícies espaciais nas regiões de espaço que especificou.
  • Processar Eventos ObservedSurfacesChanged
    • No processador de eventos, chame GetObservedSurfaces para receber um mapa de objetos SpatialSurfaceInfo. Com este mapa, pode atualizar os registos das superfícies espaciais existentes no ambiente do utilizador.
    • Para cada objeto SpatialSurfaceInfo, pode consultar TryGetBounds para determinar as extensões espaciais da superfície, expressas num sistema de coordenadas espaciais à sua escolha.
    • Se decidir pedir, malha para uma superfície espacial, chame TryComputeLatestMeshAsync. Pode fornecer opções que especificam a densidade dos triângulos e o formato dos dados de malha devolvidos.
  • Malha de receção e processamento
    • Cada chamada para TryComputeLatestMeshAsync devolverá de forma assíncrona um objeto SpatialSurfaceMesh.
    • A partir deste objeto, pode aceder aos 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 os pedir. Estes dados estarão num formato diretamente compatível com as APIs Direct3D 11 utilizadas para composição de malhas.
    • A partir daqui, a sua aplicação pode, opcionalmente, analisar ou processar os dados de malha e utilizá-los para fins de composição e raycast de física e colisão.
    • Um detalhe importante a ter em conta é que tem de aplicar uma escala às posições do vértice de malha (por exemplo, no sombreado de vértice utilizado para compor as malhas), para convertê-las das unidades de número inteiro otimizadas em que estão armazenadas na memória intermédia, em medidores. Pode obter esta escala ao chamar VertexPositionScale.

Resolução de problemas

Instruções de exemplo de código de Mapeamento Espacial

O exemplo de código de Mapeamento Espacial Holográfico inclui código que pode utilizar para começar a carregar malhas do surface para a sua aplicação, incluindo infraestrutura para gerir e compor malhas de superfície.

Agora, explicamos como adicionar a capacidade de mapeamento de superfície à sua aplicação DirectX. Pode adicionar este código ao seu projeto de modelo de aplicação Do Windows Holographic ou pode acompanhar ao navegar pelo exemplo de código mencionado acima. Este exemplo de código baseia-se no modelo de aplicação Do Windows Holographic.

Configurar a sua aplicação para utilizar a capacidade spatialPerception

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

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

A capacidade provém do espaço de nomes uap2 . Para obter acesso a este espaço de nomes 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"
    >

Verificar o suporte da funcionalidade de mapeamento espacial

Windows Mixed Reality suporta uma vasta gama de dispositivos, incluindo dispositivos, que não suportam o mapeamento espacial. Se a sua aplicação puder utilizar o mapeamento espacial ou tiver de utilizar o mapeamento espacial para fornecer funcionalidades, deve verificar se o mapeamento espacial é suportado antes de tentar utilizá-lo. Por exemplo, se o mapeamento espacial for exigido pela sua aplicação de realidade mista, deverá apresentar uma mensagem nesse efeito se um utilizador tentar executá-lo num dispositivo sem mapeamento espacial. Em alternativa, a sua aplicação pode compor o seu próprio ambiente virtual em vez do ambiente do utilizador, proporcionando uma experiência semelhante ao que aconteceria se o mapeamento espacial estivesse disponível. Em todo o caso, esta API permite que a sua aplicação tenha em atenção quando não obtém dados de mapeamento espacial e responde da forma adequada.

Para verificar o suporte de mapeamento espacial no dispositivo atual, primeiro certifique-se de que o contrato UWP está no nível 4 ou superior e, em seguida, chame SpatialSurfaceObserver::IsSupported(). Eis como fazê-lo no contexto do exemplo de código de Mapeamento Espacial Holográfico . O suporte é verificado imediatamente antes de pedir acesso.

A API SpatialSurfaceObserver::IsSupported() está disponível a partir da versão 15063 do SDK. Se necessário, volte a obter o 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 UWP for inferior ao nível 4, a aplicação deve continuar como se o dispositivo fosse capaz de fazer o mapeamento espacial.

Pedir acesso aos dados de mapeamento espacial

A sua aplicação tem de pedir permissão para aceder aos dados de mapeamento espacial antes de tentar criar observadores do surface. Eis um exemplo baseado no nosso exemplo de código de Mapeamento do Surface, com mais detalhes fornecidos mais à frente 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 espaço de nomes Windows::P erception::Spatial::Surfaces inclui a classe SpatialSurfaceObserver , que observa um ou mais volumes especificados num SpatialCoordinateSystem. Utilize uma instância spatialSurfaceObserver para aceder aos dados de malha do surface 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 indicado na secção anterior, tem de pedir acesso aos dados de mapeamento espacial para que a aplicação possa utilizá-la. 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, tem de configurar o observador de superfície para observar um volume delimitador específico. Aqui, observamos uma caixa que tem 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 alternativa, pode definir vários volumes delimitadores.

Isto é pseudocódigo:

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

Também é possível utilizar outras formas delimitadoras, como uma vista frustum ou uma caixa delimitadora que não esteja alinhada com eixos.

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 SpatialPerceptionAccessStatus não é Permitido . Por exemplo, não será permitido em PCs com dispositivos envolventes ligados porque esses dispositivos não têm hardware para mapeamento espacial. Para estes dispositivos, deve depender da fase espacial para obter informações sobre o ambiente do utilizador e a configuração do dispositivo.

Inicializar e atualizar a coleção do Surface Mesh

Se o observador do surface tiver sido criado com êxito, podemos continuar a inicializar a nossa coleção de malha do surface. Aqui, utilizamos a API do modelo de solicitação para obter imediatamente o conjunto atual de superfícies observadas:

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 existe um modelo push disponível para obter dados de malha do surface. Pode conceber a sua aplicação para utilizar apenas o modelo de solicitação se assim o preferir, caso em que irá consultar dados de vez em quando - por exemplo, uma vez por frame - ou durante um período de tempo específico, como durante a configuração do jogo. Se for o caso, o código acima é o que precisa.

No nosso exemplo de código, optámos por demonstrar a utilização de ambos os modelos para fins pedagógicos. Aqui, subscrevemos um evento para receber dados atualizados do Surface Mesh sempre que o sistema reconhecer uma alteração.

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

O nosso exemplo de código também está configurado para responder a estes eventos. Vamos explicar como fazemos isto.

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

Os dados de malha do surface são fornecidos num mapa só de leitura que armazena objetos SpatialSurfaceInfo com Platform::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 exemplo serão apresentados mais à frente 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 malhas de superfície que estão na nossa coleção de malha do surface, mas que já não estão na coleção do sistema. Para tal, temos de fazer algo semelhante ao oposto do que acabámos de mostrar para adicionar e atualizar malhas; Criamos um ciclo 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.

No nosso processador 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 memórias intermédias de dados do Surface Mesh

Obter as informações do surface mesh foi tão fácil como solicitar uma recolha de dados e processar atualizações para essa coleção. Agora, vamos entrar em detalhes sobre como pode utilizar os dados.

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

O exemplo de código inicia o processo quando recebe atualizações do surface mesh do processador de eventos que descrevemos na secção anterior. A linha de código importante nesta função é a chamada para atualizar a malha do surface: por esta altura já processámos as informações de malha e estamos prestes a obter os dados do vértice e do índice para utilização conforme entendermos.

Em 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 exemplo foi concebido para que uma classe de dados, o SurfaceMesh, processe e composição de dados de malha. Estas malhas são o que o RealtimeSurfaceMeshRenderer realmente mantém um mapa. Cada um deles tem uma referência ao SpatialSurfaceMesh de onde veio, para que possa utilizá-lo sempre que precisar de aceder ao vértice de malha ou às memórias intermédias de índice ou obter uma transformação para a malha. Por enquanto, sinalizamos a malha como necessitando de uma atualização.

No SurfaceMesh.cpp:

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

Da próxima vez que a malha for pedida para se desenhar, verificará primeiro o sinalizador. Se for necessária uma atualização, as memórias intermédias de vértice e índice serão atualizadas 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 as memórias intermédias de dados não processados:

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 memórias intermédias de dispositivos 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 utilizada no fragmento anterior, consulte o exemplo de código de Mapeamento do Surface: SurfaceMesh.cpp, GetDataFromIBuffer.h. Agora, a criação de recursos do dispositivo está concluída e considera-se que a malha está carregada e pronta para atualização e composição.

Atualizar e compor malhas do surface

A nossa classe SurfaceMesh tem uma função de atualização especializada. Cada SpatialSurfaceMesh tem a sua própria transformação e o nosso exemplo utiliza o sistema de coordenadas atual para o nosso SpatialStationaryReferenceFrame adquirir a transformação. Em seguida, atualiza a memória intermédia 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 estiver na altura de compor malhas de superfície, fazemos alguns trabalhos de preparação antes de compor a coleção. Configuramos o pipeline do shader para a configuração de composição atual e configuramos a fase do assemblador de entrada. A classe auxiliar de câmara holográfica CameraResources.cpp já configurou a memória intermédia constante de visualização/projeção.

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

Assim que isto estiver concluído, percorremos as nossas malhas e dizemos a cada uma delas para se desenharem. NOTA: Este código de exemplo não está otimizado para utilizar qualquer tipo de eliminação de frutos, mas 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 por configurar a memória intermédia de vértice e índice, o passo e a memória intermédia constante de transformação de modelos. Tal como acontece com o cubo giratório no modelo da aplicação Windows Holographic, compõemos em memórias intermédias estereoscópicas através do instancing.

No 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 composição com o Mapeamento do Surface

O exemplo de código de Mapeamento do Surface oferece código para composição apenas de oclusão de dados de malha do surface e para composição no ecrã de dados de malha do surface. O caminho que escolher - ou ambos - depende da sua aplicação. Vamos analisar ambas as configurações neste documento.

Compor memórias intermédias de oclusão para efeito holográfico

Comece por limpar a vista de destino de composição para a câmara virtual atual.

Em AppMain.cpp:

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

Este é um passe de "pré-composição". Aqui, criamos uma memória intermédia de oclusão ao pedir ao compositor de malha para compor apenas profundidade. Nesta configuração, não anexamos uma vista de destino de composição e o compositor de malha define a fase do sombreador de píxeis como nullptr para que a GPU não se incomode em desenhar píxeis. A geometria será rasterizada para a memória intermédia de profundidade e o pipeline de gráficos ficará por aqui.

// 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 adicional na memória intermédia de oclusão do Mapeamento do Surface. Neste exemplo de código, vamos compor pixéis no cubo com uma cor diferente se 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 a nossa rotina GatherDepthLess , consulte o exemplo de código de Mapeamento do Surface: SpecialEffectPixelShader.hlsl.

Compor dados do surface mesh no ecrã

Também podemos desenhar as malhas de superfície para as memórias intermédias de visualização estéreo. Optámos por desenhar rostos cheios com iluminação, mas pode desenhar wireframe, processar malhas antes de compor, aplicar um mapa de textura, etc.

Aqui, o nosso exemplo de código indica ao compositor de malha para desenhar a coleção. Desta vez, não especificamos um passe apenas de profundidade, irá anexar um sombreado de píxeis e concluir o pipeline de composição com os destinos que especificámos 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