Dynamic Batching이 효율적일까?
배칭이란 무었인가?
드로우 콜(DC)을 줄이기 위한 방법중 하나로 크게 정적배칭과 동적배칭으로 나뉜다.
그렇다면 DC란 무었이고 왜 줄여야 하는가?
DC란 그냥 CPU가 GPU에게 메시를 그려달라는 명령이다.
문제는 DC 함수를 호출할 때 마다 CPU의 부하가 발생하는 작업이 이루어 진다는 것이다.
그림발췌. ARM. The Architecture for the Digital World. API Level Optimizations.
위 그림을 보면 DC가 많을수록 오버헤드와 프로세싱의 비율이 커진다.
즉, DC를 적게 호출할 수록 오버헤드와 프로세싱이 줄어든다고 생각할 수 있다.
그렇다면 오버헤드와 프로세싱은 무슨 작업인가?
이에대한 내용은 아래 ppt의 일부에서 잘 설명되어 있다.
참고 : Unite Seoul 2016 Batch! Let's take a look at Batching!
CPU는 DC호출 시 State 변경에 대한 오버헤드와 Command Buffer의 Flushing 오버헤드를 감당해야 한다.
buffer는 데이터를 하나하나 옮기는 것이 비 효율적이라 일정량을 모아서 옮기는 것을 뜻하며
flush는 버퍼에 쌓여있는 것을 다른곳에 옮기고 버퍼를 비우는 것을 의미한다.
유니티 문서에 적혀있는 설명은 아래와 같다.
다이나믹 배칭은 말 그대로 동적 오브젝트를 배칭하는 기능이다.
동적배칭은 기본적으로 자동으로 이루어 지지만 아래와 같은 조건이 있다.
1. 동적 오브젝트 배칭은 정점마다 어느정도 오버헤드가 발생하므로 총 900개의 정점 이하의
메시로만 적용된다.
- 만약 셰이더에서 position, normal, 그리고 하나의 UV를 사용한다면 300개의 정점만 가능하며
position, normal, UV0, UV1 그리고 tangent까지 사용한다면 180개 정점까지 가능하다.
2. 트렌스폼을 미러링한 오브젝트들은 배칭되지 않는다.
(오브젝트 A의 스케일 1, 오브젝트 B의 스케일 -1일 때 이 두 오브젝트는 같이 배칭되지 않는다.)
3. 다른 머티리얼 인스턴스를 사용하면 같이 배칭되지 않는다.
4. 라이트맵이 있는 오브젝트는 추가 렌더러 파라미터(라이트맵의 인덱스나 offset/ scale)를 가지는데,
일반적으로 동적으로 라이트 매핑된 오브젝트가 완전히 동일한 라이트맵의 지점에 있어야 배칭된다.
5. 멀티 패스 셰이더를 쓰면 배칭이 안된다.
- 포워드 렌더링에서 대부분의 유니티 셰이더는 몇가지 라이트를 지원하여 효과적으로 추가 패스를 수행한다.
additional per-pixel를 위한 draw call은 배칭되지 않는다
- Legacy Deferre(light pre-pass)렌더링 경로는 두번 그려야 하기 때문에 다이나믹 배칭이 꺼져있다.
스테틱 배칭은 움직이지 않고 동일한 머티리얼을 공유한다면 DC을 줄일 수 있고 정적배칭보다
눈에 띄게 효과가 나타난다. (게임중 움직이거나 회적, 스케일이 없다는 것을 인스펙터의 Static로 설정)
현재는 MeshRenderer와 ParticleSystem만 배칭되며 Skinned MeshRenderer은 안된다.
반투명한 셰이더의 경우 오브젝트 순서상 다른것과 겹치면 배칭되지 않을 수 있으므로
순서를 잘 설정해야 한다. 유니티는 오브젝트 순서대로 렌더링을 지시하고 배칭을 시도하기 때문이다.
이상 배칭을 하기 위한 조건들을 알아보았다.
배칭은 결국 DC을 줄이기 위해 모아서 그리는 방법이다.
배칭을 사용하면 DC은 줄어들 수 있다.
그렇다면 배칭은 무조건적으로 좋은건가?
내가 생각하는 최적화는 무었인가 잘못사용하고 있는게 없다면 결국은
상황에 맞게 하나를 얻고 하나를 잃는 선택의 연속이다.
배칭 또한 그와 같다.
정적배칭의 경우 배치된 메시의 복사본을 저장하기 위해 추가 메모리가 필요하다.
즉, 메모리가 여유롭고 렌더링 성능이 딸린다면 사용하는게 좋은 판단이 될 수 있지만
그렇지 않다면 사용을 심각하게 고민해 봐야 한다.
그나마 정적 배칭은 게임의 로딩단계에서 버텍스 버퍼를 한번 생성하면 되지만
동적배칭은 어떠할까? 한번 버텍스 버퍼를 생성하면 그만일까?
동적배칭의 경우 CPU에서 트렌스폼(변환)을 수행해야 할 수도 있는데
이 경우, CPU에서의 변환 및 버텍스버퍼 구성에 걸리는 시간과 DC 횟수중
어느것을 우선시 할 건지를 생각해 봐야 한다.
모든 게임오브젝트 정점들을 CPU에서 월드공간으로 변환하여 동작하기 때문에
DC을 하는것 보다 부하가 작다면 이점이 있다.
DC의 리소스 요청은 주로 사용하는 그래픽스 API같은 여러가지 요인에 의해 결정된다.
예를들어 콘솔이나 Apple의 Metal과 같은 현대 API에서는 DC 오버헤드가 일반적으로
매우 낮기 때문에, 그다지 동적배칭이 효율적이지 않다.
어떻게 유니티는 어떤 오브젝트가 batch(일괄처리)인지 알까?
유니티는 scene에 어떤 오브젝트를 포함시킬지,
포함시키지 않을지를 결정하기 전에는 렌더링을 시작하지 않는다.
모든 Update(), FixedUpdate(), 또는 LateUpdate() 함수 안에서
오브젝트의 위치나 활성화 상태를 변경함으로써 이 결정을 할 수있다.
유니티의 프레임 실행순서는 이곳을 참조하면 된다.
유니티가 정확하게 무었을 렌더할지 알 때, 배치를 만든다.
배치는 GPU에게 보낼 데이터가 있는 패키지다.
보통은 패키지의 수가 적을수록 좋다.
하지만 유니티는 모든것을 하나의 배치로 묶을 수 없다.
상당히 많은 양의 지오메트리 정점들을 함께 배치할 수 있지만
배치마다 한번만 머티리얼 설정이 가능하다.
이는 배치가 같은 머티리얼을 쓰는 오브젝트들만 배치할수 있다는 것을 의미한다.
Reference Link
- unity doc , Draw call batching , English
- Batch! Let's take a look at Batching!
- Be aware of static and dynamic batching
- Mobile Optimization Batching in Unity
- Execution Order of Event Functions
- OpenGL ES 2.0 "Designing a High-Performance"
-
'Optimizing > Unity' 카테고리의 다른 글
MaterialPropertyBlock (2) | 2017.06.27 |
---|---|
Mesh 최적화 (0) | 2017.04.11 |
유니티 텍스처 (0) | 2017.03.22 |
animation 최적화 (0) | 2017.03.04 |
유니티에서의 메모리 관리. (0) | 2017.01.13 |