모바일 기기의 한정된 자원 속에서 60fps를 사수하는 것은 모든 클라이언트 프로그래머의 숙명과도 같습니다. 본 칼럼에서는 렌더링 병목의 주범인 드로우콜을 분석하고, 유니티 엔진에서 이를 획기적으로 줄일 수 있는 실전적인 배칭 최적화 및 렌더링 파이프라인 관리 기법에 대해 깊이 있게 다룹니다.
1. 드로우콜(Draw Call)의 본질적 이해와 병목의 원인
모바일 게임 개발에서 성능 최적화의 첫걸음은 드로우콜(Draw Call)을 이해하는 것입니다. 드로우콜이란 CPU가 GPU에게 어떤 물체를 어떻게 그릴지 명령을 내리는 호출을 의미합니다. 많은 초보 개발자들이 GPU의 성능 부족으로 프레임 드랍이 발생한다고 오해하지만, 모바일 환경에서 렌더링 병목의 80% 이상은 CPU 바운드(CPU Bound) 상태에서 발생합니다. CPU가 화면에 존재하는 수많은 오브젝트들의 재질(Material), 셰이더(Shader), 텍스처(Texture), 트랜스폼(Transform) 상태를 변경하고 GPU에 그리기 명령을 보내는 과정 자체가 엄청난 오버헤드를 발생시키기 때문입니다. 특히 모바일 기기의 CPU는 데스크탑에 비해 단일 코어 성능이 낮고 발열에 의한 스로틀링(Throttling)에 취약하므로, 드로우콜을 줄여 CPU의 부담을 덜어주는 것은 60fps 방어의 핵심입니다.
드로우콜 오버헤드를 깊이 파고들면 상태 변경(State Change)이라는 개념과 마주하게 됩니다. GPU는 이전 렌더링 명령과 동일한 상태(동일한 머티리얼, 동일한 셰이더)라면 빠르게 작업을 수행할 수 있지만, 텍스처가 바뀌거나 머티리얼 속성이 변경되면 렌더링 파이프라인을 플러시(Flush)하고 새로운 상태를 셋업해야 합니다. 이 상태 변경 비용이 순수한 렌더링 비용보다 훨씬 큽니다. 따라서 우리는 유니티 엔진이 제공하는 다양한 배칭(Batching) 기법을 활용하여 여러 오브젝트를 하나의 상태로 묶어 단일 드로우콜로 처리하도록 아키텍처를 설계해야 합니다.
2. 정적 배칭(Static Batching)과 동적 배칭(Dynamic Batching)의 실무적 한계
유니티에서 가장 기본적으로 제공하는 최적화 옵션은 정적 배칭과 동적 배칭입니다. 정적 배칭은 움직이지 않는 오브젝트들의 메쉬를 빌드 타임 혹은 런타임 초기에 하나의 거대한 메쉬로 병합하여 드로우콜을 1로 만들어주는 강력한 기능입니다. 하지만 정적 배칭은 병합된 메쉬를 메모리에 복제하여 저장하므로, 동일한 나무 프리팹 1000개를 배치했을 때 메모리 사용량이 극심하게 증가하는 치명적인 단점이 있습니다. 모바일 게임은 메모리 가용량이 매우 제한적이므로 무분별한 정적 배칭은 Out Of Memory(OOM) 크래시를 유발합니다.
반면 동적 배칭은 런타임에 움직이는 작은 오브젝트들의 정점을 CPU가 매 프레임 연산하여 하나의 버텍스 버퍼로 합치는 기법입니다. 이는 메모리를 낭비하지 않지만, 매 프레임 CPU 연산이 추가로 들어가기 때문에 오히려 CPU 오버헤드가 드로우콜 오버헤드보다 커지는 배보다 배꼽이 큰 상황을 자주 만듭니다. 유니티 공식 문서에서도 버텍스 수가 수백 개 이하인 매우 단순한 모델에만 적용하라고 권장하는 이유가 바로 이 때문입니다. 따라서 모바일 프로젝트에서는 동적 배칭에 의존하기보다는 GPU 인스턴싱이나 SRP 배처(SRP Batcher)를 적극 도입해야 합니다.
3. GPU 인스턴싱(GPU Instancing)을 통한 대규모 오브젝트 렌더링
GPU 인스턴싱은 동일한 메쉬와 동일한 머티리얼을 사용하는 다수의 오브젝트를 단 한 번의 드로우콜로 그릴 수 있게 해주는 현대적인 렌더링 기법입니다. CPU는 오브젝트들의 트랜스폼(위치, 회전, 스케일) 배열과 인스턴스별 커스텀 데이터(예: 색상, 베리에이션 속성)만을 버퍼에 담아 GPU로 전송하고, GPU가 이를 바탕으로 수천 개의 나무, 풀, 총알 등을 한 번에 그려냅니다. 모바일 환경에서도 OpenGL ES 3.0 이상이나 Vulkan, Metal API를 지원하는 기기라면 완벽하게 동작합니다.
GPU 인스턴싱을 실무에 적용할 때 가장 중요한 것은 머티리얼의 일관성입니다. 머티리얼 인스턴스를 코드에서 새로 생성(`GetComponent
4. 텍스처 아틀라스(Texture Atlas)와 UI 캔버스 분리 전략
3D 모델뿐만 아니라 2D UI(UGUI)에서도 드로우콜은 심각한 문제가 됩니다. UGUI는 캔버스(Canvas) 하위의 요소들을 머티리얼과 텍스처 기준으로 배칭하는데, 이 과정에서 여러 텍스처를 번갈아 사용하면 배칭이 끊어지며 드로우콜이 급증합니다. 이를 해결하기 위해 스프라이트 패커(Sprite Packer)나 스프라이트 아틀라스(Sprite Atlas)를 사용하여 여러 개의 작은 UI 이미지들을 하나의 커다란 텍스처로 병합해야 합니다. 텍스처 아틀라스를 적용하면 100개의 UI 엘리먼트라도 1~2개의 드로우콜로 렌더링이 가능해집니다.
또한 캔버스의 Rebuild 오버헤드도 신경 써야 합니다. UGUI는 캔버스 내의 단 하나의 UI 요소라도 변경(위치 이동, 텍스트 변경, 알파값 조절 등)되면 캔버스 전체의 정점 데이터를 다시 계산(Rebuild)합니다. 따라서 정적인 UI(배경, 프레임 등)와 동적인 UI(체력바, 데미지 텍스트, 애니메이션 아이콘 등)를 별도의 캔버스로 분리하여 계층을 나누는 것이 필수적입니다. 이렇게 하면 동적인 UI 캔버스만 Rebuild되므로 CPU 연산 시간을 대폭 절약할 수 있습니다.
5. SRP Batcher의 혁명: URP 환경에서의 새로운 패러다임
유니티가 URP(Universal Render Pipeline)를 도입하면서 선보인 SRP Batcher는 기존의 배칭 개념을 완전히 뒤집어 놓았습니다. 기존에는 메쉬나 머티리얼이 완벽히 동일해야 배칭이 되었지만, SRP Batcher는 셰이더 변종(Shader Variant)이 동일하다면 서로 다른 머티리얼을 사용하더라도 GPU 상태 변경 비용을 극단적으로 줄여줍니다. 즉, CPU가 프레임마다 거대한 상수 버퍼(Constant Buffer)를 GPU에 업로드하고, 개별 오브젝트를 그릴 때는 버퍼의 오프셋(Offset)만 변경하여 드로우콜의 오버헤드를 획기적으로 낮추는 원리입니다.
모바일 게임을 URP로 개발 중이라면 SRP Batcher를 켜는 것만으로도 막대한 CPU 성능 향상을 얻을 수 있습니다. 단, 커스텀 셰이더를 작성할 때 속성들을 `CBUFFER_START`와 `CBUFFER_END` 블록 안에 정확히 선언해야 SRP Batcher와 호환된다는 점을 명심해야 합니다. 최적화는 단순히 엔진 옵션을 체크하는 것을 넘어, 파이프라인의 동작 원리를 꿰뚫고 코드 레벨에서부터 하드웨어 친화적으로 설계하는 과정입니다. 이러한 집요한 최적화 노력이 모여 60fps라는 부드러운 유저 경험을 완성해 냅니다.
// [C#] MaterialPropertyBlock을 활용한 GPU 인스턴싱 최적화
using UnityEngine;
public class InstancedColor : MonoBehaviour
{
[SerializeField] private Color color = Color.white;
private Renderer _renderer;
private MaterialPropertyBlock _propBlock;
void Awake()
{
_renderer = GetComponent();
_propBlock = new MaterialPropertyBlock();
}
void Update()
{
// 매 프레임 색상이 변하더라도 머티리얼 인스턴스를 복제하지 않고 배칭 유지
_renderer.GetPropertyBlock(_propBlock);
_propBlock.SetColor("_BaseColor", color);
_renderer.SetPropertyBlock(_propBlock);
}
}
지금까지 모바일 게임 환경에서 60fps를 방어하기 위한 다양한 드로우콜 최적화 기법에 대해 살펴보았습니다. 정적/동적 배칭의 한계부터 GPU 인스턴싱, UGUI 최적화, 그리고 URP의 SRP Batcher까지 각각의 원리와 실무 적용 방안을 이해하는 것은 클라이언트 프로그래머의 필수 역량입니다.
최적화는 프로젝트 막바지에 하는 것이 아니라, 기획 단계에서부터 아트 리소스의 제작 가이드라인을 세우고 아키텍처를 설계하는 초기부터 고려되어야 합니다. 그래야만 기술 부채에 짓눌리지 않고 안정적인 성능을 확보할 수 있습니다.
결국 훌륭한 게임 경험은 부드러운 프레임 속에서 완성됩니다. 1ms의 렌더링 타임이라도 줄이기 위해 프로파일러와 씨름하는 모든 개발자 분들의 열정에 박수를 보내며, 본 칼럼이 여러분의 프로젝트 성능을 한 단계 끌어올리는 데 실질적인 도움이 되기를 바랍니다.