在 DirectX 中渲染

注意

本文与旧版 WinRT 本机 API 相关。 对于新的本机应用项目,建议使用 OpenXR API

Windows Mixed Reality 构建在 DirectX 的基础之上,可为用户提供丰富的 3D 图形体验。 渲染抽象层在 DirectX 的正上方,可让应用推断系统预测的全息场景观察者的位置和方向。 然后,开发人员可以基于每个相机定位全息影像,这样当用户在四处移动时,应用便在各种空间坐标系中渲染这些全息影像。

注意:本演练介绍 Direct3D 11 中的全息渲染。 Direct3D 12 Windows Mixed Reality 应用模板也随 Mixed Reality 应用模板扩展一起提供。

当前帧的更新

若要更新全息影像的应用程序状态(每帧一次),应用将执行以下操作:

  • 从显示管理系统获取 HolographicFrame
  • 使用目前对渲染完成后相机视图位置的预测更新场景。 请注意,全息场景可以有多个相机。

若要渲染到全息相机视图,在每一帧中,应用将执行以下操作:

  • 对于每个相机,使用系统的相机视图和投影矩阵渲染当前帧的场景。

创建新的全息帧并获取其预测

HolographicFrame 包含应用更新和渲染当前帧所需的信息。 应用通过调用 CreateNextFrame 方法开始每个新帧。 调用此方法时,使用可用的最新传感器数据进行预测,并封装在 CurrentPrediction 对象中。

必须针对每个渲染的帧使用新的帧对象,因为它仅在一定的时间内有效。 CurrentPrediction 属性包含相机位置等信息。 此信息被推断为预期用户可以看到帧的确切时刻。

以下代码提取自 AppMain::Update:

// The HolographicFrame has information that the app needs in order
// to update and render the current frame. The app begins each new
// frame by calling CreateNextFrame.
HolographicFrame holographicFrame = m_holographicSpace.CreateNextFrame();

// Get a prediction of where holographic cameras will be when this frame
// is presented.
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

处理相机更新

后缓冲区会随着帧的变化而改变。 应用需要验证每个相机的后缓冲区,并根据需要释放并重新创建资源视图和深度缓冲区。 请注意,预测中的一组姿势是当前帧中使用的相机的权威列表。 通常,可以使用此列表来对一组相机进行迭代。

从 AppMain::Update:

m_deviceResources->EnsureCameraResources(holographicFrame, prediction);

从 DeviceResources::EnsureCameraResources:

for (HolographicCameraPose const& cameraPose : prediction.CameraPoses())
{
    HolographicCameraRenderingParameters renderingParameters = frame.GetRenderingParameters(cameraPose);
    CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();
    pCameraResources->CreateResourcesForBackBuffer(this, renderingParameters);
}

获取用作渲染基础的坐标系

Windows Mixed Reality 允许应用创建各种坐标系,例如用于跟踪物理世界位置的附加和固定参考帧。 然后,应用可以使用这些坐标系来推理每个帧的全息影像渲染位置。 从 API 请求坐标时,始终需要传入 SpatialCoordinateSystem,将在其中表示这些坐标。

从 AppMain::Update:

pose = SpatialPointerPose::TryGetAtTimestamp(
    m_stationaryReferenceFrame.CoordinateSystem(), prediction.Timestamp());

然后,这些坐标系可用于在渲染场景中的内容时生成立体视图矩阵。

从 CameraResources::UpdateViewProjectionBuffer:

// Get a container object with the view and projection matrices for the given
// pose in the given coordinate system.
auto viewTransformContainer = cameraPose.TryGetViewTransform(coordinateSystem);

处理视线和手势输入

视线手部输入不基于时间,也不必在 StepTimer 函数中更新。 但是,应用需要在此输入中查看每一帧。

处理基于时间的更新

任何实时渲染应用需要通过某种方式来处理基于时间的更新 - Windows Holographic 应用模板使用 StepTimer 实现,类似于 DirectX 11 UWP 应用模板中提供的 StepTimer。 此 StepTimer 示例帮助器类可以提供固定时间步长更新、可变时间步长更新,默认模式为可变时间步长。

对于全息渲染,我们已选择不要在计时器函数中投入太多,因为你可以将其配置为固定时间步长。 每帧可能调用多次,但对于某些帧,也可能根本不调用,我们的全息数据更新应该每帧调用一次。

从 AppMain::Update:

m_timer.Tick([this]()
{
    m_spinningCubeRenderer->Update(m_timer);
});

在坐标系中定位和旋转全息影像

如果你在单个坐标系中操作,就像模板对 SpatialStationaryReferenceFrame 所做的那样,此过程与你在 3D 图形中习惯的过程没有什么不同。 在这里,我们旋转立方体并根据静止坐标系中的位置设置模型矩阵。

从 SpinningCubeRenderer::Update:

// Rotate the cube.
// Convert degrees to radians, then convert seconds to rotation angle.
const float    radiansPerSecond = XMConvertToRadians(m_degreesPerSecond);
const double   totalRotation = timer.GetTotalSeconds() * radiansPerSecond;
const float    radians = static_cast<float>(fmod(totalRotation, XM_2PI));
const XMMATRIX modelRotation = XMMatrixRotationY(-radians);

// Position the cube.
const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));

// Multiply to get the transform matrix.
// Note that this transform does not enforce a particular coordinate system. The calling
// class is responsible for rendering this content in a consistent manner.
const XMMATRIX modelTransform = XMMatrixMultiply(modelRotation, modelTranslation);

// The view and projection matrices are provided by the system; they are associated
// with holographic cameras, and updated on a per-camera basis.
// Here, we provide the model transform for the sample hologram. The model transform
// matrix is transposed to prepare it for the shader.
XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(modelTransform));

有关高级方案的说明:旋转立方体是有关如何在单个参考帧中定位全息影像的简单示例。 还可以在同一渲染帧中同时使用多个 SpatialCoordinateSystems

更新常量缓冲区数据

内容的模型变换照常更新。 到现在为止,你已经为要渲染的坐标系计算了有效的变换。

从 SpinningCubeRenderer::Update:

// Update the model transform buffer for the hologram.
context->UpdateSubresource(
    m_modelConstantBuffer.Get(),
    0,
    nullptr,
    &m_modelConstantBufferData,
    0,
    0
);

视图和投影变换呢? 为获得最佳结果,我们希望等到我们几乎准备好进行绘制调用后再获得这些调用。

渲染当前帧

在 Windows Mixed Reality 上渲染与在 2D 单色显示器上渲染大致相同,但有一些区别:

  • 全息帧预测很重要。 预测越接近帧的呈现时间,全息影像看起来就越好。
  • Windows Mixed Reality 控制相机视图。 渲染到每个视图,因为全息帧稍后会呈现它们。
  • 我们建议使用实例化绘图对渲染目标数组进行立体渲染。 全息应用模板使用对渲染器目标数组进行实例化绘制的建议方法,该方法在 Texture2DArray 上使用渲染器目标视图。
  • 若要在不使用立体实例化的情况下进行渲染,需要创建两个非数组 RenderTargetViews,分别对应于每只眼睛。 每个 RenderTargetViews 引用从系统提供给应用的 Texture2DArray 中的两个切片之一。 不建议这样做,因为这通常比使用实例化更慢。

获取更新的 HolographicFrame 预测

更新帧预测可增强图像稳定化的有效性。 由于预测和帧对用户可见之间的时间更短,你可以获得更准确的全息影像定位。 理想情况下,就是在渲染之前更新帧预测。

holographicFrame.UpdateCurrentPrediction();
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

渲染到每个相机

对预测中的一组相机姿势进行循环,并渲染到该组中的每个相机。

设置渲染通道

Windows Mixed Reality 使用立体渲染来增强深度错觉并进行立体渲染,因此左右显示都处于活动状态。 通过立体渲染,两个显示器之间存在偏移,大脑可以将其作为实际深度进行协调。 本部分介绍使用实例化的立体渲染,使用 Windows Holographic 应用模板中的代码。

每个相机都有自己的渲染目标(后缓冲区),以及全息空间的视图和投影矩阵。 应用需要在每个相机的基础上创建任何其他基于相机的资源 - 例如深度缓冲区。 在 Windows Holographic 应用模板中,我们提供了一个帮助器类将这些资源捆绑在 DX::CameraResources 中。 首先设置渲染目标视图:

从 AppMain::Render:

// This represents the device-based resources for a HolographicCamera.
DX::CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();

// Get the device context.
const auto context = m_deviceResources->GetD3DDeviceContext();
const auto depthStencilView = pCameraResources->GetDepthStencilView();

// Set render targets to the current holographic camera.
ID3D11RenderTargetView *const targets[1] =
    { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, depthStencilView);

// Clear the back buffer and depth stencil view.
if (m_canGetHolographicDisplayForCamera &&
    cameraPose.HolographicCamera().Display().IsOpaque())
{
    context->ClearRenderTargetView(targets[0], DirectX::Colors::CornflowerBlue);
}
else
{
    context->ClearRenderTargetView(targets[0], DirectX::Colors::Transparent);
}
context->ClearDepthStencilView(
    depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

使用预测获取相机的视图和投影矩阵

每个全息相机的视图和投影矩阵将随着每一帧而变化。 为每个全息相机刷新常量缓冲区中的数据。 在更新预测之后以及对该相机进行任何绘制调用之前执行此操作。

从 AppMain::Render:

// The view and projection matrices for each holographic camera will change
// every frame. This function refreshes the data in the constant buffer for
// the holographic camera indicated by cameraPose.
if (m_stationaryReferenceFrame)
{
    pCameraResources->UpdateViewProjectionBuffer(
        m_deviceResources, cameraPose, m_stationaryReferenceFrame.CoordinateSystem());
}

// Attach the view/projection constant buffer for this camera to the graphics pipeline.
bool cameraActive = pCameraResources->AttachViewProjectionBuffer(m_deviceResources);

此处将演示如何从相机姿势获取矩阵。 在此过程中,我们还会获取相机的当前视区。 请注意我们如何提供坐标系:这与我们用来理解视线的坐标系相同,也与我们用来定位旋转立方体的坐标系相同。

从 CameraResources::UpdateViewProjectionBuffer:

// The system changes the viewport on a per-frame basis for system optimizations.
auto viewport = cameraPose.Viewport();
m_d3dViewport = CD3D11_VIEWPORT(
    viewport.X,
    viewport.Y,
    viewport.Width,
    viewport.Height
);

// The projection transform for each frame is provided by the HolographicCameraPose.
HolographicStereoTransform cameraProjectionTransform = cameraPose.ProjectionTransform();

// Get a container object with the view and projection matrices for the given
// pose in the given coordinate system.
auto viewTransformContainer = cameraPose.TryGetViewTransform(coordinateSystem);

// If TryGetViewTransform returns a null pointer, that means the pose and coordinate
// system cannot be understood relative to one another; content cannot be rendered
// in this coordinate system for the duration of the current frame.
// This usually means that positional tracking is not active for the current frame, in
// which case it is possible to use a SpatialLocatorAttachedFrameOfReference to render
// content that is not world-locked instead.
DX::ViewProjectionConstantBuffer viewProjectionConstantBufferData;
bool viewTransformAcquired = viewTransformContainer != nullptr;
if (viewTransformAcquired)
{
    // Otherwise, the set of view transforms can be retrieved.
    HolographicStereoTransform viewCoordinateSystemTransform = viewTransformContainer.Value();

    // Update the view matrices. Holographic cameras (such as Microsoft HoloLens) are
    // constantly moving relative to the world. The view matrices need to be updated
    // every frame.
    XMStoreFloat4x4(
        &viewProjectionConstantBufferData.viewProjection[0],
        XMMatrixTranspose(XMLoadFloat4x4(&viewCoordinateSystemTransform.Left) *
            XMLoadFloat4x4(&cameraProjectionTransform.Left))
    );
    XMStoreFloat4x4(
        &viewProjectionConstantBufferData.viewProjection[1],
        XMMatrixTranspose(XMLoadFloat4x4(&viewCoordinateSystemTransform.Right) *
            XMLoadFloat4x4(&cameraProjectionTransform.Right))
    );
}

每个帧都应该设置视区。 顶点着色器(至少)通常需要访问视图/投影数据。

从 CameraResources::AttachViewProjectionBuffer:

// Set the viewport for this camera.
context->RSSetViewports(1, &m_d3dViewport);

// Send the constant buffer to the vertex shader.
context->VSSetConstantBuffers(
    1,
    1,
    m_viewProjectionConstantBuffer.GetAddressOf()
);

渲染到相机后缓冲区并提交深度缓冲区:

在尝试使用视图/投影数据之前检查 TryGetViewTransform 是否成功是一个好主意,因为如果坐标系不可定位(例如,跟踪被中断),你的应用将无法为该帧渲染。 如果 CameraResources 类指示更新成功,则模板仅在旋转立方体上调用 Render。

Windows Mixed Reality 包括图像稳定化功能,可将全息影像定位在开发人员或用户放置在世界上的位置。 图像稳定有助于隐藏渲染管道中固有的延迟,以确保为用户提供最佳的全息体验。 可以指定焦点以进一步增强图像稳定化,或者可以提供深度缓冲器来实时计算优化的图像稳定化。

为获得最佳结果,应用应使用 CommitDirect3D11DepthBuffer API 提供深度缓冲区。 然后,Windows Mixed Reality 可以使用深度缓冲区中的几何信息来实时优化图像稳定化。 Windows Holographic 应用模板默认提交应用的深度缓冲区,帮助优化全息影像稳定化。

从 AppMain::Render:

// Only render world-locked content when positional tracking is active.
if (cameraActive)
{
    // Draw the sample hologram.
    m_spinningCubeRenderer->Render();
    if (m_canCommitDirect3D11DepthBuffer)
    {
        // On versions of the platform that support the CommitDirect3D11DepthBuffer API, we can 
        // provide the depth buffer to the system, and it will use depth information to stabilize 
        // the image at a per-pixel level.
        HolographicCameraRenderingParameters renderingParameters =
            holographicFrame.GetRenderingParameters(cameraPose);
        
        IDirect3DSurface interopSurface =
            DX::CreateDepthTextureInteropObject(pCameraResources->GetDepthStencilTexture2D());

        // Calling CommitDirect3D11DepthBuffer causes the system to queue Direct3D commands to 
        // read the depth buffer. It will then use that information to stabilize the image as
        // the HolographicFrame is presented.
        renderingParameters.CommitDirect3D11DepthBuffer(interopSurface);
    }
}

注意

Windows 将使用 GPU 来处理深度纹理,因此必须可以将深度缓冲区用作着色器资源。 创建的 ID3D11Texture2D 应该是无类型格式,并且应该绑定为着色器资源视图。 以下是如何创建可用于图像稳定化的深度纹理的示例。

用于为 CommitDirect3D11DepthBuffer 创建深度缓冲区资源的代码:

// Create a depth stencil view for use with 3D rendering if needed.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
    DXGI_FORMAT_R16_TYPELESS,
    static_cast<UINT>(m_d3dRenderTargetSize.Width),
    static_cast<UINT>(m_d3dRenderTargetSize.Height),
    m_isStereo ? 2 : 1, // Create two textures when rendering in stereo.
    1, // Use a single mipmap level.
    D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE
);

winrt::check_hresult(
    device->CreateTexture2D(
        &depthStencilDesc,
        nullptr,
        &m_d3dDepthStencil
    ));

CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(
    m_isStereo ? D3D11_DSV_DIMENSION_TEXTURE2DARRAY : D3D11_DSV_DIMENSION_TEXTURE2D,
    DXGI_FORMAT_D16_UNORM
);
winrt::check_hresult(
    device->CreateDepthStencilView(
        m_d3dDepthStencil.Get(),
        &depthStencilViewDesc,
        &m_d3dDepthStencilView
    ));

绘制全息内容

Windows Holographic 应用模板通过使用建议的技术将实例化几何图形绘制到大小为 2 的 Texture2DArray 来渲染立体内容。 我们看看它的实例化部分,以及它如何在 Windows Mixed Reality 中运作。

从 SpinningCubeRenderer::Render:

// Draw the objects.
context->DrawIndexedInstanced(
    m_indexCount,   // Index count per instance.
    2,              // Instance count.
    0,              // Start index location.
    0,              // Base vertex location.
    0               // Start instance location.
);

每个实例从常量缓冲区访问不同的视图/投影矩阵。 以下是常量缓冲区结构,也就是两个矩阵组成的数组。

从 VertexShaderShared.hlsl,包含在 VPRTVertexShader.hlsl 中:

// A constant buffer that stores each set of view and projection matrices in column-major format.
cbuffer ViewProjectionConstantBuffer : register(b1)
{
    float4x4 viewProjection[2];
};

必须为每个像素设置渲染目标数组索引。 在以下代码段中,output.viewId 映射到 SV_RenderTargetArrayIndex 语义。 这需要支持可选的 Direct3D 11.3 功能,该功能允许从任何着色器阶段设置渲染目标数组索引语义。

从 VPRTVertexShader.hlsl:

// Per-vertex data passed to the geometry shader.
struct VertexShaderOutput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;

    // The render target array index is set here in the vertex shader.
    uint        viewId  : SV_RenderTargetArrayIndex;
};

从 VertexShaderShared.hlsl,包含在 VPRTVertexShader.hlsl 中:

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
    min16float3 pos     : POSITION;
    min16float3 color   : COLOR0;
    uint        instId  : SV_InstanceID;
};

// Simple shader to do vertex processing on the GPU.
VertexShaderOutput main(VertexShaderInput input)
{
    VertexShaderOutput output;
    float4 pos = float4(input.pos, 1.0f);

    // Note which view this vertex has been sent to. Used for matrix lookup.
    // Taking the modulo of the instance ID allows geometry instancing to be used
    // along with stereo instanced drawing; in that case, two copies of each 
    // instance would be drawn, one for left and one for right.
    int idx = input.instId % 2;

    // Transform the vertex position into world space.
    pos = mul(pos, model);

    // Correct for perspective and project the vertex position onto the screen.
    pos = mul(pos, viewProjection[idx]);
    output.pos = (min16float4)pos;

    // Pass the color through without modification.
    output.color = input.color;

    // Set the render target array index.
    output.viewId = idx;

    return output;
}

如果你希望将现有的实例化绘制技术与这种绘制到立体渲染目标阵列的方法结合使用,请绘制两倍于你通常拥有的实例数。 在着色器中,将 input.instId 除以 2 以获得原始实例 ID,该 ID 可以被索引到(例如)每个对象数据的缓冲区中:int actualIdx = input.instId / 2;

有关在 HoloLens 上渲染立体内容的重要说明

Windows Mixed Reality 支持从任何着色器阶段设置渲染目标数组索引的功能。 通常,由于为 Direct3D 11 定义语义的方式,这项任务只能在几何着色器阶段完成。 在这里,我们展示了一个完整的示例,说明如何在仅设置了顶点和像素着色器阶段的情况下设置渲染管道。 着色器代码如上所述。

从 SpinningCubeRenderer::Render:

const auto context = m_deviceResources->GetD3DDeviceContext();

// Each vertex is one instance of the VertexPositionColor struct.
const UINT stride = sizeof(VertexPositionColor);
const UINT offset = 0;
context->IASetVertexBuffers(
    0,
    1,
    m_vertexBuffer.GetAddressOf(),
    &stride,
    &offset
);
context->IASetIndexBuffer(
    m_indexBuffer.Get(),
    DXGI_FORMAT_R16_UINT, // Each index is one 16-bit unsigned integer (short).
    0
);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());

// Attach the vertex shader.
context->VSSetShader(
    m_vertexShader.Get(),
    nullptr,
    0
);
// Apply the model constant buffer to the vertex shader.
context->VSSetConstantBuffers(
    0,
    1,
    m_modelConstantBuffer.GetAddressOf()
);

// Attach the pixel shader.
context->PSSetShader(
    m_pixelShader.Get(),
    nullptr,
    0
);

// Draw the objects.
context->DrawIndexedInstanced(
    m_indexCount,   // Index count per instance.
    2,              // Instance count.
    0,              // Start index location.
    0,              // Base vertex location.
    0               // Start instance location.
);

有关在非 HoloLens 设备上渲染的重要说明

在顶点着色器中设置渲染目标数组索引要求图形驱动程序支持可选的 Direct3D 11.3 功能,HoloLens 支持该功能。 应用可以安全地实现该渲染技术,并且将满足在 Microsoft HoloLens 上运行的所有要求。

你可能还想使用 HoloLens 仿真器,它可能是全息应用的强大开发工具,其支持连接到 Windows 10 电脑的 Windows Mixed Reality 沉浸式头戴显示设备。 对非 HoloLens 渲染路径的支持 - 适用于所有 Windows Mixed Reality,并且也内置在 Windows Holographic 应用模板中。 在模板代码中,可以发现使全息应用能够在开发电脑的 GPU 上运行的代码。 以下是 DeviceResources 类检查此可选功能支持的方式。

从 DeviceResources::CreateDeviceResources:

// Check for device support for the optional feature that allows setting the render target array index from the vertex shader stage.
D3D11_FEATURE_DATA_D3D11_OPTIONS3 options;
m_d3dDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS3, &options, sizeof(options));
if (options.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer)
{
    m_supportsVprt = true;
}

要在没有此可选功能的情况下支持渲染,应用必须使用几何着色器来设置渲染目标数组索引。 此代码段将被添加到 VSSetConstantBuffers 之后,且在上一部分显示的代码示例中的 PSSetShader 之前,该代码示例解释了如何在 HoloLens 上渲染立体内容。

从 SpinningCubeRenderer::Render:

if (!m_usingVprtShaders)
{
    // On devices that do not support the D3D11_FEATURE_D3D11_OPTIONS3::
    // VPAndRTArrayIndexFromAnyShaderFeedingRasterizer optional feature,
    // a pass-through geometry shader is used to set the render target 
    // array index.
    context->GSSetShader(
        m_geometryShader.Get(),
        nullptr,
        0
    );
}

HLSL 备注:在这种情况下,还必须加载一个稍经修改的顶点着色器,该着色器使用始终允许的着色器语义(例如 TEXCOORD0)将渲染目标数组索引传递给几何着色器。 几何着色器不需要执行任何工作;模板几何着色器传递所有数据(但渲染目标数组索引除外,该索引用于设置 SV_RenderTargetArrayIndex 语义)。

GeometryShader.hlsl 的应用模板代码:

// Per-vertex data from the vertex shader.
struct GeometryShaderInput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;
    uint instId         : TEXCOORD0;
};

// Per-vertex data passed to the rasterizer.
struct GeometryShaderOutput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;
    uint rtvId          : SV_RenderTargetArrayIndex;
};

// This geometry shader is a pass-through that leaves the geometry unmodified 
// and sets the render target array index.
[maxvertexcount(3)]
void main(triangle GeometryShaderInput input[3], inout TriangleStream<GeometryShaderOutput> outStream)
{
    GeometryShaderOutput output;
    [unroll(3)]
    for (int i = 0; i < 3; ++i)
    {
        output.pos   = input[i].pos;
        output.color = input[i].color;
        output.rtvId = input[i].instId;
        outStream.Append(output);
    }
}

现值

启用全息框架来呈现交换链

在 Windows Mixed Reality 中,由系统来控制交换链。 然后,系统会管理向每个全息相机渲染帧,以确保具有高质量的用户体验。 系统还为每个相机提供每帧的视口更新,以优化系统的各个方面,例如图像稳定化或混合现实捕捉。 因此,使用 DirectX 的全息应用不会在 DXGI 交换链上调用 Present。 可以改用 HolographicFrame 类在完成绘制后呈现框架的所有交换链。

从 DeviceResources::Present:

HolographicFramePresentResult presentResult = frame.PresentUsingCurrentPrediction();

默认情况下,此 API 会等到帧完成之后再返回。 全息应用应该在等到前一帧完成之后再开始处理新的帧,因为这样可以减少延迟并允许从全息帧预测中获得更好的结果。 这不是一个硬性规定,如果你的帧需要多次屏幕刷新才能渲染,则可以通过将 HolographicFramePresentWaitBehavior 参数传递给 PresentUsingCurrentPrediction 来禁用这种等待。 在这种情况下,你可能会使用异步渲染线程来缓和 GPU 上的持续负载。 HoloLens 设备的刷新率为 60 Hz,其中一帧的持续时间约为 16 毫秒。 沉浸式头戴显示设备的范围可以从 60 Hz 到 90 Hz;当以 90 hz 刷新显示时,每帧的持续时间大约为 11 ms。

与 HolographicFrame 合作处理 DeviceLost 场景

DirectX 11 应用通常希望检查由 DXGI 交换链的 Present 函数返回的 HRESULT,以确定是否存在 DeviceLost 错误。 HolographicFrame 类会为你处理这个问题。 检查返回的 HolographicFramePresentResult 以确定是否需要释放和重新创建 Direct3D 设备和基于设备的资源。

// The PresentUsingCurrentPrediction API will detect when the graphics device
// changes or becomes invalid. When this happens, it is considered a Direct3D
// device lost scenario.
if (presentResult == HolographicFramePresentResult::DeviceRemoved)
{
    // The Direct3D device, context, and resources should be recreated.
    HandleDeviceLost();
}

如果 Direct3D 设备丢失,并且你重新创建,则你必须命令 HolographicSpace 开始使用新设备。 对于此设备,将重新创建交换链。

从 DeviceResources::InitializeUsingHolographicSpace:

m_holographicSpace.SetDirect3D11Device(m_d3dInteropDevice);

出现帧后,你可以返回到主程序循环,并允许其继续进入下一帧。

混合图形电脑和混合现实应用程序

Windows 10 Creators Update 电脑可能同时配置了独立 GPU 和集成 GPU。 对于这种类型的计算机,Windows 会选择头戴显示设备连接的适配器。 应用程序必须确保其创建的 DirectX 设备使用同一个适配器。

大多数通用 Direct3D 示例代码演示了使用默认硬件适配器创建 DirectX 设备,这在混合系统上可能与用于头戴显示设备的不同。

若要解决任何问题,请使用 HolographicSpace.PrimaryAdapterId() 或 HolographicDisplay.AdapterId() 中的 Holographic​Adapter​ID。 然后可以使用此 adapterId 来选择正确的 DXGIAdapter(使用 IDXGIFactory4.EnumAdapterByLuid)。

从 DeviceResources::InitializeUsingHolographicSpace:

// The holographic space might need to determine which adapter supports
// holograms, in which case it will specify a non-zero PrimaryAdapterId.
LUID id =
{
    m_holographicSpace.PrimaryAdapterId().LowPart,
    m_holographicSpace.PrimaryAdapterId().HighPart
};

// When a primary adapter ID is given to the app, the app should find
// the corresponding DXGI adapter and use it to create Direct3D devices
// and device contexts. Otherwise, there is no restriction on the DXGI
// adapter the app can use.
if ((id.HighPart != 0) || (id.LowPart != 0))
{
    UINT createFlags = 0;

    // Create the DXGI factory.
    ComPtr<IDXGIFactory1> dxgiFactory;
    winrt::check_hresult(
        CreateDXGIFactory2(
            createFlags,
            IID_PPV_ARGS(&dxgiFactory)
        ));
    ComPtr<IDXGIFactory4> dxgiFactory4;
    winrt::check_hresult(dxgiFactory.As(&dxgiFactory4));

    // Retrieve the adapter specified by the holographic space.
    winrt::check_hresult(
        dxgiFactory4->EnumAdapterByLuid(
            id,
            IID_PPV_ARGS(&m_dxgiAdapter)
        ));
}
else
{
    m_dxgiAdapter.Reset();
}

用于更新 DeviceResources::CreateDeviceResources 以使用 IDXGIAdapter 的代码

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

const D3D_DRIVER_TYPE driverType = m_dxgiAdapter == nullptr ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN;
const HRESULT hr = D3D11CreateDevice(
    m_dxgiAdapter.Get(),        // Either nullptr, or the primary adapter determined by Windows Holographic.
    driverType,                 // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Windows Runtime apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
);

混合图形和媒体基础

在混合系统上使用媒体基础可能会导致视频无法渲染或视频纹理损坏的问题,因为媒体基础默认采用系统行为。 在某些情况下,需要创建单独的 ID3D11Device 以支持多线程,并设置正确的创建标志。

初始化 ID3D11Device 时,必须将 D3D11_CREATE_DEVICE_VIDEO_SUPPORT 标志定义为 D3D11_CREATE_DEVICE_FLAG 的一部分。 创建设备和上下文后,调用 SetMultithreadProtected 以启用多线程处理。 若要将设备与 IMFDXGIDeviceManager 相关联,请使用 IMFDXGIDeviceManager::ResetDevice 函数。

用于将 ID3D11Device 与 IMFDXGIDeviceManager 相关联的代码:

// create dx device for media pipeline
winrt::com_ptr<ID3D11Device> spMediaDevice;

// See above. Also make sure to enable the following flags on the D3D11 device:
//   * D3D11_CREATE_DEVICE_VIDEO_SUPPORT
//   * D3D11_CREATE_DEVICE_BGRA_SUPPORT
if (FAILED(CreateMediaDevice(spAdapter.get(), &spMediaDevice)))
    return;                                                     

// Turn multithreading on 
winrt::com_ptr<ID3D10Multithread> spMultithread;
if (spContext.try_as(spMultithread))
{
    spMultithread->SetMultithreadProtected(TRUE);
}

// lock the shared dxgi device manager
// call MFUnlockDXGIDeviceManager when no longer needed
UINT uiResetToken;
winrt::com_ptr<IMFDXGIDeviceManager> spDeviceManager;
hr = MFLockDXGIDeviceManager(&uiResetToken, spDeviceManager.put());
if (FAILED(hr))
    return hr;
    
// associate the device with the manager
hr = spDeviceManager->ResetDevice(spMediaDevice.get(), uiResetToken);
if (FAILED(hr))
    return hr;

请参阅