Game Programming/Game Engine

Unity3D :: 게임 최적화 기법

VallistA2015.02.16 17:09

프로젝트를 하고 완성이 될 무렵, 게임이 굉장히 괴랄한 프레임을 뿌려대며 제대로 굴러가지 않기 시작했다.

필자의 컴퓨터는 사양이 굉장히 좋다고 자부할 수 있는 컴퓨터였다. 유니티 에디터가 잘못인지 소스코드 상에서 문제가 있는 것인지 보기 위해 탐색을 시작하게 되었고. 프로파일러를 통해 개선할 부분을 다수 개선하였다.


이 밑부터는 내가 경험한 것을 바탕으로 최적화를 한 방법에 대해 서술한다.

 - 프로젝트 진행시 참고했던 박민근 님의 유니티 최적화 테크닉을 참고하였다.

 - http://www.slideshare.net/agebreak/141206-42456391 [링크]

 - 아마 위 파워포인트를 정리한거라고 보면 될 듯 싶다. (그 외에 추가한 것들도 존재한다. 이것들은 오리지날..)


1. 소스코드 병목의 파악.


그 누구나 먼저 볼 곳이 아마도 자신의 소스코드에 대한 문제가 아닐까 싶다.

리소스가 문제라고 해서 리소스를 바꿧는데, 그럼에도 불구하고 문제가 있으면 그 것은 좀 아니다 싶지 않은가?


- DP Call

- 복잡한 연산

- 3D의 수많은 버텍스, 연산

- 픽셀, 오버 드로우

- 셰이더의 많은 연산

- 압축되지 않고 큰 텍스쳐들

- 고 해상도 프레임 버퍼


2. 스크립트 최적화


- 유니티 객체들은 멤버변수에 저장하여, 캐싱을 이용하는게 좋다.

 -> 예를 들자면, Find 라는 검색이 붙은 것들은 왠만하면 지속적으로 사용하지 않는게 좋다. 왜냐하면 Find 들은 프로젝트안의 모든 오브젝트를 순환하며 그 안의 클래스의 스트링을 비교하기 때문이다.


- Instantiate, Destroy 이 두 놈의 프리팹 생성/해제는 비용이 상당히 크다.

 -> 프리펩은 미리 생성해둔후 오브젝트를 풀에서 쓰도록 하자.


- Update 함수 보다는 Coroutine을 사용하는게 좋다.

 -> Update 함수를 쓰지말고, Coroutine을 무한 루틴시켜서 사용하도록 하자. 필자같은 경우에는 게임 관련은 전부 Coroutine에서 사용을 했으며 UI 같은 경우 메인 업데이트에서 돌렸다.


- 나눗셈 말고 곱셈의 사용.

 -> 나눗셈은 곱셈보다 연산속도가 월등히 느리다 100 / 10 이런식으로 사용하지말고 100 * 0.1 을 사용하라는 소리.


- 박싱과 언박싱은 부하가 큰 작업 이므로 많이 사용하지 말자.

 -> 박싱과 언 박싱이란. 

 --> http://vallista.tistory.com/entry/C-%EB%B0%95%EC%8B%B1%EA%B3%BC-%EC%96%B8%EB%B0%95%EC%8B%B1 [링크]


- Magnitude 보다 sqrMagnitude를 사용하여 비교해서 쓴다. (제곱근 계산 X)

 -> Unity 공식 문서에도 써져있다. 

 --> If you only need to compare magnitudes of some vectors, you can compare squared magnitudes of them using sqrMagnitude (computing squared magnitudes is faster).


- 삼각함수의 값은 상수로 저장하고 사용하는게 좋다.


- 문자열은 readonly 혹은 const 키워드를 사용하여, 가비지 컬렉션으로부터 벗어나도록 한다.


3. 가비지 컬렉터


- 우리가 쓰고있는 MonoBehavior는 메모리 관리에 GC가 자동호출되도록 설계되어 있는데, 이 GC가 프로그래머들한테는 좋을 수도 있고, 안좋을 수도 있다. GC가 실행되는 동안 유저들은 게임에서 갑자기 렉이 걸리며, 그렇지 않기위해 우리는 게임을 GC를 피해 만들어야 한다. (필자한테는 상당히 거리낌이 있다.) 이제 이 GC와 친하게 지내기위해 우리도 몇가지 방법을 써서 길들여야하는데, 그 방법에 대해 서술한다. 


- 무엇이든 동적 생성 및 해제는 부하가 굉장히 큰 작업이다. 그래서 위에 언급했다시피 오브젝트 풀 기법을 사용하여 메모리를 관리하도록 하자.


- 오브젝트가 해제되면 다음과정으로는 GC가 동작되서 렉이 걸릴수 밖에 없다. 즉 오브젝트를 만들어 둔 후 활성화 또는 비 활성화를 이용하여 사용하도록 하자.


- 문자열 병합은 StringBuilder의 Append를 사용하면 된다. 왜냐하면 string + string은 임시 문자열을 뱉기 때문에 가비지 컬렉션이 일어나는 환경을 제공한다.


- foreach 대신에 for를 이용하도록 한다. Foreach는 한번 돌리면 24byte의 쓰레기 메모리를 생성시키며 수많이 돌면 더 많은 메모리를 생성시키므로 for을 이용하도록 한다.


- 태그 비교에서는 CompareTag() 를 사용하도록 한다.

객체의 tag 프로퍼티를 호출하는 것은 추가 메모리를 할당하며 복사를 하게된다.


- 모든 비교문에서 .equals()를 사용하도록 하자. == 구문으로 사용하면 임시적인 메모리가 나오게 되며 가비지 컬렉션의 먹이를 준다.


- 데이터 타입은 Class 대신 Struct 를 사용하여 만들어 주면 메모리 관리가 된다. 구조체는 메모리 관리를 Stack에서 하므로 GC에 들어가지 않는다.


- 즉시 해제시에는 Dispose를 수동으로 호출하게 되면 즉시 클린업된다.


- 임시 객체를 만들어내는 API를 조심해야한다.

GetComponents<T>, Mesh, Vertices, Camera.allCameras, 이런것들..


- 객체의 변경 사항에 대해 캐싱한다. 객체의 이동과 변형에 대한 처리를 캐싱해서 매프레임당 한번만 처리


- 컴포넌트 참조를 캐싱한다.

GetComponent()는 한번만 호출되며, 객체를 캐싱해서 사용한다.


- 콜백함수 안쓰는 것들을 제거해야한다. Start(), Update(), OnDestroy() 같은 것들은 비어있어도 성능에 영향을 끼치므로 지워주도록 하자.


4. 텍스쳐


- 텍스쳐를 압축 할때는 권작 압축 텍스쳐를 사용하도록 하자.

 -> 아이폰 : PVRCT

 -> 안드로이드 (Tegra) : DXT

 -> 안드로이드 (Adreno) : ATC

 -> 안드로이드 (공통) : ETC1


- 텍스쳐 사이즈는 2의 제곱이어야 한다.

 -> POT(Power of Two)

 -> POT가 아닌경우 POT 텍스쳐로 자동 변환 로딩이 된다.

 -> 900x900은 실제로는 1024 x 1024로 된다.


- 텍스쳐 아틀라스를 활용

 -> 텍스쳐 아틀라스로 최대한 묶어 사용 (NGUI Atlas 같은 사용법)

 -> UI 만 아니라, 같은 재질의 오브젝트를 묶어 사용하게 된다.


- 압축된 텍스쳐와 밉맵을 사용한다. (대역폭 최적화)


- 32bit가 아닌 16bit 텍스쳐 사용도 상황에 맞게 고려한다.



[사진 1] 보통 이렇게 최적화 하면 된다. (안드로이드)


5. Mesh


- Import 시에 언제나 "Optimize Mesh" 옵션을 사용한다.

 -> 변환 전/ 변환 후 버텍스 캐쉬를 최적화 해준다.


- 언제나 optimize Mesh Data 옵션을 사용한다.

 -> Player Setting > Other Settings

 -> 사용하지 않는 버텍스 정보들을 줄여 준다. (tangents, Normal, Color, ETC...)


6. 오디오


- 모바일에서 스테레오는 의미 없음

 -> 모두 92kb, 모노로 인코딩


- 사운드 파일을 임포트하면 디폴트로 3D 사운드 설정

 -> 2D 사운드로 변경


- 압축 사운드 (mp3, ogg), 비압축 사운드 (wav) 구별

 -> 비압축 사운드 : 순간적인 효과음, 이펙트

 -> 압축 사운드 : 배경 음악


7. 폰트 리소스 최적화


- Packed Font를 사용

 -> R,G,B,A 채널에 저장하는 기법으로 메모리 용량을 1/4로 절약하도록 하자.

 -> 단 Packed Font는 단점이 너무 많다. 일반적으로 글씨에 그림자도 못 넣을 뿐더러 알파도 적용이 안된다. 그리고 NGUI Atlas 적용도 안됨.


8. 리소스


- ResourceLoadAsync() 함수는 엄청 느리다.

 -> 게임 레벨 로드시에 사용했을 경우, 일반 함수에 비해 수십배나 느리다.


9. 컬링


- Frustum Culling (프러스텀 컬링)

 -> Layer 별로 컬링 거리 설정이 가능 (NGUI 의 경우 Panel 에서 Smooth Culling 도 먹일 수 있다.)

 -> 멀리 보이는 중요한 오브젝트는 거리를 멀게 설정하고 중요도가 낮은 풀이나 나무등은 컬링 거리를 짧게 설정하여 컬링한다.


- Occlusion Culling (오클루젼 컬링)

 -> Window->Occlusion Culling 메뉴에서 설정 가능

 -> Occlusion Culling 이란 카메라에서 보이는 각도의 오브젝트 들만 렌더링 하는 기법을 뜻한다.


- Combine (오브젝트 통합)

 -> 드로우콜은 오브젝트에 설정된 재질의 셰이더 패스당 하나씩 일어남.

 -> 렌더러에 사용된 재질의 수 만큼 드로우 콜이 발생함.

 ->> Combine (통합)

  ->>> 성질이 동일한 오브젝트들은 하나의 메쉬와 재질을 사용하도록 통합

  ->>> Script 패키지 - CombineChildren 컴포넌트 제공

   ->>>> 하위 오브젝트를 모두 하나로 통합

  ->>> 통합하는 경우 텍스쳐는 하나로 합쳐서, Texture Atlas를 사용해야함.


- Batch


- Static Batch

 -> Edit > Project Setting > Player 에서 설정한다.

 -> 움직이지 않는 오브젝트들은 static으로 설정해서, 배칭이 되게 함.

 -> Static으로 설정된 게임 오브젝트에서 동일한 재질을 사용 할 경우, 자동으로 통합된다.

 -> 통합되는 오브젝트를 모두 하나의 커다란 메쉬로 만들어서 따로 저장한다. (메모리 사용량 증가)


- Dynamic Batch 

 -> 움직이는 물체를 대상으로 동일한 재질을 사용하는 경우, 자동으로 통합

 -> 동적 배칭은 계산량이 많으므로, 정점이 900개 미만인 오브젝트만 대상이 된다.


10. 라이팅


- 라이트 맵 사용

  -> 고정된 라이트와 오브젝트의 경우(배경) 라이트 맵을 최대한 활용

  -> 아주 빠르게 실행됨 (Per-Pixel Light 보다 2~3배)

  -> 더 좋은 결과를 얻을 수 있는 GI와 Light Mapper를 사용할 수 있다.


- 라이트 렌더 모드

 -> 라이팅 별로 Render Mode : Important / Not Important 설정이 가능하다.

 -> 게임에서 중요한 동적 라이팅만 Important 로 설정 (Per-Pixel Light)

 -> 그렇지 않은 라이트들은 Not Important로 설정한다.


11. Overdraw


- 화면의 한 픽셀에 두 번 이상 그리게 되는 경우 (Fill rate)

 -> DP Call의 문제만큼 Overdraw 로 인한 프레임 저하도 중요한 문제

 -> 특히 2D 게임에서는 DP Call 보다 더욱 큰 문제가 된다.


- 기본적으로 앞에서 뒤로 그린다.

 -> Depth testing 으로 인해서 오버드로우를 방지한다.

 -> 하지만 알파 블렌딩이 있는 오브젝트의 경우에는 알파 소팅 문제가 발생한다.


- 반투명 오브젝트의 개수의 제한을 건다.

 -> 반투명 오브젝트는 뒤에서부터 앞으로 그려야 한다. -> Overdraw 증가

 -> 반투명 오브젝트의 지나친 사용에는 주의해야 한다.


- 유니티의 Render Mode를 통해서 overdraw 확인이 가능하다.


12. 유니티 셰이더


- 기본 셰이더는 모바일용 셰이더 사용

 -> 기본 셰이더를 사용할 경우, 모바일용 셰이더를 사용한다.

  ->> Mobile > VertexLit는 가장 빠른 셰이더


- 복잡한 수학 연산

 -> pow, exp, log, cos, sin, tan 같은 수학 함수들은 고비용

 -> 픽셀별 그런 연산을 하나 이상 사용하지 않는 것이 좋다.

 -> 텍스쳐 룩업 테이블을 만들어서 사용하는 방법도 좋다.

 -> 알파 테스트 연산 (discard)는 느리다.

 -> 기본적인 연산 보다는 최적화 시키고 간략화시킨 공식들을 찾아서 사용할 수 있다.


- 실수 연산

 -> float : 32bit - 버텍스 변환에 사용. 아주 느린 성능 (픽셸 셰이더에서 사용은 피함)

 -> Half : 16bit - 텍스쳐 uv에 적합. 대략 2배 빠름

 -> fixed : 10bit - 컬러, 라이트 계산과 같은 고성능 연산에 적합. 대략 4배 빠르다.


- 라이트 맵을 사용하자.

 -> 고정된 라이트와 오브젝트의 경우 (배경) 라이트 맵을 최대한 활용.

 -> 아주 빠르게 실행됨 (Per-Pixel Light 보다 2~3배)

 -> 더 좋은 결과를 얻을 수 있는 GI와 Light Mapper 사용가능


13. Fixed Update 주기 조절


- FixedUpdate()는 Update와 별도로 주기적으로 불리는 물리엔진 처리 업데이트


- 디폴트는 0.02초 1초에 50번


- TimeManager에서 수정


- 게임에따라 0.2초 정도로 수정해도 문제 없음. 


14. 물리 엔진 설정


- Static Object

 -> 움직이지 않는 배경 물체는, static으로 설정


- 충돌체의 이동

 -> 리지트 바디가 없는 고정 충돌체를 움직이면, CPU 부하 발생

 -> 이럴 경우 리지드 바디를 추가하고, IsKinematic 옵션 사용


- Maximum Allowed Timestep 조정

 -> 시스템에 부하가 걸려, 지정된 시간보다 오래 걸릴 경우, 물리 계산을 건너뛰는 설정


- Solver Iteration Count 조정

 -> 물리 관련 계산을 얼마나 정교하게 할지를 지정. (높을수록 정교)

 -> Edit > Project Setting > Physics


- Sleep 조절

 -> 리지드 바디의 속력이 설정된 값보다 작을 경우, 휴면 상태에 들어감

 -> Physics.Sleep() 함수를 이용하면, 강제 휴면 상태를 만들기 가능


15. 물리 엔진 스크립트


- 래그돌 사용 최소화

 -> 래그돌은 물리 시뮬레이션 루프의 영역이 아니기 때문에, 꼭 필요할 때만 활성화 함.


- 태그 대신 레이어

 -> 물리 처리에서 레이어가 훨씬 유리하다. 성능과 메모리에서 장점을 가진다.


- 메쉬 콜라이더는 절대 사용하지 않는다.


- 레이캐스트와 Sphere Check 같은 충돌 감지 요소를 최소화 한다.


16. Tilemap Collision Mesh


- 2D 게임에서 타일맵의 Collision Mesh를 최적화 하라.

 -> Tilemap을 디폴트로 사용해서, 각 타일별로 충돌 메쉬가 있는 경우, 물리 부하가 커짐.

 -> 연결된 Tilemap을 하나의 Collision Mesh로 물리 연산을 최적화 하라





저작자 표시
신고

댓글

댓글쓰기 폼

VallistA

병특이 끝나서 게임에서 웹으로 스위칭한 프로그래머.
프로그래밍 정보등을 공유합니다.

자고 싶습니다. ㅠㅠ

페이스북    :: 링크
카카오톡    :: kingbye1
Github      :: 링크

궁금한점 문의 주시면 답변드리도록 하겠습니다

VISITED

Today : 13

Total : 206,370

SNS

  • 페이스북아이콘
  • 카카오톡아이콘
  • 트위터아이콘