상세 컨텐츠

본문 제목

DrawCall(드로우콜)과 렌더링 최적화

유니티 학습/C#과 유니티

by 남민우_ 2026. 4. 4. 17:56

본문

DrawCall(드로우콜)이란?

모든 게임 프로젝트를 진행하다보면, 혹은 게임을 플레이 할 때면 화면에 각종 오브젝트가 그려지고 화려한 그래픽이 보일 것이다.

이 그래픽 관련해서는 GPU, 그래픽카드가 관여한다는 것에 대해서는 다들 알고 있을텐데, 이 때 CPU가 GPU한테 이 오브젝트를 그려라! 라고 명령하는 행위를 DrawCall 이라고 한다.

 

그리는 행위 자체는 GPU가 수행한다고 해도, 어떤 것을 그릴지, 어떻게 그릴지에 대해서는 GPU가 결정하지 않는다.

이 현재 프레임에 어떤 것(Mesh)을 그려야 할지, 어떻게(Material) 그려야 할지를 정하고 명령을 내리는 것이다.

이때 하나의 Mesh 와 하나의 Material 대상으로 한 번의 호출이 발생하는데, 이 과정이 많은 자원을 소모한다고 한다.

 

여기서 한가지 생각해볼 것은, 우리가 게임을 만드는 과정에서 Mesh 를 꾸미다보면, 한 개의 Mesh 임에도 불구하고 다수의 Material 이 적용되는 경우가 있을 것이다. 이런 경우에는 메테리얼 갯수만큼 드로우콜 횟수가 증가한다. GPU는 한 번의 드로우콜 동안 하나의 메테리얼 정보만 그릴 수 있기에, 하나의 Mesh 에 여러 개의 메테리얼이 있다면 각 메테리얼 마다 SetPass Call(후에 이어서 설명)이 발생하고 이러한 이유로 텍스처 아틀라스(Texture Atlas) 등으로 메테리얼을 하나로 합치려는 시도를 하는 것이다.

 

이어서, 우리는 흔히 DrawCall 이라는 용어에 대해서만 알고 있는데, 더 넓은 범위의 개념인 Batch 가 있다.

이 Batch 는 먼저 간단히 설명해서 DrawCall 에 Render State 변경을 포함한 넓은 개념의 드로우콜을 말하는데, 이를 설명하기에 앞서 Render State 가 뭔지 이해하고 넘어가야 한다.

 

Render State

Render State 는 사실 단어의 의미 그대로 쉽게 이해 가능하다.

렌더 상태, 즉 렌더링 파이프라인에서 렌더링 동작을 제어하는 설정들의 집합으로, 드로우콜이 수행될 때 어떻게 그려지고, 어떤 방식으로 그려지도 등에 대한 정보를 가지고 있다.

 

오브젝트를 화면에 렌더링하려면, 오브젝트가 렌더링 대상인지 판단이 필요한데 이 과정을 컬링(Culling)이라고 한다

컬링을 거친 오브젝트가 렌더링 되기 위해서는 CPU에서 GPU에게 정보 전달이 필요한데, 이러한 정보에는 다음과 같은 항목들이 포함된다.

1. 메시 정보

2. 텍스트 정보

3. 쉐이더 정보

4. 트랜스폼 정보

5. 알파 블렌딩 여부

Etc...

 

이러한 정보들을 묶어 Render State 라 말하는데, 앞서 말했듯 이 Render State 를 GPU에게 전달하는 과정이 필요하다.

이 정보 전달하는 과정을 SetPass Call 이라 부른다.

 

SetPass Call

앞서 Batch 가 DrawCall 에 Render State 변경을 포함한 넓은 범위의 드로우콜이라고 말했는데, 이 때 Render State 의 변경을 호출하는 과정이 SetPass Call 이다.

Material 의 변화 동작, 다시 말해 메시를 제외한 Render State Change 를 발생시키는 명령이라고 이해할 수 있다.

 

잠시 생각해보면 이 드로우콜과 SetPass Call 을 구분한 이유가 궁금해질 수 있다. 한 번의 동작으로 처리해도 되지 않을까?

그 이유는 GPU의 동작 방식에 있는데, 먼저 CPU에서 DrawCall 을 발생시키면 Material 의 변화가 있는지(Render State 체크)를 확인하고, 없으면 SetPass Call 없이 렌더링을 진행한다. Render State 에 변화가 있을 경우에 SetPass Call 이 이루어지기 때문에 동작이 나뉘는 것이다.

 

결과적으로 이 Batch 자체의 갯수를 줄이는 데에는 한계가 있어, 보통 SetPass Call 을 줄이는 방식으로 최적화가 이루어진다.

 

동작 방식에 대해 더 자세히 살펴보자면, GPU는 상태 머신(State Machine) 이라고도 볼 수 있다.

"자, 이제부터 1번 쉐이더랑 빨간 텍스처 쓸 거야!" (SetPass - 엄청 무거움)
"이 데이터 그려!" (DrawCall)
"저 데이터 그려!" (DrawCall)
"그만! 이제 2번 쉐이더로 바꿀 거야!" (SetPass - 다시 무거워짐)

다음과 같이 Batch가 이루어졌다고 할 때, 1, 4번의 SetPass Call 은 '기계를 교체하는 수준'의 무거운 작업이 진행된다.

반면 2, 3번의 DrawCall은 '기계는 놔두고, 재료만 교체하는 정도'의 가벼운 동작인 것이다.

따라서 자연스럽게 SetPass Call 을 최소화하는 방향으로 최적화가 되는 것이다.

 

이 SetPass Call 은 쉐이더 프로그램을 교체하거나, 텍스처 메모리를 통째로 갈아 끼울 때 발생한다.

따라서 최적화 전략으로는 서로 다른 메시더라도 같은 쉐이더와 같은 텍스처를 쓰게 만드는 것이다.

출처 : https://blog.naver.com/blue9954/222740883052

게임을 개발하다보면 다음과 같은 이미지를 종종 보게 되는데, 이것이 같은 텍스처를 쓰게 만드는 가장 대표적인 방법인 텍스처 아틀라스(Texture Atlas)이다.

서로 다른 메시에 적용되는 텍스처라고 할 지라도, 하나의 텍스처 파일 안에 몰아넣고, 에디터 내부에서 잘라 쓰도록 하여 같은 텍스처를 공유하는 식으로 적용하는 것이다.

이렇게 여러 메시의 텍스처를 하나로 묶어, 별도의 SetPass Call 없이 많은 메시들을 렌더링 할 수 있다.

 

DrawCall 의 최소화

내용이 많이 돌아왔지만 여기까지가 이해가 된다면, 드로우콜은 자연스럽게 최소화하는 것이 좋다는 것을 받아들일 수 있다.
간단히 생각해봐도, 매 프레임마다 1개의 메시 - 1개의 메테리얼을 대상으로 드로우콜이 실행되는데, 많으면 많을 수록 프레임 저하가 이어지지 않겠는가?

드로우콜의 병목 해결을 위해서는 이 발생 횟수 자체를 줄이는 수밖에 없다.

CPU 가 GPU에게 DrawCall 을 하는 과정에서 CPU 자원을 소모하게 되는데, 이 때 DrawCall 자체의 횟수가 과도하게 많을 경우 GPU보다 CPU가 먼저 지치는 CPU Bound 현상이 발생하며 프레임이 떨어질 수 있다.

 

이 발생 횟수 자체를 줄이는 가장 명확한 방법은 여러 오브젝트를 포함한 통 메시를 만들어 한번에 그리는 것이다.

Unreal 엔진이나 Unity 엔진이 익숙한 사람이라면, 레벨 디자인을 할 때 레벨에 배치된 메시들을 하나하나 묶어 아예 하나의 거대한 메시로 묶는 경우가 있었을 것이다. 이 방법도 사실 DrawCall 횟수를 줄이는 명확한 방법 중 하나다.

 

다만 이게 좋은 방법이라고 할 순 없는 것이, 메시가 말 그대로 거대한 경우 화면에 전체가 담기지 않아도 전체 폴리곤을 처리해야 한다는 점 때문이다.

게임 내 카메라는 자연스럽게 특정 구역을 비추거나 캐릭터의 주위 시야를 보여주게 되는데, 그럴 경우 카메라뷰에 담기지 않는 메시들이 분명히 존재한다. 여러 개의 메시를 거대한 메시를 만든 경우, 카메라 뷰 밖의 정점(Vertex) 연산을 낭비하게 된다.

이 동작이 DrawCall 횟수 자체는 줄어 CPU는 이득을 볼 수 있으나, GPU에게 막대한 짐을 넘기는 것이다.

 

결과적으로 DrawCall 을 줄이는 좋은 방법이란, 중복되는 Mesh / Material 이 많아지도록 모듈 형태로 에셋을 만드는 것으로, Batch 수를 감소시키는 방법으로 연결된다고 할 수 있다.

 

 

추가로 Render State 는 포인터로 넘겨주기 때문에 병목 현상과는 크게 관련이 없다라고도 할 수 있으면서 아예 관계가 없진 않다.

그 이유로, Render State 자체는 CPU가 메모리 주소(포인터)를 GPU에게 전달하는 방식이라 데이터 전송 자체의 부하가 줄어드는 것은 맞지만, Context Switching 비용이 발생한다.

 

GPU는 상태가 변할 때마다 내부 파이프라인을 재설정하고, 새로 넘겨받은 포인터가 가리키는 새로운 쉐이더나 텍스터를 GPU가 활성화하고 계산 준비를 마치는 과정에서 대기 시간(오버헤드, Overhead) 가 발생한다.

이러한 비용을 Context Switching 비용이라고 부른다.

 

정리

DrawCall

CPU가 GPU에게 이 메시를 설정된 상태로 그려라! 하는 최종 명령을 말한다.

 

Render State

쉐이더, 텍스처, 알바 블렌딩 등 렌더링 정보에 대한 설정값을 말한다.

 

SetPass Call

Render State 를 바꾸는 동작으로, 메테리얼이 바뀌면 무조건 1번 발생한다.

 

Batch

SetPass Call 과 DrawCall 을 합친 하나의 렌더링 단위를 말한다.

만약 메테리얼 변화가 없다면(Render State 변화가 없다면) SetPass Call 은 일어나지 않으며 DrawCall 만 일어나게 되는데, 이때의 DrawCall 은 Batch 와 같다고 취급할 수 있다.

 

이어서 최적화 기법에 관한 내용을 정리할 텐데, 그 전에 Batching 이라는 용어에 대해 알아야 한다.

Batching

Batch 는 DrawCall 에 Render State 변경을 포함한 넓은 개념의 드로우콜이라고 말했다.

이 Batching 은 Batch 에 대한 최적화 기법으로, 동일한 메테리얼을 공유하는 복수의 드로우콜을 하나로 묶어 처리하는 최적화 기법이다.

 

예를 들어 레벨에 나무, 바위가 있는데 서로 메테리얼이 같다고 가정해보자.

이 경우 나무와 바위를 하나의 큰 메시로 합치자! 라고 하는 것이 Batching 의 핵심이다.

기존대로 처리할 경우 나무, 바위에 대해 총 2번 호출 될 DrawCall 이 나무 + 바위인 메시를 대상으로 1번으로 줄어드는 것이다.

 

여기서 본인이 개인적으로 궁금하기 시작했던 점에서 파생된 정보를 소개하고자 한다.

Batch 가 하나의 렌더링 단위인데, 왜 최적화 기법이 Batching 이라는 유사한 단어로 정의되었을까 부터 시작된다.

 

Batch 는 본래 영어에서 빵 한번을 구울 때 나오는 '한 묶음'을 말한다고 한다.

판에 여러 개의 반죽을 올려서 한 판을 통째로 굽는 행위인 Batching 에서 최적화가 유래된 것이다.

컴퓨팅 초기 시절부터 Batch Processing(일괄 처리)라는 용어가 존재했는데, 데이터가 생길 때마다 처리하는 것이 아니라 어느 정도 모아서 한번에 처리하는 방식을 의미한다.

따라서 여러 개의 작업을 하나로 묶는 Grouping 행위 자체가 Batching 이고, 그 결과로 나오는 한 덩어리의 명령이 Batch 인 것이다.

 

여기서 의문이 이어진다.

데이터를 모아서 처리하는 방식이 Batching 이라고 한다면, DrawCall 또한 한 프레임 내에서 렌더링 할 메시 정보를 모아서 이루어지는데 이것도 Batching 이라고 할 수 있는가? 라는 것이다.

결과부터 이야기하자면 그것은 아니다.

그 이유는 예시를 들어 이야기 할 수 있다.

//기존 방식
하루(프레임) 동안 100개의 물건을 보내야 합니다.
택배 기사님(CPU)이 벨을 누르고 1호 집 물건 하나 주고 갑니다. (Draw Call 1)
다시 벨 누르고 2호 집 물건 하나 주고 갑니다. (Draw Call 2)
이걸 100번 반복합니다. 하루라는 시간 안에 다 보내긴 했지만, 벨을 100번 누르고 왔다 갔다 한 오버헤드가 엄청납니다.

//배칭이 있는 경우 (Batching):
택배 기사님이 1호부터 100호까지 물건을 커다란 박스 하나에 싹 담습니다. (Batching 행위)
벨을 딱 한 번 누르고 경비실에 "여기 100개 한꺼번에 왔습니다!" 하고 던져줍니다. (Draw Call 1 / Batch 1)

이처럼, 한 프레임 안에 N 개의 드로우콜이 일어났다면 그것은 명령 N개가 순차적으로 GPU 대기열(Command Buffer)에 쌓여서 실행된 것이지, 하나의 덩어리(Batching) 이 된 것은 아니라는 것이다.

 

Batching 은 절대 자동으로 이루어지지 않는다.

레벨에 배치된 나무와 바위는 메모리 주소도 다르고 Vertex 데이터도 완전히 다를 것이다.

이 메시들을 그대로 놔두면 당연히 따로 계산할 수 밖에 없으니, 이 둘을 사람이 판단해서 '합쳐서 계산해라'라고 명령을 수정하지 않는 이상 Batching 이 일어나지 않는 것이다.

 

이어서 렌더링 최적화 기법들에 대한 이야기로 이어져야 하지만, 내용이 너무 길어서 포스팅을 나누도록 하겠다.

관련글 더보기