Unreal Engine:accessing/saving 游戏内图像到磁盘而不阻塞游戏线程

Unreal Engine: accessing/saving in-game images to disk without blocking game thread

我正在研究基于 Unreal 的开源无人机模拟 (Microsoft AirSim),我试图从连接到无人机的相机捕获和保存图像。下面的图片给出了游戏的外观。底部最右边的视图是相机的实际视图,另外两个只是同一图像的处理版本。

现在它的设置方式是这样的:有一个相机资产,通过代码读取它作为捕获组件。屏幕截图中的三个视图链接到此捕获组件。当无人机在游戏中飞来飞去时,这些视图毫无问题地流式传输。但是当涉及到录制屏幕截图时,当前代码从该捕获组件设置一个 TextureRenderTargetResource,随后调用 ReadPixels 并将该数据保存为图像(请参见下面的代码流程)。按原样使用 ReadPixels() 会直接阻塞游戏线程并大大降低整个游戏速度:当我开始录制时,从 ~120 FPS 下降到不到 10 FPS。

bool saveImage() {
  USceneCaptureComponent2D* capture = getCaptureComponent(camera_type, true);
  FTextureRenderTargetResource* RenderResource = capture->TextureTarget->GameThread_GetRenderTargetResource();
  width = capture->TextureTarget->GetSurfaceWidth();
  height = capture->TextureTarget->GetSurfaceHeight();

  TArray<FColor> imageColor;
  imageColor.AddUninitialized(width * height);
  RenderResource->ReadPixels(bmp);
}

查看 this article,ReadPixels() "will block the game thread until the rendering thread has caught up" 似乎很明显。该文章包含 'non-blocking' 读取像素方法的示例代码(通过删除 FlushRenderingCommands() 并使用 RenderCommandFence 标志来确定任务何时完成),但它并没有显着提高性能:速率为保存的图像略高,但游戏线程仍然以大约 20 FPS 的速度运行,因此很难控制无人机。是否有任何更有效的异步方法可以实现我正在尝试做的事情,比如说,在一个单独的线程中?我也有点困惑为什么代码可以尽可能快地在屏幕上传输这些图像,但保存图像似乎要复杂得多。即使图像仅以 15 Hz 左右的频率保存到磁盘也可以,只要它不会过多地干扰游戏的原生 FPS。

也许您应该将屏幕截图存储在 TArray 中直到 "game" 完成,然后处理所有存储的数据?

或者看看从无人机相机截屏而不是处理像素数据?我假设左侧和中间的无人机摄像头图像是基础摄像头图像的 material 修改版本。

保存图片时,是写入图片格式文件还是.uasset?

最后 - 这是一个完整的暗中拍摄 - 创建一个自定义 UActorComponent,将其添加到无人机,并让它观察游戏中的一些指令。当你想写入图像时,将 RenderTarget 数据推送到自定义 UActorComponent。由于 actor 组件可以在任何线程上进行 tick(在构造函数中使用 BCanTickOnAnyThread=true,我认为这是变量名称),actor 组件的 tick 可以监视传入的图像数据并在那里进行处理。我从来没有这样做过,但我已经在任何线程上制作了一个 actor 组件(我的正在检查物理)。

(...) it seems evident that ReadPixels() "will block the game thread until the rendering thread has caught up"

使用尝试访问和复制 GPU 纹理数据的函数总是很痛苦。我发现这些功能的完成时间取决于硬件或驱动程序版本。您可以尝试 运行 在不同的(最好更快或更新的)GPU 平台上运行您的代码。

回到代码:为了绕过单线程问题,我会尝试使用 TextureRenderTarget2D::ConstructTexture2D 方法将 UTextureRenderTarget2D 复制到 UTexture2D。当您复制图像时(我希望它能更快),您可以以 15 FPS 的比率将其推送到消费者线程。可以使用 FRunnable 或异步引擎模块(使用所谓的任务图)创建线程。消费者线程可以使用纹理的 PlatformData->Mips[0]->BulkData 访问克隆的纹理。确保您在 BulkData->Lock(...)BulkData->Unlock() 调用之间读取纹理数据。

我不确定,但这可能会有所帮助,因为您复制的纹理不会被渲染线程使用,因此您可以锁定它的数据而不会阻塞渲染线程。我只担心锁定和解锁操作的线程安全(没有找到任何线索),除非有引擎 RHI 调用,否则应该是可能的。

Here 是一些可能有用的相关代码。

您可以将保存到磁盘的操作从游戏线程移动到其他线程。
详情请查看Async.h

线程的限制是您不能在其他线程中modify/add/delete uobject/uactor。 Multi-thread for UE4

我参加派对已经很晚了,尽管以防万一有人对此仍有疑问:我构建了一个非阻塞版本,灵感来自 AirSim 和 UnrealCV 的插件代码,因为我有一些严重的编译和版本控制问题那些..但在我自己拍摄图像时需要一个流畅的 运行 相机。这也是基于 async.h class of unreal API 提供的建议接受的答案。 我为此设置了一个教程回购: https://github.com/TimmHess/UnrealImageCapture.

希望对您有所帮助。