DX11屏幕捕获HDR画面过曝问题求解
问题分析与解决方案
问题根源
你遇到的过曝和全0数据问题,核心原因在于HDR桌面纹理的格式特性:
- HDR纹理格式不兼容8位UNORM:HDR显示器的桌面纹理通常采用
R10G10B10A2_UNORM或R16G16B16A16_FLOAT这类高动态范围格式,直接复制到8位B8G8R8A8_UNORM时,超出SDR亮度范围(>1.0)的像素会被直接截断为255,导致画面过曝。 - 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
相关产品推荐
相关产品推荐

