Asignación espacial en DirectX

Nota:

Este artículo está relacionado con las API nativas de WinRT heredadas. Para nuevos proyectos de aplicaciones nativas, se recomienda usar la API de OpenXR.

En este tema se describe cómo implementar la asignación espacial en la aplicación DirectX, incluida una explicación detallada de la aplicación de ejemplo de asignación espacial empaquetada con el SDK de Plataforma universal de Windows.

En este tema se usa código del ejemplo de código HolographicSpatialMapping para UWP.

Nota:

Los fragmentos de código de este artículo muestran actualmente el uso de C++/CX en lugar de C++17 compatible con C++/WinRT como se usa en la plantilla de proyecto holográfico de C++. Los conceptos son equivalentes para un proyecto de C++/WinRT, aunque deberá traducir el código.

Compatibilidad con dispositivos

Característica HoloLens (1.ª generación) HoloLens 2 Cascos envolventes
Asignación espacial ✔️ ✔️

Introducción al desarrollo de DirectX

El desarrollo de aplicaciones nativas para la asignación espacial usa las API del espacio de nombres Windows.Perception.Spatial . Estas API proporcionan control total de la funcionalidad de asignación espacial, de la misma manera que Unity expone las API de asignación espacial.

API de Percepción

Los tipos principales proporcionados para el desarrollo de asignaciones espaciales son los siguientes:

  • SpatialSurfaceObserver proporciona información sobre las superficies de las regiones de espacio especificadas por la aplicación cerca del usuario, en forma de objetos SpatialSurfaceInfo.
  • SpatialSurfaceInfo describe una única superficie espacial existente, incluido un identificador único, el volumen de límite y la hora del último cambio. Proporcionará un SpatialSurfaceMesh de forma asincrónica a petición.
  • SpatialSurfaceMeshOptions contiene parámetros usados para personalizar los objetos SpatialSurfaceMesh solicitados a SpatialSurfaceInfo.
  • SpatialSurfaceMesh representa los datos de malla de una sola superficie espacial. Los datos de las posiciones de vértices, las normales de vértices y los índices de triángulos se encuentran en los objetos SpatialSurfaceMeshBuffer miembros.
  • SpatialSurfaceMeshBuffer encapsula un único tipo de datos de malla.

Al desarrollar una aplicación con estas API, el flujo de programa básico tendrá este aspecto (como se muestra en la aplicación de ejemplo que se describe a continuación):

  • Configuración de SpatialSurfaceObserver
    • Llame a RequestAccessAsync para asegurarse de que el usuario ha dado permiso para que la aplicación use las funcionalidades de asignación espacial del dispositivo.
    • Cree una instancia de un objeto SpatialSurfaceObserver.
    • Llame a SetBoundingVolumes para especificar las regiones de espacio en las que desea obtener información sobre las superficies espaciales. Puede modificar estas regiones en el futuro llamando a esta función de nuevo. Cada región se especifica mediante SpatialBoundingVolume.
    • Regístrese para el evento ObservedSurfacesChanged , que se desencadenará cada vez que haya nueva información disponible sobre las superficies espaciales en las regiones de espacio que haya especificado.
  • Procesar eventos ObservedSurfacesChanged
    • En el controlador de eventos, llame a GetObservedSurfaces para recibir un mapa de objetos SpatialSurfaceInfo. Con este mapa, puede actualizar los registros de las superficies espaciales que existen en el entorno del usuario.
    • Para cada objeto SpatialSurfaceInfo, puede consultar TryGetBounds para determinar las extensiones espaciales de la superficie, expresadas en un sistema de coordenadas espaciales de su elección.
    • Si decide solicitar una malla para una superficie espacial, llame a TryComputeLatestMeshAsync. Puede proporcionar opciones que especifiquen la densidad de los triángulos y el formato de los datos de malla devueltos.
  • Malla de recepción y proceso
    • Cada llamada a TryComputeLatestMeshAsync devolverá asincrónicamente un objeto SpatialSurfaceMesh.
    • Desde este objeto, puede acceder a los objetos SpatialSurfaceMeshBuffer contenidos, lo que le proporciona acceso a los índices de triángulos, las posiciones de vértice y las normales de vértice de la malla si los solicita. Estos datos estarán en un formato directamente compatible con las API de Direct3D 11 que se usan para representar mallas.
    • Desde aquí, la aplicación puede analizar o procesar opcionalmente los datos de malla, y usarlos para la representación y la difusión y colisión de rayos de física.
    • Un detalle importante a tener en cuenta es que debe aplicar una escala a las posiciones de vértice de malla (por ejemplo, en el sombreador de vértices que se usa para representar las mallas) para convertirlas de las unidades de entero optimizadas en las que se almacenan en el búfer, a metros. Para recuperar esta escala, llame a VertexPositionScale.

Solución de problemas

Tutorial de ejemplo de código de asignación espacial

El ejemplo de código de asignación espacial holográfica incluye código que puede usar para empezar a cargar mallas de superficie en la aplicación, incluida la infraestructura para administrar y representar mallas de superficie.

Ahora, le guiaremos por cómo agregar la funcionalidad de asignación de superficie a la aplicación DirectX. Puede agregar este código al proyecto de plantilla de aplicación holográfica de Windows , o bien puede seguirlo examinando el ejemplo de código mencionado anteriormente. Este ejemplo de código se basa en la plantilla de aplicación Holográfica de Windows.

Configuración de la aplicación para usar la funcionalidad spatialPerception

La aplicación puede usar la funcionalidad de asignación espacial. Esto es necesario porque la malla espacial es una representación del entorno del usuario, que puede considerarse datos privados. Declare esta funcionalidad en el archivo package.appxmanifest de la aplicación. Aquí le mostramos un ejemplo:

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

La funcionalidad procede del espacio de nombres uap2 . Para obtener acceso a este espacio de nombres en el manifiesto, insclúyelo como un atributo xlmns en el <elemento Package> . Aquí le mostramos un ejemplo:

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

Comprobación de la compatibilidad con características de asignación espacial

Windows Mixed Reality admite una amplia gama de dispositivos, incluidos los dispositivos, que no admiten la asignación espacial. Si la aplicación puede usar la asignación espacial o debe usar la asignación espacial para proporcionar funcionalidad, debe comprobar si se admite la asignación espacial antes de intentar usarla. Por ejemplo, si la aplicación de realidad mixta requiere la asignación espacial, debería mostrar un mensaje en ese sentido si un usuario intenta ejecutarla en un dispositivo sin asignación espacial. O bien, la aplicación puede representar su propio entorno virtual en lugar del entorno del usuario, lo que proporciona una experiencia similar a lo que ocurriría si estuviera disponible la asignación espacial. En cualquier caso, esta API permite que la aplicación tenga en cuenta cuándo no obtendrá datos de asignación espacial y responderá de la manera adecuada.

Para comprobar si el dispositivo actual es compatible con la asignación espacial, primero asegúrese de que el contrato de UWP está en el nivel 4 o superior y, a continuación, llame a SpatialSurfaceObserver::IsSupported(). A continuación se muestra cómo hacerlo en el contexto del ejemplo de código de asignación espacial holográfica . La compatibilidad se comprueba justo antes de solicitar acceso.

La API SpatialSurfaceObserver::IsSupported() está disponible a partir de la versión 15063 del SDK. Si es necesario, vuelva a dirigir el proyecto a la versión de plataforma 15063 antes de usar 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 ...

Cuando el contrato de UWP es menor que el nivel 4, la aplicación debe continuar como si el dispositivo fuera capaz de realizar la asignación espacial.

Solicitud de acceso a datos de asignación espacial

La aplicación debe solicitar permiso para acceder a los datos de asignación espacial antes de intentar crear observadores de superficie. Este es un ejemplo basado en nuestro ejemplo de código de asignación de Surface, con más detalles proporcionados más adelante en esta 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.
    }
}

Creación de un observador de superficie

El espacio de nombres Windows::P erception::Spatial::Surfaces incluye la clase SpatialSurfaceObserver , que observa uno o varios volúmenes que especifique en spatialCoordinateSystem. Use una instancia de SpatialSurfaceObserver para acceder a los datos de malla de superficie en tiempo real.

Desde 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 se indicó en la sección anterior, debe solicitar acceso a los datos de asignación espacial para que la aplicación pueda usarlos. Este acceso se concede automáticamente en 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();

A continuación, debe configurar el observador de superficie para observar un volumen de límite específico. Aquí observamos un cuadro de 20x20x5 metros, centrado en el origen del 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);

En su lugar, puede establecer varios volúmenes de límite.

Se trata de pseudocódigo:

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

También es posible usar otras formas delimitadoras, como un frustum de vista o un rectángulo delimitador que no esté alineado con el eje.

Se trata de pseudocódigo:

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

Si la aplicación necesita hacer algo diferente cuando los datos de asignación de superficies no están disponibles, puede escribir código para responder al caso en el que SpatialPerceptionAccessStatus no es Permitido ; por ejemplo, no se permitirá en equipos con dispositivos envolventes conectados porque esos dispositivos no tienen hardware para la asignación espacial. Para estos dispositivos, en su lugar, debe basarse en la fase espacial para obtener información sobre el entorno del usuario y la configuración del dispositivo.

Inicializar y actualizar la colección de surface mesh

Si el observador de superficie se creó correctamente, podemos continuar inicializando nuestra colección de malla de superficie. Aquí, usamos la API del modelo de extracción para obtener el conjunto actual de superficies observadas de inmediato:

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

También hay un modelo de inserción disponible para obtener datos de malla de superficie. Puedes diseñar tu aplicación de forma gratuita para usar solo el modelo de extracción si lo eliges, en cuyo caso sondearás los datos con tanta frecuencia (por ejemplo, una vez por fotograma) o durante un período de tiempo específico, como durante la configuración del juego. Si es así, el código anterior es lo que necesita.

En nuestro ejemplo de código, elegimos demostrar el uso de ambos modelos con fines pedagógicos. Aquí, nos suscribimos a un evento para recibir datos actualizados de malla de superficie cada vez que el sistema reconoce un cambio.

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

Nuestro ejemplo de código también está configurado para responder a estos eventos. Veamos cómo hacemos esto.

NOTA: Es posible que esta no sea la manera más eficaz para que la aplicación controle los datos de malla. Este código se escribe para mayor claridad y no está optimizado.

Los datos de malla de superficie se proporcionan en un mapa de solo lectura que almacena objetos SpatialSurfaceInfo mediante Platform::Guids como valores clave.

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

Para procesar estos datos, buscamos primero los valores clave que no están en nuestra colección. Los detalles sobre cómo se almacenan los datos en nuestra aplicación de ejemplo se presentarán más adelante en este tema.

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

También tenemos que quitar las mallas de superficie que están en nuestra colección de mallas de superficie, pero que ya no están en la colección del sistema. Para ello, tenemos que hacer algo parecido a lo contrario de lo que acabamos de mostrar para agregar y actualizar mallas; recorremos en bucle la colección de la aplicación y comprobamos si el Guid que tenemos está en la colección del sistema. Si no está en la colección del sistema, la quitamos de la nuestra.

Desde nuestro controlador de eventos en AppMain.cpp:

m_meshCollection->PruneMeshCollection(surfaceCollection);

La implementación de la eliminación de malla en 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);
    }
}

Adquisición y uso de búferes de datos de surface mesh

Obtener la información de la malla de superficie era tan fácil como extraer una recopilación de datos y procesar las actualizaciones de esa recopilación. Ahora, vamos a profundizar en cómo puede usar los datos.

En nuestro ejemplo de código, elegimos usar las mallas de superficie para la representación. Este es un escenario común para la oclusión de hologramas detrás de superficies del mundo real. También puedes representar las mallas, o representar las versiones procesadas de ellas, para mostrar al usuario qué áreas de la sala se examinan antes de empezar a proporcionar funcionalidad de aplicación o juego.

El ejemplo de código inicia el proceso cuando recibe actualizaciones de surface mesh del controlador de eventos que describimos en la sección anterior. La línea de código importante de esta función es la llamada para actualizar la malla de superficie: en este momento ya hemos procesado la información de la malla y estamos a punto de obtener los datos de vértice e índice para usarlos como consideremos oportuno.

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

Nuestro código de ejemplo está diseñado para que una clase de datos, SurfaceMesh, controle el procesamiento y la representación de datos de malla. Estas mallas son las que realtimeSurfaceMeshRenderer realmente mantiene un mapa de. Cada uno de ellos tiene una referencia a SpatialSurfaceMesh de la que procede, por lo que puede usarlo cada vez que necesite acceder a los búferes de índice o vértice de malla, u obtener una transformación para la malla. Por ahora, marcamos la malla como que necesita una actualización.

Desde SurfaceMesh.cpp:

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

La próxima vez que se pida a la malla que se dibuje a sí misma, comprobará primero la marca. Si se necesita una actualización, los búferes de vértices e índices se actualizarán en la GPU.

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

En primer lugar, adquirimos los búferes de datos sin procesar:

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;

A continuación, creamos búferes de dispositivos Direct3D con los datos de malla proporcionados por 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 ver la función auxiliar CreateDirectXBuffer usada en el fragmento de código anterior, consulta el ejemplo de código de asignación de Surface: SurfaceMesh.cpp, GetDataFromIBuffer.h. Ahora se ha completado la creación de recursos del dispositivo y se considera que la malla está cargada y lista para actualizarse y representarse.

Actualizar y representar mallas de superficie

Nuestra clase SurfaceMesh tiene una función de actualización especializada. Cada SpatialSurfaceMesh tiene su propia transformación y nuestro ejemplo usa el sistema de coordenadas actual para que spatialStationaryReferenceFrame adquiera la transformación. A continuación, actualiza el búfer de constantes del modelo en la 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
        );
}

Cuando es el momento de representar mallas de superficie, se realiza un trabajo de preparación antes de representar la colección. Configuramos la canalización del sombreador para la configuración de representación actual y configuramos la fase del ensamblador de entrada. La clase auxiliar de cámara holográfica CameraResources.cpp ya ha configurado el búfer de constantes de vista/proyección.

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

Una vez hecho esto, en bucle en nuestras mallas y le decimos a cada uno que se dibuje a sí mismo. NOTA: Este código de ejemplo no está optimizado para usar ningún tipo de selección de frustum, pero debe incluir esta característica en la aplicación.

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

Las mallas individuales son responsables de configurar el búfer de vértices e índices, el paso y el búfer de constantes de transformación del modelo. Al igual que con el cubo giratorio en la plantilla de aplicación holográfica de Windows, se representa en búferes estereoscópicos mediante la creación de instancias.

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

Opciones de representación con asignación de Surface

El ejemplo de código asignación de Superficie ofrece código para la representación de datos de malla de superficie solo oclusión y para la representación en pantalla de datos de malla de superficie. La ruta de acceso que elija, o ambas, depende de la aplicación. Recorreremos ambas configuraciones en este documento.

Representación de búferes de oclusión para efecto holográfico

Para empezar, desactive la vista de destino de representación de la cámara virtual actual.

Desde AppMain.cpp:

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

Se trata de un pase de "representación previa". Aquí, creamos un búfer de oclusión solicitando al representador de malla que represente solo la profundidad. En esta configuración, no adjuntamos una vista de destino de representación y el representador de malla establece la fase del sombreador de píxeles en nullptr para que la GPU no se moleste en dibujar píxeles. La geometría se rasterizará en el búfer de profundidad y la canalización de gráficos se detendrá allí.

// 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 dibujar hologramas con una prueba de profundidad adicional en el búfer de oclusión de asignación de superficie. En este ejemplo de código, representamos los píxeles en el cubo de un color diferente si están detrás de una superficie.

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

Basado en el 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 ver nuestra rutina GatherDepthLess , consulta el ejemplo de código asignación de surface: SpecialEffectPixelShader.hlsl.

Representación de datos de malla de superficie en la pantalla

También podemos dibujar las mallas de superficie a los búferes de pantalla estéreo. Elegimos dibujar caras completas con iluminación, pero puedes dibujar tramas alámbricas, procesar mallas antes de representarlas, aplicar un mapa de texturas, etc.

Aquí, nuestro ejemplo de código indica al representador de malla que dibuje la colección. Esta vez no se especifica un paso de solo profundidad, se adjuntará un sombreador de píxeles y se completará la canalización de representación con los destinos especificados para la cámara virtual actual.

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

Vea también