[열혈강의 유니티] 19. 프로젝트 최적화
개발도구 이야기/Unity Engine 2014. 12. 26. 11:22유니티의 퍼포먼스는 다른 엔진에 비해 좋은편이라고 말할 수 없다.
PC 플렛폼이라면 크게 신경쓰지않아도 되지만, 스마트폰 플렛폼 대상이라면 많은 신경을 써줘야한다.
( PC의 쿼드코어와 스마트폰의 쿼드코어와의 성능차이는 어마무시하게 차이가 난다. )
■ CPU 영역의 최적화
1.1 스크립트
- 무거운 충돌 검출은 일정 프레임마다 연산한다.
예시 ) 카트레이싱과 같이 8방향 충돌 검출을 할 경우. 한 화면에 8대의 차량이 있다면
8x8 = 64번의 충돌 검사가 매 프레임 발생한다. 이를 5~7프레임당 한번씩만 호출
하도록 한다면 많은 프레임을 확보 할 수 있다.
- 필요없는 콜백이나 함수 호출은 그때그때 삭제한다.
콜백은 되도록 사용하지 않아야한다. 텅 빈 콜백조차 성능에 영향을 준다.
- FixedDeltaTime을 줄여서 사용한다.
기본 FixedDeltaTime 0.02로 두어 1초에 50번 FixedUpdate() 함수가 호출
실무에서는, 0.04 ~ 0.06으로 사용해도 일반적인 게임에서는 별 문제가 없다.
충돌 검출 횟수 감소 및 RigidBody의 물리 시뮬레이션으로 인한 부하를 줄일 수 있다.
만약 물리 시뮬레이션이 자연스럽지 않다면 Rigidbody 컴포넌트에서 Interpolation 옵션을
사용하면 좀 더 자연스러운 연출이 가능하다.
- GetComponet() 함수 호출을 줄인다.
컴포넌트 이름으로 접근시 캐싱해 놓은 데이터를 사용하것이 아니라 매번 검색이 이뤄지기때문에 부하가 많이 발생한다.
void Update(){
transform.Translate( 0, 1, 0 );
}
대신
Transform myTransform = transform; // 캐싱
void Update(){
myTransform.Translate( 0, 1, 0 );
}
- 단순한 데이터 구조체는 Class보다는 Struct를 사용한다.
Struct는 메모리 할당이 이뤄지지 않고, 스택에 할당된다. 값 전달로 클래스보다 빠르다.
- 유니티 GUI 사용을 자제한다.
기존 4.6 버전 미만의 버전에서는 퍼포먼스의 문제로 가급적 사용을 자제하라.
- iOS 에서는 스크립트 호출 최적화 옵션을 사용한다.
- 가비지 컬렉션( Garbage Collection)을 최적화한다.
오브젝트 생성하는 것과 같은 동적 할당을 하지 않는다는 전제하에 개발해 나가는 것이 좋습니다. 하지만 피할 수 없다면 가비지 컬렉터를 주기적으로 호출하는 방법이 있습니다.
1) 잦은 메모리 할당과 1MB이하의 게임 오브젝트가 자주 생성되는 경우
// 주기적인 가비지 컬랙터 호출
if( Time.frameCount % 30 == 0 ){
System.GC.Collect();
}
가비지 컬렉터는 주의해서 사용해야한다.
꼭 프로파일러 통계를 보고 이를 바탕으로 결정을 내려야한다.
2) 한번에 큰 메모리를 할당하여 사용할 경우
// 큰 메모리 할당하는 경우. 예시.
void Start(){
Object[] tmp = new System.Object[1024];
for( int i; i < 1024; i++ ){
tmp[i] = new byte[1204];
tmp = null;
}
}
// 타이밍을 정해 놓고 가비지 컬랙터를 호출( 일시중지 or 로딩 )
System.GC.Collect();
- Update() 함수 안의 소스 코드를 최적화한다.
C/C++ 처럼 접근하지 말라. 스크립트당 120줄 이내로 간결하게 작성하는것을 원칙으로 삼자.
- 적당한 코루틴을 사용한다.
많이 사용하거나 잘못사용할 경우 프로그램이 무거워 질 가능성이 크다.
- 카메라에 보이는 오브젝트 수에 주의한다.
컬링도 CPU 파워를 사용한다는 점을 유의하자.
- FindObject 계열 함수들은 느립니다.
미리 캐싱해서 사용할 것을 권장!
- 동적 Instantiate와 Destory 보다 풀링을 사용한다.
예상되는 오브젝트는 미리 생성해놓고 비활성화시켰다가 원할 때 다시 활성화 시켜서 사용하는것이 좋다.
- 박싱과 언박싱은 사용을 자제한다.
생각보다 부하가 큰 작업이다.
- 나눗셈보다는 곱셈을 사용한다.
나눗셈보다 곱셈이 몇십배 빠르다.
- 고정 문자열은 readonly와 const 키워드를 사용한다.
이는 가비지 콜렉터에 의한 데이터 유실 문제를 피하며 고정 문자열에 대한 접근도 쉽게 해준다.
- 배열은 되도록 고정 타입을 사용한다.
배열 안의 요소들의 타입이 한가지라면 고정 타입을 사용하라.
ArrayList vec = new ArrayList() 보다 Vector3[] vec = new Vector3[1024]가 훨씬 빠르고 좋습니다.
1.2 물리 영역의 최적화
- 단순한 움직임은 물리 시뮬레이션을 이용하기보다 직접 이동한다.
- 캐릭터 컨트롤러 컴포턴트의 Move() 함수는 무겁다.
- 천( Colth ) 물리 사용을 자제한다.
- 트리거의 Stay 콜백 사용에 주의한다.
- 메시 충돌체 사용을 자제한다.
- 레이 캐스트 사용을 자제한다.
- FixedUpdate() 함수는 최대한 간결하게 사용한다.
- Maximum Allowed Timestep을 조절한다.
- Slover InteratioCount를 조절한다.
- Sleep을 조절한다.
- 충돌체를 사용할 때 주의사항
1) 불필요한 충돌체를 사용하지 마시오.
2) 충돌체들끼리 겹쳐 있으면 예상치 못한 충돌 검출 부하가 발생. 절대로 충돌체를 겹쳐서 설정하지 마세요
3) 메시 충돌체보다는 구 충돌체나 박스 충돌체를 사용하세요
- 관절( Joint )을 사용할 때 주의 사항
1.3 애니메이션
CPU 부하에 있어 큰 요소중 하나가 캐릭터 애니메이션! ( CPU는 연속적으로 모든 움직임을 실시간 보간 )
- 클립수는 최대한 적게 유지
- 같은 뼈대의 오브젝트는 애니메이션을 공유( 메카님 )
- 하나의 게임 오브젝트는 하나의 SkinMeshRender를 사용
- 애니메이션되는 오브젝트의 바이패드 뼈대개수는 적을 수록 좋다.
( 뼈대 개수는 15 ~ 60개[모바일~PC], 최대 99개가 넘지 않도록 하는 것이 좋다. )
- 유니티 4.0 이상에서 새로 생긴 Animator는 느리다.
Legacy 애니메이션을 더이상 지원하지 않는다지만 성능상의 이유로 계속 사용 될 수 있다.
■ GPU 영역의 최적화
1.1 Draw Call 에 대해서
DrawCall은 적을 수록 성능 향상에 좋다.
- Mertarial 재활용
- 움직이지 않은 오브젝트들은 정적(Static) 오브젝트로 설정
2.2 GPU 성능 개선 요령
- 물체를 여러번 렌더링하게 하는 그래픽 요소들( 반사, 그림자, 픽셀당 조명 등 ) 을 될 수 있으면 적게 사용하세요.
- 수많은 잘게 쪼개진 텍스처들보다 합쳐진 텍스처 아틀라스는 성능 개선에 많은 도움이 됩니다. 텍스처는 플랫폼에 맞는 적절한 압축을 한 정사각형( 2의 거듭제곱 ) 크기를 유지할수록 좋습니다.
- 카메라에서 멀리 있는 오브젝트들은 될 수 있으면 평명 오브젝트나 간단한 오브젝트로 대체하는 것이 좋습니다.
- 실시간 그림자보다는 라이트맵을 구워서 사용하는 것이 좋고 라이트맵 크기는 프로젝트에 맞추어 최대한 작게 사용하는 것이 좋다.
- 오브젝트와 오브젝트 사이의 쓸모없는 메시 정점 데이터는 그때그때 디자이너가 정리해주는 것도 아주 좋은 방법입닏.
- 텍스처의 Generate Mip Maps 옵션은 반드시 사용하세요. 밈맵을 끄면 픽셀이 깨지지 않는 깨끗한 텍스처를 볼 수는 있지만, 성능에는 엄청난 부담이 됩니다.
- 캐릭터나 배경에 조합 메시( Combien Mesh )를 사용하는 것도 성능을 개선하는 아주 좋은 방법입니다.
- 유니티의 파티클 시스템은 편하고 멋지지만 쓸데없는 성능 부하가 심합니다. 가급적 가벼운 작은 평명 오브젝트를 겹쳐서 만들어 사용하는 것도 성능 향상에 도움이 됩니다.
- 플렛폼별 권장 정점수( fer frame )
스펙에 따라 차이가 날 수 있고, 프로젝트 상황에 따라 다를 수 있다. 참고만 하자
1) PC : 20만 ~ 300만
2) iOS : 1만 ~ 2만
3) 안드로이드 : 5만 ~ 10만
- 유니티의 물리 엔진이 원활하게 동작하는 데 필요한 최소 FPS는 22프레임이다.
( 22프레임 이하로 내려가면 충돌 검출이 이루어지지 않을 수도있다 -> 30프레임 이상을 기준으로 잡고 프로젝트를 진행하라 )
- 될 수 있으면 Scene 하나당 재질( Materials ) 종류 수를 적게 유지하세요.
오브젝트들끼리 그리고 가능한 한 재질을 많이 공유하세요.
- 움직이지 않은 물체에 Static 속성을 적용해서 Static batching 같은 내부 최적화를 할 수 있게 하세요.
- 필요하지 않다면 픽셀 조명을 사용하지 말고, 지오메트리( Geometry )에 영향을 주는 픽셀 조명( 될 수 있으면 Directional Light)을 하나만 있게끔 하세요. 태양은 하나이며 부수적인 지역 조명들이 많으면 많을수록 드로우 콜도 상승하고 GPU에 부하를 줍니다.
- 필요하지 않다면 동적 조명을 사용하지 말고 대신 라이트맵을 사용하세요.
- 필요하지 않다면 안개를 사용하지 말고, 스카이박스보다는 간단한 평면 오브젝트나 작은 오브젝트 들을 사용하세요.
- 가능하다면 압축된 텍스처 형식을 사용하세요. 사용할 수 없다면 32bit보다 16bit를 주로 사용해보세요.
- 오클루전 컬링( Occlusion Culling)은 복잡한 정적 오브젝트가 많은 경우 성능 개선에 큰 도움을 줍니다. 실무에서는 레벨을 디자인하는 단계에서부터 오클루전 컬링을 고려하기도 합니다.
2.3 셰이더
셰이더는 모든 정점이나 픽셀마다 연산하는 그래픽 언어입니다.
변수만 적절히 변경해도 성능상의 이익을 볼 수 있다.
- 셰이더가 Cg/HLSL로 작성되어 있다면 다음과 같습니다.
1) float : 32bit. 정점 변형에 적합하지만, 성능 면에서는 가장 느립니다.
픽셀 셰이더에서는 가급적 피하고, 정점 셰이더에서 위치 계산용으로 사용하는
것이 좋습니다.
2) half : 16bit로 줄인 부동 소수점 형식으로 텍스처 UV 좌표에 적합하고 대략 float보다
두배정도 빠릅니다. 텍스처 UV 좌표용으로 사용하는 것이 좋습니다.
3) fixed : 10bit 부동 소수점 형식으로 색상, 조명 계산과 다른 고비용 연산에 적합.
float보다 4배정도 빠릅니다.
색상, 조명 정보와 법선용으로 사용하는 것이 좋습니다.
- 셰이더 작성 요령은 다음과 같습니다.
> 색상이나 단위 벡터( 길이가 1.0 )의 경우 fixed를 사용하세요.
> 다른 것들은 범위와 정확도가 괜찮다면 half를 사용하세요. 아니라면 float를 사용하세요.
> 유니티 내장 셰이더를 사용하고 있다면 모바일이나 Unlit 범주에 있는 것을 고르세요.
이 셰이더들은 기존 복잡한 셰이더를 간략화하고 비슷한 효과를 내게 만든 형태로, 모바일이 아닌 플랫폼에서도 잘 동작합니다.
> 여러 텍스처를 섞으러면 다중 패시 방식 대신, 픽셀 셰이더나 텍스처 합성기( Text Combiner )를 사용하세요
> 픽셀 셰이더에서 pow, sin, cos등과 같은 복잡한 수학 연산 사용을 최소화하세요.
> 프래그먼트( Fragment, 화면 픽셀 ) 당 텍스처를 적게 사용하세요.
■ Memory 영역
- Script에서 String 연산을 되도록 줄이세요. String 연산을 사용하면 가비지 컬랙터가 수거할 수 없는 쓰레기 메모리들이 생깁니다.
- 반복문을 foreach 보다 for 구문으로 교채하라.
foreach 반복문이 한번 돌 때마다 24Byte의 쓰레기 메모리를 생성합니다.
- 태그 비교 방법 수정
if( go.tag == "Enemy" ) -> if( go.CompareTag("Enemy") )
tag 속성을 호출하는 것은 추가적인 메모리를 할당하고 복사하며, 이해할 수 없는 쓰레기 메모리가 생성 된다.
- 메모리 오브젝트 풀을 반드시 사용하라.
- 가급적 LINQ 명령어를 사용하지 않는게 좋다.
LINQ 명령어는 중간 버퍼를 할당하는 경향이 있는데, 이는 가비지 컬렉터에게 부하를 주어 프레임이 튀는 현상이 발생할 수도 있다.
- 모바일 플랫폼에서 개발할 때 메모리 과다사용의 주범은 텍스처 이다.
iOS에서는 한번에 너무나 많은 메모리를 할당하면 운영체제 자체적인 보호를 위해 앱을
강제종료 시킨다. 1024x1024 이상급의 텍스처 사용은 가급적 자제하세요.
- 유니티 엔진에서 제공하는 각 플랫폼별로 텍스처 크기를 지정해주는 옵션은 정말 중요!
- 사용하지 않은 애셋은 메모리에 오래 있지 않도록 적절한 관리가 필요
Resources.UnloadUnusedAssets() -> 만능은 아님!
- 배경 사운드 파일은 그때그때 로드하는 것보다 한번 로드해 놓고 메모리에 남겨두는 것이 좋습니다. 이때 많은 배경 음악을 사용한다면, 안 쓰는 배경 음악과 교체하는 식으로 다른 계획이 필요합니다.
- 가급적 메모리 프로파일러를 자주 테스트해보는 것이 좋다. MonoBehaviour 역시 메모리 유실을 유발할 수 있다.
- 텍스처의 경우 None Power Texture는 메모리에 있어서 최악의 선택입니다.
하지만 UI라면 어쩔수 없기에, 이때 바로 텍스처를 합쳐놓은 텍스처 아틀라스를 사용하는 것이 좋습니다.
- 가급적 텍스처를 압축해서 사용하는 것이 좋고, WAV 파일이나 BMP 포맷은 사용하지 않는 것이 좋습니다. 마찬가지로 PSD 파일은 절대 재질로서 사용하지 마세요.
'개발도구 이야기 > Unity Engine' 카테고리의 다른 글
유니티 작업에 대한 50 팁 (모범 사례) 50 Tips for Working with Unity (Best Practices) (0) | 2014.12.24 |
---|