본문으로 건너뛰기

Unity3d 성능 최적화

· 약 19분
karais89

메모리와 리스소를 최적화 해야 되는 이유

  1. 용량이 50MB를 넘게 되면 다운로드하는 유저 수가 절반으로 줄어든다.
  2. 메모리를 많이 쓰면 게임 유저 수에 제약이 생긴다. (저사양 핸드폰에서는 돌아가지 않는다)
  3. 최적화를 하지 않을 시 발생하는 오버헤드는 일반 앱과 비교가 되지 않을 정도로 크다.

02. 스크립트 연산 최적화

01. 유니티 게임 오브젝트를 찾지 말고 캐싱하라.

FindObject 계열 함수들은 매우 느립니다.

찾은 오브젝트는 꼭 변수에 할당해서 캐싱해 놓은 후에 사용합시다.

02. 오브젝트 풀링 기법을 사용하라.

유니티에서 반복적으로 등장하고 제거되는 오브젝트들을 관리하려면 오브젝트 풀링은 필수입니다. Instanitate와 Destory 함수를 이용한 프리팹의 생성/해제는 비용이 크기 때문입니다.

03. Update 함수 보다는 Coroutine을 활용하자.

Update는 매 프레임마다 호출됩니다.

매 프레임마다 호출되야 되는 경우가 아닌 경우에는 코루틴을 활용해 봅시다.

아래는 코루틴이 성능이 좋은 이유입니다.

예를 들어 코루틴에서 yield return new WaitForSeconds(10)이라는 명령을 수행하면 코루틴은 유니티 엔진에게 WaitForSeconds(10)이라는 데이터를 보내고 쉬기 시작합니다. 유니티 엔진은 이를 받고 기록해두었다가 묵묵하게 자기 할 일을 진행 하면서 10초가 지나면 쉬고 있는 코루틴을 깨웁니다. 코루틴이 없이 일반적으로 이를 구현 한다면 Update 구문에서 Time.deltaTime을 사용하여 매 프레임마다 시간을 더해서 10초가 지났는지 감지해야 하는데, 프레임의 평균 소요 시간이 0.01초라고 한다면, 아이러니하게 10초 동안 대기하기 위해 스크립트는 Update 함수를 1000번 호출해야 합니다. 코루틴을 사용하면 10초 동안 스크립트가 쉬는데 말이지요~

04. 문자열을 연결할 땐 StringBuilder를 쓰자.

일반 String + String을 쓰면 임시 문자열이 생성됩니다. (가비지 컬렉터 생성)

StringBuilder.Append() 함수를 사용하여 병합합시다.

05. 나누기 10보단 곱하기 0.1

나눗셈보다 곱셈의 연산 속도가 몇십 배 빠릅니다. 곱하기로도 가능한 나눗셈 연산은 곱하기로 표현하는게 좋습니다.

06. 가비지 컬렉션에서 벗어나자.

문자열은 readonly 혹은 const를 사용하여 가비지 컬렉션에서 벗어나도록 합니다.

07. 객체 캐싱을 활용하라

컴포넌트 참조 GetComponent() 함수는 한 번만 호출하여 객체를 캐싱해 놓습니다.

ex) transform 컴포넌트 캐싱

private Transform _tr;
void Awake()
{
_tr = GetComponent<Transform>();
}

08. 빈 콜백 함수는 제거

Start()나 Update() 같은 콜백함수는 비어있어도, 성능에 영향을 끼칩니다. 사용하지 않는 경우에는 제거 해 줍시다.

03. 리소스 최적화로 메모리 사용량 줄이기

[텍스처 압축 방식별 차지하는 메모리 용량]

[standalone & WebGL]

압축 방식메모리 사용량 (bytes/pixel)
RGB crunched DXT1variable
RGBA Crunched DXT5variable
RGB Compressed DXT10.5 bpp
RGBA Compressed DXT51 bpp
RGB 16bit2 bpp
RGB 24bit3 bpp
Alpha 8bit1 bpp
RGBA 16bit2 bpp
RGBA 32bit4 bpp

[iOS]

압축 방식메모리 사용량 (bytes/pixel)
RGB Compressed PVRTC 2 bits0.25 bpp (bytes/pixel)
RGBA Compressed PVRTC 2 bits0.25 bpp
RGB Compressed PVRTC 4 bits0.5 bpp
RGBA Compressed PVRTC 4 bits0.5 bpp
RGB 16bit2 bpp
RGB 24bit3 bpp
Alpha 8bit1 bpp
RGBA 16bit2 bpp
RGBA 32bit4 bpp

[Android]

압축 방식메모리 사용량 (bytes/pixel)
RGB Compressed DXT10.5 bpp (bytes/pixel)
RGBA Compressed DXT51 bpp
RGB Compressed ETC10.5 bpp
RGB Compressed PVRTC 2 bits0.25 bpp
RGBA Compressed PVRTC 2 bits0.25 bpp
RGB Compressed PVRTC 4 bits0.5 bpp
RGBA Compressed PVRTC 4 bits0.5 bpp
RGB 16bit2 bpp
RGB 24bit3 bpp
Alpha 8bit1 bpp
RGBA 16bit2 bpp
RGBA 32bit4 bpp

텍스쳐 압축은 메모리 사용량과 관련이 깊습니다.

이미지 파일 사이즈와 상관없이 이미지가 메모리상에서 차지하는 용량 계산 방식은 다음과 같습니다.

메모리 사용량 = 가로 픽셀 세로 픽셀 압축 방식의 메모리 사용량(bpp)

이미지 메모리 사용량을 잘 관리하는 방법은 눈에 보이는 이미지는 압축하지 않고, 잘 보이지 앟는 이미지는 압축을 많이 하는 것입니다.

특히나 유저 인터페이스의 경우네는 압축을 많이 하면 이미지가 깨지기 때문에 게임의 퀄리티가 상당히 낮아 보입니다. 이런 이유로 유저 인터페이스는 압축을 피하는 것이 좋습니다.

하지만 3D 몬스터 캐릭터의 텍스쳐 등은 조금 압축을 하더라도 퀄리티에 크게 영향을 주지 않습니다. 스마트폰 게임 특성상 제한된 리소스를 효율적으로 써야 하기에 퀄리티를 강조하고 싶은 영역에는 보다 화질 좋은 텍스쳐를 쓰는 것이 현명한 선택이라 할 수 있습니다.

01. 디바이스별로 권장하는 압축 텍스쳐 포맷

  • 아이폰(powerVR) : PVRCT
  • 안드로이드(Tegra) : DXT
  • 안드로이드(Adreno) : ATC
  • 안드로이드(공통) : ETC!

02. 이미지 가로세로 사이즈는 무조건 2의 제곱

게임에서 사용하는 이미지 가로세로 사이즈는 무조건 2의 제곱으로 되어야 한다(Power Of Two).

컴퓨터에서 이미지를 사용할 때에는 개념적으로 “[1번] 디스크에서 이미지 불러오기→[2번] 이미지 압축 포맷 압축 해제→[3번] 1024×1024×32비트 메모리 블록에 해당 이미지 할당” 과정을 거친다.

1024×1024×32비트 RGBA 기준으로 이미지를 압축한 png 용량은 313KB에 불과하다. 하지만 압축을 해제하면 메모리상 이미지 사이즈는 4MB나 된다. 2048×2048이라면 16MB에 이른다.

이렇게 가로세로 사이즈가 2의 제곱으로 된 이미지가 아닌 경우에는 상당한 메모리 낭비가 일어나게 된다. 예를 들어 900×900 사이즈 이미지가 있다고 하자. 메모리상에서 900×900 사이즈 이미지를 도르할 때 해당 이미지를 똑같이 1024×1024로 변환해 다시 메모리에 저장하게 된다. 다시 말해 거의 배에 가까운 이미지 메모리가 낭비되는 것이다.

이런 이유 때문에 유니티3D를 비롯한 여러 게임 개발 엔진에서 아틀라스(Atlas)라는 리소스 단위를 사용하게 된다. 이미지를 POT(2의 제곱) 방식으로 바꿔서 항상 활용하기를 권한다.

03. 압축된 텍스처와 밉맵 활용

32bit가 아닌 16bit 텍스쳐도 상황에 맞게 적절히 활용하는 것이 좋습니다. 밉맵은 렌더링 속도를 향상시키기 위해 기본 텍스쳐와 이를 연속적으로 미리 축소시킨 텍스쳐들로 이루어진 비트맵 이미지의 집합입니다. 메모리와 리소스 최적화를 위해서는 이런 밉맵을 사용하는 것도 도움이 됩니다.

04. 오디오는 92kb 모노 인코딩으로

모바일에서 스테레오는 의미가 없습니다. 그러니 모두 92kb 모노로 인코딩하는 것이 좋습니다. 92kb 모노 인코딩은 유니티 엔진에서 간편하게 설정할 수 있습니다. 또한 사운드 파일을 임포트하면 디폴트 값으로 3d 사운드가 설정됩니다. 이를 2d 사운드로 설정 변경하는 것도 리소스 낭비를 줄이는데 도움이 됩니다.

05. 오디오 파일은 .wav 형식으로 저장

오디오 파일은 용량을 줄일려고 굳이 .mp3 형식으로 임포트할 필요가 없습니다. 왜나혀면 .wav 파일을 임포트해도 자체 인코더가 용량을 원하는 데로 압축해주기 때문입니다. 음향 손실을 피하려면 wav로 저장하여 유니티 내부 인코더를 활용하는 편이 낫습니다.

04. 캐싱 활용법

페이스북이나 카카오톡과 게임을 연동하게 되면 프로필 이미지를 자주 불러오게 됩니다. 프로필 이미지를 매번 다운로드해서 표시한다면 프로필 이미지가 뜨기까지 시간이 아주 길어집니다. 이를 매끄럽게 하기 위해서는 꼭 캐싱 기법을 사용해야 합니다.

캐싱은 자주 사용하는 데이터를 디스크나 메모리에 저장해두는 기법입니다. 이를 활용하면 네트워크 대역폭을 크게 줄일 수 있습니다. 인터넷에서 다운로드한 이미지를 디스크에 저장해 활용하는 캐싱 기법에 대해 알아봅시다. 이 과정은 총 두번의 스텝으로 이루어집니다.

  • 다운로드할 이미지가 디스크에 있는지 체크 (void LoadProfile)
  • 디스크에 없으면 인터넷에서 이미지를 받아서 디스크에 저장(IEnumerator DownloadProfile)

과정 중간에 에러나 예외가 발생하면 null을 반환합니다.

null을 반환시 사용자는 null 처리를 해주시며 됩니다(ex) 디폴트 이미지를 뿌려준다던지..)

예제를 통해 확인해 봅시다.

캐싱 기법을 활용한 메모리/리소스 최적화 방법은 단편적인 솔루션입니다.

하지만 게임에서 가장 많은 용량과 메모리를 차지하는 이미지 리소스 최적화에서는 아주 기본적인 단계이기도 합니다.

05. 그래픽스 최적화 주요 지표

최적화의 기본은 병목현상을 파악하고 제거하는 것이 핵심입니다.

에디터에서 Game-Stats 메뉴를 클릭하면 통계 수치를 확인할 수 있습니다.

항목설명
Time per frame and FPS하나의 프레임을 처리 및 렌더링 하는 데 걸린 시간 및 상호 관계에 있는 프레임/초, 이 숫자는 프레임 업데이트 및 게임 뷰의 렌더링에 걸린 시간만 포함되는 것에 유의합니다. 에디터가 신 뷰 인스펙터의 그리기 및 에디터 전용 작업을 수행하는 데 걸린 시간은 포함되지 않습니다.
Batches(배칭)배칭이란 여러 오브젝트를 메모리 덩어리로 결합시키는 작업을 말합니다.
Saved by batching(결합된 배칭 수)결합된 배칭의 수입니다. 좋은 배칭을 이끌어내기 위해서는 가능한 한 많은 매터리얼을 공유하도록 하는 게 좋습니다.
Tris and Verts(삼각형의 정점 수)그려진 삼각형과 정점 수
Screen화면 크기, 안티 앨리어신 레벨 및 메모리 사용량
SetPass드로우 콜(Draw Call)과 같은 단어라고 보면 됩니다. 렌더링 패스의 수, 각 패스에 대해서 유니티 런타임은 CPU 오버헤드를 가져올 수 있는 새로운 쉐이더를 바인딩합니다.
Visible Skinned Meshes렌더링 스킨 메시 수
Animations재생 애니메이션 수

06. 배칭

배칭이란 3D 오브젝트들을 하나의 메모리 덩어리로 만들어서 한 번에 그리도록 도와주는 작업을 말합니다. 유니티 메뉴의 [Window > Frame Debugger]를 사용하면 배칭 연산이 이루어지는 단계별로 스냅샷을 볼 수 있습니다.

유니티에서 지원하는 배칭에는 스태틱 배칭과, 다이내믹 배칭 두 가지가 있습니다.

유니티 메뉴에서 Edit-Project Setting-Player로 들어가면 스태틱 배칭을 설정할 수 있습니다.

1) 스태틱 배칭

변하지 않는 오브젝트의 인스펙터탭에서 static 체크박스를 선택함으로써 스태틱 배칭이 일어나도록 설정할 수 있습니다.

2) 다이내믹 배칭

동적으로 비슷한 재질의 오브젝트를 하나의 연산 단위로 묶는 방식을 말합니다.

3D 오브젝트이 정점 수에 영향을 받습니다.

07. 오버드로우와 세이더

1) 오버 드로우

오버 드로우 : 한 픽셀에 여러 번 그리는 행위

화면의 한 픽셀에 두번 이상 그리게 되는 경우에, 오버 드로우가 발생한다고 합니다.

한 픽셀에 어러 번 그리는 만큼 그래픽 부하도 증가하는 건 당연합니다.

2d게임에서 여러 반투명 이미지들을 겹쳐서 표현하는 과정에서 발생합니다.

꼭 반투명이 필요한 이미지만 반투명 옵션을 주고, 굳이 반투명 옵션이 필요하지 않은 경우에는

최대한 옵션 선택을 줄이는 형식으로 반투명 오브젝트에 개수 제한을 걸면 오버드로우 문제를 해결할 수 있습니다.

2) 유니티 세이더

기본 세이더는 모바일용 세이더를 사용합니다. 가장 빠른 세이더는 VertexLit 입니다.

Mobile-VertexLit로 선택하면 됩니다. 화질 차이가 매우 중요한 오브젝트 외에는 모바일에 최적화된

세이더를 쓰는 것이 좋습니다.

참조