
게임 제작에서는 텍스처, 메시, 애니메이션, 사운드 등 다양한 종류의 에셋을 많이 다루게 된다. 이 장에서는 이러한 에셋에 대해 성능 튜닝을 할 때 주의해야 할 설정 항목 등 실무적인 지식을 정리한다.
텍스처의 원천이 되는 이미지 데이터는 게임 제작에 있어 필수적인 요소이다. 반면, 메모리 소비량이 상대적으로 많기 때문에 적절한 세팅이 필요하다.
아래 그림은 Unity의 텍스처 임포트 설정이다.

이 중 특히 주의해야 할 부분을 소개한다.
이 옵션은 기본적으로 비활성화되어 있다. 비활성화 상태에서는 GPU 메모리에만 텍스처가 전개된다. 활성화하면 GPU 메모리뿐만 아니라 메인 메모리에도 복사되므로, 소비량이 2배로 증가한다. 따라서 Texture.GetPixel 이나 Texture.SetPixel 같은 API를 사용하지 않고 Shader에서만 접근하는 경우 반드시 비활성화해야한다.
또한 런타임에 생성한 텍스처라는 아래 예시와 같이 makeNoLongerReadable을 true로 설정하면 메인 메모리에 복사되는 것을 방지할 수 있다.
<예시> : makeLongerReadable 설정 방법
texture2D.Apply(updateMipmaps, makeNoLongerReadable: true)
GPU 메모리에서 메인 메모리로 텍스처를 전송하는 데 시간이 오래 걸리므로, 텍스처를 읽을 수 있는 경우 양쪽 모두에 텍스처를 전개하는 것이 성능 향상에 도움이 된다.
밉맵 설정을 활성화하면 텍스처의 메모리 사용량이 약 1.3배 증가한다. 이 설정은 일반적으로 3D상의 오브젝트에 대해 설정하는 것이 일반적이며, 먼 거리에 있는 오브젝트에 대해 흔들림 완화 및 텍스처 전송량 감소를 목적으로 설정한다. 2D 스프라이트나 UI 이미지에 대해서는 기본적으로 필요하지 않으므로 비활성화 해두자.
Aniso Level은 오브젝트를 얕은 각도로 그릴 때 텍스처가 흐릿하게 보이지 않도록 하는 기능이다. 이 기능은 주로 지면이나 바닥 등 멀리까지 이어지는 오브젝트에 사용된다. Aniso Level 값이 높을수록 더욱 선명하게 보이지만, 처리 비용은 더 많이 든다.

설정값은 0~16까지 있지만 조금 특별한 기능이 존재한다.
0 : 프로젝트 설정에 관계없이 항상 비활성화.
1 : 기본적으로 비활성화. 단, 프로젝트 설정이 Forced On인 경우 9~16의 값으로 고정된다.
그 외 : 해당 값으로 설정
텍스처를 임포트하면 기본적으로 1로 설정되어 있다. 따라서 고사양 단말기가 아닌 이상 Forced On 설정은 권장하지 않는다. Forced On 설정은 'Project Settings -> Quality'의 Anisotropic Textures에서 설정할 수 있다.

Aniso Level 설정이 효과가 없는 오브젝트에서 활성화되어 있지 않은지, 효과가 있는 오브젝트에 대해 쓸데없이 높은 설정값이 설정되어 있지 않은지 확인하자.
Aniso Level에 의한 효과는 선형적이지 않고 단계적으로 전환되는 동작을 하고 있다. 0~1, 2~3, 4~7, 8 이상 등 4단계로 변하는 것 같다.
텍스처는 특별한 이유가 없는 한 압축하는 것이 좋다. 만약 프로젝트 내에 비압축 텍스처가 존재한다면, 개발자의 실수이거나 규정이 없는 것일 수 있다. 압축 설정에 대한 자세한 내용은 [2.3.3 이미지 압축]에서 확인할 수 있다.
이러한 압축 설정은 실수를 방지하기 위해 TextureImporter를 이용하여 자동화하는 것을 권장한다.
<예시> : TextureImpoter 자동화 예시
using UnityEditor;
public class ImporterExample : AssetPostprocessor
{
private void OnPreprocessTexture()
{
var importer = assetImporter as TextureImporter;
// Read/Write 설정 등도 가능
importer.isReadable = false;
var settings = new TextureImporterPlatformSettings();
// Android = "Android", PC = "Standalone" 지정
settings.name = "iPhone";
settings.overridden = true;
settings.textureCompression = TextureImporterCompression.Compressed;
// 압축 형식 지정
settings.format = TextureImporterFormat.ASTC_6x6;
importer.SetPlatformTextureSettings(settings);
}
}
또한 모든 텍스처가 동일한 압축 형식일 필요는 없다. 예를 들어, UI 이미지 중 전체적으로 그라데이션이 적용된 이미지는 압축을 인한 품질 저하가 눈에 띄기 쉽다. 이 경우 대상 이미지 중 일부 이미지만 압축률을 낮게 설정하는 것이 좋다. 반대로 3D모델과 같은 텍스처는 품질 저하가 눈에 잘 띄지 않으므로 압축률을 높이는 등 적절한 설정을 찾는 것이 좋다.
Unity로 임포트한 메쉬(모델)를 다룰 때 주의해야 할 사항을 소개한다. 임포트한 모델 데이터는 설정에 따라 성능이 향상된다. 주의해야 할 점은 다음 4가지이다.
Mesh의 첫 번째 주의 사항은 Read/Write Enabled이다. 모델 인스펙터에 있는 이 옵션은 기본적으로 비활성화되어 있다.

런타임 동안 메시를 사용할 필요가 없다면 비활성화하는 것이 좋다. 구체적으로 모델을 Unity에 배치하고 AnimationClip을 재생하는 정도라면 Read/Write Enabled는 비활성화해도 문제없다.
활성화하면 CPU에서 접근할 수 있는 정보를 메모리에 보관하기 때문에 메모리를 두배로 소모한다. 비활성화하는 것만으로도 메모리를 절약할 수 있으니 꼭 확인해보자.
Vertex Compression은 메시의 버텍스 정보 정확도를 float에서 half로 변경하는 옵션이다. 이를 통해 런타임 시 메모리 사용량과 파일 크기를 줄일 수 있다. 'Project Settings -> Player'의 Other에서 설정할 수 있으며, 기본 설정은 다음과 같다.

단, 이 Vertex Compression은 다음과 같은 조건에 해당하면 비활성화되므로 주의해야한다.
Mesh Compression은 메시의 압축률을 변경할 수 있다. 압축률이 높을수록 파일의 크기가 작아져서 저장공간을 차지하는 비율이 줄어든다. 압축된 데이터는 런타임에 압축이 풀린다. 따라서 런타임 메모리 사용량에는 영향을 미치지 않는다.
Mesh Compression의 압축 설정에는 4가지 옵션이 있다.

[4.2.2 Vertex Compression] 에서도 언급했지만, 이 옵션을 활성화하면 Vertex Compression이 비활성화된다. 특히 메모리 사용량 제한이 심한 프로젝트에서는 이 단점을 잘 파악할 후 설정하자.
Optimize Mesh Data는 메시에 불필요한 버텍스 데이터를 자동으로 삭제하는 기능이다. 불필요한 버텍스 데이터 판단은 사용 중인 Shader에서 자동으로 판단한다. 이는 런타임 시 메모리, 스토리지 모두 절감 효과가 있다. 'Project Settings -> Player'의 Other에서 설정이 가능하다.

이 옵션은 버텍스 데이터가 자동 삭제되어 편리하지만, 예기치 못한 문제를 일으킬 수 있으므로 주의해야 한다. 예를 들어 런타임에 material이나 shader를 전환할 때, 접근한 프로퍼티가 삭제되어 렌더링 결과가 이상해질 수 있다. 또한, Mesh만 에셋 번들링할 때 머티리얼 설정이 잘못되면 불필요한 버텍스 데이터로 판단되는 경우도 있다. 이는 Particle System과 같이 Mesh에 대한 레퍼런스만 갖게 하는 경우가 많다.
머티리얼은 오브젝트를 어떻게 그릴지 결정하는 중요한 기능이다. 익숙한 기능이지만, 잘못 사용하면 쉽게 메모리 누수가 발생할 수 있다. 이번 절에서는 안전한 머티리얼을 사용하는 방법을 소개한다.
머티리얼의 가장 큰 주의점은 파라미터에 접근하는 것만으로도 머티리얼이 복제된다는 점이다. 그리고 복제된 것을 알아차리기 힘들다는 점이다.
아래 예시 코드를 보자.
<예시> : Material 복제
Material material;
void Awake()
{
material = renderer.material;
material.color = Color.green;
}
머티리얼의 color 프로퍼티에 Color.green을 설정한 간단한 처리이다. 이 렌더러의 머티리얼은 복제되어 있다. 그리고 복제된 오브젝트는 명시적으로 Destroy해야 한다.
<예시> : 복제된 Material 삭제
Material material;
void Awake()
{
material = renderer.material;
material.color = Color.green;
}
void OnDestroy()
{
if(material != null)
{
Destroy(material)
}
}
이렇게 복제된 머티리얼을 Destroy하면 메모리 누수를 방지할 수 있다.
동적으로 생성한 머티리얼도 메모리 누수의 원인이 되기 쉽다. 생성한 머티리얼도 다 사용한 후에는 반드시 Destroy하자.
<예시> : 동적으로 생성한 material 삭제
Material material;
void Awake()
{
material = new Material(); // 머티리얼을 동적으로 생성
}
void OnDestroy()
{
if(material != null)
{
Destroy(material) // 다 사용한 머티리얼을 Destroy
}
}
생성한 머티리얼은 다 사용한 시점에 Destroy하자. 프로젝트의 규칙이나 사양에 따라 적절한 타이밍에 머티리얼을 Destroy하자.
애니메이션은 2D,3D를 막론하고 많이 사용되는 에셋이다. 이번 절에서는 Animation Clip과 Animator에 대한 실습을 소개한다.
모션은 내부적으로 각 버텍스가 어떤 뼈의 영향을 얼마나 받는지 계산하여 위치를 업데이트하고 있다. 이 위치 계산에 고려하는 뼈의 수를 스킨웨이트 수 또는 인플루언스 수라고 한다. 따라서 스킨웨이트 수를 조정하면 부하를 줄일 수 있다. 단, 스킨 웨이트 수를 줄이면 외형이 이상하게 보일 수 있으므로 조정할 때는 검증을 거쳐야 한다.
스킨 웨이트 수는 'Project Settings -> Quality'의 Other에서 설정할 수 있다.

이 설정은 스크립트에서 동적으로 조정할 수도 있다. 따라서 저사양 단말기는 Skin Weights를 2로 설정하고, 고사양 단말기는 4로 설정하는 등의 미세 조정이 가능하다.
<예시> : SkinWeight 설정 변경
// QualitySettings를 통째로 바꾸는 방법
// 인수의 번호는 QualitySettings의 순서대로 0부터 시작한다.
QualitySettings.SetQualityLevel(0);
// SkinWeights만 변경하는 방법
QualitySettings.skinWeights = SkinWeights.TwoBones;
애니메이션 파일은 키 수에 의존하여 저장공간과 런타임 메모리를 압박한다. 키 수를 줄이는 방법 중 하나로 Anim. Compression이라는 기능이 있다. 이 옵션은 모델 임포트 설정에서 애니메이션 탭을 선택하면 나타난다. Anim. Compression을 활성화하면 에셋 임포트 시 불필요한 키가 자동으로 삭제된다.

Keyframe Reduction은 값의 변화가 적은 경우 키를 줄인다. 구체적으로는 직전 커브와 비교하여 오차 (Error) 범위 안에 들어갔을 때 삭제된다. 이 오차 범위는 조정이 가능하다.

조금 복잡하지만 Error 설정은 항목에 따라 값의 단위가 다르다. Rotation은 각도, Position과 Scale은 퍼센트이다. 캡처한 이미지의 허용 오차는 Rotation이 0.5도, Position과 Scale은 0.5%가 된다. 자세한 알고리즘은 유니티 문서(https://docs.unity3d.com/Manual/class-AnimationClip.html#tolerance
)에 있으니 궁금하면 살펴보자.
Optimal은 좀 더 이해하기 어려운데, Dense Curve라는 형식과 Keyframe Reduction이라는 두 가지 감소 방법을 비교하여 데이터가 작아지는 쪽을 채택한다. 여기서 주의해야 할 점은 Dense는 Keyframe Reduction에 비해 크기가 작아진다는 것이다. 하지만 노이즈가 발생하기 쉬워 애니메이션 품질이 떨어질 수 있다. 이 특성을 파악한 후, 실제 애니메이션을 눈으로 확인하여 허용 가능한지 확인해야 한다.
애니메이터는 기본 설정으로 화면에 보이지 않아도 매 프레임마다 업데이트가 이루어진다. 이 업데이트 방식을 변경할 수 있는 컬링 모드라는 옵션이 있다.

각 옵션의 의미는 다음과 같다.
| 종류 | 의미 |
|---|---|
| Always Animate | 화면 밖에서도 항상 업데이트한다. (기본 설정) |
| Cull Update Transform | 화면 밖에 있을 때 IK나 Transform을 쓰지 않는다. 스테이트 머신의 업데이트는 수행한다. |
| Cull Completely | 화면 밖에 있을 때 스테이트 머신을 업데이트하지 않는다. 애니메이션이 완전히 멈춘다. |
각 옵션에 대한 주의 사항이 있다. 먼저 Cull Completely를 설정하는 경우, Root모션을 사용할 때는 주의가 필요하다. 예를 들어 화면 밖에서 프레임 인하는 애니메이션의 경우, 화면 밖에 있기 때문에 애니메이션이 즉시 멈춰버린다. 그 결과 언제까지나 프레임 인을 하지 않게 된다.
다음은 Cull Update Transform입니다. 이 옵션은 Transform의 업데이트를 건너뛰기만 하면 되므로 매우 편리한 옵션으로 느껴진다. 하지만 흔들림과 같이 Transform에 의존하는 처리가 있는 경우 주의해야 한다. 예를 들어, 캐릭터가 프레임 아웃되면 해당 타이밍의 포즈에서 업데이트가 되지 않는다. 그리고 다시 프레임 인할 때 새로운 포즈로 갱신되기 때문에 그 탄력으로 흔들림이 크게 움직일 수 있다. 각 옵션의 장단점을 파악한 후 설정을 변경하는 것이 좋다.
또한, 이러한 설정을 사용하더라도 애니메이션의 업데이트 빈도를 동적으로 세밀하게 변경하는 것은 불가능하다. 예를 들어 카메라와 거리가 먼 오브젝트의 애니메이션 업데이트 빈도를 절반으로 줄이는 등의 최적화이다. 이 경우 AnimationClipPlayable을 이용하거나 Animator를 비활성화한 후 직접 Animator.Update를 호출해야 한다. 둘다 자체적으로 스크립트를 작성해야 하지만, 전자에 비해 후자가 더 쉽게 사용할 수 있을 것이다.