Unreal引擎UI截图功能图像质量下降问题修复咨询
Unreal Widget截图保存后质量下降的解决办法
核心问题分析
你的代码存在几个关键问题导致截图质量损失:
- 渲染目标分辨率不足:创建的RenderTarget使用了视口逻辑尺寸而非实际像素尺寸(高DPI环境下视口缩放比例>1,实际屏幕像素是视口尺寸×缩放比例),相当于把高分辨率UI缩放到低分辨率渲染,再保存自然模糊。
- PNG压缩质量未指定:默认的PNG压缩可能采用了有损压缩或低质量参数。
- 坐标取整导致的像素偏移:对裁剪坐标直接取整,可能导致画面边缘模糊或错位。
具体修复方案
1. 提升渲染目标分辨率并优化设置
创建RenderTarget时使用实际屏幕像素尺寸(视口尺寸×视口缩放比例),同时开启抗锯齿,避免渲染过程中的模糊:
// 计算实际屏幕像素尺寸 FVector2D viewportSize; if (!GEngine || !GEngine->GameViewport) return false; GEngine->GameViewport->GetViewportSize(viewportSize); float viewportScale = UWidgetLayoutLibrary::GetViewportScale(targetImage); FIntPoint actualScreenSize = FIntPoint(FMath::RoundToInt(viewportSize.X * viewportScale), FMath::RoundToInt(viewportSize.Y * viewportScale)); // 创建高分辨率RenderTarget并开启抗锯齿 UTextureRenderTarget2D* renderTarget = NewObject<UTextureRenderTarget2D>(); renderTarget->InitCustomFormat(actualScreenSize.X, actualScreenSize.Y, PF_B8G8R8A8, false); renderTarget->RenderTargetFlags.Enable(RTF_GPUSharedFlag); renderTarget->SetAntiAliasQuality(EAntiAliasQuality::High); // 开启高抗锯齿
2. 使用无损PNG压缩
调用FImageUtils::CompressImageArray的重载函数,指定最高压缩质量(100为无损):
TArray<uint8> pngData; // 第三个参数为压缩质量,范围0-100,100是无损 FImageUtils::CompressImageArray(width, height, croppedBitmap, pngData, 100);
3. 修正裁剪坐标计算
不再对坐标做除法缩放,直接使用基于实际屏幕像素的坐标:
FGeometry geometry = targetImage->GetCachedGeometry(); FVector2D localSize = geometry.GetLocalSize(); // 获取实际屏幕像素位置和尺寸,无需除以viewportScale FVector2D pixelPos = geometry.GetAbsolutePosition() * viewportScale; FVector2D pixelSize = localSize * geometry.Scale * viewportScale; int32 startX = FMath::RoundToInt(pixelPos.X); int32 startY = FMath::RoundToInt(pixelPos.Y); int32 width = FMath::RoundToInt(pixelSize.X); int32 height = FMath::RoundToInt(pixelSize.Y);
4. 确保Widget渲染状态最新
在绘制前更新Widget的状态,避免渲染过时内容:
FWidgetRenderer widgetRenderer(true); // 先Tick Widget确保状态更新 rootWidget->TickWidget(0.f); widgetRenderer.DrawWidget(renderTarget, rootWidget->TakeWidget(), actualScreenSize, 0.f);
修改后的完整代码
bool UTestFunction::CaptureMinimapToPNG(UImage* targetImage, const FString& savePath) { if (!targetImage) return false; UUserWidget* rootWidget = targetImage->GetTypedOuter<UUserWidget>(); if (!rootWidget) return false; float viewportScale = UWidgetLayoutLibrary::GetViewportScale(targetImage); FGeometry geometry = targetImage->GetCachedGeometry(); FVector2D localSize = geometry.GetLocalSize(); // 计算实际屏幕像素的位置和尺寸 FVector2D pixelPos = geometry.GetAbsolutePosition() * viewportScale; FVector2D pixelSize = localSize * geometry.Scale * viewportScale; int32 startX = FMath::RoundToInt(pixelPos.X); int32 startY = FMath::RoundToInt(pixelPos.Y); int32 width = FMath::RoundToInt(pixelSize.X); int32 height = FMath::RoundToInt(pixelSize.Y); FVector2D viewportSize; if (!GEngine || !GEngine->GameViewport) return false; GEngine->GameViewport->GetViewportSize(viewportSize); FIntPoint actualScreenSize = FIntPoint(FMath::RoundToInt(viewportSize.X * viewportScale), FMath::RoundToInt(viewportSize.Y * viewportScale)); // 创建高分辨率、带抗锯齿的RenderTarget UTextureRenderTarget2D* renderTarget = NewObject<UTextureRenderTarget2D>(); renderTarget->InitCustomFormat(actualScreenSize.X, actualScreenSize.Y, PF_B8G8R8A8, false); renderTarget->RenderTargetFlags.Enable(RTF_GPUSharedFlag); renderTarget->SetAntiAliasQuality(EAntiAliasQuality::High); FWidgetRenderer widgetRenderer(true); // 更新Widget状态后再绘制 rootWidget->TickWidget(0.f); widgetRenderer.DrawWidget(renderTarget, rootWidget->TakeWidget(), actualScreenSize, 0.f); FTextureRenderTargetResource* rtResource = renderTarget->GameThread_GetRenderTargetResource(); TArray<FColor> fullBitmap; rtResource->ReadPixels(fullBitmap); TArray<FColor> croppedBitmap; croppedBitmap.SetNum(width * height); for (int32 y = 0; y < height; ++y) { int32 srcY = startY + y; if (srcY < 0 || srcY >= actualScreenSize.Y) continue; for (int32 x = 0; x < width; ++x) { int32 srcX = startX + x; if (srcX < 0 || srcX >= actualScreenSize.X) continue; int32 srcIndex = srcY * actualScreenSize.X + srcX; int32 dstIndex = y * width + x; croppedBitmap[dstIndex] = fullBitmap[srcIndex]; } } TArray<uint8> pngData; // 使用无损PNG压缩 FImageUtils::CompressImageArray(width, height, croppedBitmap, pngData, 100); FString finalPath = FPaths::ConvertRelativePathToFull(savePath); return FFileHelper::SaveArrayToFile(pngData, *finalPath); }
额外优化建议
- 如果只需要截图目标Image区域,可以直接渲染该Image的Widget而非整个RootWidget,进一步提升效率和质量:将
rootWidget->TakeWidget()替换为targetImage->TakeWidget(),同时RenderTarget尺寸设为pixelSize对应的整数尺寸。 - 测试时关闭编辑器的"高DPI缩放"选项(编辑->编辑器偏好设置->用户界面->高DPI),确保坐标计算一致。
内容的提问来源于stack exchange,提问作者mindol
相关产品推荐
相关产品推荐

