DirectX 中的空间映射
注意
本文与旧版 WinRT 原生 API 相关。 对于新的本机应用项目,建议使用 OpenXR API。
本主题介绍如何在 DirectX 应用中实现空间映射,包括 Universal Windows Platform SDK 随附的空间映射示例应用程序的详细说明。
本主题使用 HolographicSpatialMapping UWP 代码示例中的代码。
注意
本文中的代码片段当前演示了如何使用 C++/CX,而不是 C++17 兼容的 C++/WinRT,后者在 C++ 全息项目模板中使用。 这些概念与 C++/WinRT 项目等同,但将需要转换代码。
设备支持
功能 | HoloLens(第一代) | HoloLens 2 | 沉浸式头戴显示设备 |
空间映射 | ✔ | ✔ | ❌ |
DirectX 开发概述
空间映射的本机应用程序开发使用 Windows.Perception.Spatial 命名空间中的 API。 这些 API 使您能够完全控制空间映射功能,其方式与 Unity 公开空间映射 API 的方式相同。
感知 API
为空间映射开发提供的主要类型如下:
- SpatialSurfaceObserver 以 SpatialSurfaceInfo 对象的形式提供与用户附近的应用程序指定区域中的表面相关的信息。
- SpatialSurfaceInfo 描述单个现存空间图面,包括唯一 ID、边界卷和上次更改时间。 它将根据请求异步提供 SpatialSurfaceMesh。
- SpatialSurfaceMeshOptions 包含用于自定义 SpatialSurfaceInfo 中请求的 SpatialSurfaceMesh 对象的参数。
- SpatialSurfaceMesh 表示单个空间图面的网格数据。 顶点位置、顶点法线和三角索引的数据包含在成员 SpatialSurfaceMeshBuffer 对象中。
- SpatialSurfaceMeshBuffer 环绕单个类型的网格数据。
使用这些 API 开发应用程序时,基本程序流如下所示(如下面描述的示例应用程序所示):
- 设置 SpatialSurfaceObserver
- 调用 RequestAccessAsync,以确保用户为应用程序提供了使用设备的空间映射功能的权限。
- 实例化 SpatialSurfaceObserver 对象。
- 调用 SetBoundingVolumes 以指定空间区域,在其中你需要有关空间图面的信息。 以后可以通过再次调用此函数来修改这些区域。 每个区域都使用 SpatialBoundingVolume 指定。
- 注册 ObservedSurfacesChanged 事件,只要有新信息可用于指定空间区域中的空间图面,就会触发该事件。
- 处理 ObservedSurfacesChanged 事件
- 在事件处理程序中,调用 GetObservedSurfaces 以接收 SpatialSurfaceInfo 对象的映射。 使用此地图,可以更新 用户环境中存在的空间图面的记录。
- 对于每个 SpatialSurfaceInfo 对象,可以查询 TryGetBounds 以确定图面的空间范围(以所选的空间坐标系统表示)。
- 如果决定为空间图面请求网格,请调用 TryComputeLatestMeshAsync。 可以提供指定三角形密度的选项和返回网格数据的格式。
- 接收和处理网格
- 对 TryComputeLatestMeshAsync 的每次调用都将以异步方式返回一个 SpatialSurfaceMesh 对象。
- 通过此对象,可访问已包含的 SpatialSurfaceMeshBuffer 对象,这样就可以访问网格的三角形索引、顶点位置和顶点法线(如果请求)。 此数据将采用与用于呈现网格的 Direct3D 11 APIs 直接兼容的格式。
- 从这里,应用程序可以选择分析或处理网格数据,并将其用于呈现以及物理光线投射和冲突。
- 需要注意的一个重要细节是,必须对网格顶点位置(例如,在用于呈现网格的顶点着色器中)应用缩放,以将它们从缓冲区中存储的优化整数单位转换为米。 可以通过调用 VertexPositionScale 来检索此缩放。
疑难解答
- 不要忘记使用 SpatialSurfaceMesh.VertexPositionScale 返回的比例在顶点着色器中缩放网格顶点位置
空间映射代码示例演练
全息空间映射代码示例包含可用于开始将图面网格加载到应用中的代码,其中包括用于管理和呈现图面网格的基础结构。
现在,我们将演练如何向 DirectX 应用添加图面映射功能。 你可以将此代码添加到 Windows 全息应用模板项目中,也可以浏览并遵循上述代码示例。 此代码示例基于 Windows 全息应用模板。
设置应用以使用 spatialPerception 功能
应用可使用空间映射功能。 这是必需的,因为空间网格是用户环境的表示形式,可能被视为私有数据。 在应用的 package.appxmanifest 文件中声明此功能。 下面是一个示例:
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
此功能来自 uap2 命名空间。 若要在清单中访问此命名空间,请将其作为 xlmns 属性包含在 <Package> 元素中。 下面是一个示例:
<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"
>
检查空间映射功能支持
Windows Mixed Reality 支持各种设备,包括不支持空间映射的设备。 如果你的应用可以使用空间映射,或者必须使用空间映射来提供功能,则在尝试使用之前应检查确保支持空间映射。 例如,如果你的混合现实应用需要空间映射,则当用户尝试在没有空间映射的设备上运行该应用时,它应显示一条消息提示该影响。 或者,你的应用可以呈现自己的虚拟环境来代替用户环境,提供的体验与可用空间映射时的情况类似。 在任何情况下,此 API 都能使你的应用了解不获取空间映射数据的情况,并以适当的方式做出响应。
若要检查当前设备的空间映射支持,请首先确保 UWP 协定处于 4 级或更高级别,然后调用 SpatialSurfaceObserver::IsSupported()。 下面介绍了如何在全息空间映射代码示例的上下文执行此操作。 在请求访问之前已检查支持。
从 SDK 版本 15063 开始提供 SpatialSurfaceObserver::IsSupported() API。 如有必要,请在使用此 API 之前将项目重定向到平台 15063 版本。
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 ...
当 UWP 协定小于 4 级时,应用应继续运行,就像设备能够进行空间映射一样。
请求访问空间映射数据
在尝试创建任何图面观察器之前,应用需要请求访问空间映射数据的权限。 下面是一个基于我们的图面映射代码示例的例子,本页后面提供了更多详细信息:
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.
}
}
创建图面观察器
Windows::Perception::Spatial::Surfaces 命名空间包括 SpatialSurfaceObserver 类,该类用于观察在 SpatialCoordinateSystem 中指定的一个或多个卷。 使用 SpatialSurfaceObserver 实例实时访问图面网格数据。
通过 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;
如前一部分所述,必须先请求对空间映射数据的访问权限,然后应用才能使用。 将在 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();
接下来,需要配置图面观察器来观察特定的边界卷。 在这里,我们观察到一个 20x20x5 计量的框,该框以坐标系统的原点为中心。
// 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);
可以改为设置多个边界卷。
这是伪代码:
m_surfaceObserver->SetBoundingVolumes(/* iterable collection of bounding volumes*/);
还可以使用其他边界形状(如视锥)或不对齐轴的边界框。
这是伪代码:
m_surfaceObserver->SetBoundingVolume(
SpatialBoundingVolume::FromFrustum(/*SpatialCoordinateSystem*/, /*SpatialBoundingFrustum*/)
);
如果你的应用在图面映射数据不可用时需要执行任何不同的操作,则可以编写代码来响应不允许 SpatialPerceptionAccessStatus 的情况(例如,在连接有沉浸式设备的电脑上不允许这样做,因为这些设备没有用于空间映射的硬件)。 对于这些设备,你应改用空间阶段来了解有关用户环境和设备配置的信息。
初始化和更新图面网格集合
如果已成功创建图面观察器,我们可以继续初始化图面网格集合。 在这里,我们使用拉取模型 API 立即获取当前观察到的图面集:
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);
}
还有一个可用于获取图面网格数据的推送模型。 如果你愿意,可以自由地将你的应用设计为仅使用拉取模型,在这种情况下,你将每隔一段时间(例如,每帧一次)或在特定时间段(例如游戏设置期间)轮询数据。 如果是这样,则需要用到上述代码。
在我们的代码示例中,我们为教学目的选择了演示这两种模型的使用。 在这里,我们会订阅事件,以便在系统识别更改时接收最新的图面网格数据。
m_surfaceObserver->ObservedSurfacesChanged += ref new TypedEventHandler<SpatialSurfaceObserver^, Platform::Object^>(
bind(&HolographicDesktopAppMain::OnSurfacesChanged, this, _1, _2)
);
我们的代码示例也配置为响应这些事件。 我们来看看如何执行此操作。
注意:这可能不是你的应用处理网格数据的最高效方法。 此代码的编写重在清晰,未进行优化。
图面网格数据在只读映射中提供,该映射使用 Platform::Guids 作为键值存储 SpatialSurfaceInfo 对象。
IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection = sender->GetObservedSurfaces();
若要处理此数据,首先查找不在集合中的键值。 本主题稍后将详细介绍如何在示例应用中存储数据。
// 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);
}
}
我们还必须删除在图面网格集合中、但不再在系统集合中的图面网格。 为此,我们需要执行一些与刚才演示的添加和更新网格相反的操作;我们循环访问应用的集合,并检查 Guid 是否在系统集合中。 如果不在系统集合中,我们会将其删除。
通过 AppMain.cpp 中的事件处理程序:
m_meshCollection->PruneMeshCollection(surfaceCollection);
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);
}
}
获取和使用图面网格数据缓冲区
获取表面网格信息就像拉取数据集合和处理该集合的更新一样简单。 现在,我们将详细介绍如何使用数据。
在代码示例中,我们选择使用图面网格进行呈现。 这是在真实表面后面遮挡全息影像的常见方案。 还可以呈现网格或呈现已处理的版本,以向用户显示在开始提供应用或游戏功能之前已扫描房间的哪些区域。
代码示例在从上一部分所述的事件处理程序接收图面网格更新时启动进程。 此函数中的重要代码行是更新图面网格的调用:此时已经处理了网格信息,即将获取顶点和索引数据,供我们根据情况使用。
通过 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());
}
我们的示例代码旨在让数据类 SurfaceMesh 处理网格数据处理和呈现。 这些网格是 RealtimeSurfaceMeshRenderer 实际保留其映射的网格。 每一个都有对其来源的 SpatialSurfaceMesh 的引用,因此可以随时用它来访问网格顶点或索引缓冲区,或者获取网格的转换。 目前,我们将网格标记为需要更新。
通过 SurfaceMesh.cpp:
void SurfaceMesh::UpdateSurface(SpatialSurfaceMesh^ surfaceMesh)
{
m_surfaceMesh = surfaceMesh;
m_updateNeeded = true;
}
下次要求网格自行绘制时,它将首先检查标记。 如果需要更新,顶点和索引缓冲区将在 GPU 上更新。
void SurfaceMesh::CreateDeviceDependentResources(ID3D11Device* device)
{
m_indexCount = m_surfaceMesh->TriangleIndices->ElementCount;
if (m_indexCount < 3)
{
// Not enough indices to draw a triangle.
return;
}
首先,获取原始数据缓冲区:
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;
然后,使用由 HoloLens 提供的网格数据创建 Direct3D 设备:
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;
}
注意:有关上一代码段中使用的 CreateDirectXBuffer 帮助程序函数,请参阅 Surface Mapping 代码示例:SurfaceMesh.cpp、GetDataFromIBuffer.h。 现在,设备资源创建已完成,网格被视为已加载并准备好进行更新和呈现。
更新和呈现图面网格
SurfaceMesh 类具有专用的更新函数。 每个 SpatialSurfaceMesh 都有自己的转换,示例使用 SpatialStationaryReferenceFrame 的当前坐标系来获取转换。 然后,它会更新 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
);
}
当需要呈现图面网格时,在呈现集合之前进行一些准备工作。 我们为当前的呈现配置设置了着色器管道,并设置了输入汇编程序阶段。 全息相机帮助程序类 CameraResources.cpp 现已设置视图/投影常量缓冲区。
通过 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
);
}
完成此操作后,我们将循环访问网格,并告知每个网格绘制自身。 注意:此示例代码未优化为使用任何类型的截锥剔除,但应在应用中包括此功能。
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);
}
各个网格负责设置顶点和索引缓冲区、步幅和模型转换常量缓冲区。 与 Windows Holographic 应用模板中的旋转立方体一样,我们使用实例化呈现到立体缓冲区。
通过 SurfaceMesh::Draw:
// 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.
);
使用 Surface Mapping 的呈现选项
Surface Mapping 代码示例提供了图面网格数据的仅遮挡呈现代码,以及图面网格数据的屏上呈现代码。 选择的路径(或两者)取决于应用程序。 本文档将介绍这两种配置。
呈现全息效果的遮挡缓冲区
首先清除当前虚拟相机的呈现目标视图。
在 AppMain.cpp 中:
context->ClearRenderTargetView(pCameraResources->GetBackBufferRenderTargetView(), DirectX::Colors::Transparent);
这是一个“预呈现”传递。 在这里,我们通过要求网格呈现器仅呈现深度来创建遮挡缓冲区。 在此配置中,我们不会附加呈现器目标视图,网格呈现器将像素着色器阶段设置为 nullptr,这样 GPU 就不需要绘制像素。 几何图形将栅格化为深度缓冲区,图形管道将在那里停止。
// 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);
我们可以对 Surface Mapping 遮挡缓冲区进行额外的深度测试来绘制全息影像。 在此代码示例中,如果像素位于图面后面,我们将在立方体上呈现不同的颜色。
在 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()
);
基于 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);
}
注意:有关 GatherDepthLess 例程,请参阅 Surface Mapping 代码示例:SpecialEffectPixelShader.hlsl。
将图面网格数据呈现到显示器
我们还可以将图面网格绘制到立体显示缓冲区。 我们选择使用照明绘制完整的面,但你可随意绘制线框、在呈现前处理网格、应用纹理映射等。
此处,代码示例告知网格呈现器绘制集合。 这一次,我们未指定仅深度传递,它会附加像素着色器,然后使用我们为当前虚拟相机指定的目标完成呈现管道。
// 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);