https://dev.epicgames.com/documentation/ko-kr/unreal-engine/optimization-guidelines-for-umg-in-unreal-engine

https://coding-hell.tistory.com/78

 

[UE4] 언리얼 ui 최적화 기법

[영어원문] https://topic.alibabacloud.com/a/ui-optimization-tips-in-unreal-engine-4_8_8_10274886.html UI optimization tips in Unreal Engine 4 At the Unreal Open Day 2017 event, Epic Games developer support engineer Mr. Guo Chunbiao introduced the UI op

coding-hell.tistory.com

 

https://topic.alibabacloud.com/a/ui-optimization-tips-in-unreal-engine-4_8_8_10274886.html

 

UI optimization tips in Unreal Engine 4

At the Unreal Open Day 2017 event, Epic Games developer support engineer Mr. Guo Chunbiao introduced the UI optimization techniques in Unreal Engine 4 to the developers present. The following is a speech record. Hello everyone, I'm Guo Chunbiao, a develope

topic.alibabacloud.com

https://blog.nullbus.net/94

 

UMG 드로우콜 분석

한줄요약 드로우 콜은 같은 리소스와 같은 레이어 ID라는 두 가지 조건이 동시에 만족되어야 하나로 합칠 수 있습니다. UMG에서의 드로우 콜 언리얼 공식 문서에는 드로우 콜과 관련하여 이런 설

blog.nullbus.net

 

Slate Render Process

일반적인 Rendring Process의 개략도

Game Thread

  • Game Thread에서 Slate Tick은 Frame당 2번 WidgetTree를 순회한다.
    • 한 번은 Paint할 Widget의 크기를 계산하기 위해 PrePass 과정에서.
    • 다른 한 번은 Paint 과정에서 Draw Elements를 계산하기 위해 OnPaint 과정에서.
  • 조금 더 풀어 설명하면 다음과 같다.
    • Common Widget의 Type과 Parameter에 해당하는 Vertex Buffer 생성
    • Widget의 Render Transform이 Vertex Buffer로 계산 되어 Layer ID 및 Material 정보에 따라 Batch Merge 수행
    • 마지막 User Widget이 하나 이상의 Draw Elements 생성
    • 각 Draw Element가 Draw call에 해당하는 Render Thread에 Draw Element를 전달

Render Thread

  • Render Thread에서의 Slate Rendering 작업은 다음 순서를 거친다.

Widget Render

  • UI의 RTT(Render to Texture) 수행
  • Retainer Box를 사용하면 Draw Elements가 Retainer Box의 Retain Target으로 Rendering 된다.

Slate Render

  • Draw Elements를 Back Buffer에 Render
  • Retainer Box를 사용하는 경우, Retainer Box의 Texture Resource가 Back Buffer에 Rendering 된다.

Invalidation

https://dev.epicgames.com/documentation/en-us/unreal-engine/invalidation-in-slate-and-umg-for-unreal-engine

  • Slate Widget을 캐싱하고 Paint, Layout, 계층 정보 등의 변경점을 관리하는 기능.
    • Widget의 위와 같은 정보가 변경되지 않으면, 매 Frame마다 Widget을 다시 그리는 대신 Cache를 출력한다.
    • 위 정보들에 유효한 변경사항이 발생하면 Slate가 이를 재 계산하여 다시 그려준다.

Invalidation Box

  • Child Widget의 Geometry를 Cache 하고 관리하는 UI
    • 해당 Widget의 Geometry가 바뀌지 않는 한 Cache 된 Geometry로 대체되어 CPU 사용량을 크게 줄여준다.
  • Invalidation Box는 감싸진 UI 뿐 아니라 그 하위 계층의 모든 UI에 대해 Geometry Cache를 진행합니다.

Global Invalidation

  • SWindow의 Invalidation을 이용해 효과적으로 모든 UI를 Invalidation Box로 Wrapping하는 기능.
    • 이 SWindow에 포함 된 모든 Invalidation Box는 무효화 되고, SWindow의 Invalidation Box만 동작하게 된다.
  • Slate.EnableGlobalInvalidation을 true로 트리거하여 활성화
void SWidget::Invalidate(EInvalidateWidgetReason InvalidateReason)
{
	SLATE_CROSS_THREAD_CHECK();

	if (InvalidateReason == EInvalidateWidgetReason::None || !IsConstructed())
	{
		return;
	}

	SCOPED_NAMED_EVENT_TEXT("SWidget::Invalidate", FColor::Orange);

	// Backwards compatibility fix:  Its no longer valid to just invalidate volatility since we need to repaint to cache elements if a widget becomes non-volatile. So after volatility changes force repaint
	if (EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Volatility))
	{
		InvalidateReason |= EInvalidateWidgetReason::PaintAndVolatility;
	}

	if (EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Prepass))
	{
		MarkPrepassAsDirty();
		InvalidateReason |= EInvalidateWidgetReason::Layout;
	}

	if (EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::ChildOrder) || !PrepassLayoutScaleMultiplier.IsSet())
	{
		MarkPrepassAsDirty();
		InvalidateReason |= EInvalidateWidgetReason::Prepass;
		InvalidateReason |= EInvalidateWidgetReason::Layout;
	}

	const bool bVolatilityChanged = EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Volatility) ? Advanced_InvalidateVolatility() : false;

	if(FastPathProxyHandle.IsValid(this))
	{
		// Current thinking is that visibility and volatility should be updated right away, not during fast path invalidation processing next frame
		if (EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Visibility))
		{
			SCOPED_NAMED_EVENT(SWidget_UpdateFastPathVisibility, FColor::Red);
			UpdateFastPathVisibility(FastPathProxyHandle.GetProxy().Visibility.MimicAsParent(), FastPathProxyHandle.GetInvalidationRoot_NoCheck()->GetHittestGrid());
		}

		if (bVolatilityChanged)
		{
			SCOPED_NAMED_EVENT(SWidget_UpdateFastPathVolatility, FColor::Red);

			TSharedPtr<SWidget> ParentWidget = GetParentWidget();

			UpdateFastPathVolatility(ParentWidget.IsValid() ? ParentWidget->IsVolatile() || ParentWidget->IsVolatileIndirectly() : false);

			ensure(!IsVolatile() || IsVolatileIndirectly() || EnumHasAnyFlags(UpdateFlags, EWidgetUpdateFlags::NeedsVolatilePaint));
		}

		FastPathProxyHandle.MarkWidgetDirty_NoCheck(InvalidateReason);
	}
	else
	{
#if WITH_SLATE_DEBUGGING
		FSlateDebugging::BroadcastWidgetInvalidate(this, nullptr, InvalidateReason);
#endif
		UE_TRACE_SLATE_WIDGET_INVALIDATED(this, nullptr, InvalidateReason);
	}
}

Retainer Panel

  • Child Widgets을 유저의 화면에 Render 하기 전에 하나의 Texture로 병합

  • 여기서 Phase는 Render를 시작하는 Frame, Phase Count는 Render가 되는 Frame 주기이다.
    • 예를 들어 위의 경우 0 Frame에서 시작하여 3 Frame 단위로 Retainer Panel의 Child Widgets이 Render 된다.
  • 이러한 기능들은 매 Frame마다 호출되는 UI의 Draw call을 줄여주는 기능을 제공한다.
  • 하지만 Retainer Panel은 다시 그려질 때 큰 overhead를 가지면서,
    Widget 개개인이 점유하는 Memory가 Invalidation Box보다 크다.
    • 이는 Retainer Panel이 각 Widget의 Invalidation Data 뿐 아니라 고유한 Render Target도 가지기 때문이다.
  • 때문에 UI의 CPU 점유율을 낮추려 한다면 우선 Invalidation Box부터 써야 한다.
    • 그럼에도 Draw Call을 줄이고 싶다면, Retainer Panel을 이용함으로 CPU 사용량을 더 압축할 수 있다.
  • 이는 성능 제약이 빡빡한 저성능 모바일에서 유용다.

How Invalidation works

  • Widget이 화면에 그려질 때 다음 작업들이 순차적으로 발생한다.
    • 계층 구조 - Slate가 root widget과 그들의 child를 모두 포함한 계층에 따라 widget tree를 생성한다.
    • Layout - Slate가 Render Transform을 기반으로 Widget의 크기와 스크린 상 위치를 계산한다.
    • Paint - Slate가 각 Widget의 Geometry를 계산한다.
  • 각 작업들은 수행 할 때 뒤이은 작업을 반드시 수행해야 한다.
    • 예를 들어 계층 구조 작업이 수행되면 반드시 Layout, Paint 작업이 수행되어야 한다.
  • Invalidation System은 위 과정에서 발생하는 모든 데이터를 Memory에 Cache한다.
    • Widget이 Invalidation Box를 사용하든, SWidget의 Global Invalidation을 사용하든.
  • Memory에 Cache된 Widget들은 변경사항이 발생하지 않는 한 재연산을 하지 않는다.
    • 만약 변경사항이 발생하게 되면 Dirty List에 추가되고,
      이 List의 Widget들은 다음 Frame에서 재연산이 이루어진다.
  • Invalidation에서 Cache된 Data를 갱신하게 되는 타입은 대략 다음과 같다.
/**
 * The different types of invalidation that are possible for a widget.
 */
enum class EInvalidateWidgetReason : uint8
{
	None = 0,

	/**
	 * Use Layout invalidation if your widget needs to change desired size.  This is an expensive invalidation so do not use if all you need to do is redraw a widget
	 */
	Layout = 1 << 0,

	/**
	 * Use when the painting of widget has been altered, but nothing affecting sizing.
	 */
	Paint = 1 << 1,

	/**
	 * Use if just the volatility of the widget has been adjusted.
	 */
	Volatility = 1 << 2,

	/**
	 * A child was added or removed.   (this implies prepass and layout)
	 */
	ChildOrder = 1 << 3,

	/**
	 * A Widgets render transform changed
	 */
	RenderTransform = 1 << 4,

	/**
	 * Changing visibility (this implies layout)
	 */
	Visibility = 1 << 5,

	/**
	 * Attributes got bound or unbound (it's used by the SlateAttributeMetaData)
	 */
	AttributeRegistration = 1 << 6,

	/**
	 * Re-cache desired size of all of this widget's children recursively (this implies layout)
	 */
	Prepass = 1 << 7,

	/**
	 * Use Paint invalidation if you're changing a normal property involving painting or sizing.
	 * Additionally if the property that was changed affects Volatility in anyway, it's important
	 * that you invalidate volatility so that it can be recalculated and cached.
	 */
	PaintAndVolatility = Paint | Volatility,
	/**
	 * Use Layout invalidation if you're changing a normal property involving painting or sizing.
	 * Additionally if the property that was changed affects Volatility in anyway, it's important
	 * that you invalidate volatility so that it can be recalculated and cached.
	 */
	LayoutAndVolatility = Layout | Volatility,


	/**
	 * Do not use this ever unless you know what you are doing
	 */
	All UE_DEPRECATED(4.22, "EInvalidateWidget::All has been deprecated.  You probably wanted EInvalidateWidget::Layout but if you need more than that then use bitwise or to combine them") = 0xff
};
  • Invalidation은 자주 변경되지 않은 UI에서 최적화된 기능이다.
    • Widget이 오랫동안 변경되지 않을수록 Slate가 Cache Data를 오래 들고 있고, 그 만큼 CPU 부하를 줄여준다.
  • 이는 특히 MMORPG나 라이브 서비스 게임의 Depth가 있는 메뉴와 같이 규모가 크고 복잡한 UI 작업에서 중요하다.

Volatile Widget

  • 간혹 특정 Widget은 가능한 매 Frame마다 업데이트가 되어야 하는 경우가 있다.
    • 이 경우 Widget은 변경사항이 있을 때마다 매 tick이 Invalidate 된다.
    • 하지만 CPU 부하는 Invalidation을 사용할 때와 동일하게 발생한다.
    • 더불어 Hiearchy를 Cache하기 위해 Memory도 점유하게 된다.
  • 이를 해결하기 위해 자주 사용하는 Widget에 Volatile 선언을 해주는 것이 좋다.
    • Volatile 선언이 된 Widget과 그 Child Widget들은 Paint Data가 Cache되지 않는다.
  • 비록 Geometry는 매 Frame마다 재계산되어 다시 그려지겠지만,
    Slate는 직접적인 변경사항이 없는 이상 Layout 계산은 계속해서 Skip한다.
  • 이는 UI를 전반적으로 Invalidation 하고 싶지만,
    자주 갱신되는 소수의 Widget으로 인해 이득을 보지 못할 때 유용하다.

개발자가 주의해야 할 점

OnTick/OnPaint 사용 지양

  • OnTick이나 OnPaint에서 작업을 하게 되면 그 작업이 매 Frame마다 호출하게 됨.
  • 가급적 Event Dispatch나 Delegate를 이용할 것을 권장

Attribute Bind 대신 Event-driven Update 사용

  • Unreal Engine에서 제공하는 Attribute Bind 역시 매 Frame마다 할당 됨.
  • 이 역시 Event와 Delegate를 이용해 변경사항이 있을 때에만 Widget에 적용 되도록 작업할 것을 권장 함.

Widget Construction

Reduce unused widgets

  • Widget의 모든 child는 시각화 여부와 무관하게 항상 construct 됨.
    • 이는 Render가 되지 않더라도 Loading time, Construction time, Memory를 점유한다는 의미.
  • 때문에 당장 사용되지 않는 Widget들은 Hierarchy에서 제거하는 것이 옳다.

Break complex widgets

  • 특히 Main System에서 사용되는 Widget의 경우 실제로 표시되는 것은 몇 없지만 Cild가 수 천개씩 있는 경우가 있다.
    • 이러한 Widget을 한꺼번에 Load하게 되면 불필요한 Child로 인해 Loading이 지연되고 Memory를 점유하게 된다.
  • 때문에 일정 규모 이상의 Widget은 어느정도 Child Widget을 세분화 하는 것이 좋다. 
  • 예를 들어, 다음과 같이 Widget을 구분하여 작업 방향성을 정할 수 있다. 
    • 항상 표시되는 Widget
      • BaseWidget과 같이 Load하면서 화면에 바로 출력
    • 가능한 빨리 표시되어야 하는 Widget
      • 당장 사용하지 않을 수도 있지만 반응성이 높아야 함.
      • BaseWidget과 같이 Load 하되, 화면에는 출력하지 않고 Visibility로 컨트롤.
    • 조금 늦게 표시되어도 괜찮은 Widget
      • 가끔 사용되거나 아얘 사용되지 않는 경우도 있음.
      • BaseWidget과 별개로 필요할 때마다 Async Load.
  • 이러한 방식은 Memory를 크게 절약할 뿐 아니라 Load 할 때의 CPU 영향도 줄일 수 있다.

Layout

CanvasPanel 사용 지양

  • CanvasPanel은 좌표 평면과 Widget 별 Anchor를 이용해 다른 Widget의 위치를 지정할 수 있는 강력한 Widget이다.
    • 이는 Widget을 원하는 위치에 정확하게 지정하면서,
      동시에 Screen의 외각을 기준으로 Widget의 위치를 유지할 수 있다.
  • 하지만 그와 동시에 높은 성능을 요구하는 Widget이기도 하다.
    • Slate의 Draw call은 Widget의 Layer ID별로 발생한다.
    • VerticalBox나 HorizontalBox등의 다른 Container Widget들은 Child Widget의 Layer ID를 통합한다.
    • 하지만 CanvalsPanel은 Child Widget이 필요할 때 다른 Widget 위에 Render 될 수 있도록 ID를 증가시킨다.
  • 결과적으로 CanvasPanel은 단기로 여러 개의 Draw call을 발생시켜 높은 CPU 사용량을 요구하게 된다.
    • 비록 CanvasPanel보다 용처가 제한적이지만, OverlayPanel 역시 Draw call을 증가시킨다.
    • GridPanel은 Slot마다 Layer를 직접 지정할 수 있지만, 보통은 Child를 Iterate하며 LayerID를 새로 부여한다.
    • ScrollBox는 Scroll Bar가 Layer ID를 증가시킨다.
    • Border 역시 Layer ID를 증가시킨다.
  • HUD나 Menu System의 Root Widget으로 CanvasPanel을 사용하는 것은 크게 문제가 되지 않는다.
    • 이 경우에는 상세한 위치 조정이나 복잡한 Z-Order 배치가 필요할 가능성이 높기 때문이다.
  • 다만 Template 속성이 있는 Widget은 CanvasPanel 위에서 작업하는 것을 지양해야 한다.
    • TextBox, Custom Button과 같이 다른 Widget의 구성요소로 사용되는 Custom Widget들
  • 또는 CanvasPanel을 다수의 Layer에서 과도하게 사용하면, 최종 Layer를 혼돈하기 쉽다.
  • 일반적으로 하나의 요소로 구성된 Widget은 Canvas Panel로 감쌀 필요가 전혀 없다.
  • 또한 HUD나 Menu 같은 경우에도,
    Overlay나 SizeBox를 HorizontalBox, VerticalBox, GridBox와 같이 사용하여 CanvasPanel 사용을 대체할 수 있다.

SizeBox 대신 가능한 Spacer 사용

  • SizeBox는 자신의 크기를 계산하고 Render하는데 다양한 값을 사용한다.
  • 만약 Widget이 특정 Width와 Height를 고정적으로 가진다면, Spacer가 훨씬 가볍다.

ScaleBox와 SizeBox를 같이 사용하지 않기

  • ScaleBox와 SizeBox를 같이 사용하면 매 Frame마다 각자 서로의 Size를 오가며 Update하는 Loop에 빠지게 된다.
  • 이 둘에 의존하기 보다는 Layout이 Content의 Native Size에 따라 동작하도록 만드는 것이 적절하다.

RichTextWidget 사용 지양

  • RichTextWidget은 강력한 Format 지정 기능을 제공하는 만큼 표준 TextBox보다 훨씬 무겁다.
  • 만약 RichTextWidget의 모든 기능이 필요한게 아니라 디자인적인 표현만이 필요하다면,
    원하는 외형을 표현할 수 있는 Font를 제작하여 TextWidget에서 사용하는 것이 훨씬 가볍다.

Visibility

  • 화면 출력되어야 하는 Widget이 Visible인 경우, 클릭 시 Click 반응이 동작해 오버헤드가 발생 함.
    • visible 대신 HitTestInvisible, SelfHitTestInvisible을 사용할 것을 권장
  • Hidden의 경우 화면에 보이지 않더라도 영역을 차지하기 위해 Layout Space를 사용 함.
    • Layout Space를 사용하면 Prepass 계산이 매 Frame마다 수행 됨.
    • 완전히 화면에서 숨기면서 영역을 차지할 필요가 없다면 Collapse를 사용하는 것을 권장.

Texture

Merged Texture

  • Widget에서 여러 개의 Texture를 사용하는 경우, Texture의 갯수만큼 Draw call이 발생한다.
    • 때문에 가능하면 Merge Texture 1개를 사용하는 것이 좋다.
  • Widget의 Sprite를 사용하면 Merged Texture를 사용하거나 편집할 수 있다.

Atlas Group

https://velog.io/@devkcy/ue5-textureatlas

 

[UE5] Texture Atlas

텍스처 아틀라스(Texture Atlas)란 여러 텍스처를 포함하는 텍스처로, 빈 공간에 다른 텍스처를 추가하여 메모리 낭비를 줄인다.

velog.io

 

  • Atlas를 효율적으로 사용하는 방안
    • 위 Merged Texture를 Sprite로 사용하는 것도 Atlas를 거친다.
  • 보통 Texture는 2의 지수로 저장이 된다.
    • 300 * 300인 경우 실제 크기는 512 * 512가 된다.
    • 이 때 필요한 영역 외의 Pixel 너비 만큼 메모리를 낭비하게 된다.
  • Atlas Group은 이런 Texture를 하나의 Texture로 묶는 방식이다.
    • 이는 같은 Widget에서 사용되는 Image들에게 주로 사용되며, Context Switching을 줄이는 효과가 있다.
    • 반대로 다른 Widget의 Image를 묶어주면 Context Switch가 증가한다.
  • 일반적인 Atlas Group은 2048 * 2048 크기를 가진다.

Animation Cost

Material만 있는 Animation

  • GPU로 처리되기 때문에 CPU 비용이 발생하지 않는다.
  • 발광 이펙트, 배경 Scroll, Material 변화로만 표현 가능한 Effect들이 포이에 포함된다.
  • Animation을 Material에 포함시킬 수 있다면, 가장 우선적으로 사용하는 것을 권장한다.

Blueprint로 작업 되었지만 Sequencer가 필요 없는 Animation

  • 실행 비용은 거의 같지만,
    Sequencer Animation은 실행 전 Animation Object Initialize와 담당 Property Path 해석이 요구된다.
    • 즉, Sequencer Animation이 BP Animation보다 CPU 비용이 조금 더 높다.
  • 비교적 짧고 자주 사용되는 Animation의 경우 Sequencer를 지양하고 BP Script로 작업할 것을 권장.

UMG의 Animation Editor로 만들어진 Sequencer Animation

  • UMG의 Animation Editor은 Sequencer 구현체이다.
    • Color 같이 Attribute를 변경하는 경우에는 Widget을 다시 그리게 하지만 Layout을 Invalidate 하지 않는다.
    • 반대로 Render Transform의 변화를 가져오는 모든 Animation은 Layout을 Invalidate한다.
  • 가급적 이러한 작업은 피하거나, Volatile 선언을 해줘야 한다.

Layout 변경을 유발하는 Animation

  • CPU 비용이 가장 높음

기타 최적화 방안

Switch Material

  • 성능이 낮은 Machine에서 Material Effect의 부하를 줄이거나 제거하는 방안
  • DYNAMIC_MULTICAST Framework를 사용

Manager Class

  • 모든 USerWidget과 Brush, Font등 UI Resource를 관리하는 Manager Class를 만드는 것을 권장.

Free Texture Memory

  • Texture를 미리 Setting 하는 것이 아니라 코드를 통해 수동으로 Load/Set/Destroy 하는 것을 전제
    • Editor에서는 Texture를 설정하지 않으면 CDO에서 이 Texture 객체를 참조하는 것을 피할 수 있다.
    • CDO Reference는 ShardPtr의 Reference Count를 최소 1로 만들어 앱이 종료될 때까지 제거되지 않는다.
  • Editor에서 Image Property가 설정되어 있고 Texture를 삭제하는 경우,
    CookStage에서 UImage와 UTexture 사이 Reference를 제거하여 UserWidget의 CDO가
    UTexture를 Reference 하지 않아야 한다.
void UWeakRefImage::Serialize(FArchive& Ar)
{
    Super::Serialize(Ar);
    
    if (Ar.IsCooking() && Ar.IsSaving())
    {
        // Let's suppose user always intend to set a valid resource texture here.
        UObject* ImageRes Brush.GetResourceObject();
        if (ImageRes != nullptr)
        {
            ImageWeakRef = ImageRes;
            Brush.SetResourceObject(nullptr);
            SetBrush(Brush);
        }
    }
    Ar << ImageWeakRef;
}
  • 이 외에 Texture를 Load하는 코드는 다음과 같다.
void UWeakRefImage::LoadTextureResource(bool bAsync/*= true*/)
{
    if (ImageWeakRef.ResolveObject() || (!bAsync && ImageWeakRef.TryLoad()))
    {
    	Brush.SetResourceObject(ImageWeakRef.ResolveObject()); 
        SetBrush(Brush);
    }
    else
    {
        if (StreamableMgr == nullptr)
        {
        	StreamableMgr = new FStreamableManager();
        }
        StreamableMgr->RequestAsyncLoad(ImageWeakRef,
        	FStreamableDelegate::CreateUObject(this, &UWeakRefImage:: LoadTextureDeferred));
    }
}

void UWeakRefImage::LoadTextureDeferred()
{
    if (ImageWeakRef.ResolveObject())
    {
    	Brush.SetResourceObject(ImageWeakRef.ResolveObject()); 
        SetBrush (Brush);
    }
}
  • Texture Unload하는 코드는 다음과 같다.
void UWeakRefImage::UnloadTextureResource()
{
    TSharedPtr<FSlateRenderer> Renderer = FSlateApplicationBase::Get().GetRenderer(); 
    if (Renderer.IsValid())
    {
        FSlateApplicationBase::Get().GetRenderer()->ReleaseDynamicResource(Brush);
        FSlateApplicationBase::Get().GetRenderer()->ReleaseAccessedResources(true);
    }
    Brush.SetResourceObject(nullptr);
    SetBrush (Brush);
}

3D RTT 최적화

  • SceneCaptureComponent2D는 매 Frame에서 tick이 호출된다.
    • 매 Frame마다 Image Update가 발생하는 것을 취소할 수는 있다.
  • 이 때 Animation의 Update 빈도는 30 fps 정도로 충분하므로 BP에서 Tick 간격을 설정할 수 있다.
    • 이 경우 BP에서 Capture을 수동으로 호출해야 한다.
  • 이와 별개로, SceneCapturecomponent2D는 Render Target이 크면 그 자체만으로 성능이 악화된다.

'UE5 > UI' 카테고리의 다른 글

[UI] Common UI FAQ  (0) 2024.07.10
[UI] CommonUI Technical Guide  (0) 2024.07.10
[UI] Common UI Widget  (0) 2024.07.10
[UI] Common UI Introduction  (0) 2024.07.10
[UI] UMG ViewModel  (0) 2024.07.08

+ Recent posts