2월 알쓸유잡 : 메모리 최적화를 위한 에셋 관리
💡 메모리에 영향을 미치는 Asset 관리
🍊 Asset Dependency
- 중복 리소스 체크
- 유니티는 중복 파일을 체크하지 않음
- 실수로 동일한 파일을 다른 폴더에 넣지 않았는지 확인
- Mesh, Material, Texture는 종속 관계가 존재 가능하다.
- Ex : 탱크 오브젝트에서 사용하는 Material과 해당 Material이 사용하는 Texture
→ 탱크는 Material에 종속, Material은 Texture에 종속
- 많은 종속 관계는 일대다 관계일 수 있다.
→ Asset Bundle 사용 시 중복 발생 가능
- Ex : C Texture를 사용하는 A, B Material을 각각 다른 Bundle로 묶는 경우 Texture 중복 발생
→ Addressables 사용 시 중복 관리가 수월함
🍋 Audio
- Force To Mono
- 쉽게 메모리 줄이기 가능
- Stereo 정보를 없애고 Mono로 만들어줌
- 모바일에서는 좋은 사운드 퀄을 요구하지 않기 때문에 사용해도 괜찮음
- Load Type
- 사운드 타입에 따라 적절하게 설정
- 배경 음악은 빠른 속도 요구 X
- 발소리, 총성, 타격음 등은 빠른 속도 요구
- Decompress on Load (< 256kb)
- 메모리에 올릴 때 압축을 풀어서 올림
- 재생 속도가 매우 빠름
- 재생 속도가 매우 빠르고 사이즈가 작은 오디오가 아니라면 사용하면 안 되는 옵션
- Compressed into memory (< 1mb)
- Streaming (> 1mb)
- Compression Format
- 매우 짧은 클립은 ADPCM
- 대부분의 경우는 Vorbis(ogg) 권장
- iOS에서는 MP3 사용 이점이 적음
- 일반적으로 iOS 앱 개발에는 MP3 사용이 권장되지만 (iOS가 MP3를 하드웨어 레벨에서 디코딩 가능하기 때문), 유니티는 디코딩을 소프트웨어로 처리하기 때문
- Mute 되어도 메모리에는 존재한다.
- 배경 음악 선택 옵션이나 바꿔야 하면 그때 그때 메모리에 올리고 내리는 방식으로 구현해야 함
🍏 Mesh
- Mesh Compression은 저장 용량
→ 메모리와는 무관함
- Read/Write Enabled 옵션 해제할 것
- 활성화하면 메모리 복제가 발생
→ API에서 런타임 중에 접근해서 Mesh를 변형하는 등의 작업을 위해 활성화하는 옵션
→ API에서 접근하는 것은 CPU에서, Mesh 사용은 GPU에서 하게 되므로 양쪽 메모리에 존재하게 됨
- 2019.3 버전부터는 기본적으로 비활성화
- 불필요하면 비활성화 해야 되는 옵션들
- Rig
- BlendShapes
- Normal
- Tangent
- Lightmap UVs
- Generate Colliders
🍑 Shader Variants
- 패키지 용량 이슈가 크지만 메모리도 영향 있다.
- 하나의 Shader는 수많은 바이너리 조각으로 파생된다.
- 수많은 조건들의 조합 경우의 수
- 노멀맵, 포그, 라이트맵, 인스턴싱, 플랫폼 등
- 에디터에서의 작동 ≠ 플레이어 빌드에서의 작동
- 에디터에서는 미리 모든 경우의 Shader를 만들어놓지 않음
→ 그때 그때 필요한 것들만 렌더링
- Shader Variants를 줄이기 위한 기법들
- Graphics API 지정 (Player Settings)
- Graphics API마다 Variants를 만들어야 하므로 사용하는 API만 설정
- Shader Stripping (Graphics / URP Settings)
- 특정 옵션을 지정하면 다른 옵션과 연관된 Shader들은 만들지 않음
- URP Asset에서 미 사용 기능 비활성화
- multi_compile vs shader_feature
- ShaderVariantCollection (Hiccup vs Memory)
- 미리 Loading하게 되면 런타임 동안 프레임이 튀는 현상은 줄어들지만 미리 메모리를 선점하게 됨
- 상황에 맞게 사용
- Log Shader Compilation
🍒 Text Mesh Pro
- 글자들을 미리 Texture로 제작한다.
- 영미권 언어는 큰 문제가 없다.
- 한글은 초성, 중성, 종성 조합 방식
- 허용 글자 범위
- 한글 : 2350자
- 모든 글자를 허용하게 되면 10000자를 넘게 됨
- Static vs Dynamic
- Static
- 사용될 모든 문자 Atlas를 미리 생성
- 런타임 시 원본 폰트가 필요 없음
- 개발자가 예측 가능한 범위에 사용
- Dynamic
- 실시간으로 필요한 폰트 Atlas 갱신
- 런타임 시 원본 폰트 필요
- 사용 문자들의 범위를 예측 불가능할 때 유용
- Multi Atlas Textures
- Draw call vs 대역폭(Atlas Resolution)
- 4096으로 하게 되면 모바일에서는 느려질 수 있음
- 대역폭을 줄이게 되면 여러 개의 Atlas를 생성
→ Draw call Batching이 깨질 수 있음
🍇 Baked Lighting
- Lightmap
- Texture에 Lighting 결과를 미리 구워서 저장
- static object에 적용
- 저장 형태 : Texture2D
- 넓은 씬에서는 Lightmap을 많이 만들어내므로 메모리 이슈 발생 가능성 있음
- Subtractive, Baked Indirect, Shadowmask에 따라 Lightmap을 여러 개 만들어낼 수 있음
- Light Probe
- 주로 dynamic object 대응 용도 (static도 가능)
- Diffuse term만 가능 (specular term 불가능)
- 저장 형태 : 개당 float * 30 +
- 메모리를 많이 차지하는 편은 아님
- Reflection Probe
- Specular term을 위한 데이터
- 저장 형태 : 개당 Cubemap Texture
- 많이 사용할수록 메모리 이슈 발생 가능성 큼
🥭 Texture
- 가능한 사이즈를 작게 할 것
- 적절한 Texture Compression Format
- 2D 및 UI는 SpriteAtlas 적극 활용할 것
- Read/Write Enabled 옵션은 해제하기
- Mipmap
- 2D 및 UI에서는 대부분 필요 없음
- 3D 게임에서는 필수
🥝 Texture Compression
- PNG, JPEG 등 원본 이미지 Format과는 무관하다.
- 기기별 지원되는 Texture Format 사용해야 한다.
- 기기 미 지원 포맷 사용 시 압축이 풀릴 수 있음
- 모바일에서는 ASTC 추천
- POT vs NPOT
- Power of Two (256, 512, 1024…)
- ASTC는 NPOT를 지원하지만 밉맵 사용 시 POT
🍍 POT vs NPOT
- Power of Two (256, 512, 1024…)
- 일반적으로 사용하는 Texture들은 POT 사이즈를 사용하도록 강제되어 왔음
- ASTC는 NPOT(Not POT) 사이즈를 지원한다.
- Mipmap 사용 시 POT 사용으로 강제된다.
- 즉 3D 에셋용 Texture는 사실상 POT만 사용
- UI 및 2D는 NPOT가 가능하다.
- 하지만 Sprite Atlas 권장
- Sprite Atlas는 POT
🍉 Mipmap
- 가까이 있는 오브젝트는 고해상도 Texture를, 멀리 있는 오브젝트는 저해상도 Texture를 입혀서 메모리 대역폭을 효율적으로 만들어주는 것
- 3D 게임에서는 무조건 활성화
- 원본 Texture의 작은 버전을 여러 개 만들어냄 → 원래 Texture보다 33.333% 정도 메모리가 늘어남
- 2D나 UI는 원근법이 필요 없기 때문에 Mipmap 사용 X
- Texture LOD 같은 개념
🍐 Scene loading
- Scene A 로드 → Scene B 로드 → Scene A 제거
- Scene A에 대한 Resource가 있는 상태에서 Scene B에 대한 Resource도 메모리에 올라오게 되면 순간적으로 메모리가 터질 수 있음
(Scene A, B의 메모리가 무거운 경우)
- 무거운 Scene 사이를 이동할 때는 가벼운 중간 Scene을 Load 할 것을 권장
끗....!