お久しぶりです。
ブログ再開しましたので、改めてよろしくお願いします!
はじめに
突然ですが、UE4には「標準スクリーンショット」と「高解像度スクリーンショット」の
2種類のスクリーンショット機能が用意されています。
docs.unrealengine.com
使い方は↑を見て頂くとして…使ってみると分かるのですが
スクリーンショットを撮った瞬間にカクついてしまいます。
一瞬とは言えカクつくのは見栄え的によくないですし、
VRコンテンツの場合はそのカクつきが酔いに直結してしまいます…
ということで、エンジン改造して高速化しました
…色々と保証できない + テストを殆どしていないので
ご利用は計画的に!
あ、エンジンはUE4.14.0です。
あ、あと、解説とかいらねえよという方は「まとめ」を見て下さい。
高速化の方針
今回はVR向けに「カクつきが起きない」ことを最優先とします。
ということで、高解像度スクリーンショットは対応しません。
(そもそも複数回レンダリングする手法なので…負荷大き過ぎます…)
まずは、標準スクリーンショットである"shot"コマンドが
どういう流れで処理が走って、どの処理が重いのかを見ます。
- "shot"でエンジンコードを検索し、GameViewportClient.cppで文字列解析してることを確認
- 走っている処理を追い、bIsScreenshotRequestedというフラグをtrueにしている部分を発見
- bIsScreenshotRequestedを返すUnrealClientのIsScreenshotRequested関数がどこで使わているかを調べます
- UGameViewportClient::ProcessScreenShotsという関数で使わているようです!ゴールは近そうです!
- その関数の中を見ると…Save the contents of the array to a png fileというコメントが…!見つけたぞ!世界の歪みを!
// Save the contents of the array to a png file. TArray<uint8> CompressedBitmap; FImageUtils::CompressImageArray(Size.X, Size.Y, Bitmap, CompressedBitmap); FFileHelper::SaveArrayToFile(CompressedBitmap, *ScreenShotName);
この時点で予想できる処理のボトルネックは3つです
↑のコードをコメントアウトして、1の処理の負荷を確認します…
「お、あまり負荷ない!」ということで、2と3の処理を高速化します。
↑で載せたコードをコメントアウトし、その部分に新しいコードを追加していきます。
png画像への変換処理をOFF
png画像のほうが色々と便利だとは思うのですが、処理負荷軽減のため、
シンプルにbmp画像で出力します。
各ピクセルの色情報は、Bitmap内に保存されています。このデータと
Bitmap画像を出力するCreateBitmap関数を使います。
CreateBitmap | Unreal Engine 5.4 Documentation | Epic Developer Community
ScreenShotName = FPaths::GetBaseFilename(ScreenShotName, false); FFileHelper::CreateBitmap(ScreenShotName.GetCharArray().GetData(), GScreenshotResolutionX, GScreenshotResolutionY, Bitmap.GetData());
これでpng画像ではなく、bmp画像を出力するようになりました。
かなり軽くはなりますが…まだ若干カクつくと思います。さらなる高速化が必要です。
画像出力処理を非同期に
ファイルIO的に重い画像出力処理ですが…not 非同期処理です。
画像出力処理が終わるまでゲーム全体の処理を待つようになっています…
ということで、非同期にして待ち時間をなくすようにしました!
これでカクつきがほぼ無くなりました!簡単!
ScreenShotName = FPaths::GetBaseFilename(ScreenShotName, false); AsyncTask(ENamedThreads::BackgroundThreadPriority, [ScreenShotName, Size, Bitmap]() { FFileHelper::CreateBitmap(ScreenShotName.GetCharArray().GetData(), Size.X, Size.Y, Bitmap.GetData()); });
…もっと丁寧に組みたい方は↓を参考にするといいと思います
A new, community-hosted Unreal Engine Wiki - Announcements - Epic Developer Community Forums
まとめ
GameViewportClient.cppのUGameViewportClient::ProcessScreenShots関数にある
// Save the contents of the array to a png file. TArray<uint8> CompressedBitmap; FImageUtils::CompressImageArray(Size.X, Size.Y, Bitmap, CompressedBitmap); FFileHelper::SaveArrayToFile(CompressedBitmap, *ScreenShotName);
を
ScreenShotName = FPaths::GetBaseFilename(ScreenShotName, false); AsyncTask(ENamedThreads::BackgroundThreadPriority, [ScreenShotName, Size, Bitmap]() { FFileHelper::CreateBitmap(ScreenShotName.GetCharArray().GetData(), Size.X, Size.Y, Bitmap.GetData()); });
に書き換えることで、
スクリーンショット使用時のカクつきを軽減することができました!
ただし、色々と保証できない + テストを殆どしていないです!
ご利用は計画的に!
(…プラグイン化試してみよっかな。あまり期待はしないで下さい)