【问题标题】:Unreal Engine: accessing/saving in-game images to disk without blocking game thread虚幻引擎:在不阻塞游戏线程的情况下访问/保存游戏内图像到磁盘
【发布时间】:2017-08-30 07:54:26
【问题描述】:

我正在开发基于 Unreal 的开源 UAV 模拟 (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()“会阻塞游戏线程,直到渲染线程赶上”。该文章包含读取像素的“非阻塞”方法的示例代码(通过删除 FlushRenderingCommands() 并使用 RenderCommandFence 标志来确定任务何时完成),但它并没有显着提高性能:速率为保存的图像略高,但游戏线程仍然以大约 20 FPS 的速度运行,因此很难控制无人机。有没有更有效的异步方法可以实现我想要做的事情,也许,比如说,在一个单独的线程中?我也有点困惑,为什么代码可以尽可能快地在屏幕上流式传输这些图像,但保存图像似乎要复杂得多。即使图像仅以 15 Hz 左右的频率保存到磁盘也可以,只要它不会过多地干扰游戏的原生 FPS。

【问题讨论】:

    标签: unreal-engine4


    【解决方案1】:

    我参加聚会已经很晚了,但万一有人仍然对此有疑问:我构建了一个非阻塞版本,灵感来自 AirSim 和 UnrealCV 插件的代码,因为我遇到了一些严重的编译和版本控制问题。 . 但在自己拍摄图像时需要一台运行平稳的相机。这也是基于 async.h 类的虚幻 API 提供的,如已接受的答案所建议的那样。 我为此设置了一个教程仓库: https://github.com/TimmHess/UnrealImageCapture.

    希望能有所帮助。

    【讨论】:

      【解决方案2】:

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

      线程的限制是你不能在其他线程中修改/添加/删除uobject/uactor。 Multi-thread for UE4

      【讨论】:

        【解决方案3】:

        (...) 显然 ReadPixels()“会阻塞游戏线程,直到渲染线程赶上”

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

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

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

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

        【讨论】:

        • 感谢您的详细评论:不幸的是,此方法还将我的游戏内 FPS 降至 10 左右,而这仅适用于 ConstructTexture2D() 函数。是否有可能在一个单独的线程中完成整个操作? ConstructTexture2D() 函数本身在内部有一些调用来检查它是否是从游戏线程调用的..
        【解决方案4】:

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

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

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

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

        【讨论】:

        • 您能否进一步解释一下您所说的“看从无人机相机截屏而不是操纵像素数据”的意思?因为我知道可以提取捕获的图像数据的唯一方法是通过 ReadPixels() 命令。现在,它正在编写压缩的 PNG 文件。如果我要将它们存储在 TArray 中,我猜你建议我在单独的线程中执行它?
        • 我刚刚在我的帖子中添加了另一个想法 :) 您可以调用控制台命令从播放器相机截取屏幕截图。也许您可以找到该命令的来源,然后了解如何为其提供不同的源相机。 screenshot 命令保存一个文件,我认为是 targa。抱歉,渲染不是我的专业领域。这里的问题是您正在录制视频,而不仅仅是拍摄单个图像。也许考虑添加第三方媒体库?
        • 我也觉得在游戏结束后保存图片不会导致fps下降,如果你想拥有这样的东西,顺便试试虚幻docs.unrealengine.com/latest/INT/Engine/Basics/Screenshots的捕捉功能,它也节省了图片本身,并且会比你已经在做的更快。
        猜你喜欢
        • 2020-09-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-08
        • 1970-01-01
        • 2015-03-15
        • 1970-01-01
        • 2018-10-16
        相关资源
        最近更新 更多