PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

UE4 SceneCaptureComponent2DでTextureStreamingが走らない場合の対処法

目次

環境

UE4.25.0 Preview7
Visual Studio Community 2019

概要

SceneCaptureComponent2Dを使用してCapture Every Frameフラグを有効化しているにも関わらず
Capture先でTextureStreamingが走らないという経験をした方向けの記事です。

具体的な現象としては遠方でのCapture時に発生します。
さらにめんどうなことに再現するのに少し手間がかかります。
一度正常にStreamingが走った後はEditorを再起動しないと再現ができません。
Streamingした結果がCacheされているからでしょうね。

問題の再現

まずはどういった状態になるのかを見ていきましょう。
こちらがCaptureしたRenderTargetをPlaneに貼ったものです。
TextureStreamingが走っていないため、Textureが粗いものになっています。
CaptureするActorはSubLevelに分けて(1000000.0, 1000000.0, 0.0)の遠方を基準に配置しました。
f:id:PaperSloth:20200502171242p:plain

そしてこちらがTextureStreamingが走った後の期待値です。
f:id:PaperSloth:20200502171310p:plain

岩のMeshが一番違いが分かりやすいと思います。
原因としてはMainCamera以外ではStreamingManagerが走らないことが原因と思われます。
そのため、MainCameraに映らない遠方のCaptureしたActor等は手動で走らせてやる必要があります。

回避方法

こちらのAnswerHubでの回答にあるように手動でUpdateSceneCaptureContents() を呼び出して更新してやる必要があります。
このアプローチのためにはC++コードの記述が必要になります。
追記に書きましたがBlueprint側から利用可能なノードも用意されています。
ただし、挙動が違うものになりますので注意が必要です。
https://answers.unrealengine.com/questions/841147/view.html

ざっくりと書くとStereamingManagerに位置情報を送り
SceneInterfaceからUpdateSceneCaptureContents() を呼び出すだけです。

IStreamingManager::Get().AddViewSlaveLocation(CaptureLocation);
World->Scene->UpdateSceneCaptureContents(CaptureComponent);

USceneCaptureComponent2Dを継承してコードを書くのが手っ取り早いですが
なんとなくUBlueprintFunctionLibraryを継承してコードを用意しました。
USceneCaptureComponent2D版はAnswerHubのリンク先にコード例があります。

今回追加したのはUpdateCapture() で位置とSceneCaptureComponent2Dを渡し、boolで成功判定を返す関数です

// BlueprintFunctionLibrary.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Engine/SceneCapture2D.h"
#include "SandboxFunctionLibrary.generated.h"

UCLASS()
class SANDBOX425_API USandboxFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

    UFUNCTION(BlueprintCallable, Category = "Sandbox", meta = (WorldContext = "WorldContextObject"))
    static bool UpdateCapture(UObject* WorldContextObject, FVector CaptureLocation, USceneCaptureComponent2D* CaptureComponent);
};

// BlueprintFunctionLibrary.cpp
#include "SandboxFunctionLibrary.h"

bool USandboxFunctionLibrary::UpdateCapture(UObject* WorldContextObject, FVector CaptureLocation, USceneCaptureComponent2D* CaptureComponent)
{
    UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
    if (!World || !World->Scene)
    {
        return false;
    }

    IStreamingManager::Get().AddViewSlaveLocation(CaptureLocation);
    World->Scene->UpdateSceneCaptureContents(CaptureComponent);
    return true;
}

あとはこの関数を呼び出すだけでStreamingManagerが期待通りに動作し、Textureの粗さは改善されます。
Level Blueprintから以下の呼び出しをしました。
(確認用に作成したBlueprintで処理としてはかなり悪いのでここは参考にしない方が良いです)
f:id:PaperSloth:20200502172506p:plain


この解決方法の問題点として、Textureの粗さが改善された後もMainCamera以外で無駄にTextureStreamingを走らせてしまうことです。
なにかしらの方法で終了判定を取得して更新を止めた方が良いです。

ひとまず以上で無事にTextureの粗さは改善されます。
同様の問題を踏んだ方はこちらの方法を検討されてみてもよいかもしれません。
f:id:PaperSloth:20200502171310p:plain



最後に余談ですがSceneCaptureComponentでは
Actorを選択した時にViewport右下にCapture先がPreviewされないため
確認用にCameraComponentを追加するのもアリかもしれません。
完全に無駄なComponentのため、リリース時には必ず削除する必要がありますがテスト用としてはオススメです。
f:id:PaperSloth:20200502173209p:plain

[追記] Blueprintで動作させる

カニパンチさんに教えていただきました。
情報を提供いただき、ありがとうございました。

Prestream Textures ノードを使用することでActor内の全StaticMeshComponent, StaticMeshComponentごとにStreamingを走らせることが可能なようです。
Actor側のPrestreamTexturesの引数Secondsに0.0fを設定していると60.0f*60.0f*24.0f*30.0fの30日分走ります。
そのため、実際に運用される場合は適切な数値を入れたほうがよいでしょう。
f:id:PaperSloth:20200502183524p:plain

例えば岩のActorのみを更新したい場合は下記のようになります。
f:id:PaperSloth:20200502182151p:plain

動作させると岩のActorのみが更新されていることが確認できました。
f:id:PaperSloth:20200502182255p:plain