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

Unreal引擎UI截图功能图像质量下降问题修复咨询

Unreal Widget截图保存后质量下降的解决办法

核心问题分析

你的代码存在几个关键问题导致截图质量损失:

  1. 渲染目标分辨率不足:创建的RenderTarget使用了视口逻辑尺寸而非实际像素尺寸(高DPI环境下视口缩放比例>1,实际屏幕像素是视口尺寸×缩放比例),相当于把高分辨率UI缩放到低分辨率渲染,再保存自然模糊。
  2. PNG压缩质量未指定:默认的PNG压缩可能采用了有损压缩或低质量参数。
  3. 坐标取整导致的像素偏移:对裁剪坐标直接取整,可能导致画面边缘模糊或错位。

具体修复方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 08:44:53