You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让Direct2D将D2D1_COLOR_F视为sRGB色彩空间?

解决Direct2D将D2D1_COLOR_F视为sRGB色彩空间的问题

我之前帮朋友处理过几乎一模一样的问题——在广色域显示器上用Direct2D做非游戏窗口,默认画出来的红色饱和度拉满,和Chrome显示的柔和sRGB红色差异明显。结合你的场景(HWND渲染目标/设备上下文、交换链用DXGI_FORMAT_B8G8R8A8_UNORM),给你几个可行的方案:

核心思路

Direct2D默认会把D2D1_COLOR_F的数值直接映射到设备原生色彩空间(也就是广色域显示器的最大色域),这就是为什么你的#ff0000会显得特别浓烈。我们需要让D2D把输入的颜色值识别为sRGB,再自动转换到设备色彩空间,实现类似Chrome的效果。

方案一:用DeviceContext配合sRGB颜色上下文(推荐)

HWND渲染目标对像素格式和色彩空间的支持比较受限,这也是你尝试DXGI_FORMAT_B8G8R8A8_UNORM_SRGB报错的原因。改用ID2D1DeviceContext可以更灵活地控制色彩管理:

  1. 创建sRGB颜色上下文
    先告诉D2D我们要使用的是sRGB色彩空间:

    ID2D1ColorContext* sRgbColorContext = nullptr;
    HRESULT hr = d2dFactory->CreateColorContext(
        D2D1_COLOR_SPACE_SRGB,  // 指定sRGB色彩空间
        nullptr,
        0,
        &sRgbColorContext
    );
    
  2. 创建支持sRGB的交换链与目标Bitmap
    创建交换链时指定sRGB格式的像素格式,再从交换链后台缓冲区创建关联sRGB上下文的Bitmap:

    // 假设你已经创建了IDXGISwapChain* swapChain
    IDXGISurface* dxgiBackBuffer = nullptr;
    hr = swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer));
    
    ID2D1Bitmap1* targetBitmap = nullptr;
    hr = d2dDeviceContext->CreateBitmapFromDxgiSurface(
        dxgiBackBuffer,
        D2D1_BITMAP_PROPERTIES1{
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1_PIXEL_FORMAT{DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, D2D1_ALPHA_MODE_IGNORE},
            0.0f, 0.0f,  // DPI,可根据窗口设置调整
            sRgbColorContext  // 关联sRGB色彩上下文
        },
        &targetBitmap
    );
    
  3. 启用色彩管理并设置绘制目标
    确保D2D开启色彩管理,然后把创建好的Bitmap设为绘制目标:

    d2dDeviceContext->SetTarget(targetBitmap);
    d2dDeviceContext->SetColorManagementMode(D2D1_COLOR_MANAGEMENT_MODE_ENABLED);
    

    之后你调用FillRectangle()DrawTextLayout()传入的D2D1_COLOR_F就会被当作sRGB处理,自动转换到设备色彩空间。

方案二:给HWND渲染目标关联sRGB上下文(兼容旧场景)

如果你坚持要用ID2D1HwndRenderTarget,可以尝试给它绑定sRGB颜色上下文,但注意这个方法在旧版本Windows上可能存在兼容性问题:

// 先创建sRGB颜色上下文(同方案一)
ID2D1ColorContext* sRgbColorContext = nullptr;
HRESULT hr = d2dFactory->CreateColorContext(D2D1_COLOR_SPACE_SRGB, nullptr, 0, &sRgbColorContext);

// 配置渲染目标属性时关联sRGB上下文
D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties(
    D2D1_RENDER_TARGET_TYPE_DEFAULT,
    D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
    dpiX, dpiY,
    D2D1_RENDER_TARGET_USAGE_NONE,
    D2D1_FEATURE_LEVEL_DEFAULT
);
renderTargetProps.colorContext = sRgbColorContext;

// 创建HWND渲染目标
ID2D1HwndRenderTarget* hwndRenderTarget = nullptr;
hr = d2dFactory->CreateHwndRenderTarget(
    renderTargetProps,
    D2D1::HwndRenderTargetProperties(hwnd, size),
    &hwndRenderTarget
);

创建完成后,同样要确保色彩管理模式是启用的:

hwndRenderTarget->SetColorManagementMode(D2D1_COLOR_MANAGEMENT_MODE_ENABLED);

关键注意点

  • 为什么DXGI_FORMAT_B8G8R8A8_UNORM_SRGB之前不兼容?因为ID2D1HwndRenderTarget是系统封装的渲染目标,它依赖于桌面窗口的显示格式,通常不支持sRGB格式的像素缓冲区,而ID2D1DeviceContext配合独立交换链则没有这个限制。
  • 色彩管理必须启用:如果SetColorManagementMode设为D2D1_COLOR_MANAGEMENT_MODE_DISABLED,D2D还是会直接把颜色值当作设备空间处理,达不到预期效果。

内容的提问来源于stack exchange,提问作者Sea Coast of Tibet

火山引擎 最新活动