效果图中的精度和数字剪裁

使用 Direct2D 呈现效果的应用程序必须小心,才能在数值精度方面达到所需的质量和可预测性级别。 本主题介绍 Direct2D 中的最佳做法和相关设置,在以下情况下非常有用:

  • 效果图依赖于 [0, 1] 范围之外的高数值精度或颜色,你希望确保这些始终可用
  • 或者,效果图依赖于呈现实现将中间颜色固定到 [0, 1] 范围,并且你希望确保始终发生此固定

Direct2D 通常将效果图划分为多个部分,并在单独的步骤中呈现每个部分。 某些步骤的输出可能存储在中间 Direct3D 纹理中,默认情况下,这些纹理的数字范围和精度有限。 Direct2D 不保证这些中间纹理是否使用或在哪里使用。 此行为可能因 GPU 功能以及 Windows 版本而异。

在Windows 10中,Direct2D 使用较少的中间纹理,因为它使用着色器链接。 因此,Direct2D 可能会生成与以前的 Windows 版本不同的默认设置结果。 这主要影响在效果图中可以进行着色器链接的方案,并且该图还包含产生扩展范围输出颜色的效果。

效果呈现和中间项概述

为了呈现效果图,Direct2D 首先查找“转换”的基础图,其中转换是效果中使用的图形节点。 有不同类型的转换,包括提供 Direct3D 着色器供 Direct2D 使用的转换。

例如,Direct2D 可能会呈现效果图,如下所示:

具有中间纹理的效果图

Direct2D 寻找减少用于呈现效果图的中间纹理数量的机会;此逻辑对应用程序是不透明的。 例如,Direct2D 可以使用一个 Direct3D 绘图调用呈现以下图形,而没有中间纹理:

没有中间纹理的效果图

在Windows 10之前,如果在同一效果图中使用了多个像素着色器,Direct2D 将始终使用中间纹理。 大多数仅调整颜色值的内置效果 (例如,亮度或饱和度) 使用像素着色器执行此操作。

在Windows 10中,Direct2D 现在可能避免在这种情况下使用中间纹理。 它通过内部链接相邻的像素着色器来执行此操作。 例如:

具有多个像素着色器且没有中间纹理的 windows 10 效果图

请注意,并非图形中的所有相邻像素着色器都可以链接在一起,因此只有某些图形会在Windows 10上生成不同的输出。 有关完整详细信息,请参阅 效果着色器链接。 主要限制是:

  • 如果第一个效果作为多个效果的输入连接,则效果不会与使用其输出的效果链接。
  • 如果第一个效果在与输出不同的逻辑位置对效果的输入采样,则效果不会与效果集作为其输入进行关联。 例如,颜色矩阵效果可能与其输入链接,但卷积效果不会链接。

内置效果行为

许多内置效果可能会在非premultiplied颜色空间中的 [0, 1] 范围之外产生颜色,即使其输入颜色在该范围内也是如此。 发生这种情况时,此类颜色可能会受到数字剪裁。 请注意,考虑非多乘空间中的颜色范围非常重要,尽管内置效果通常在预乘空间中生成颜色。 这可确保颜色保持在范围内,即使其他效果随后取消了它们。

可能会发出这些超出范围的颜色的某些效果提供“ClampOutput”属性。 其中包括:

将这些效果上的 ClampOutput 属性设置为 TRUE 可确保无论着色器链接等因素如何,都能获得一致的结果。 请注意,固定发生在非占位空间中。

其他内置效果也可能在非专用空间中生成超出 [0, 1] 范围的输出颜色,即使其颜色像素 (和“Color”属性(如果任何) 在该范围内)。 其中包括:

在效果图中强制进行数值剪裁

在使用上面列出的不具有 ClampOutput 属性的效果时,应用程序应考虑强制进行数字固定。 这可以通过将附加效果插入到固定其像素的图形中来完成。 可以使用颜色矩阵效果,其“ClampOutput”属性设置为 TRUE,并将“ColorMatrix”属性保留为默认 (直通) 值。

实现一致结果的第二个选项是请求 Direct2D 使用精度更高的中间纹理。 下面对此进行了介绍。

控制中间纹理的精度

Direct2D 提供了几种控制图形精度的方法。 在 Direct2D 中使用高精度格式之前,应用程序必须确保 GPU 充分支持它们。 若要检查此问题,请使用 ID2D1DeviceContext::IsBufferPrecisionSupported

应用程序可以使用 WARP (软件仿真) 创建 Direct3D 设备,以确保支持所有缓冲区精度,而不依赖于设备上的实际 GPU 硬件。 在保存到磁盘时将效果应用到照片等方案中,建议使用此方法。 即使 Direct2D 支持 GPU 上的高精度缓冲区格式,由于某些低功耗移动 GPU 上着色器算术和采样的精度有限,因此建议在此方案中在功能级别 9.X GPU 上使用 WARP。

在下面的每种情况下,请求的精度实际上是 Direct2D 将使用的最小精度。 如果不需要中间值,可以使用更高的精度。 Direct2D 还可以共享同一图形或不同图形的不同部分的中间纹理。 在这种情况下,Direct2D 使用针对所有涉及的操作请求的最大精度。

ID2D1DeviceContext::SetRenderingControls 中的精度选择

控制 Direct2D 中间纹理精度的最简单方法是使用 ID2D1DeviceContext::SetRenderingControls。 这可以控制所有中间纹理的精度,前提是精度不是直接针对效果或转换手动设置的。

if (Device->IsBufferPrecisionSupported(D2D1_BUFFER_PRECISION_32BPC_FLOAT))
{
  // Get the current rendering controls
  D2D1_RENDERING_CONTROLS renderingControls = {};
  Context->GetRenderingControls(&renderingControls);

  // Switch the precision within the rendering controls and set it
  renderingControls.bufferPrecision = D2D1_BUFFER_PRECISION_32BPC_FLOAT;
  Context->SetRenderingControls(&renderingControls);
}
              

从输入和呈现目标进行精确选择

应用程序还可能依赖于效果图输入的精度来控制中间纹理的精度。 只要未使用 ID2D1DeviceContext::SetRenderingControls 指定缓冲区精度,并且不会在效果上手动设置和直接转换,则这是正确的。

效果输入的精度通过图形传播,以选择下游中间值的精度。 如果效果图中的不同分支相遇,则使用任何输入的最大精度。

基于 Direct2D 位图选择的精度取决于其像素格式。 为 ID2D1ImageSource 选择的精度取决于用于创建 ID2D1ImageSource 的基础 IWICBitmapSource 的 WIC 像素格式。 请注意,Direct2D 不允许使用 Direct2D 和 GPU 不支持的精度通过 WIC 源创建图像源。

Direct2D 可能无法根据其输入为效果分配精度。 如果效果没有输入,或者使用了 ID2D1CommandList ,但没有特定精度,则会发生此情况。 在这种情况下,中间纹理的精度取决于作为上下文当前呈现目标的位图集。

直接对效果和转换进行精度选择

中间纹理的最小精度也可以在效果图中的显式位置设置。 仅建议用于高级方案。

可以使用属性对效果设置最小精度,如下所示:

if (Device->IsBufferPrecisionSupported(D2D1_BUFFER_PRECISION_32BPC_FLOAT))
{
  hr = Effect->SetValue(D2D1_PROPERTY_PRECISION, D2D1_BUFFER_PRECISION_32BPC_FLOAT);
}
              

在效果实现中,可以使用 ID2D1RenderInfo::SetOutputPrecision 设置最小精度,如下所示:

if (EffectContext->IsBufferPrecisionSupported(D2D1_BUFFER_PRECISION_32BPC_FLOAT))
{
  hr = RenderInfo->SetOutputBuffer(
  D2D1_BUFFER_PRECISION_32BPC_FLOAT,
  D2D1_CHANNEL_DEPTH_4);
}
              

请注意,除非对这些下游效果设置了不同的精度,否则对效果设置的精度将传播到同一效果图中的下游效果。 在效果中对转换设置的精度不会影响下游转换节点的精度。

下面是用于确定存储给定转换节点输出的中间缓冲区的最小精度的完整递归逻辑:

中间缓冲区最小精度逻辑