You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DX11屏幕捕获HDR画面过曝问题求解

问题分析与解决方案

问题根源

你遇到的过曝和全0数据问题,核心原因在于HDR桌面纹理的格式特性:

  1. HDR纹理格式不兼容8位UNORM:HDR显示器的桌面纹理通常采用R10G10B10A2_UNORM或R16G16B16A16_FLOAT这类高动态范围格式,直接复制到8位B8G8R8A8_UNORM时,超出SDR亮度范围(>1.0)的像素会被直接截断为255,导致画面过曝。
  2. CopyResource不支持跨格式转换:FormatB8G8R8A8UnormSrgb与HDR格式不属于Direct3D定义的兼容格式对,CopyResource无法完成格式转换,最终导致staging纹理无有效数据,映射后得到全0字节。

可行解决方案

方案1:使用Direct3D着色器硬件色调映射

通过Pixel Shader实现HDR到SDR的色调映射,利用GPU加速完成颜色转换:

// 获取桌面纹理实际格式
Texture2DDesc desktopTextureDesc;
desktopTexture->GetDesc(&desktopTextureDesc);
bool isHdr = desktopTextureDesc.Format == Format.FormatR10G10B10A2Unorm || 
             desktopTextureDesc.Format == Format.FormatR16G16B16A16Float;

// 创建SDR渲染目标纹理
Texture2DDesc renderTargetDesc = new()
{
    Format = Format.FormatB8G8R8A8UnormSrgb,
    Width = desktopTextureDesc.Width,
    Height = desktopTextureDesc.Height,
    MipLevels = 1,
    ArraySize = 1,
    SampleDesc = { Count = 1, Quality = 0 },
    Usage = Usage.Default,
    BindFlags = (uint)BindFlag.RenderTarget,
    CPUAccessFlags = 0
};
ID3D11Texture2D renderTargetTexture;
device->CreateTexture2D(&renderTargetDesc, null, &renderTargetTexture);

// 创建渲染目标视图
ID3D11RenderTargetView renderTargetView;
device->CreateRenderTargetView(renderTargetTexture, null, &renderTargetView);

// 编译并设置色调映射Pixel Shader(HLSL示例)
// float4 PS(float4 pos:SV_POSITION, float4 color:COLOR):SV_TARGET {
//     // Reinhard色调映射
//     float3 mapped = color.rgb / (color.rgb + float3(1.0,1.0,1.0));
//     // Gamma校正(SRGB标准2.2)
//     mapped = pow(mapped, float3(1.0/2.2));
//     return float4(mapped, color.a);
// }
ID3D11PixelShader pixelShader;
device->CreatePixelShader(shaderBytecode, shaderBytecodeLength, null, &pixelShader);
immediateContext->PSSetShader(pixelShader, null, 0);

// 绘制全屏四边形将HDR纹理渲染到SDR目标
immediateContext->OMSetRenderTargets(1, &renderTargetView, null);
// 此处需补充全屏Quad的顶点绘制逻辑(省略基础顶点缓冲区/输入布局代码)
DrawFullScreenQuad(immediateContext, desktopTexture);

// 将渲染目标复制到可读取的staging纹理
Texture2DDesc stagingDesc = new()
{
    Format = Format.FormatB8G8R8A8UnormSrgb,
    Width = desktopTextureDesc.Width,
    Height = desktopTextureDesc.Height,
    Usage = Usage.Staging,
    CPUAccessFlags = (uint)CpuAccessFlag.Read
};
ID3D11Texture2D stagingTexture;
device->CreateTexture2D(&stagingDesc, null, &stagingTexture);
immediateContext->CopyResource(stagingTexture, renderTargetTexture);

// 映射读取SDR数据
MappedSubresource mappedSubresource;
immediateContext->Map(stagingTexture, 0, Map.Read, 0, &mappedSubresource);
var span = new ReadOnlySpan<byte>(mappedSubresource.PData, (int)mappedSubresource.DepthPitch);

方案2:使用Direct2D自动颜色转换

Direct2D内置颜色空间转换逻辑,无需手动编写着色器即可完成HDR到SDR的转换:

// 初始化Direct2D设备与上下文
ID2D1Factory1 d2dFactory;
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory1), null, &d2dFactory);

ID2D1Device d2dDevice;
d2dFactory->CreateDevice((IUnknown*)device, &d2dDevice);

ID2D1DeviceContext d2dContext;
d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2dContext);

// 将桌面纹理转换为Direct2D Bitmap
IDXGISurface dxgiSurface;
desktopTexture->QueryInterface<IDXGISurface>(&dxgiSurface);

ID2D1Bitmap1 d2dHdrBitmap;
d2dContext->CreateBitmapFromDxgiSurface(dxgiSurface, null, &d2dHdrBitmap);

// 创建SDR格式的可读取Bitmap
D2D1_BITMAP_PROPERTIES1 bitmapProps = new()
{
    pixelFormat = { format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED },
    bitmapOptions = D2D1_BITMAP_OPTIONS_CPU_READ
};

ID2D1Bitmap1 d2dSdrBitmap;
d2dContext->CreateBitmap(
    D2D1_SIZE_U{ desktopTextureDesc.Width, desktopTextureDesc.Height },
    null, 0, &bitmapProps, &d2dSdrBitmap
);

// 绘制并自动完成颜色转换
d2dContext->SetTarget(d2dSdrBitmap);
d2dContext->BeginDraw();
d2dContext->DrawBitmap(d2dHdrBitmap, D2D1_RECT_F{0,0,desktopTextureDesc.Width,desktopTextureDesc.Height});
d2dContext->EndDraw();

// 读取SDR数据
D2D1_MAPPED_RECT mappedRect;
d2dSdrBitmap->Map(D2D1_MAP_OPTIONS_READ, &mappedRect);
var span = new ReadOnlySpan<byte>(mappedRect.bits, (int)(mappedRect.pitch * desktopTextureDesc.Height));

方案3:软件层面手动色调映射

若不想依赖GPU加速,可读取原始HDR数据后手动转换:

// 创建与桌面纹理格式匹配的staging纹理
Texture2DDesc stagingDesc = new()
{
    Format = desktopTextureDesc.Format,
    Width = desktopTextureDesc.Width,
    Height = desktopTextureDesc.Height,
    Usage = Usage.Staging,
    CPUAccessFlags = (uint)CpuAccessFlag.Read
};
ID3D11Texture2D stagingTexture;
device->CreateTexture2D(&stagingDesc, null, &stagingTexture);
immediateContext->CopyResource(stagingTexture, desktopTexture);

// 映射读取HDR原始数据
MappedSubresource mappedSubresource;
immediateContext->Map(stagingTexture, 0, Map.Read, 0, &mappedSubresource);
ReadOnlySpan<uint> hdrPixels = new ReadOnlySpan<uint>(mappedSubresource.PData, (int)(mappedSubresource.DepthPitch / sizeof(uint)));
byte[] sdrData = new byte[desktopTextureDesc.Width * desktopTextureDesc.Height * 4];

// 处理R10G10B10A2_UNORM格式像素
for (int i = 0; i < hdrPixels.Length; i++)
{
    uint pixel = hdrPixels[i];
    // 提取10位颜色分量并归一化到0-1范围
    float r = ((pixel >> 22) & 0x3FF) / 1023.0f;
    float g = ((pixel >> 12) & 0x3FF) / 1023.0f;
    float b = ((pixel >> 2) & 0x3FF) / 1023.0f;

    // Reinhard色调映射+Gamma校正
    float mappedR = r / (r + 1.0f);
    float mappedG = g / (g + 1.0f);
    float mappedB = b / (b + 1.0f);
    mappedR = (float)Math.Pow(mappedR, 1.0/2.2);
    mappedG = (float)Math.Pow(mappedG, 1.0/2.2);
    mappedB = (float)Math.Pow(mappedB, 1.0/2.2);

    // 转换为8位SRGB字节(BGR顺序)
    sdrData[i*4] = (byte)(mappedB * 255);
    sdrData[i*4+1] = (byte)(mappedG * 255);
    sdrData[i*4+2] = (byte)(mappedR * 255);
    sdrData[i*4+3] = 255;
}

内容的提问来源于stack exchange,提问作者MakesYT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:12:09