Phase 8: 최적화와 실전 워크플로우 | 예상 학습 시간: 40분


🎯 학습 목표

  • Draw Call이 무엇이고 왜 많아지면 성능이 떨어지는지 GPU 파이프라인 관점에서 설명할 수 있다
  • Static Batching과 Dynamic Batching, SRP Batcher, GPU Instancing의 차이를 구분하고 각각이 적용되는 조건을 알 수 있다
  • Frame Debugger로 실제 씬의 Draw Call과 배칭 여부를 직접 확인할 수 있다

1. Draw Call이란 무엇인가

Draw Call은 CPU가 GPU에게 “이 메시를 이 머티리얼로 그려줘"라고 내리는 명령 한 건을 말합니다. 씬에 오브젝트가 100개 있고 모두 다른 머티리얼을 쓴다면, 이론상 CPU는 100번의 Draw Call을 GPU에 전달해야 합니다.

문제는 Draw Call 자체가 GPU 렌더링 부하가 아니라 CPU→GPU 통신 오버헤드라는 점입니다. Draw Call 하나를 준비할 때마다 CPU는 상태 전환(머티리얼, 셰이더, 텍스처, 버퍼 바인딩 등)을 GPU 드라이버에 전달해야 하고, 이 과정이 누적되면 GPU가 놀고 있어도 CPU가 병목이 되는 “CPU-bound” 상황이 발생합니다. 특히 모바일처럼 드라이버 오버헤드가 큰 환경에서는 Draw Call 수가 프레임레이트에 직접적인 영향을 미칩니다.

상황병목 지점
Draw Call이 많고 폴리곤 수는 적음CPU (드라이버 오버헤드)
Draw Call은 적지만 셰이더 연산이 복잡함GPU (프래그먼트 셰이더)
텍스처 해상도가 과도하게 큼GPU (메모리 대역폭)

💡 실무 팁: “폴리곤 수를 줄이면 최적화된다"는 생각은 절반만 맞습니다. 모바일/저사양 환경에서는 폴리곤 수보다 Draw Call 수와 오버드로우(Overdraw)가 체감 성능에 더 큰 영향을 주는 경우가 많습니다.


2. Batching의 원리 — 여러 오브젝트를 하나의 Draw Call로

배칭(Batching)은 여러 오브젝트를 하나의 Draw Call로 묶어 CPU 오버헤드를 줄이는 기법입니다. Unity는 크게 네 가지 배칭 방식을 제공합니다.

2-1. Static Batching

  • 절대 움직이지 않는 오브젝트(건물, 지형 소품 등)를 Inspector에서 Static 체크박스로 표시하면, Unity가 빌드 시점에 이들을 하나의 큰 메시로 미리 합칩니다.
  • 같은 머티리얼을 공유해야 배칭 대상이 됩니다. 머티리얼이 다르면 여전히 별도 Draw Call로 처리됩니다.
  • 장점: 런타임 CPU 비용이 거의 없음(빌드 타임에 미리 계산). 단점: 메모리 사용량 증가(정점 데이터가 중복 저장됨), 움직이면 안 됨.
Inspector → GameObject 선택 → 우측 상단 "Static" 체크박스 활성화
(또는 Static 드롭다운에서 "Batching Static"만 개별 선택 가능)

2-2. Dynamic Batching

  • 움직이는 오브젝트 중 정점 수가 적은(약 300 vertex attribute 이하) 것들을 매 프레임 CPU가 실시간으로 병합합니다.
  • 조건이 까다롭습니다: 같은 머티리얼, 동일한 셰이더 변형, 균일 스케일(non-uniform scale 불가) 등을 모두 만족해야 합니다.
  • 최신 Unity 버전(URP 사용 시)에서는 SRP Batcher가 이 역할을 대체하는 경우가 많아, Dynamic Batching의 중요도는 예전보다 낮아졌습니다.

2-3. SRP Batcher (URP/HDRP 전용)

  • Universal Render Pipeline에서 기본으로 제공하는 배칭 방식으로, 오브젝트를 하나로 합치는 대신 “머티리얼이 같은 오브젝트들의 GPU 상태 전환 비용"을 줄입니다.
  • Draw Call 수 자체는 줄지 않을 수 있지만, 각 Draw Call 사이의 CPU 셋업 비용이 크게 감소합니다.
  • 활성화 조건: 셰이더가 SRP Batcher를 지원해야 하며(Shader Graph로 만든 URP 셰이더는 기본 지원), MaterialPropertyBlock으로 개별 오브젝트 속성을 덮어쓰면 SRP Batcher가 깨질 수 있으니 주의가 필요합니다.
Project Settings → Graphics → Universal Render Pipeline Asset
→ SRP Batcher 옵션이 기본적으로 활성화되어 있는지 확인

2-4. GPU Instancing

  • 완전히 동일한 메시+머티리얼을 가진 오브젝트를 대량으로(수풀, 돌, 군중 등) 그릴 때 사용합니다.
  • 셰이더에 #pragma multi_compile_instancing을 추가하고 머티리얼 Inspector에서 “Enable GPU Instancing"을 체크하면, 각 인스턴스의 Transform 정보만 GPU에 배열로 전달해 단 한 번의 Draw Call로 수백~수천 개를 그립니다.
Graphics.DrawMeshInstanced(mesh, 0, material, matrices, matrices.Length);
배칭 방식적용 대상오브젝트 이동머티리얼 조건
Static Batching정적 오브젝트불가동일 머티리얼
Dynamic Batching저폴리곤 동적 오브젝트가능동일 머티리얼, 균일 스케일
SRP BatcherURP/HDRP 전반가능SRP Batcher 호환 셰이더
GPU Instancing동일 메시 대량 반복가능동일 메시+머티리얼

3. Frame Debugger로 실제 Draw Call 확인하기

이론만으로는 내 씬이 실제로 배칭되고 있는지 알 수 없습니다. Unity의 Frame Debugger를 사용하면 프레임 하나가 렌더링되는 과정을 Draw Call 단위로 하나씩 재생해볼 수 있습니다.

Window → Analysis → Frame Debugger → Enable 클릭
→ 좌측 목록에서 각 Draw Call을 순서대로 클릭하며 확인
→ "Batch Cause" 항목에서 배칭이 되었는지, 안 됐다면 왜 안 됐는지 사유 확인 가능

Frame Debugger 좌측 트리에서 각 항목을 클릭하면 우측에 그 시점까지 렌더링된 화면이 표시되고, 하단에 “Why this draw call can’t be batched with the previous one” 같은 사유가 나타나는 경우도 있어 배칭 실패 원인을 직접 추적할 수 있습니다. 대표적인 배칭 실패 사유는 다음과 같습니다.

  • 서로 다른 머티리얼 인스턴스 참조 (같은 셰이더라도 인스턴스가 다르면 실패)
  • MaterialPropertyBlock으로 개별 속성을 덮어씀 (SRP Batcher 대상에서 제외됨)
  • 정렬 순서상 사이에 다른 렌더 큐의 오브젝트가 끼어듦(불투명→투명 전환 등)
  • 서로 다른 라이트맵 인덱스를 사용하는 정적 오브젝트

💡 실무 팁: Frame Debugger는 에디터 재생 모드에서만 정확히 동작합니다. 실제 타깃 기기(모바일 등)의 수치는 Unity Profiler의 “Rendering” 모듈이나 플랫폼 전용 프로파일러(Xcode Instruments, Android GPU Inspector)로 별도 확인해야 합니다.


📝 핵심 요약

  1. Draw Call은 GPU 렌더링 자체보다 CPU→GPU 상태 전환 오버헤드에 가까우며, 많아지면 GPU가 놀아도 프레임이 떨어지는 CPU 병목이 발생한다
  2. Static Batching은 빌드 타임에 정적 오브젝트를 병합하고, Dynamic Batching은 저폴리곤 동적 오브젝트를 런타임에 병합하지만 둘 다 동일 머티리얼 조건이 필요하다
  3. URP를 사용한다면 SRP Batcher가 기본 최적화 수단이며, MaterialPropertyBlock 남용은 SRP Batcher를 깨뜨릴 수 있다
  4. 동일한 메시를 대량 반복해서 그릴 때는 GPU Instancing이 가장 효율적이다
  5. Frame Debugger로 실제 씬의 Draw Call과 배칭 실패 사유를 직접 확인하는 습관이 최적화의 출발점이다

🔗 참고 자료


⬅️ 이전: Day 49 — 7주차 정리: 연출이 들어간 인터랙션 씬 제작 | 다음: Day 51 — Profiler로 성능 분석하기 ➡️