Asignación espacial en DirectX
Nota
Este artículo se relaciona con las API nativas heredadas de WinRT. En el caso de los 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áfica 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 nativo de aplicaciones para la asignación espacial usa las API en el espacio de nombres Windows.Perception.Spatial . Estas API proporcionan un 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 sola superficie espacial existente, incluido un identificador único, un volumen delimitador y la hora del último cambio. Proporcionará un SpatialSurfaceMesh de forma asincrónica a petición.
- SpatialSurfaceMeshOptions contiene parámetros que se usan para personalizar los objetos SpatialSurfaceMesh solicitados desde SpatialSurfaceInfo.
- SpatialSurfaceMesh representa los datos de malla de una sola superficie espacial. Los datos de posiciones de vértices, normales de vértice e índices de triángulo se encuentran en 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 concedido 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 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 activará cada vez que haya nueva información disponible sobre las superficies espaciales en las regiones del espacio 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, mesh para una superficie espacial, llame a TryComputeLatestMeshAsync. Puede proporcionar opciones que especifican la densidad de triángulos y el formato de los datos de malla devueltos.
- Malla de recepción y proceso
- Cada llamada a TryComputeLatestMeshAsync devolverá de forma asincrónica un objeto SpatialSurfaceMesh.
- Desde este objeto, puede acceder a los objetos SpatialSurfaceMeshBuffer contenidos, lo que le proporciona acceso a los índices de triángulo, las posiciones de vértice y los valores 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 representar y físicamente raycasting y colisión.
- Un detalle importante que debe 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 usan para representar las mallas), para convertirlos de las unidades de entero optimizadas en las que se almacenan en el búfer, a medidores. Para recuperar esta escala, llame a VertexPositionScale.
Solución de problemas
- No olvide escalar las posiciones de vértice de malla en el sombreador de vértices con la escala devuelta por SpatialSurfaceMesh.VertexPositionScale
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, se explica cómo agregar la funcionalidad de asignación de superficies a la aplicación directX. Puedes agregar este código a tu proyecto de plantilla de aplicación de Windows Holographic , o puedes seguirlo navegando por el ejemplo de código mencionado anteriormente. Este ejemplo de código se basa en la plantilla de aplicación Windows Holographic.
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 se puede considerar datos privados. Declare esta funcionalidad en el archivo package.appxmanifest de la aplicación. Este es 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, inclúyelo como un atributo xlmns en el <elemento Package> . Este es 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 que 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 para ese efecto 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 la que ocurriría si la asignación espacial estuviera disponible. 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 la compatibilidad actual con la asignación espacial, asegúrate primero de que el contrato para UWP esté en el nivel 4 o superior y, a continuación, llama a SpatialSurfaceObserver::IsSupported(). Aquí se muestra cómo hacerlo en el contexto del ejemplo de código de asignación espacial holográfica . El soporte técnico 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 poner el proyecto en la versión 15063 de la plataforma 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 para UWP es inferior al nivel 4, la aplicación debe continuar como si el dispositivo fuese 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 cualquier observador de superficie. Este es un ejemplo basado en el ejemplo de código de asignación de Surface, con más detalles que se proporcionan 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 especificados en spatialCoordinateSystem. Use una instancia spatialSurfaceObserver para acceder a los datos de malla expuestas 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 antes de que la aplicación pueda usarla. 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 20 x 20 x 5 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.
Seudocódigo:
m_surfaceObserver->SetBoundingVolumes(/* iterable collection of bounding volumes*/);
También es posible usar otras formas delimitador, como un frustum de vista o un rectángulo de límite que no está alineado con el eje.
Seudocó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 está permitido ; por ejemplo, no se permitirá en equipos con dispositivos inmersivos conectados porque esos dispositivos no tienen hardware para la asignación espacial. Para estos dispositivos, en su lugar debe confiar en la fase espacial para obtener información sobre el entorno del usuario y la configuración del dispositivo.
Inicialización y actualización de la colección de mallas de superficie
Si el observador de superficie se creó correctamente, podemos continuar inicializar nuestra colección de mallas de superficie. Aquí, usamos la API del modelo de extracción para obtener el conjunto actual de superficies observadas inmediatamente:
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 la aplicación para que use solo el modelo de extracción si lo eliges, en cuyo caso sondearás los datos cada vez que lo hagas (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, decidimos demostrar el uso de ambos modelos con fines educativos. Aquí, nos suscribimos a un evento para recibir datos de malla de superficie actualizados 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 de que la aplicación controle los datos de malla. Este código se escribe para mayor claridad y no está optimizado.
Los datos de la 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, primero buscamos los valores de clave que no están en nuestra colección. Más adelante en este tema se mostrarán detalles sobre cómo se almacenan los datos en nuestra aplicación de ejemplo.
// 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 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, necesitamos hacer algo parecido al contrario de lo que acabamos de mostrar para agregar y actualizar mallas; se realiza un bucle en la colección de la aplicación y se comprueba 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 poda 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 malla de superficie
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 colección. Ahora, veremos en detalle cómo puede usar los datos.
En nuestro ejemplo de código, decidimos usar las mallas de superficie para la representación. Se trata de un escenario común para ocluir hologramas detrás de superficies reales. 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 la funcionalidad de la aplicación o del juego.
El ejemplo de código inicia el proceso cuando recibe actualizaciones de malla de superficie del controlador de eventos que hemos descrito en la sección anterior. La línea de código importante de esta función es la llamada a 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 su uso como vemos.
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 lo que realtimeSurfaceMeshRenderer realmente mantiene un mapa de. Cada una tiene una referencia a SpatialSurfaceMesh de la que procede, por lo que puede usarla cada vez que necesite acceder a los búferes de índice o vértices de malla, o bien obtener una transformación para la malla. Por ahora, marcamos la malla como si necesitamos 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, 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 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 de dispositivo y se considera que la malla se carga y está lista para actualizar y representar.
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 nuestra muestra usa el sistema de coordenadas actual para nuestro SpatialStationaryReferenceFrame para adquirir 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, hacemos algún 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 y proyección por ahora.
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, recorremos nuestras mallas y le dimos a cada uno que se dibuje. NOTA: Este código de ejemplo no está optimizado para usar ningún tipo de frustum de selección, 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 intervalo y el búfer de constantes de transformación del modelo. Al igual que con el cubo giratorio en la plantilla de aplicación Windows Holographic, 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 de asignación de Surface ofrece código para la representación solo de oclusión de datos de malla de superficie y para la representación en pantalla de los datos de malla de superficie. La ruta de acceso que elija (o ambas) depende de la aplicación. Veremos ambas configuraciones en este documento.
Representación de búferes de oclusión para el efecto holográfico
Para empezar, borre 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 pidiendo 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 superficies. En este ejemplo de código, representamos píxeles en el cubo 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 nuestra rutina GatherDepthLess , consulta el ejemplo de código de 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 en los búferes de pantalla estéreo. Decidimos dibujar caras completas con iluminación, pero es libre de dibujar wireframe, procesar mallas antes de representar, aplicar un mapa de textura, etc.
Aquí, nuestro ejemplo de código indica al representador de malla que dibuje la colección. Esta vez no especificamos un paso de solo profundidad, adjuntará un sombreador de píxeles y 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);