在高/标准动态范围显示器上将 DirectX 与高级颜色配合使用

本主题介绍如何将 DirectX 与高级色彩方案结合使用,包括高动态范围 (HDR)、具有自动系统色彩管理功能的宽色域 (WCG) 以及高位深度。 至少具备上述一种增强功能的高级个人电脑 (PC) 显示器正在普及,提供比传统标准动态范围 (SDR) 显示器高得多的色彩保真度。

在本主题中,您将大致了解 Windows 高级色彩支持背后的关键技术概念。 您将了解到将 HDR、WCG 和高位深度 DirectX 内容呈现到其中一个显示器的要求和说明。 如果您有色彩管理应用(例如,使用 ICC 配置文件),那么您将了解自动色彩管理如何为您的应用场景提供更好的色彩准确性。

Windows 中的高级色彩简介

高级色彩 是操作系统 (OS) 技术的一个伞式术语,其色彩保真度明显高于标准显示器。 以下各节介绍了主要的扩展功能。 首次为 Windows 10 1709 版本 (Fall Creators Update) 的 HDR 显示器引入高级色彩功能,并为 Windows 11 22H2 版本 (10.0; Build 22621) 中引入,用于特殊配置的 SDR 显示器。

高动态范围

动态范围是指场景中最大亮度和最小亮度之间的差异;这通常以尼特(坎德拉斯每平方厘米)测量。 现实场景,如这一日落,亮度的动态范围往往达到 10 个数量级:人类眼睛在适应后可以辨别出更大的范围。

已标记场景中亮度和最暗点的日落图片

自 Direct3D 9 以来,图形引擎已能够在内部呈现具有这种物理准确保真度级别的场景。 但是,典型的标准动态范围显示器只能重现略高于 3 个数量级的亮度,因此任何 HDR 呈现的内容都必须将音调映射(压缩)到显示器的有限范围内。 新型 HDR 显示器,包括符合 HDR10 (BT.2100) 标准的显示器突破了此限制:例如,高质量的自发射显示器可以达到超过 6 个数量级。

宽色域

色域是指显示器可以重现的色调的范围和饱和度。 人眼可以感知到的最饱和的自然色彩由纯单色光组成,例如激光产生的光。 然而,主流消费类显示器通常只能再现 sRGB 色域内的色彩,这仅占人类可感知的所有色彩的约 35%。 下图是人类“光谱定位点”或所有可感知色彩(在给定亮度级别下)的示意图,其中较小的三角形是 sRGB 色域。

人类光谱定位点和 sRGB 域图

长期以来,高端专业电脑显示器一直支持比 sRGB 宽得多的色域,例如 Adobe RGB 和 DCI-P3,它们覆盖了人类可感知色彩的一半左右。 这些宽域显示器越来越常见。

自动系统色彩管理

色彩管理是确保跨设备进行准确且一致的色彩复制的技术和实践。 如果您是数字内容创建者,那么您的视觉内容(如照片、产品图像或徽标)中的色彩至关重要,它应与在受众的各种数字设备上显示的色彩相同。

自 Windows 2000 以来,Windows 就提供了色彩管理支持 API,包括图像色彩管理 (ICM) 和更高版本的 Windows 色彩系统 (WCS) API。 但是,这些 API 只是希望/需要执行色彩管理的应用辅助程序;而大多数应用和数字内容只是假设行业标准 sRGB 色彩空间,操作系统从未对其进行过色彩管理。 这是过去一个合理的假设,但高质量的宽域显示器正变得越来越常见。

新版本的 Windows 支持自动系统色彩管理;确保每个 Windows 应用中的所有色彩(无论它们是否具有色彩感知性)都能准确且一致地显示在每个受支持的显示器上。

注意

自动色彩管理不是显示硬件的属性;相反,它是一项 Windows 功能,可正确支持色域大于 sRGB 的显示器。

深度精度/位深度

数值精度或位深度是指用于唯一识别色彩的信息量。 较高的位深度意味着可以区分非常相似的色彩,而不会出现诸如条带之类的伪影。 主流电脑显示器支持每色通道 8 位,而人眼至少需要 10-12 位精度才能避免可感知失真。

模拟每色彩通道 2 位与每通道 8 位的风车图片

在高级色彩之前,桌面窗口管理器 (DWM) 限制窗口应用只能以每个色彩通道 8 位输出内容,即使显示器支持更高的位深度也是如此。 启用高级色彩后,DWM 将使用 IEEE 半精度浮点 (FP16) 执行合成,消除任何瓶颈,并允许使用显示器的完整精度。

Windows 高级色彩系统体系结构

本节中的信息对于生成高级色彩应用是可选的;但是,了解技术的工作原理有助于优化应用的呈现和行为。

在本节中,我们将使用简化的关系图来描述 Windows 图形堆栈的相关组件:

Windows 图形堆栈的块图:应用到 DWM 以显示内核

现有 Windows:8 位/sRGB 显示

几十年来,消费类显示器和 Windows 图形堆栈都是基于大约 8 位/通道(每个像素 24 位)的 sRGB 内容。 使用图形 API(如 DirectX)的应用可以使用高位深度和扩展的色彩空间执行内部渲染;但是,OS 仅支持使用隐式 sRGB 且无系统色彩管理的 8 位整数:

SDR 显示堆栈的块图:限制为 sRGB、8 位,无色彩管理

这意味着在显示应用时,应用呈现的任何其他色彩数据都将丢失;应用必须自行执行色彩管理,以确保在显示器上准确再现。

Windows 10,1703 版本:使用高级色彩的 HDR 显示器

Windows 10,1703 版本为 HDR 显示器引入了第一个高级色彩功能。 这需要在 OS 图形堆栈中取得多项重大进展:

  • HDR 显示信号支持
  • 使用高位深度、规范色彩空间的系统组合
  • 自动系统色彩管理

HDR 显示堆栈的块图:FP16、scRGB 和自动色彩管理

下面各小节将介绍每项改进。 最终结果是,扩展的应用色彩数据现在由 OS 正确保留,并在 HDR 显示器上准确重现。

HDR 显示信号支持

基于显示连接器(如 DisplayPort 和 HDMI)的 HDR 信号主要使用每通道 10 位精度(或更高)和 BT.2100 ST.2084 色彩空间。 显示内核、显示驱动程序和基础 GPU 硬件都需要支持检测、选择和驱动这种信号模式。

使用高位深度、规范色彩空间的系统组合

BT.2100 ST.2084 色彩空间是编码 HDR 色彩的有效标准,但它不适合许多呈现和合成(混合)操作。 我们还希望未来的 OS 能够支持远超 BT.2100 的技术与色彩空间,因为 BT.2100 覆盖的人类可见色彩不到 2/3。 最后,在可能的情况下,我们希望尽量减少 GPU 资源消耗,以提高功率和性能。

在 HDR 模式下,桌面窗口管理器 (DWM) 使用规范合成色彩空间 (CCCS),其定义如下:

  • scRGB 色彩空间(带线性伽玛的 BT.709/sRGB 原色)
  • IEEE 半精度(FP16 位深度)

这在上述所有目标之间提供了良好的平衡。 CCCS 允许 [0, 1] 数值范围之外的色彩值;给定有效 FP16 值的范围,它可以表示比自然人类视觉范围更多的色彩数量级,包括亮度值超过 500 万尼特。 FP16 对于线性伽玛混合运算具有出色的精度,但 GPU 内存消耗和带宽成本只有传统单精度 (FP32) 的一半,且没有明显的质量损失。

自动系统色彩管理

Windows 是一种多任务环境,用户可以在重叠窗口中同时运行任意数量的 SDR 和 HDR 应用。 因此,当输出到显示器时,所有类型的内容都应看起来正确并达到最高质量,这一点至关重要;例如,具有 BT.2100 ST.2084 (HDR) 视频窗口的 sRGB (SDR) 生产力应用正在播放它。

在 HDR 模式下,Windows 在两个阶段执行色彩管理操作:

  1. 在混合之前,DWM 会将每个应用从其原生色彩空间转换为 CCCS。
  2. 显示内核将 OS 帧缓冲区从 CCCS 转换为线路格式色彩空间 (BT.2100 ST.2084)。

DWM 和显示内核中自动色彩管理的块图DWM 和显示内核中自动色彩管理的块图,第二部分

注意

在这两个阶段,色彩管理操作由色彩空间转换(矩阵和 1DLUT)组成。 超出显示屏目标色域的色彩会被数字剪裁。

Windows 11,22H2 版本:使用高级色彩的 SDR 显示器

虽然 HDR 显示器的普及率正在迅速增长,但 SDR 显示器在未来几年内仍将非常重要。 Windows 10 版本 1703 中的 HDR 支持也奠定了增强 SDR 显示器所需的大部分基础。 Windows 11 版本 22H2 将高级色彩和自动色彩管理功能扩展到某些符合条件的 SDR 显示器。 高级色彩 SDR 显示器的图形块图看起来与 HDR 非常相似:

SDR AC 显示堆栈的块图:FP16、scRGB 和自动色彩管理

具有高位深度的 SDR 显示信号支持

尽管 Windows 11 22H2 版本支持每个通道 10 位及更高版本,但SDR 显示器的基础信号保持不变,具体取决于显示器的功能。

使用高位深度、规范色彩空间的系统组合

DWM 高级色彩功能(包括 CCCS 中的混合)与 HDR 显示器几乎完全相同。 主要区别是 DWM 在 SDR 显示器上使用显示参考亮度,而在 HDR 显示器上使用场景参考亮度。 这会更改操作系统解释高级色彩呈现内容的方式:

显示类型 亮度行为 如何解释 1.0f
SDR Display-referred 作为显示器的参考白电平
HDR 场景引用 80 尼特(标称参考白色)

自动系统色彩管理

OS 系统色彩管理功能也与 HDR 显示器基本保持一致。 主要区别在于,显示内核将转换为由显示器着色和校准数据定义的显示器参考色彩空间,而不是用于 HDR 显示器的标准 BT.2100 ST.2084 色彩空间。

需要显示配置

需要来自 MHC ICC 配置文件的准确数据来定义显示内核的输出色彩管理操作。 因此,只有由制造商或显示器校准提供商专门提供有效配置文件的 SDR 显示器才有资格使用自动色彩管理功能。 有关详细信息,请参阅使用高级色彩的 ICC 配置文件行为

系统需求和操作系统支持

Windows 10 版本 1709 首次为 HDR 显示器提供高级色彩支持。 Windows 11 版本 22H2 版本为具有准确预配数据的 SDR 显示器添加了高级色彩支持。

本主题假设您的应用程序针对 HDR 显示器使用 Windows 10 2004 版(或更高版本),针对 SDR 显示器使用 Windows 11 22H2 版(或更高版本)。

显示器

高动态范围显示器必须实现 HDR10 或 BT.2100 ST.2084 标准。 HDR 显示器质量可能会大不相同,我们强烈推荐通过认证的显示器,例如 VESA DisplayHDR。 从 Windows 11 22H2 版本开始,Windows 会在设置应用中显示已知显示器的认证状态。

标准动态范围显示器必须具有准确的色彩预配数据才能支持高级色彩。 在 Windows 11 22H2 版本中,唯一支持重写此数据的方法是通过 MHC ICC 配置文件;此外,用户或显示器制造商必须已启用自动色彩管理。 有关详细信息,请参阅使用高级色彩的 ICC 配置文件行为

图形处理器 (GPU)

若要在 SDR 和 HDR 显示器上实现完整的高级色彩功能,需要最新的 GPU:

  • AMD Radeon RX 400 系列 (Polaris)或更新版本
  • NVIDIA GeForce 10 系列(帕斯卡)或更新版本
  • 选定的 Intel Core 第 10 代(冰湖)或更新版本*

注意

Intel 代码名称 Comet Lake(5 位数模型代码)芯片不提供完整功能。

根据不同的应用场景,可能会有其他硬件要求,包括硬件编解码器加速(10 位 HEVC、10 位 VP9 等)和 PlayReady 支持 (SL3000)。 请与 GPU 供应商联系,获取详细信息。

图形驱动程序 (WDDM)

强烈建议从Windows 更新或 GPU 供应商或电脑制造商的网站获取最新的可用图形驱动程序。 本主题依赖于 WDDM 2.7(Windows 10,2004 版)中用于 HDR 显示器的驱动程序功能,以及 WDDM 3.0(Windows 11,21H2 版)中用于 SDR 显示器的驱动程序功能。

受支持的呈现 API

Windows 10 支持各种呈现 API 和框架。 高级色彩支持从根本上依赖于应用能够使用 DXGI 或视觉层 API 执行新式演示。

因此,任何可以输出到这些呈现方法之一的呈现 API 都可以支持高级色彩。 这包括(但不限于)以下各项。

  • Direct3D 11
  • Direct3D 12
  • Direct2D
  • Win2D
    • 需要使用较低级别的 CanvasSwapChainCanvasSwapChainPanel API。
  • Windows.UI.Input.Inking
    • 支持使用 DirectX 进行自定义干墨渲染。
  • XAML
    • 支持使用 MediaPlayerElement 播放 HDR 视频。
    • 支持使用 Image 元素解码 JPEG XR 图像。
    • 支持使用 SwapChainPanel 进行 DirectX 互操作。

处理动态显示功能

Windows 10 支持各种支持高级色彩的显示器,从节能集成面板到高端游戏监视器和电视。 Windows 用户希望您的应用能够无缝处理所有这些变体,包括无处不在的现有 SDR 显示器。

Windows 10 为用户提供控制 HDR 和高级色彩的功能。 您的应用必须检测当前显示器的配置,并动态响应功能的任何更改。 出现这种情况的原因有很多,例如,用户启用或禁用了某项功能,或在不同的显示屏之间移动了应用程序,或系统的电源状态发生了变化。

选项 1:AdvancedColorInfo

注意

AdvancedColorInfo Windows 运行时 API 独立于呈现 API 可用,支持 SDR 显示的高级色彩,并使用事件在功能更改时发出信号。 但是,它仅适用于通用 Windows 平台 (UWP) 应用;桌面应用(没有 CoreWindow)无法使用它。 有关详细信息,请参阅桌面应用中不支持的 Windows 运行时 API

首先,从 DisplayInformation::GetAdvancedColorInfo 获取 AdvancedColorInfo 的实例

若要检查当前处于活动状态的高级色彩类型,请使用 AdvancedColorInfo::CurrentAdvancedColorKind 属性。 这是需要检查的最重要属性,应该根据活动类型配置您的呈现和演示管道:

高级色彩类型 显示功能
SDR 没有高级色彩功能的 SDR 显示器
WCG 具有高位深度和自动色彩管理的 SDR 显示器
HDR 具有所有高级色彩功能的 HDR 显示器

若要查看支持哪些高级色彩类型,但不一定处于活动状态,请调用 AdvancedColorInfo::IsAdvancedColorKindAvailable。 例如,您可以使用这些信息来提示用户导航到 Windows 设置应用,以便启用 HDR 或自动色彩管理。

AdvancedColorInfo 的其他成员提供与 SMPTE ST.2086 静态 HDR 元数据对应的面板物理颜色量(亮度和色度)的定量信息。 尽管 ST.2086 最初是为 HDR 显示器设计的,但该信息非常有用,并且可用于 HDR 和 SDR 显示器。 您应使用该信息来配置应用的色调映射和色域映射。

若要处理高级色彩功能中的更改,请注册 DisplayInformation::AdvancedColorInfoChanged 事件。 如果显示屏的高级色彩功能的任何参数因任何原因而更改,则会触发该事件。

通过获取 AdvancedColorInof 的新实例并检查哪些值已更改来处理该事件。

IDXGIOutput6

注意

DirectX 图形基础结构 IDXGIOutput6 接口适用于任何使用 DirectX 的应用,无论是桌面还是通用 Windows 平台 (UWP)。 但是,IDXGIOutput6 不支持 具有高级色彩功能的 SDR 显示器,例如自动色彩管理;它只能识别 HDR 显示器。

如果您要编写 Win32 桌面应用并使用 DirectX 进行呈现,请使用 DXGI_OUTPUT_DESC1 获取显示功能。 通过 IDXGIOutput6::GetDesc1 获取该结构的实例。

若要检查当前处于活动状态的高级色彩类型,请使用 ColorSpace 属性,该属性的类型为 DXGI_COLOR_SPACE_TYPE,并包含以下值之一:

DXGI_COLOR_SPACE_TYPE 显示功能
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 没有高级色彩功能的 SDR 显示器
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 具有所有高级色彩功能的 HDR 显示器

注意

具有高级色彩功能的 SDR 显示器也报告为 DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;DXGI 不允许区分这两种类型。

注意

DXGI 无法让您查看目前支持但未激活的高级色彩种类。

DXGI_OUTPUT_DESC1 的大多数其他成员提供与 SMPTE ST.2086 静态 HDR 元数据对应的面板物理颜色量(亮度和色度)的定量信息。 尽管 ST.2086 最初是为 HDR 显示器设计的,但该信息非常有用,并且可用于 HDR 和 SDR 显示器。 您应使用该信息来配置应用的色调映射和色域映射。

Win32 桌面应用没有本机机制来响应高级色彩功能更改。 相反,如果您的应用使用呈现循环,则应在每个帧中查询 IDXGIFactory1::IsCurrent。 如果报告 FALSE,则应获取新的 DXGI_OUTPUT_DESC1,并检查哪些值已更改。

此外,Win32 消息泵应处理 WM_SIZE 消息,这表示应用可能在不同的显示器之间移动。

注意

若要获取新的 DXGI_OUTPUT_DESC1,必须获取当前显示器。 但是,不应调用 IDXGISwapChain::GetContainingOutput。 这是因为一旦 DXGIFactory::IsCurrent为 fasel,交换链会返回过时的 DXGI 输出;而重新创建交换链以获取当前输出结果时,会出现暂时的黑屏。 相反,我们建议您枚举所有 DXGI 输出的边界,并确定哪一个与应用程序窗口边界的交集最大。

以下示例代码来自 GitHub 上的 Direct3D 12 HDR 示例应用

// Retrieve the current default adapter.
ComPtr<IDXGIAdapter1> dxgiAdapter;
ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, &dxgiAdapter));

// Iterate through the DXGI outputs associated with the DXGI adapter,
// and find the output whose bounds have the greatest overlap with the
// app window (i.e. the output for which the intersection area is the
// greatest).

UINT i = 0;
ComPtr<IDXGIOutput> currentOutput;
ComPtr<IDXGIOutput> bestOutput;
float bestIntersectArea = -1;

while (dxgiAdapter->EnumOutputs(i, &currentOutput) != DXGI_ERROR_NOT_FOUND)
{
    // Get the retangle bounds of the app window
    int ax1 = m_windowBounds.left;
    int ay1 = m_windowBounds.top;
    int ax2 = m_windowBounds.right;
    int ay2 = m_windowBounds.bottom;

    // Get the rectangle bounds of current output
    DXGI_OUTPUT_DESC desc;
    ThrowIfFailed(currentOutput->GetDesc(&desc));
    RECT r = desc.DesktopCoordinates;
    int bx1 = r.left;
    int by1 = r.top;
    int bx2 = r.right;
    int by2 = r.bottom;

    // Compute the intersection
    int intersectArea = ComputeIntersectionArea(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2);
    if (intersectArea > bestIntersectArea)
    {
        bestOutput = currentOutput;
        bestIntersectArea = static_cast<float>(intersectArea);
    }

    i++;
}

// Having determined the output (display) upon which the app is primarily being 
// rendered, retrieve the HDR capabilities of that display by checking the color space.
ComPtr<IDXGIOutput6> output6;
ThrowIfFailed(bestOutput.As(&output6));

DXGI_OUTPUT_DESC1 desc1;
ThrowIfFailed(output6->GetDesc1(&desc1));

设置 DirectX 交换链

确定显示器当前支持高级色彩功能后,请按如下步骤配置交换链。

使用翻转演示文稿模型效果

使用 CreateSwapChainFor[Hwnd|Composition|CoreWindow] 方法之一创建交换链时,必须通过选择 DXGI_SWAP_EFFECT_FLIP_SEQUENTIALDXGI_SWAP_EFFECT_FLIP_DISCARD 选项来使用 DXGI 翻转模型,这使得交换链符合 DWM 高级色彩处理和各种全屏优化的要求。 有关详细信息,请参阅使用 DXGI 翻转模型获得最佳性能

选项 1. 使用 FP16 像素格式和 scRGB 颜色空间

Windows 10 支持高级色彩的像素格式和色彩空间的两个主要组合。 根据应用的特定要求选择一个。

我们建议常规用途应用使用选项 1。 它是适用于所有类型的高级彩色显示器、内容和呈现 API 的唯一选项。 创建交换链时,请在 DXGI_SWAP_CHAIN_DESC1 中指定 DXGI_FORMAT_R16G16B16A16_FLOAT。 默认情况下,使用浮点像素格式创建的交换链被视为使用 DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 色彩空间。 这与 DWM 使用的像素格式和色彩空间相同。

该组合提供数值范围和精度来指定任何物理上可能的色彩,并执行任意处理,包括混合。

但是,该选项每像素消耗 64 位,与传统的 UINT8 像素格式相比,这使 GPU 带宽和内存消耗增加了一倍。 此外,scRGB 使用规范化 [0, 1] 范围之外的数值来表示 sRGB 域之外的颜色和/或大于 80 nit 的亮度。 例如,scRGB (1.0, 1.0, 1.0) 以 80 尼特对标准 D65 白光进行编码;但 scRGB (12.5, 12.5, 12.5) 则以更亮的 1000 尼特对相同的 D65 白光进行编码。 某些图形操作需要规范化数值范围,必须修改操作或重新规范化色彩值。

使用该选项解释亮度值的方式在 SDR 和 HDR 显示器之间有所不同;请参阅下文。

选项 2:使用 UINT10/RGB10 像素格式和 HDR10/BT.2100 色彩空间

选项 2 是一种性能优化,仅在应用满足以下所有条件时才可用:

  • 面向 HDR 显示器
  • 使用 Direct3D 12 或 Direct3D 11
  • 交换链不需要与 alpha/transparency 混合

如果应用不符合所有这些条件,则必须使用选项 1。

但是,如果您的应用符合选项 2 条件,那么如果您的应用正在使用 HDR10 编码的内容(如视频播放器),或者它主要用于全屏场景(如游戏),则选项 2 可能会提供更好的性能。 创建交换链时,应考虑在 DXGI_SWAP_CHAIN_DESC1 中指定 DXGI_FORMAT_R10G10B10A2_UNORM。 默认情况下,这被视为使用 sRGB 色彩空间;因此,必须显式调用 IDXGISwapChain3::SetColorSpace1,并将其设置为色彩空间 DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020,也称为 HDR10/BT.2100。

该选项与传统的 UINT8 SDR 像素格式一样,每个像素消耗 32 位。 此外,在某些 GPU 上,这消除了将内容转换为 HDR10 线路格式所需的一些处理。

当显示器处于 SDR 模式时,使用高级色彩交换链

即使显示器不支持所有高级色彩功能,也可以使用高级色彩交换链。 在这些情况下,桌面窗口管理器 (DWM) 将通过执行数字剪辑来向下转换内容,以适应显示器功能。 例如,如果呈现到 FP16 scRGB 交换链并面向标准显示器,则将剪裁 [0, 1]数字范围之外的所有内容。

如果应用窗口横跨两台或多台具有不同高级色彩功能的显示器,也会发生这种向下转换行为。 AdvancedColorInfoIDXGIOutput6 被抽象化为仅报告 显示器的特征(主要 定义为包含窗口中心的显示器)。

将应用程序的参考白平衡与操作系统 SDR 参考白平衡相匹配

注意

参考白光仅适用于 HDR 显示器;对于 SDR 高级彩色显示器,(1.0, 1.0, 1.0)总是指显示器能再现的最大白色亮度。

在许多情况下,应用需要同时呈现 SDR 和 HDR 内容;例如,通过 HDR 视频或 UI 呈现字幕或传输控件,或将 UI 呈现到游戏场景中。 了解 SDR 参考白电平的概念非常重要,这样才能确保 SDR 内容在 HDR 显示器上看起来正确无误。 参考白色表示 HDR 场景中出现的漫反射白色物体(如纸张,有时也包括 UI)的亮度。 由于 HDR 色彩值具有 场景引用亮度,因此特定颜色值应以绝对亮度级别显示,而不是相对于可能的最大面板值。 例如,scRGB(1.0、1.0、1.0)和 HDR10(497、497、497)在 80 尼特亮度下完全编码 D65 白色。 Windows 允许用户根据自己的偏好调整 SDR 参考白电平;即 Windows 将呈现 sRGB(1.0、1.0、1.0、1.0)的亮度。 在态势 HDR 监视器上,SDR 参考白电平通常设置为 200 尼特左右。

HDR 应用必须允许用户设置所需的参考白电平,或读取系统配置的值。 您必须将场景中的漫射白色值映射到 SDR 参考白电平。 这就需要在线性伽玛空间中乘以应用程序帧缓冲。

注意

在支持亮度控件(如笔记本电脑)的显示器上,Windows 还会调整 HDR(场景引用)内容的亮度,以匹配用户所需的亮度级别,但这对应用不可见。 除非您试图保证 HDR 信号的位准确再现,否则通常可以忽略这一点。

如果您的应用始终将 SDR 和 HDR 呈现为单独的表面,并且依赖于 OS 组合,则 Windows 将自动执行正确的调整,以将 SDR 内容提升到所需的白电平。 例如,如果您的应用使用 XAML,并将 HDR 内容呈现为其自己的 SwapChainPanel

但是,如果您的应用自行将 SDR 和 HDR 内容组合成单个表面,则您负责自行执行 SDR 参考白电平调整。 否则,在典型的桌面查看条件下,SDR 内容可能过于暗淡。 首先,必须获取当前的 SDR 参考白电平,然后必须调整要呈现的任何 SDR 内容的色彩值。

步骤 1. 获取当前的 SDR 参考白电平

可以通过以下方法之一获取当前的 SDR 参考白电平:

步骤 2. 调整 SDR 内容的色彩值

Windows 在 80 尼特处定义名义上或默认参考白电平。 因此,如果要将标准 sRGB(1.0、1.0、1.0)白色呈现到 FP16 交换链,则会以 80 尼特亮度重现。 若要匹配实际用户定义的参考白电平,必须将 SDR 内容从 80 尼特调整为通过 AdvancedColorInfo.SdrWhiteLevelInNits 指定的级别。

如果要使用 FP16 和 scRGB 进行渲染,或者使用线性 (1.0) 伽玛的任何色彩空间,则只需将 SDR 色彩值乘以 AdvancedColorInfo.SdrWhiteLevelInNits / 80。 如果使用 Direct2D,则有一个预定义的常量 D2D1_SCENE_REFERRED_SDR_WHITE_LEVEL,其值为 80。

D2D1_VECTOR_4F inputColor; // Input SDR color value.
D2D1_VECTOR_4F outputColor; // Output color adjusted for SDR white level.
auto acInfo = ...; // Obtain an AdvancedColorInfo.

float sdrAdjust = acInfo->SdrWhiteLevelInNits / D2D1_SCENE_REFERRED_SDR_WHITE_LEVEL;

// Normally in DirectX, color values are manipulated in shaders on GPU textures.
// This example performs scaling on a CPU color value.
outputColor.r = inputColor.r * sdrAdjust; // Assumes linear gamma color values.
outputColor.g = inputColor.g * sdrAdjust;
outputColor.b = inputColor.b * sdrAdjust;
outputColor.a = inputColor.a;

如果要使用非线性伽玛颜色空间(如 HDR10)进行渲染,则执行 SDR 白电平调整更为复杂。 如果要编写自己的像素着色器,请考虑转换为线性伽马来应用调整。

使用色调映射将 HDR 内容适应显示器的功能

HDR 和高级色彩显示器的功能差异很大。 例如,在最小和最大亮度以及能够重现的色彩范围中。 在许多情况下,HDR 内容将包含超过显示功能的色彩。 为了获得最佳图像质量,执行 HDR 色调映射非常重要,实质上是压缩颜色范围以适应显示器,同时最好地保留内容的视觉意图。

要适应的最重要单一参数是最大亮度,也称为 MaxCLL(内容光级别);更复杂的是调映射器还将适应最小亮度 (MinCLL) 和/或颜色原色。

步骤 1. 获取显示器的色量功能

通用 Windows 平台 (UWP) 应用

使用 AdvancedColorInfo 获取显示器的色量。

Win32(桌面版)DirectX 应用

使用 DXGI_OUTPUT_DESC1 获取显示器的色量。

步骤 2. 获取内容的色量信息

根据 HDR 内容来自何处,有多种可能的方法来确定其亮度和色域信息。 某些 HDR 视频和图像文件包含 SMPTE ST.2086 元数据。 如果内容是动态呈现的,则可以从内部呈现阶段提取场景信息,例如场景中最亮的光源。

更常规但计算成本较高的解决方案是在呈现的帧上运行直方图或其他分析传递。 GitHub 上的 Direct2D 高级彩色图像呈现示例应用演示如何使用 Direct2D 执行此操作;最相关的代码片段如下所示:

// Perform histogram pipeline setup; this should occur as part of image resource creation.
// Histogram results in no visual output but is used to calculate HDR metadata for the image.
void D2DAdvancedColorImagesRenderer::CreateHistogramResources()
{
    auto context = m_deviceResources->GetD2DDeviceContext();

    // We need to preprocess the image data before running the histogram.
    // 1. Spatial downscale to reduce the amount of processing needed.
    DX::ThrowIfFailed(
        context->CreateEffect(CLSID_D2D1Scale, &m_histogramPrescale)
        );

    DX::ThrowIfFailed(
        m_histogramPrescale->SetValue(D2D1_SCALE_PROP_SCALE, D2D1::Vector2F(0.5f, 0.5f))
        );

    // The right place to compute HDR metadata is after color management to the
    // image's native colorspace but before any tonemapping or adjustments for the display.
    m_histogramPrescale->SetInputEffect(0, m_colorManagementEffect.Get());

    // 2. Convert scRGB data into luminance (nits).
    // 3. Normalize color values. Histogram operates on [0-1] numeric range,
    //    while FP16 can go up to 65504 (5+ million nits).
    // Both steps are performed in the same color matrix.
    ComPtr<ID2D1Effect> histogramMatrix;
    DX::ThrowIfFailed(
        context->CreateEffect(CLSID_D2D1ColorMatrix, &histogramMatrix)
        );

    histogramMatrix->SetInputEffect(0, m_histogramPrescale.Get());

    float scale = sc_histMaxNits / sc_nominalRefWhite;

    D2D1_MATRIX_5X4_F rgbtoYnorm = D2D1::Matrix5x4F(
        0.2126f / scale, 0, 0, 0,
        0.7152f / scale, 0, 0, 0,
        0.0722f / scale, 0, 0, 0,
        0              , 0, 0, 1,
        0              , 0, 0, 0);
    // 1st column: [R] output, contains normalized Y (CIEXYZ).
    // 2nd column: [G] output, unused.
    // 3rd column: [B] output, unused.
    // 4th column: [A] output, alpha passthrough.
    // We explicitly calculate Y; this deviates from the CEA 861.3 definition of MaxCLL
    // which approximates luminance with max(R, G, B).

    DX::ThrowIfFailed(histogramMatrix->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, rgbtoYnorm));

    // 4. Apply a gamma to allocate more histogram bins to lower luminance levels.
    ComPtr<ID2D1Effect> histogramGamma;
    DX::ThrowIfFailed(
        context->CreateEffect(CLSID_D2D1GammaTransfer, &histogramGamma)
        );

    histogramGamma->SetInputEffect(0, histogramMatrix.Get());

    // Gamma function offers an acceptable tradeoff between simplicity and efficient bin allocation.
    // A more sophisticated pipeline would use a more perceptually linear function than gamma.
    DX::ThrowIfFailed(histogramGamma->SetValue(D2D1_GAMMATRANSFER_PROP_RED_EXPONENT, sc_histGamma));
    // All other channels are passthrough.
    DX::ThrowIfFailed(histogramGamma->SetValue(D2D1_GAMMATRANSFER_PROP_GREEN_DISABLE, TRUE));
    DX::ThrowIfFailed(histogramGamma->SetValue(D2D1_GAMMATRANSFER_PROP_BLUE_DISABLE, TRUE));
    DX::ThrowIfFailed(histogramGamma->SetValue(D2D1_GAMMATRANSFER_PROP_ALPHA_DISABLE, TRUE));

    // 5. Finally, the histogram itself.
    HRESULT hr = context->CreateEffect(CLSID_D2D1Histogram, &m_histogramEffect);
    
    if (hr == D2DERR_INSUFFICIENT_DEVICE_CAPABILITIES)
    {
        // The GPU doesn't support compute shaders and we can't run histogram on it.
        m_isComputeSupported = false;
    }
    else
    {
        DX::ThrowIfFailed(hr);
        m_isComputeSupported = true;

        DX::ThrowIfFailed(m_histogramEffect->SetValue(D2D1_HISTOGRAM_PROP_NUM_BINS, sc_histNumBins));

        m_histogramEffect->SetInputEffect(0, histogramGamma.Get());
    }
}

// Uses a histogram to compute a modified version of MaxCLL (ST.2086 max content light level).
// Performs Begin/EndDraw on the D2D context.
void D2DAdvancedColorImagesRenderer::ComputeHdrMetadata()
{
    // Initialize with a sentinel value.
    m_maxCLL = -1.0f;

    // MaxCLL is not meaningful for SDR or WCG images.
    if ((!m_isComputeSupported) ||
        (m_imageInfo.imageKind != AdvancedColorKind::HighDynamicRange))
    {
        return;
    }

    // MaxCLL is nominally calculated for the single brightest pixel in a frame.
    // But we take a slightly more conservative definition that takes the 99.99th percentile
    // to account for extreme outliers in the image.
    float maxCLLPercent = 0.9999f;

    auto ctx = m_deviceResources->GetD2DDeviceContext();

    ctx->BeginDraw();

    ctx->DrawImage(m_histogramEffect.Get());

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = ctx->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    float *histogramData = new float[sc_histNumBins];
    DX::ThrowIfFailed(
        m_histogramEffect->GetValue(D2D1_HISTOGRAM_PROP_HISTOGRAM_OUTPUT,
            reinterpret_cast<BYTE*>(histogramData),
            sc_histNumBins * sizeof(float)
            )
        );

    unsigned int maxCLLbin = 0;
    float runningSum = 0.0f; // Cumulative sum of values in histogram is 1.0.
    for (int i = sc_histNumBins - 1; i >= 0; i--)
    {
        runningSum += histogramData[i];
        maxCLLbin = i;

        if (runningSum >= 1.0f - maxCLLPercent)
        {
            break;
        }
    }

    float binNorm = static_cast<float>(maxCLLbin) / static_cast<float>(sc_histNumBins);
    m_maxCLL = powf(binNorm, 1 / sc_histGamma) * sc_histMaxNits;

    // Some drivers have a bug where histogram will always return 0. Treat this as unknown.
    m_maxCLL = (m_maxCLL == 0.0f) ? -1.0f : m_maxCLL;
}

步骤 3. 执行 HDR 色调映射操作

色调映射本质上是一个丢失过程,可以针对许多感知或目标指标进行优化,因此没有单个标准算法。 Windows 在 Direct2D 和 Media Foundation HDR 视频播放管道中提供内置的 HDR 色调映射器效果。 其他一些常用的算法包括 ACES Filmic、Reinhard 和ITU-R BT.2390-3 EETF(电电传输功能)。

下一个代码示例中显示了简化的 Reinhard tonemapper 运算符。

// This example uses C++. A typical DirectX implementation would port this to HLSL.
D2D1_VECTOR_4F simpleReinhardTonemapper(
    float inputMax, // Content's maximum luminance in scRGB values, e.g. 1.0 = 80 nits.
    float outputMax, // Display's maximum luminance in scRGB values, e.g. 1.0 = 80 nits.
    D2D1_VECTOR_4F input // scRGB color.
)
{
    D2D1_VECTOR_4F output = input;

    // Vanilla Reinhard normalizes color values to [0, 1].
    // This modification scales to the luminance range of the display.
    output.r /= inputMax;
    output.g /= inputMax;
    output.b /= inputMax;

    output.r = output.r / (1 + output.r);
    output.g = output.g / (1 + output.g);
    output.b = output.b / (1 + output.b);

    output.r *= outputMax;
    output.g *= outputMax;
    output.b *= outputMax;

    return output;
}

捕获 HDR 和 WCG 屏幕内容

支持指定像素格式的 API(例如 Windows.Graphics.Capture 命名空间中的像素格式)和 IDXGIOutput5::D uplicateOutput1 方法中提供捕获 HDR 和 WCG 内容而不丢失像素信息的功能。 请注意,获取内容帧后,需要执行其他处理。 例如,HDR 到 SDR 色调映射(例如,Internet 共享的 SDR 屏幕截图副本)和以适当格式保存内容(例如 JPEG XR)。

对旧版色彩管理和 ICC 配置文件行为的更改

高级色彩和自动色彩管理可确保所有应用、旧式和新式应用的显示色彩一致且可对称准确显示色彩。 但是,某些应用可能会使用国际色彩联盟 (ICC) 色彩配置文件执行自己的显式色彩管理。

当高级色彩在 SDR 或 HDR 显示器上处于活动状态时,以非向后兼容方式更改显示 ICC 配置文件的行为。 如果您的应用适用于显示 ICC 配置文件,Windows 将提供兼容性帮助程序,以确保您的应用继续获得正确的行为。

有关对 ICC 配置文件行为的更改以及如何调整应用以最大化与高级色彩的兼容性的详细信息,请参阅 具有高级色彩的 ICC 配置文件行为

其他资源