토이 프로젝트로 진행하고 있는 프로젝트에서, Unity Profiler를 통해 최적화를 연구했던 내용을 공유하고자 한다.




🔷 시작

🔶 프레임 드랍이 너무 심해!

현재 클라이언트 개발자로 참여하고 있는 토이 프로젝트 엄마찾아 탕탕탕! 프로젝트를 진행하던 도중, PC Editor 환경에서는 큰 문제가 없으나 빌드 후 모바일 환경에서는 FPS가 너무 심해 게임을 정상적으로 플레이 할 수 없다는 제보를 받았다.


이 말이 사실인지 확인하기 위해 직접 빌드를 돌려 테스트 해보려 했지만, 개인 휴대폰인 IPhone 13 Pro Max 에서는 무리 없이 부드럽게 게임이 돌아가는 모습을 확인했다.

따라서 해당 이슈를 제시해주셨던 팀장님께 어떤 기종인지 확인해본 결과, 해당 이슈가 발생하는 디바이스는 퍼포먼스가 상당히 떨어지는 구형 안드로이드 모바일 기기였다.


논의 결과, 해당 디바이스를 최소 사양으로 정하고 진행했으면 좋겠다는 의견이 있었기에 일단 급한대로 주변에서 구형 안드로이드 기기를 구해보기로 했다.

주변에서 어렵사리 구한 Galaxy Tab A7


구한 테스트 디바이스는 Galaxt Tab A7이다. 아래는 Galaxy Tab A7의 하드웨어 스펙이다.

Galaxy Tab A7 기기 스펙


테스트를 시작해보자




🔷 테스트 진행

🔶 안드로이드에서 빌드 후 테스트

프로젝트를 빌드 후, 해당 기기에서 테스트를 진행하자 충격적인 사실을 마주했다.

평균 FPS : 7

GIF 수준으로 끊겨서 보이는 것을 확인했다. 이 정도면 정상적인 게임 진행 플레이는 커녕 캐릭터 조작도 어려울 지경이었다. 액션 슈팅 아케이드 게임인 본 프로젝트에서 이 정도 FPS는 너무 처참한 결과였다.

일단 USB 케이블로 PC와 디바이스를 연결한 후, Unity Profiler를 사용하여 분석에 들어갔다. 연결 후 디버깅을 해보니, 다음과 같은 모습을 확인할 수 있었다.

Gfx.WaitForPresentOnGfxThread

프로파일러를 보니 무려 Gfx.WaitForPresentOnGfxThread 라는 항목에서 엄청난 시간을 소비하고 있다는 것을 알았다.

전지전능하신 Unity Korea의 유튜브 가이드에 따르면, 최적화의 첫 시작은 바로 지금 발생하는 이슈가 CPU 바운드 인지, GPU 바운드 인지를 파악하는 것이라고 한다.


❓ GPU 바운드 vs CPU 바운드

한 줄 요약 :

  • CPU 바운드 : GPU는 일이 없어 널널한데, CPU가 너무 바빠서 프레임 드랍이 발생하는 현상
  • GPU 바운드 : CPU는 일이 없어 널널한데, GPU가 너무 바빠서 프레임 드랍이 발생하는 현상


현재 상황이 CPU 바운드 냐, GPU 바운드 냐에 따라 최적화 전략이 결정되므로, 엄청난 시간이 소요되고 있는 Gfx.WaitForPresentOnGfxThread 연산이 어떤 종류의 바운드인지를 먼저 검색해 보았다.


📚 참고자료 :


혹시 테스트하고 있는 기기 자체가 이런 게임을 플레이할 스펙이 안되는것은 아닐까? 라고 생각해서, 급하게 비슷한 그래픽이라고 생각되는 궁수의 전설이라는 게임을 다운로드 받아 플레이 해 보았다. 궁수의 전설 또한 Unity 엔진으로 구동되는 게임이고 2019년에 출시된 게임이므로, 테스트해볼 가치가 있다고 생각했다.

결과는 아래와 같다.

같은 기기에서 궁수의 전설 플레이 시

60fps가 정상적으로 출력되는 것도 모자라, GPU도 상당히 널널한 모습을 볼 수 있었다. 그럼 도대체 뭐가 문제일까? 혹시 프로젝트에 보여지고 있는 리소스가 너무 무거운 것은 아닐까? 이는 Unity Editor에서 간단하게 확인할 수 있다. Game View 상단의 Stats 버튼을 누르면 현재 랜더링 정보를 확인 할 수 있는데, 확인한 정보는 아래와 같다.

사용중인 리소스의 삼각형 개수는 35.7K, 버텍스 개수는 27.6K

일단 지금 불러온 Scene에서는 삼각형 개수는 35.7K, 버텍스 개수는 27.6K 정도임을 확인하였다. 몬스터나 투사체와 같은 오브젝트가 더 생긴다면 더 많이 사용되겠지만, 그런 오브젝트가 없어도 안드로이드 기기가 힘들어했으므로, 해당 수치가 높은 수치인지 확인해봐야겠다고 생각했다.

8년 전에도, Tri 70 ~ 80K 정도는 문제가 전혀 없었다는 답변
Verts 100K 미만을 유지하라는 게시글

Tri 70 ~ 80K 정도는 전혀 무거운게 아니었고, Verts 또한 권장량의 1/4 수준으로 매우 낮은 모습을 볼 수 있었다. 그렇다면 뭐가 문제인 것일까? 계속 구글링을 통해 자료를 찾다가, Unity 포럼에서 흥미로운 답변을 하나 발견하였다.

Unity Staff의 답변

답변으로 미루어보아, 리소스 문제는 역시 아닌것 같았다. 다만 흥미로운 점은 하단 내용이었는데, 바로 카메라 스택 기능을 사용하고 있는지? 에 대한 질문이었다. 놀랍게도 본 프로젝트안에서는 Camera Stacking 기능을 시험해보고 있었다는 것을 기억해 내었다.

프로젝트에서 사용중인 Camera Stacking 기능

해당 기능을 제거하고, 다시 빌드 테스트를 진행해보았다. 그랬더니 전보다 확실히 나아진 모습을 확인 할 수 있었다.

프로젝트에서 사용중인 Camera Stacking 기능

흠… 하지만 아직 원활한 플레이가 가능한 느낌은 아니다. 혹시 다른 문제가 있는지 조금 살펴보던 중, 우연히 맵 객체를 생성하지 않으면 부드럽게 60 fps가 나온다는 사실을 확인하였다.

맵 객체를 생성하지 않자, 프레임이 올라가는 모습

맵 객체에 따로 설정해둔 내용은 따로 없었기에, 맵 객체의 Material을 살펴보자, shader가 Lit 셰이더로 되어 있는 것을 확인했다. Lit 셰이더는 쉽게 말하면 조명에 의해 영향을 받는 셰이더로, 당연하게도 라이팅 연산이 추가로 들어간다. 본 프로젝트에서는 퍼포먼스가 더 중요하다고 판단하였기에, 일단 더 가벼운 Unlit 셰이더로 변경하기로 했다.

Lit 셰이더에서 Unlit 셰이더로 변경

그러자 fps가 25까지 올라온 모습을 확인할 수 있었다.

Unlit 머터리얼을 사용하자, fps가 25까지 올라간 모습

그러나, 역시나 아직 부족하다. 다른 방법을 찾아봐야겠다고 생각했다. 다시한번 Profiler를 보면서 어떤 부분이 가장 오래걸리는지 확인하기로 했다.

PostLateUpdate.FinishFrameRendering에서 병목이 생기는 현상을 찾음

프로파일러를 확인해보니, PostLateUpdate.FinishFrameRendering 이라는 항목에서 병목이 생기는 것을 확인할 수 있었다. 해당 내용을 구글에 검색해보니, 해당 시간은 UPR의 메인 렌더링 함수가 소비되는 시간으로, 여러가지 요소에 의해 늘어날 수 있다는 내용이었다. 그러던 중, Post Processing 기능도 해당 함수의 연산 시간에 영향을 준다는 사실을 알아내었다.

PostLateUpdate.FinishFrameRendering에 대한 설명

그래서, 맵에 있던 Post Processing 기능을 비활성화 하고, 다시 빌드해 fps를 확인해 보았다. 깜빡하고 사진은 찍지 못했지만, fps도 조금 더 개선된 모습을 확인할 수 있었다. 다시 프로파일러를 확인해보니, 그래프가 확실히 좋아졌지만, 일정 주기로 피크가 튀는 모습을 확인할 수 있었다.

보이진 않지만, 가끔 Semaphore.WaitForSignal 때문에 피크가 튐

좀 더 살펴보니, 일정 주기로 Semaphore.WaitForSignal 이 호출되면서, 시간을 과도하게 소비, 피크가 만들어지는 것을 확인했다. 마찬가지로 구글링해보니 Player Setting에서 Multi Thread Rendering을 비활성화하는 것이 효과가 있었다는 내용을 발견했다.

Multi Thread Rendering을 꺼보세요

📚 참고자료 :


글의 내용에 따라, Player Setting에서 Multi Thread Rendering을 비활성화 했다.

Player Setting > Android > Multi Thread Rendering을 비활성화

여기까지 따라하니, fps가 45까지 올라온 것을 확인했다.

Post Processing과 Multi Thread Rendering을 비활성화 하니 FPS가 45까지 올라온 모습

하지만 조금 더 최적화를 해보고싶다는 생각에, 다시한번 프로파일러를 확인했다. 피크 문제는 여전히 존재했는데, 이번엔 Device.Present 에서 피크가 생기는 것을 확인했다.

이번에는 Device.Present 때문에 피크가 생기는 모습

이번에도 또다시, 해당 항목을 검색해보니, Player Setting 에서 Optimized Frmae Pacing 항목을 비활성화 하라는 내용을 찾아냈다.

Optimized Frmae Pacing을 꺼보세요

📚 참고자료 :


Player Setting에서 해당 내용을 비활성화하고 테스트를 해보니, 평균 FPS는 크게 달라진 것이 없어 보였다. ( 45 -> 46 )

Optimized Frmae Pacing을 비활성화 했을 때

하지만, 중간 중간 튀는 피크가 많이 좋아진 모습을 확인할 수 있었다.

피크가 좋아진 모습

마지막으로, 프로젝트를 더 살펴보던 중, Unlit Shader를 사용한다면 아예 Shadow 연산을 하지 않는 방법이 있다는 사실을 알아냈다.

URP 렌더파이프라인 에셋 설정
카메라 설정

이렇게 설정을 하고 빌드를 해보니, fps가 51까지 올라온 모습을 볼 수 있었다.

그림자 옵션을 끄니, FPS가 50대에 들어온 모습

다른 업무가 많았기 때문에, 여기까지만 최적화를 진행하기로 했다.




🔷 결론

이 포스트의 결론은 작성된 솔루션을 시도했을 때, 당신의 프로젝트의 프레임드랍이 해결된다는 것이 아니다. 이 내용들은 내가 진행중인 프로젝트에 해당되는 내용일 뿐, 당신 프로젝트에는 전혀 효과가 없을 수 있다.

이 포스트를 작성한 이유는, 누군가에게 최적화를 시작하는 방법 에 대해 공유하기 위해서이다. 일단 현재 문제가 CPU 바운드인지, GPU 바운드인지 확인을 하고, 프로파일러를 보면서 어떤 문제가 있는지 찾아내는 것이 중요하다.

프로젝트가 느리다면, 일단 프로파일러를 켜보자.