본문 바로가기

Unreal Engine

언리얼 최적화 가이드 (Expert's guide to unreal engine performance)

https://dev.epicgames.com/community/learning/tutorials/3o6/expert-s-guide-to-unreal-engine-performance

 

Expert's guide to unreal engine performance | Community tutorial

Set of performance tips to improve the speed of your project.

dev.epicgames.com

 

위 글을 요약한 것임. (2022년 4월 1일 기준으로 작성됨)

 

GPU 사이드에서는 엔진의 셋팅을 조정하는 부분이 잘 문서화 되어있지만, 잘 작동하는 게임 코드를 짜는 방법을 익히는 것은 쉽지 않다.

UE에서 사람들이 같은 실수를 반복하는 것을 수 년간 보아왔다. 다양한 규모의 프로젝트에서 내가 찾아낸 퍼포먼스 관련 정보들을 모아서, 여기에 공유하기로 한다.

이 정보들은 유니티 개발자들에게도 유용할 것이다 - 많은 부분이 두 엔진에서 유사하기 때문이다.

GPU가 아닌 CPU 와 코드 쪽의 퍼포먼스에 초점을 두었다.

1. 프로파일러에 대해 알고 있을 것 (Know your profiler)

최적화를 할 때 가장 중요한 점은, 당신이 한 것을 측정할 수 있어야 한다는 것이다. 무엇이 빠르고, 무엇이 느린지, 어느 부분에 수고를 들일 지를 확인하려면 프로파일링 툴을 활용할 필요가 있다. 언리얼 엔진에는 여러 가지 프로파일링 툴이 있지만, 가장 최신인 것은 언리얼 인사이트이다.

https://www.youtube.com/watch?v=TygjPe9XHTw 

 

2. 믿을 만한 퍼포먼스 테스트를 구성할 것 (Setup reliable performance tests)

프로파일링을 할 때, 이전과 이후를 비교할 수 있는, 어느 정도 신뢰할 만한 테스트 환경을 갖는 것이 좋다. 만약 gpu 최적화를 진행중이라면, 어떤 위치로 카메라를 이동했을 때, 항상 그 위치에서 퍼포먼스를 기록하는 것이 가장 좋다. cpu 코드에서는 대개 테스트 하고 싶은 오브젝트를 각자의 맵에서 isolate 시킨 후 simulate 해 볼것이다. 멀티플레이 게임에서는, 프로파일링을 위한 리플레이를 사용하는 것이다.

리플레이는 대부분의 코드를 재실행하기 때문에 아주 신뢰할 수 있는 수단이다.

Make a Replay System with C++ in Unreal Engine 4

 

3. 개발 첫 날부터 최적화할 것 (Optimize from day 1)

많은 사람들이 “섣부른 최적화는 모든 악의 근원이다” 라고 앵무새처럼 말하고 다닌다. 이것은 잘못된 말이고, 잘못 인용된 것이다. 만약 당신의 게임이 콘솔에서 60FPS 로 안정적으로 돌아가길 바란다면, 개발 프로세스 전체에 있어서 이것을 목표로 해야 한다. 게임내 모든 시간대에서 60FPS를 유지할 필요는 없지만, 80%정도의 시간대에서 55 FPS를 개발 초기부터 목표하는 것은 좋은 생각이다. 이렇게 하면 당신의 게임이 항상 좋은 퍼포먼스일 것이 보다 확실해지고, 프로젝트 말미에 크런치하는 것을 피할 수 있다.

(문맥상 크런치 = 단기간 내에 무리하게 최적화를 진행함을 의미하는 듯 함)

 

4. 갯수를 고려할 것 (Mind your counts)

게임 개발을 할 때, 기록되는 것들에 대한 퍼포먼스를 생각해야 한다.좋은 기준은 게임 내에 "어떤 것"이 몇 개 있는지에 대해 신경쓰는 것이다. 즉, 게임에서 사용되는 모든 오브젝트, 이펙트, 애니메이션 등의 수를 고려해야 한다. 이를 고려하지 않으면, 게임이 느려질 수 있기 때문이다. 대부분의 게임들은 플레이어 캐릭터 1개이기 때문에, 비용이 많이 드는 호출을 최적화 할 필요가 별로 없다. 이런 경우에는 블루프린트도 괜찮다. 그러나 만약 적 브롤러 같은 것이 10개 있다면, 애니메이션이나 블루프린트, default character 코드에서 발생하는 오버헤드를 신경써야 한다. 만약 어떤 것이 100개 있다면, 초반부터 속도를 고려해서 설계해야 한다. 느린 코드 x 100은 아주 빠르게 누적된다. 이렇게 갯수를 고려하게 되면, 특수한 경우가 아니라면 블루프린트 사용은 피해야 한다.

1000개 이상의 갯수에서는, 블루프린트를 사용하지 않아야 할 뿐 아니라, 언리얼 엔진의 Actor 대신 컴포넌트를 사용하는 것이 좋고, 최적화된 C++ 코드로 모든 것을 작성해야 한다.

5. Scene Component는 비싸다 (Scene Components are expensive)

언리얼 내부의 객체가 이동할 때, 그에 속한 모든 scene component들의 transform도 업데이트되고, 그 scene component들의 자식들도 같이 업데이트된다. 이 코드는 전혀 빠르지 않을 뿐만 아니라, 싱글 스레드이다. 큰 컴포넌트 계층구조를 주의하자. 커스터마이징 가능한 캐릭터들이나 탈 것에 거대한 계층구조를 흔히 볼 수 있다. 언리얼 엔진의 cpp code 베이스에 포함된 skeletal mesh merge tool을 사용하는 것을 고려해 볼 것..

Working with Modular Characters

 

Working with Modular Characters

Describes the different methods you can use to create modular characters comprised of multiple Skeletal Meshes.

docs.unrealengine.com

 

6. 블루프린트 Tick 사용은 피할 것 (Avoid blueprint tick)

블루프린트 틱은 c++ 틱에 비해 상당한 오버헤드를 발생시킨다. 100~150개를 측정했을 때, 아무것도 연결되어 있지 않아도 cpu time의 1ms를 소비했다. 만약 ticking이 필요하면 c++애소 사용할 것.

(이 글에서는 블루프린트에서 time 관련 동작이 필요할 경우 Timeline이나 timer 를 사용하라고 되어 있지만, 측정 결과 timeline 또한 cpu time을 상당히 잡아먹는 것으로 확인했습니다.)

7. 게임 플레이에서 Tick을 Enable / Disable 할 것
(Enable/Disable tick while the game plays)

Ticking이 필요한 오브젝트들이 있는 것은 일반적인 일이지만, 항상 ticking이 필요한 것은 아니다. 액터와 컴포넌트들이 필요할 때만 Tick 하고 있는지 확인하자. SetActorTickEnabled()를 사용한다.

 

8. Tick rate를 낮추는 것을 고려할 것 (Consider lower tick rates)

모든 오브젝트들이 1초에 60번 (또는 그보다 더 자주..!) 업데이트 되어야 할 필요는 없다. 이 중 많은 것들이 더 낮은 주기로 tick 할 수 있다. 7번에서 언급한 것 처럼 런타임에 tick rate를 변경하는 것이 가능하기 때문에, 오브젝트가 activated 되기 전에는 1초에 2번 tick하게끔 하는 것도 좋은 방법이다.

 

9. 로직의 LOD (Logic Level of Detail)

7번과 8번과 연관해서, 메시의 level of detail이 있는 것 처럼, 게임 오브젝트들에도 level of detail을 적용할 수 있다. RPG 게임을 만들 때, npc들은 아주 단순화된 Actor를 사용하는 것이 좋다. 그리고 만약 플레이어가 가까이 가면, 더 복잡한 로직과 더 나은 애니메이션을 가진 원래 NPC Actor로 전환된다. 플레이어에게 영향을 끼치기에는 너무 먼 오브젝트들에 대해서는 physics를 비활성화 하는 것도 고려해 본다.

 

10. 만약 나무가 숲속에서 쓰러지면… (If a tree falls in a forest…)

어떤 것이 플레이어에게 영향을 줄 수 있는지 살펴보고, 플레이어가 못 보는 것은 수행하지 말어라.

플레이어로부터 멀리 떨어진 오브젝트들은 파티클 효과나 사운드를 재생할 필요가 없다 - 플레이어가 보거나 듣지 못할 것이기 때문이다. 이런 이벤트들을 피하면 게임이 빨라질 수 있다.

 

11. Replicated 되는 변수를 가진 컴포넌트들을 최소화 할 것.
(Minimize the amount of components with replicated variables (Multiplayer))

언리얼 엔진 네트워킹은 플레이어에서 어떤 오브젝트들이 근처에 있는 지 수집하고, 그 오브젝트들의 속성에서 바뀐 것이 있는지 체크하는 방식으로 동작한다. 바뀐 속성을 체크할 때 드는 비용은 대부분 오브젝트들의 갯수에 비례한다 - 프로퍼티의 양에 비례하지 않는다. PlayerCharacter 가 100개의 replicate 되는 속성을 가지는 것이, player character + 9 개의 컴포넌트가 있고, 각 컴포넌트가 replicated 되는 속성을 10개씩 가지는 것보다 빠르다. 이것은 좋은 architecture와는 거리가 멀 수 있지만, PushModel 네트워킹이 안정화될 때 까지는 좋은 방법이 없다. (UE 4.25와 (unreal 4.25 와 그 이후)

bWithPushModel = true 일 경우, 5.1 이후에서 잘 동작한다는 글이 있음.

Push Model Networking

 

Push Model Networking

In the .Target.cs file you need to add bWithPushModel = true to the server target. I tested that this works (assuming you’ve done the params thing and set net.IsPushModelEnabled) on a 5.1.1 source build of the engine, on a dedicated server.

forums.unrealengine.com

 

12. 일반적으로 Spawn 되는 액터들을 Pool하라.
(Pool your commonly-spawning Actors)

언리얼의 Spawn은 아주 느릴 수 있다. 발사체나 폭발 같은 것에 쓰기에는 garbage collector에 많은 부담을 주고, hitch를 발생시킬 수 있다. 발사체나, 빈번히 respawn되는 다른 오브젝트들에는 object pool을 사용하는 것이 아주 좋다. Robo Recall이 참고하기 좋은 예제이다.

Robo Recall

 

Robo Recall

Robo Recall is an action-packed virtual reality first-person shooter with gratifying gameplay and an in-depth scoring system. Explore immersive environments as you take on a variety of rogue robots, unlock an expanding arsenal of weapons and access all-new

www.epicgames.com

 

13. 에니메이션에 신경을 많이 쓸 것 (Be very careful with animation)

언리얼 엔진 게임에서 애니메이션 트리는 일반적인 병목이다. 애니메이션 트리를 구성할 때, 모든 것이 fast-path를 사용하는 것을 확인하고, 총 blend 사용량를 최소화하자. blend 대신 state machine을 사용하는 것이 보통 더 빠르다.

 

14. Movable 오브젝트에 Overlap event는 지양할 것
(Avoid Overlap events on moveable objects)

Overlap event가 enabled 되어 있는 모든 컴포넌트들은 움직일 때 마다 매번 physics check를 수행한다. 이것은 매우 느리다 - 특히 무기 같은 오브젝트들에 overlap을 사용하는 경우에 특히 문제가 되는데, 매 프레임 여러 번 움직일 수 있는 애니메이팅 된 메쉬에 attach 되어 있기 때문이다. Overlap event는 플레이어 캐릭터와 Trigger 같은 곳에 주로 사용되는 것이 좋다. 발사체나 무기 등에 overlap event를 사용하지 말고, direct trace와 sweep를 사용하자. 게임 로직 내에서 overlapActors 를 호출하는 것은 괜찮다.

 

15. movement call을 최소화할 것. (Minimize the amount of movement calls)

14번과 5번과 연관됨. SetLocation 과 SetRotation을 호출 할 때마다, 언리얼은 Transform chain 전체를 업데이트 할 거시고, 그 중 physics 오브젝트가 있을 경우, physics도 업데이트 할 것이다. 움직이는 오브젝트들에 대해 1 프레임 당 정확히 1개의 SetLocation (또는 그 비슷한 호출) 을 하는 것을 목표로 하자. 언리얼 엔진의 SetActorLocationAndRotation으로 rotation 과 location을 2개가 아닌 1개의 호출로 변경할 수 있다. SetLocation과 SetRotation을 각각 호출하는 것 보다 2배 더 빠르다.

 

16. 오브젝트 로직을 그룹화 할 것 (Group object logic)

참고할 만한 좋은 기준으로, 그룹화된 오브젝트들을 1개의 Manager actor로 컨트롤 하는 쪽이, 각각의 로직을 갖는 것 보다 보통 더 빠르다. 만약 어떤 것이 아주 많은 갯수로 존재한다면, 이 오브젝트들을 업데이트 할 manager로 중앙화 (centralize)하는 것이 좋다. 발사체, 데미지 숫자, Ai 로직 일부가 그 좋은 예시이다. 이것은 Logic LOD 개념과도 궁합이 좋다 - 글로벌 시스템 측에서 어떤것이 업데이트 될 것인지 아닌지를 결정할 수 있다. 매 프레임마다 오브젝트들이 1/3만 업데이트 되거나 하게 할 수도 있다. 아래 프레젠테이션이 이에 대해 잘 설명해 준다.

https://www.youtube.com/watch?v=CBP5bpwkO54 

 

17. Replication graph를 사용할 것 (Use the Replication Graph)

Replication graph는 에픽게임즈에서 포트나이트의 서버 퍼포먼스 최적화를 위해 추가한 기능이다. replication graph는 언리얼 네트워킹 로직의 “이 플레이어에 어떤 오브젝트들이 relevant 한가” 를 오버라이드 할 수 있다. 이렇게 하면 각 zone별로 네트워크 업데이트 주기를 다르게 하거나, replication range에 있는 오브젝트들을 알아내는 계산을 최적화 할 수 있다. ShooterGame은 PlayerInfo를 업데이트하는 주기를 제한하는 (예를 들어, 2명의 플레이어 데이터만을 매 업데이트마다 보내는 등의) 여러 가지 흥미로운 구현을 포함하고 있다.

Replication graph를 잘 사용하면, 멀티플레이어 게임에서 200 또는 그 이상의 플레이어까지 수용할 수 있다. 이것은 새 기능이고 버그거 있을 수 있다.