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

本主题介绍如何将 DirectX 与高级颜色方案配合使用,包括高动态范围 (HDR) 、具有自动系统颜色管理的宽色域 (WCG) 以及高位深度。 具有上述至少一项增强功能的高级个人计算机 (电脑) 显示器正在普及,其颜色保真度明显高于传统标准动态范围 (SDR) 显示器。

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

Windows 中的高级颜色简介

高级颜色 是操作系统 (操作系统) 技术,其颜色保真度明显高于标准显示器。 以下各节介绍了主要的扩展功能。 首次为具有 Windows 10 版本 1709 (Fall Creators Update) 的 HDR 显示器引入高级颜色功能,以及针对具有 Windows 11 的特别预配的 SDR 显示器,版本 22H2 (10.0;版本 22621) 版本。

高动态范围

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

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

自 Direct3D 9 以来,图形引擎一直能够以这种物理上准确的保真度在内部渲染其场景。 但是,典型的标准动态范围显示器只能重现略高于 3 个数量级的亮度,因此任何 HDR 渲染的内容都必须进行色调映射 (压缩) 到有限的显示器范围内。 新的 HDR 显示器,包括符合 HDR10 (BT.2100) 标准的显示器,将突破此限制;例如,高质量的自发光显示器可以实现大于 6 个数量级。

宽色域

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

人类光谱位点和 sRGB 范围图

高端专业电脑显示器长期以来支持的色域比 sRGB 宽得多,例如 Adobe RGB 和 DCI-P3,它们覆盖了大约一半的人类可感知颜色。 这些宽域显示器正变得越来越普遍。

自动系统颜色管理

颜色管理是确保跨设备准确且一致的色彩再现的技术和做法。 如果你是数字内容创建者,则视觉内容(如照片、产品图像或徽标)中的颜色在显示器上与在受众的各种数字设备上显示的颜色相同至关重要。

自 Windows 2000 以来,Windows 一直通过图像颜色管理 (ICM) 和更高版本的 Windows 颜色系统 (WCS) API 提供颜色管理 支持 API。 但是,这些 API 只是希望/需要执行颜色管理的应用的帮助程序;而大多数应用和数字内容只是假定行业标准 sRGB 颜色空间,并且从未由 OS 管理颜色。 这在过去是一个合理的假设,但高质量的宽域显示器正变得越来越普遍。

新版本的 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 颜色的有效标准,但它不太适用于许多渲染和合成 (混合) 操作。 我们还希望将来证明操作系统能够支持远远超出 BT.2100 的技术和颜色空间,该空间涵盖不到 2/3 的人类可见颜色。 最后,在可能的情况下,我们希望尽量减少 GPU 资源消耗,以提高电源和性能。

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

  • 具有线性伽玛) 的 scRGB 颜色空间 (BT.709/sRGB 主色
  • IEEE 半精度 (FP16 位深度)

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

自动系统颜色管理

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

处于 HDR 模式时,Windows 分两个阶段执行颜色管理操作:

  1. DWM 在混合之前将每个应用从其本机颜色空间转换为 CCCS。
  2. 显示内核将 CCCS 中的 OS 帧缓冲区转换为 BT.2100 ST.2084) (线格式颜色空间。

DWM 中发生的自动颜色管理的框图和DWM 和显示内核中发生的自动颜色管理的显示内核框图,第 2 部分

注意

在这两个阶段中,颜色管理操作包括颜色空间转换 (矩阵和 1DLUT) 。 超过显示器目标色域的颜色将按数字剪裁。

Windows 11版本 22H2:具有高级颜色的 SDR 显示器

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

SDR AC 显示堆栈的框图:FP16、scRGB,具有自动颜色管理

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

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

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

DWM 高级颜色功能(包括 CCCS 中的混合)与 HDR 显示器几乎完全保持不变。 main区别在于,DWM 将显示器引用的亮度与 SDR 显示器一起使用,将场景引用的亮度用于 HDR 显示器。 这会更改操作系统解释高级颜色呈现内容的方式:

显示类型 亮度行为 如何解释 1.0f
SDR 显示引用 作为显示器的参考白级别
高动态范围图像 场景引用 作为 80 尼特 (标称参考白色)

自动系统颜色管理

OS 系统颜色管理功能也大多与 HDR 显示器不一样。 main区别在于,显示内核将转换为由显示器的色法和校准数据定义的显示器参考颜色空间,而不是 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 系列 (北极星) 或更高版本
  • NVIDIA GeForce 10 系列 (Pascal) 或更高版本
  • 所选 Intel Core 第 10 代 (Ice Lake) 或更高版本*

注意

Intel 代号 Comet Lake (5 位型号代码) 芯片集不提供完整功能。

根据方案,可能会有其他硬件要求,包括硬件编解码器加速 (10 位 HEVC、10 位 VP9 等,) 和 PlayReady 支持 (SL3000) 。 有关更具体的信息,请与 GPU 供应商联系。

图形驱动程序 (WDDM)

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

支持的呈现 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 显示器

若要检查支持但不一定处于活动状态的高级颜色类型,请调用 AdvancedColorInfo::IsAdvancedColorKindAvailable。 例如,你可以使用该信息提示用户导航到 Windows 设置 应用,以便他们可以启用 HDR 或自动颜色管理。

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

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

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

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 为 false,交换链就会返回过时的 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|合成|CoreWindow] 方法,必须通过选择 “DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL ”或“ DXGI_SWAP_EFFECT_FLIP_DISCARD ”选项来使用 DXGI 翻转模型,这使交换链有资格从 DWM 进行高级颜色处理和各种全屏优化。 有关详细信息,请参阅 为获得最佳性能,请使用 DXGI 翻转模型

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

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

建议常规用途应用使用选项 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 尼特亮度的颜色。 例如,scRGB (1.0、1.0、1.0) 将标准 D65 白色编码为 80 尼特;但 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 编码的内容(如视频播放器),或者它主要用于全屏场景(如游戏),则这可能会提供更好的性能。 创建交换链时,应考虑在 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 被抽象化,仅报告main显示器的特征, (main定义为包含窗口中心) 的显示器。

将应用的引用白色与 OS SDR 引用白级别匹配

注意

参考白色仅适用于 HDR 显示器;对于 SDR 高级颜色显示器, (1.0、1.0、1.0) 始终表示显示器可以重现的最大白色亮度。

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

HDR 应用必须允许用户设置其所需的引用白级别,或读取系统配置的值。 必须将场景中的漫射白色值映射到 SDR 引用白色级别。 这需要在线性伽玛空间中将应用 framebuffer 相乘。

注意

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

如果你的应用始终将 SDR 和 HDR 呈现到单独的图面,并且依赖于操作系统组合,则 Windows 将自动执行正确的调整,以将 SDR 内容提升到所需的白色级别。 例如,如果你的应用使用 XAML,并且将 HDR 内容呈现为其自己的 SwapChainPanel

但是,如果你的应用将自己的 SDR 和 HDR 内容组合到单个图面中,则你负责自行执行 SDR 参考白级别调整。 否则,在典型的桌面查看条件下,SDR 内容可能显得太暗。 首先,必须获取当前的 SDR 引用白级别,然后必须调整要呈现的任何 SDR 内容的颜色值。

步骤 1。 获取当前 SDR 参考白级别

可以通过以下方式之一获取当前 SDR 引用白级别:

步骤 2。 调整 SDR 内容的颜色值

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

如果使用 FP16 和 scRGB 进行渲染,或者使用线性 (1.0) gamma 的任何颜色空间,则只需将 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 (内容光级) ;更复杂的色调映射器还将调整 min 亮度 (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 以及媒体基础 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 命名空间中的 API 和 IDXGIOutput5::D uplicateOutput1 方法)提供在不丢失像素信息的情况下捕获 HDR 和 WCG 内容的功能。 请注意,获取内容帧后,需要进行其他处理。 例如,HDR 到 SDR 的色调映射 (例如,用于 Internet 共享) 和内容保存的 SDR 屏幕截图副本 (例如 JPEG XR) 。

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

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

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

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

其他资源