출처 :

https://loadofprogrammer.tistory.com/148

https://trello.com/c/ZnFgG0n9/36-%EC%B5%9C%EC%A0%81%ED%99%94

CleanShot 2023 07 14 at 02.02.56

최적화의 시작은 병목 파악부터

CPU

 – 너무 많은 DP CALL

 – 복잡한 스크립트나 물리 연산

Vertex Processing

 – 너무 많은 버텍스들

 – 버텍스당 너무 많은 연산 (Vertex Shader)

Fragment Processing

 – 너무 많은 픽셀, 오버 드로우 (Over Draw)

 – 프래그먼트당 너무 많은 연산 (Fragment Shader / Pixel Shader)

Band Width

 – 크고, 압축되지 않은 텍스쳐

 – 고해상도 프레임 버퍼

스크립트 최적화

# 유니티의 핵심 기능은 모두 C++로 제작되어 있다.

# 예) Transform.position 에서 Transform은 C# 속성, Position은 C++ 영역

# 유니티 객체들을 멤버 변수에 저장해서 캐싱하여 사용하는 것이 좋다.

# FindObject 계열 함수들은 매우 느리다. (미리 찾아서 캐싱)

 – 예를 들어 Find 라는 검색이 붙은 것들은 웬만하면 지속적으로 사용하지 않는 것이 좋습니다. Find는 프로젝트 안의 모든 오브젝트를 순환하며 그 안의 클래스의 스트링을 비교하기 때문입니다.

# Instanitate 와 Destory 함수를 이용한 프리팹의 생성/해제는 비용이 크다

 – 활성화/비활성화를 활용한 오브젝트 풀을 사용하는 것이 좋습니다.

# Update 함수보다는 Coroutine을 활용한다.

# 박싱과 언박싱은 부하가 큰 작업이다.

 – C# 박싱과 언박싱에 관한 자세한 내용은 http://vallista.tistory.com/entry/C-%EB%B0%95%EC%8B%B1%EA%B3%BC-%EC%96%B8%EB%B0%95%EC%8B%B1 아주 간단한 설명은 제 블로그 내용인 http://loadofprogrammer.tistory.com/79 을 참조하시면 됩니다.

# 나눗셈보다는 곱셈이 몇십 배 빠르다.

 – 나눗셈은 곱셈보다 연산 속도가 월등히 느립니다. 100 / 10 이런 식이 아닌 100 * 0.1을 사용

# magnitude 보다는 sqrMagnitude를 사용해서 비교한다. (제곱근 계산 x)

 – Unity 공식 문서에서 쓰여있습니다.

# 삼각함수의 값은 상수로 저장하고 사용하는 것이 좋다.

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

만흉의 원인 : 가비지 컬렉터

가비지 컬렉터(GC)는 언제 일어날지 모릅니다.

# Mono의 동적 메모리 관리 때문에 메모리 해제를 위해 GC가 자동 호출된다.

# GC는 언제 일어날지 모른다.

# GC가 일어나면 게임이 멈추는 현상이 발생하게 된다.

# 동적 메모리 해제가 가능한 일어나지 않도록 하는 것이 GC 관리의 핵심

– 우리가 쓰고 있는 MonoBehavior는 메모리 관리에 GC가 자동 호출되도록 설계되어 있습니다. 이 GC가 프로그래머의 입장에서 좋을 수도 있고 그렇지 않을 때도 있습니다. GC가 실행되는 동안 많은 양을 처리하게 되면 렉 현상을 겪게되며 그렇지 않기 위해서는 게임을 만들 때 GC를 고려하여 만들어야합니다. 가비지 컬렉터의 할 일을 줄여주기 위한 방법에 대해 알아보겠습니다.

[1] 무엇이든 동적 생성 및 해제는 굉장히 비용이 큰 작업입니다.

 위에 언급된 바가 있는 오브젝트 풀링 기법을 사용해 메모리를 관리하는 것이 좋습니다.

[2] 오브젝트가 해제되면 다음 과정으로는 GC가 동작되어 렉이 걸릴 수 밖에 없습니다.

 즉 오브젝트를 만들어둔 후 활성화 또는 비활성화를 이용해 사용하도록 하는 것이 좋습니다.

[3] 문자열 병합은 StringBuilder의 Append를 사용하면 좋습니다.

 왜냐하면 string + string은 임시 문자열을 뱉기 때문에 가비지 컬렉션이 일어나는 환경을 제공하기 때문입니다.

[4] foreach 대신에 for를 이용하도록 합니다.

 foreach는 한번 돌리면 24byte의 가비지 메모리를 생성시키게 되며 수많이 돌면 더 많은 메모리를 생성시키게 되므로 for 문을 이용하도록 하는 편이 좋습니다.

[5] 태그 비교에서는 CompareTag()를 사용하도록 합니다.

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

[6] 모든 비교문에서 .equals()를 사용하도록 합니다.

 “==” 구문으로 사용하게되면 임시적인 메모리가 남게 되며 가비지 컬렉션이 할 일이 늘게 됩니다.

[7] 데이터 타입은 Class 대신 Struct를 사용하여 만들어 주면 메모리 관리가 된다.

 구조체는 메모리 관리를 Stack에서 하므로 GC에 들어가지 않게 됩니다.

[8] 즉시 해제시에는 Dispose를 수동으로 호출하게 되면 즉시 클린업됩니다.

[9] 임시 객체를 만들어내는 API를 조심해야합니다.

 GetComponents<T>, Mesh, Vertices, Camera.allCameras 등등..

[10] 객체의 변경 사항에 대해 캐싱합니다.

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

[11] 컴포넌트 참조를 캐싱한다.

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

[12] 콜백 함수중 쓰지 않는 함수는 제거합니다.

 Start(), Update(), OnDestroy() 등.. 비어있어도 성능에 영향을 끼치므로 지워주도록 합니다.

CleanShot 2023 07 14 at 02.05.00

리소스 최적화

★ 권장 압축 텍스쳐 사용하기

  • 아이폰(PowerVR) : PVRCT
  • 안드로이드(Tegra) : DXT
  • 안드로이드(Adreno) : ATC
  • 안드로이드(공통) : ETC1

★ 텍스쳐

# 텍스쳐 사이즈는 무조건 2의 제곱이어야합니다.

  •  POT(Power of Two)
  •  POT가 아닌 경우 POT 텍스쳐로 변환되어 로딩된다.
  •  900 x 900 -> 실제로는 1024 X 1024로 변환
  •  화면 해상도에 맞추어 1280으로 만드는 경우엔 실제 메모리에 2048로 생성됩니다.

# 텍스쳐 아틀라스를 활용하라.

  •  텍스쳐 아틀라스로 최대한 묶음
  •  UI만이 아니라 같은 재질의 오브젝트들을 묶음
  •  한 화면에 나오는 텍스쳐끼리(UI)
  •  알파가 있는 텍스쳐끼리, 알파가 없는 텍스쳐끼리 묶어야합니다.
  •  압축된 텍스쳐와 밉맵을 사용하자. (대역폭 최적화)
  •  32 bit가 아닌 16 bit 텍스쳐 사용도 상황에 맞게 고려합니다.

# 텍스쳐 메모리 사용량 프로파일링

  •  메모리 사용량 로그를 서버에 남기면 프로파일링에 도움이 됩니다.

★ Mesh

# Import시에 언제나 “Optimize Mesh” 옵션 사용

  •  변환 전, 후 버텍스 캐쉬를 최적화 해줍니다.

# 언제나 Optimize Mesh Data 옵션을 사용한다.

  •  Player Setting -> Other Settings
  •  사용하지 않는 버텍스 정보들을 줄여준다.

★ 오디오

# 모바일에서 스트레오는 의미 없다.

  •  모두 92kb, 모노로 인코딩

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

  •  2D 사운드로 변경

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

  •  비압축 사운드 : 순간적인 효과음, 이펙트 등..
  •  압축 사운드 : 배경 음악

★ 폰트 리소스 최적화

# Packed Font를 사용

  •  R, G, B, A 채널에 저장하는 기법으로 메모리 용량을 1/4로 절약하도록 합니다.
  •  Packed Font는 단점이 너무 많습니다. 일반적으로 글씨에 그림자도 못 넣고 알파도 적용이 안됩니다. NGUI Atlas 적용도 되지 않습니다.

★ 리소스 기타

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

  •  게임 레벨 로드시에 사용했을 경우, 일반 함수에 비해 수십 배나 더 느립니다.

그래픽스 최적화

Draw Call (DP Call)

 “적절한 DP Call은 얼마 정도 인가요?” 일반적으로 100 이하를 추천합니다. 보통 70 ~ 100 정도가 일반적입니다.

 하지만 실제로는 케이스 바이 케이스

Culling

 가장 빠르게 그리는 방법은 아무것도 그리지 않는 것입니다.

프러스텀(Frustum) 컬링

 각 Layer 별로 컬링 거리를 설정하는 것이 가능합니다.

 멀리 보이는 중요한 오브젝트(ex. 성, 산맥..)는 거리를 멀게 설정하고 중요도가 낮은 풀이나 나무 등은 컬링 거리를 짧게 설정합니다.

오클루젼(Occlusion) 컬링

 Window -> Occlusion Culling 메뉴에서 설정 가능합니다.

 카메라에 보이는 각도의 오브젝트들만 렌더링하는 기법.

오브젝트 통합 (Combine)

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

 렌더러에 사용된 재질의 수만큼 드로우 콜이 발생합니다.

 Combine (통합)

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

   [2] Script 패키지 – CombineChildren 컴포넌트 제공 (하위 오브젝트를 모두 하나로 통합)

   [3] 통합하는 경우 텍스쳐는 하나로 합쳐서, Texture Atlas를 사용해야된다.

Batch

 Static Batch

   [1] Edit -> Project Setting -> Player에서 설정합니다.

   [2] 움직이지 않는 오브젝트들은 static으로 설정해서 배칭이 되게 합니다.

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

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

 Dynamic Batch

   [1] 움직이는 물체를 대상으로 동일한 재질을 사용하는 경우 자동으로 통합합니다.

   [2] 동적 배칭은 계산량이 많으므로 정점이 900개 미만인 오브젝트만 대상이 됩니다.

Lighting (라이팅)

 라이트 맵을 사용하자

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

   [2] 아주 빠르게 실행됩니다. (Per-Pixel Light 보다 2~3배)

   [3] 더 좋은 결과를 얻을 수 있는 GI와 Light Mapper를 사용할 수 있습니다.

 라이트 렌더 모드

   [1] 라이팅 별로 Render Mode : Important / Not Important 설저어 가능

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

   [3] 그렇지 않은 라이트들은 Not Important로 설정

Overdraw

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

   [1] DP Call의 문제만큼이나 Overdraw로 인한 프레임 저하도 중요한 문제

   [2] 특히 2D 게임에서는 DP Call보다 더욱 큰 문제가 됩니다.

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

   [1] Depth testing으로 인해 오버드로우를 방지합니다.

   [2] 하지만 알파 블렌딩이 있는 오브젝트의 경우에는 알파 소팅 문제가 발생합니다.

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

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

   [2] 반투명 오브젝트의 지나친 사용에는 주의해야합니다.

 유니티 Render Mode를 통해 overdraw 확인이 가능합니다.

유니티 셰이더

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

   [1] 기본 셰이더를 사용할 경우에 모바일용 셰이더를 사용합니다. Mobile -> VertexLit은 가장 빠른 셰이더

 복잡한 수학 연산

   [1] pow, exp, log, cos, sin, tan 같은 수학 함수들은 고비용입니다.

   [2] 픽셀별 그런 연산을 하나 이상 사용하지 않는 것이 좋습니다.

   [3] 텍스쳐 룩업 테이블을 만들어 사용하는 방법도 좋습니다.

   [4] 알파 테스트 연산(discard)은 느립니다.

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

 실수 연산

   [1] float : 32bit – 버텍스 변환에 사용. 아주 느린 성능 (픽셀 셰이더에서 사용은 피함)

   [2] Half : 16bit – 텍스쳐 uv에 적합하며 대략 2배 빠릅니다.

   [3] fixed : 10bit – 컬러, 라이트 계산과 같은 고성능 연산에 적합. 대략 4배 빠릅니다.

 라이트 맵을 사용하자

   [1] 위의 라이팅에서 설명한 라이트 맵을 사용합니다.

물리엔진 최적화

Fixed Update 주기 조절

 – FixedUpdate()는 Update와 별도로 주기적으로 불리며, 주로 물리 엔진 처리

 – 디폴트는 0.02초, 즉 1초에 50번 호출합니다.

 – TimeManager에서 수정이 가능합니다.

 – 게임에 따라 0.2초 정도(혹은 이상)로 수정해도 문제 없습니다.

물리 엔진 설정

 Static Object

   [1] 움직이지 않는 배경 물체는 Static으로 설정합니다.

 – 충돌체의 이동

   [1] 리지드 바디가 없는 고정 충돌체를 움직이면 CPU 부하가 발생합니다 – 물리 월드 재구성

   [2] 이럴 경우에 리지드 바디를 추가하고 IsKinematic 옵션을 사용.

 – Maximum Allowed timestep 조정

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

 – Solver Iteration Count 조정

   [1] 물리 관련 계산을 얼마나 정교하게 할지를 지정합니다. (높을수록 정교함)

   [2] Edit -> Project Setting -> Physics

 – Sleep 조절

   [1] 리지드바디의 속력이 설정된 값보다 작을 경우 휴면 상태에 들어갑니다.

   [2] Physics.Sleep() 함수를 이용하면 강제 휴면 상태를 만들 수 있습니다.

2D 물리 vs 3D 물리

 – 2D 게임에는 2D 물리를 (RigidBody2D), 3D 게임에는 3D 물리를(RigidBody)

물리 엔진 스크립트

 – 래그돌 사용을 최소화합니다.

   [1] 랙돌은 물리 시뮬레이션 루프의 영역이 아니기 때문에 꼭 필요할 때만 활성화합니다.

 – 태그 대신 레이어 사용

   [1] 물리 처리에서 레이어가 훨씬 유리합니다. 성능과 메모리에서 장점을 가집니다.

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

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

Tilemap Collision Mesh

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

   [1] Tilemap을 디폴트로 사용해서 각 타일별로 충돌 메쉬가 있는 경우 물리 부하가 커집니다.

   [2] 연결된 Tilemap을 하나의 Collision Mesh로 물리 연산을 최적화 합니다.


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다