[그래픽스 최적화] Draw Call
카테고리: Unity
Main Reference:
- 유니티 그래픽스 최적화
🐳 Draw Call
Draw Call이란 CPU가 GPU에게 무엇을(Mesh), 어떻게(Material) 그릴지 정해주고 그리라고 명령하는 것이다. 다시 말해 하나의 mesh와 하나의 material로 하나의 드로우콜이 발생한다. 별것 아닌것처럼 들리지만 굉장히 큰 자원을 소모한다고 한다. Draw call은 기본적으로 CPU에서 병목이 발생하는 cpu bound bottleneck이다. 이때 render state의 정보는 직접 넘겨주는 것이 아니라 포인터로만 넘겨주기 때문에 텍스처의 크기, 메시의 폴리곤 갯수 등은 드로우콜 병목과 상관이 없다. 드로우콜 병목을 해결하기 위해서는 발생 횟수를 줄이는 수밖에 없다.
🐳 Batch & SetPass
먼저 Batch의 개념에 대해 알아보자. Batch란 Draw Call에 Render State변경을 포함한 넓은 개념의 draw call이다. 즉 render state변화가 없다면 draw call과 batch는 같은 의미이다. SetPass call은 material의 변화를 의미한다. 다시 말해 메시를 제외한 render state change를 발생시키는 명령이다. 굳이 이러한 개념을 구분하는 이유는 다음과 같은 GPU의 작동 방식때문이다. CPU에서 Draw Call이 실행되면 먼저 material이 변화되었는지 체크하게 된다. 그리고 만약 material의 변화가 없다면 setPass call없이 바로 렌더링을 진행한다. Batch의 갯수를 줄이기에는 현실적인 한계가 있기 때문에 보통은 setPass call을 줄이는 방식으로 최적화가 진행된다.
Draw Call | SetPass Call |
---|---|
간단한 예를 들어보자. 왼쪽 그림의 경우 10개의 메시가 존재하지만 하나의 머티리얼을 공유한다. 따라서 setPass call은 맨 처음 1회만 발생하고, 나머지 배치에서는 setPass call이 발생하지 않는다. 반면 오른쪽 그림의 경우 모든 메시들이 서로 다른 머티리얼을 가지고 있다. 따라서 10개의 배치가 발생할 때 10번의 setPass call이 발생한다.
Modular Assets | Map |
---|---|
Draw Call을 줄이는 가장 확실한 방법은 여러 오브젝트들을 포함한 통 메시를 만들어 한번에 그리는 것이다. 하지만 조금만 생각해보면 이는 좋은 방법이 아니라는 것을 알 수 있다. 메시가 엄청나게 큰 경우, 메시의 일부분만 화면에 나오더라도 전체 폴리곤을 처리해야 한다. 카메라 뷰 밖에 존재하는 많은 vertex 연산들이 낭비되므로 draw call이 주는 CPU입장에서 이득일지 몰라도, GPU입장에서는 엄청난 손해이다. 따라서 가장 좋은 방법은 중복되는 mesh, 중복되는 material이 최대한 많아지도록 모듈 형태로 에셋을 만드는 것이다. 실내 맵 같은 경우 모듈 방식을 이용하면 batch의 수를 크게 감소시킬 수 있다.
SetPass Call을 줄이는 또 다른 대표적인 방법은 Texture Atlas기법이다. 여러 메시의 텍스처를 하나로 묶으면 setPass call없이 많은 메시들을 렌더링할 수 있다. 텍스처를 조금만 모아도 굉장히 큰 해상도의 텍스처가 될 수 있는데 이 경우 메모리 대역폭 이슈가 발생할 수 있으므로 이를 고려해야 한다.
🐳 Static Batching
Static Batching 기법은 material을 공유하는 메시들을 묶어서 draw call하는 방법이다. 예를 들어 material을 공유하는 3개의 메시들을 각각 따로 draw call 하면 3번의 배치가 발생하지만 메시들을 하나로 묶어서 draw call하면 한번의 배치로 렌더할 수 있다. 위에서 언급한 module방식 asset들에 적용하기 좋은 최적화 기법이다. 다만 각 메시들과 별개로 메시들을 합친 새로운 메시 형태의 메모리가 추가적으로 필요하다. 즉 메모리를 추가적으로 소모하며 최적화하는 방식이기 때문에 메모리 이슈가 있는 경우 static batching을 해재해야 할 수도 있다. Static Batching을 이용할 때 주의해야 할 지점이 있다. GetComponent 메서드를 이용해 material을 가져온다면 material의 복사본이 생겨 서로 다른 객체취급을 생기고 배칭이 되지 않는다.
🐳 Dynamic Batching
Dynamic Batching 기법은 런타임에 움직이는 오브젝트들을 묶어서 draw call하는 방법이다. 매 프레임마다 메시들을 수집&재생성하기 때문에 추가적인 메모리 소모와 오버헤드가 발생한다. 일반적으로 world space transform은 GPU에서 이루어지지만, dynamic batching을 사용할 때에는 transform 이후에 메시들을 묶어야 하므로 CPU에서 world space 변환이 이루어진다. 만약 폴리곤이 아주 많은 큰 메시의 경우 draw call을 줄여서 얻는 이득보다 추가적으로 발생하는 오버헤드가 훨씬 크기때문에 이런 경우 dynamic batching을 하지 않는 것이 좋다.
🐳 GPU Instancing
Batching 이외에도 draw call을 줄이는 GPU Instancing 기법이 존재한다. 한번의 draw call로 동일한 오브젝트의 여러 복사본을 렌더링하는 방식이다. 얼핏 들으면 static batching과 비슷하게 보이지만, GPU Instancing은 런타임 오버헤드가 훨씬 적다. 예를 들어 static batching의 경우 런타임에 CPU에서 vertex 연산을 수행하고 이들을 취헙해야 한다. 하지만 GPU Instancing은 CPU에서 연산하지 않고 각각의 오브젝트들을 transform 정보와 함께 GPU로 보내 GPU에서 연산한다. 따라서 사이즈가 큰 추가 메모리가 별도로 필요하지 않고, transform 연산을 GPU에서 수행하기 때문에 static 메시가 아니여도 괜찮다. 유니티의 Prefab이 딱 이 경우이다.
🐳 기타
CombineMeshes
유니티에서 자동으로 제공해주는 시스템 이외에도 수동으로 원하는 오브젝트들을 배칭 처리해주는 방법이 존재한다. Mesh.CombineMeshes 메서드를 이용하면 된다.
댓글 남기기