Notice
Recent Posts
Recent Comments
Link
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Archives
Today
Total
관리 메뉴

메피카타츠의 블로그

메모리 관리에 대한 탐구 (11) - Unity의 Asset 압축/최적화로 메모리/디스크/다운로드 용량 줄이기 본문

개발/공부

메모리 관리에 대한 탐구 (11) - Unity의 Asset 압축/최적화로 메모리/디스크/다운로드 용량 줄이기

메피카타츠 2024. 10. 27. 16:46

유니티로 모바일 게임을 개발하다보면 메모리 용량을 확보해야 하는 상황이 온다. (여기서 말하는 메모리는 RAM이다) 이뿐만 아니라, 디스크 용량 확보도 중요한 요소이다. 게임의 용량이 작은 것을 유저들이 더 선호하기도 하며, 다운로드 용량이 클수록 부담해야 하는 비용도 늘어나기 때문이다.

 

 

일례로, AssetBundle 다운로드에 AWS를 많이 사용하게 될 텐데, AWS의 S3 요금제를 살펴보면 위와 같다.

 

많이 사용할수록 가격이 감소하지만, 최저 요금을 기준으로 봐도 1GB당 $0.108로 2024년 10월 27일 현재 환율로 150원 정도의 비용이 들어가는 셈이다.

 

10만명의 유저가 다운로드를 받는다고 가정했을 때 다운로드 용량을 1GB 줄이는 경우, 최저 요금을 기준으로 봐도 $10,800, 현재 환율로 약 1500만원을 절약할 수 있다.

 

특히 모바일 게임은 업데이트를 자주 하기 때문에 다운로드 용량을 줄이면 지속적으로 비용을 절감하는데 크게 도움이 될 것이다.

 

에셋 최적화는 사실 대부분 압축인데, 아주 조금일지라도 퀄리티 저하가 발생할 수 있다.

그러나 최적화는 불가피하게 해야 하는 경우가 많기 때문에 사람이 인지하지 못할 정도의 퀄리티 저하를 감수하면서 용량을 적당히 줄일 수도 있고, 인지 가능한 정도의 퀄리티 저하를 감수하며 용량을 크게 줄일 수도 있다.

 

아트팀에서는 이런 퀄리티 저하를 못마땅하게 여길 가능성이 높기 때문에, 최적화로 인한 퀄리티의 변화에 대해 어느정도 선까지 최적화를 해도 괜찮을지 아트팀과 협의를 할 필요가 있을 것이다.

 

나는 현재 참여 중인 프로젝트에서 에셋 최적화/압축을 통해 아래와 같은 효과를 볼 수 있었다. (어느 정도는 최적화가 되어있던 프로젝트이다.)

 

초기 다운로드 용량: 2.0GB -> 750MB (63.4% 감소)

전체 번들 용량: 2.54GB -> 1.50GB (41% 감소)

 

https://www.youtube.com/live/52ehLUfk3DQ?si=luUhzZLe4butzuKw

 

아마 대부분 내용이 위 영상에 나와있을 것 같은데, 유니티 코리아 공식 영상이니 위 영상을 참조하면 더 좋을 것이다.

 

일단 생각나는 것들 위주로 적어볼 예정이다. 간단하게 어떤 것들이 있는지 정도만 적을 예정이니 상세한 내용은 직접 찾아보면서 적용하면 될 것이다. 적용 난이도와 효과를 기준으로 주관적인 우선순위를 매겨 순서대로 적어보겠다.

 

==============================================================

 

1. Texture 최적화

 

1-1. ASTC - 텍스처 압축 방식이 굉장히 많은데, 오늘날에는 ASTC 포맷이 제일 좋다는 것 같다. AOS, iOS 가리지 않고 다 쓸 수 있으며 웬만한 구기기가 아니면 다 적용이 되기 때문이다. 특히나 최신 (내가 확인한 것으로는 2022.3 이상 버전) 버전에서는 Alpha 채널을 사용하는지 아닌지도 알아서 판단해서 압축을 해주기 때문에 사용도 엄청나게 간편하다. 특히나 용도에 따라서 압축을 얼마나 할 것인지를 유동적으로 조절할 수도 있기 때문에 ASTC 포맷을 사용하는 것을 추천한다.

 

4x4 block ~ 12x12 block의 포맷이 있는데, 압축할 때 1블럭의 크기를 어느 정도로 할 것인지 정할 수 있는 셈이다. 4x4 block을 했을 때 가장 퀄리티가 좋고, 12x12 block을 했을 때 가장 압축 효율이 좋다.

 

4x4 block의 경우에는 압축을 하지 않았을 때와 퀄리티 차이를 찾기가 어려울 정도로 퀄리티가 좋기 때문에, UI에 표시되는 Sprite의 경우는 4x4 block으로 설정하고, 이외에는 10x10 block으로 일괄 설정을 해놓으면 편리하다. 물론 용도에 따라 더 세분화를 하면 퀄리티와 효율을 더 챙길 수 있을 것이다. 때문에 이런 용도에 따라서 에셋을 폴더 별로 분리해놓으면 관리하기가 매우 수월하다.

 

코드를 하나 만들어서, 전체 파일을 돌면서 jpg, png 등등 파일에 대해서 TextureImporter를 가져오고 세팅해주면 된다. 아마 찾아보면 참고할만한 코드들이 있을 것이다.

 

1-2. Mipmap - Mipmap은 3D 게임에 주로 쓰이는데, 멀리 있는 텍스처에는 저해상도 텍스처를 적용해주는데 사용된다. 이렇게 하면 대역폭도 절약할 수 있고, 고해상도 텍스처를 보여주는 것보다 퀄리티가 오히려 좋게 보일 수도 있다. 단점은 미리 생성을 해놓기 때문에 텍스처 크기가 33% 늘어난다는 것이다. 때문에 UI에 사용되는 경우와 같이 텍스처를 멀리서 보여줄 일이 없다면 꺼놓는 것이 메모리와 디스크 용량을 절약할 수 있는 방법이다.

 

1-3.  Read/Write Enabled - 텍스처를 코드 상에서 읽고 쓰며 변경해준다면 필요할 수 있는 기능이지만, 대부분의 경우는 필요없을 것이다. 이게 켜지게 되면 GPU 메모리뿐만 아니라 CPU 메모리에도 텍스처가 할당되어 메모리 용량을 2배 차지하게 된다. 아마 폰트를 Dynamic으로 설정한 경우, 읽고 쓰는 기능이 필요하여 이 기능이 자동으로 켜지고, 때문에 메모리 용량을 2배로 차지하는 것 같다. (때문에 폰트 Texture에서는 켜고 끄는 기능을 지원하지 않는 것 같고, 아마 Static으로 해놓는다면 용량을 2배로 차지하지 않을 것 같다.)

 

==============================================================

 

2. Audio 최적화

 

2-1. 오디오 압축: Compression Format을 Vorbis로 하고, Quality를 40 정도로 하면 용량은 크게 줄어들지만 오디오 퀄리티에는 크게 영향이 없다. (아마 경우에 따라 다를 것이다) 좋은 퀄리티로 들려줘야 할 사운드가 있다면 따로 설정해놔도 좋을 것이다. Load type은 대부분 Decompress On Load로 충분한 것 같다. 메모리를 상대적으로 많이 차지하기는 하지만, 다른 옵션의 경우 압축을 지속적으로 해제하면서 사용할 거라 CPU에 약간 부하를 줄 수 있을 것이다. 메모리가 부족한 게 아니라면 Decompress On Load로 해도 괜찮을 듯 하고 메모리를 줄여야 한다면 용량이 큰 BGM의 경우 Streaming으로 설정해주면 좋을 것 같다. 다른 옵션들은 압축을 해제하는데 시간이 약간 걸려서 사운드 타이밍이 중요한 효과음의 경우는 용량도 작으니 Decompress On Load를 사용하는 것이 좋겠다.

 

==============================================================

 

3. Mesh 최적화

 

3-1. Mesh 압축: Mesh 압축에는 2가지 방식이 있는데, Vertex Compression과 Mesh Compression이다. Vertex Compression은 전역적으로 적용되는 Mesh 압축인데, Player Settings에서 설정이 가능하고, 기본적으로 켜져있다. 유니티에서 권장하는 압축 방식이 버텍스 압축이다. 아마 실제로 버텍스의 수를 줄이는 것 같아서 GPU 성능이 약간 향상될 수도 있다는 것 같다. 압축 효율은 경우에 따라 다르지만 대략 30% 정도 줄어드는 것 같다. 단, Vertex Compression은 제한 사항이 많은데, Mesh가 Skinned Mesh면 적용이 안 되고(Skinned Mesh Renderer에서 사용되는 경우) Read/Write Enabled가 켜져있으면 안 되는 등의 조건이 있다. (자세한 내용은 유니티 Documentation 참고) 또, Mesh에서 직접 Mesh Compression을 설정하는 경우에도 적용되지 않는다.

 

Mesh Compression은 Mesh에서 직접 설정을 하는데, Low, Medium, High 3가지 옵션이 있다. Skinned Mesh거나 Read/Write Enabled가 켜져있어도 적용되는 것 같다. 대략 Low일 때 47% 정도 감소, Medium일 때 53% 정도 감소, High일 때 60% 정도 감소? 대략 이 정도로 용량이 줄어들었던 것 같다. 메시에 따라 다르게 적용될 수 있으니 대략적인 경향만 참고하면 좋을 것 같다.

 

Mesh Compression은 메모리에는 영향을 끼치지 않는다고 되어있다. 찾아보면 메모리 용량도 줄었다는 얘기가 있는데 실제로 어떠한지는 모르겠다. 다만 얘기로는 데이터 상에서 압축을 해놓고, 사용할 때 압축을 해제하는 방식이라는 듯. 때문에 최초 로딩 시에 시간이 소요될 수 있다고 하는데 딱히 체감할 정도는 아니었다. 압축 정도가 높을수록 모델 퀄리티에 영향을 주는데, High로 압축하는 경우 Vertex가 많은 부분, 특히 캐릭터의 입술이 부분이 다소 일그러지는 모습을 볼 수 있었다. Medium으로 압축해도 High와 용량에는 큰 차이가 없고, Medium의 경우에는 압축을 하지 않은 경우와 퀄리티 차이가 크게 없었기 때문에 Medium으로 일괄 압축을 적용한 상태이다.

 

Mesh Compression을 변경하는 경우, 모델을 다시 Import하기 때문에 에디터에서 시간이 많이 소요될 수 있다는 점을 참고하길 바란다. (대략 2만개의 모델에서 1~2시간 정도 소요되었던 것 같다.)

 

==============================================================

 

4. Animation 최적화

 

4-1. Animation 압축: 이거는 찾아보다가 나왔는데 이미 적용이 되어있어서 효과는 잘 모르겠지만, 애니메이션도 압축이 가능하다는 것 같다.

 

Animation Compression in Unity - techarthub

 

Animation Compression in Unity - techarthub

An in-depth look at Unity's available animation optimization methods with some practical examples thrown in.

techarthub.com

 

위 링크를 참조하거나 다른 내용을 찾아서 적용해보면 좋을 것이다.

 

==============================================================

 

5. Dependencies 관련 최적화

 

5-1. 불필요한 Dependencies 제거하기: 예를 들면... (우리 프로젝트의 예시지만) 대부분 번들에서 Shader 번들을 참조하는데, Shader 번들에 누군가가 만들어놓은 Material이 Effect 번들의 Texture를 참조하고 있고, 이 Effect 번들에서는 각종 몬스터의 Texture를 참조하고 있다고 하면, 대부분 번들에서 Shader - Effect - 각 Monster들에 대한 Dependencies를 갖게 되고, 이건 불필요한 참조 관계 데이터를 저장하게 될 뿐 아니라, 로드할 때도 굉장히 비효율적이게 될 것이다. (Dependencies가 있는 번들들을 미리 로드할 테니 말이다.) 이 부분은 우리의 경우 약간의 에셋 중복이 발생하더라도 복제해서 각 번들에 넣어주는 방식으로 해결했다.

 

5-2. 많은 번들에서 참조하는 에셋을 Bundle에 포함시켜주기: 특정한 에셋에 대해, 번들이 지정되어 있지 않은 경우, 이 에셋에 Dependency를 가지는 번들에 복사되어 들어간다. 그런데 이 에셋이 용량이 큰 경우거나 많은 번들에서 참조하는 경우에 문제가 발생할 수 있다. 예를 들어서 1MB짜리 텍스처가 있다고 치고, 번들 A, B, C, D, E, F, G 총 7개의 번들에서 이 텍스처를 사용한다고 하면, 각각의 번들에 하나씩 들어가기 때문에 7MB만큼 디스크 용량이 늘어나게 될 것이다. 또, 이 번들들에 들어간 텍스처는 각기 다른 에셋으로 취급되기 때문에 이전에 다루었던 메모리 중복도 발생할 수 있다. 그러나 이 1MB짜리 텍스처를 H라는 번들에 명시적으로 포함시켜주면, A B C D E F G 각 번들은 H 번들에 대한 Dependency를 갖게 되고, 디스크에서 차지하는 용량은 1MB가 되며 메모리 상에서 중복도 일어나지 않을 것이다.

 

==============================================================

 

6. 불필요한 Asset 제외

 

이건 굉장히 막연한 부분이지만, 가장 확실한 방법이기도 하다. 일례로 내가 참여하고 있는 프로젝트에서 용량이 큰 번들들을 하나씩 체크하고 있었는데, 개당 5.3MB에 달하는 bmp 파일이 디스크 상에서 총 약 800MB 가량을 차지하고 있었다. 맵 관련 텍스처로 보여서 맵 담당하시는 분께 여쭤보니 splat 맵이라고, 맵 만들 때 사용하시는 툴에서 데이터를 뽑아서 유니티에 옮길 때 1번만 사용하는 파일이라고 하셨다. 즉, 지워도 된다는 말이다. 아직 서비스 전이니 굳이 말하자면 문제는 아니었지만, 이런식으로 눈 먼 파일들이 게임에 포함되는 경우가 꽤 많을 것 같고, 아직도 있을 것 같다. 하나하나 체크하기가 힘들어서 그렇지. ㅋㅋ... 용량이 큰 번들들을 확인하면서 용량이 큰 불필요한 파일들을 걸러주는 것도 꽤나 큰 효과를 볼 수 있을 것이다.

 

==============================================================

 

7. Shader Variant Stripping

 

이건 메모리 용량을 줄이는 데에도 아주 효과적인데, 알고보니 메모리 용량이 줄어드는 만큼 디스크 용량도 줄일 수 있는 방법이었다. Shader 용량이 총 350MB 정도 된다고 쳤을 때, Shader Variant Stripping을 하면 메모리 용량과 더불어 디스크 용량도 300MB 가량 확보할 수 있는, 매우매우 훌륭하고 좋은 방법이다.

 

단, 단점이 있다면 적용하기가 상당히 까다롭고 위험성이 존재한다는 점이다.

 

Shader Variant Stripping에 대해 간단히 설명하자면 Shader에서 Material을 어떻게 보여줄 것인지 결정하는데, 이때 Keyword라는 것이 사용된다. 이 Keyword의 조합에 따라 Shader Variant 라는 게 만들어지는데, 사용되지 않는 Shader Variant들이 워낙에 많기 때문에 이걸 제거해줌으로써 최적화를 하는 방식이다.

 

Shader Variant Stripping을 하는 방식은 크게 2가지가 있을 것 같은데, 1번째는 "사용하지 않는 키워드가 포함된 Shader Variant들을 제거하는 방식"이다. 다만 사용하지 않는 키워드를 사용하게 되었을 때 적절히 대응해주지 않으면 Material들이 깨져보일 수 있다는 점이나, 여전히 사용하지 않는 Shader Variant들이 다수 포함될 수 있다는 점이 문제이다. 2번째는 "사용하는 Shader Variant를 제외한 Shader Variant들을 제거하는 방식"이다. 유니티 2022부터 제공하는 Strict Shader Variant Matching 기능을 사용하면 이전에 비해 다소 쉽게 적용할 수 있지만, 문제가 있다면 사용되는 Shader Variant들을 직접 수집해야 한다는 점이고, 이 과정에서 누락되는 경우 Material들이 깨져보일 수 있다는 점이다. Material이 깨지는 경우, 아예 투명하게 안 보이는 경우도 있고 마젠타 색으로 보이는 경우도 있다.

 

우리는 2번째 방식을 사용하는 중인데, 덕분에 Shader 용량은 90% 이상 줄어들었지만, 지금도 종종 미수집된 Shader Variant들이 발견되기도 하며, 모바일 게임이다보니 업데이트가 종종 있을 것이고, 이때마다 새로운 Shader Variant들이 추가될 가능성이 높은데, 이걸 완벽히 수집할 수 있을까? 라는 걱정이 있다.(지금은... 일례로 어떤 직업에 빛나는 구체를 날리는 스킬이 추가되었을 때, 이 구체가 Shader Variant에 영향을 준다면, 모든 맵에서 이 스킬을 사용해보지 않는 이상 모든 Shader Variant가 수집될 것이라는 보장이 없다. 업데이트가 없는 경우라면 꼼꼼한 수집 + 정기적인 업데이트 적용으로 누락된 Shader Variant들을 추가해주면 어느정도 해결이 되겠지만, 지속적으로 추가되는 경우에는 문제가 될 수 있을 것 같다. 이전에 모든 Material들을 로드해서 수집하는 방식을 사용하기도 했었는데, 이게 맵에 따라서나 주변 구조물에 따라서 등 동적으로 변경되는 경우가 상당히 많아서 완벽한 수집이 어려웠다. 만약에 나중에 처음부터 새로 개발을 할 수 있다면, 이렇게 동적으로 바뀔 수 있는 Keyword에 대해서 컨트롤할 수 있으면 좋겠다는 생각이 든다. 지금은 현실적으로 어려운 상황이지만 개발 초기부터 TA와의 긴밀한 협력을 통해 모든 multi_compile 키워드에 대해 관리를 할 수 있으면 Shader Variant Stripping을 더 안전하고 효과적으로 할 수 있지 않을까? 라는 생각이 든다.