Unity 6에 새롭게 추가된 GPU Resident Drawer는 드로우 콜을 최대 50%까지 줄여줍니다. 그런데 정확히 어떻게 그렇게 되는 것인지가 이 포스팅의 핵심입니다.
이 포스팅은 GPU Resident Drawer가 내부적으로 어떤 원리로 동작하는지, 어떤 조건에서 효과가 있고 어떤 경우에는 작동하지 않는지를 파고듭니다. “그냥 켜면 빨라지는 기능”이 아니라, 원리를 이해하고 써야 제대로 활용할 수 있습니다.
GPU Resident Drawer 완전 분석 시리즈
1편 — GPU Resident Drawer 동작 원리 완전 분석 (현재 글)
2편 — 커스텀 셰이더와 DOTS 인스턴싱 완전 정복
목차
- 기존 렌더링 파이프라인의 병목
- BatchRendererGroup — GPU Resident Drawer의 토대
- GPU Instancing과 간접 렌더링
- GPU 오클루전 컬링
- DOTS 인스턴싱 — 셰이더 조건
- 실제 수치로 보는 효과
- 활성화 방법 (URP 기준)
- 호환되지 않는 경우
- 디버깅 — 작동 확인 방법
- 결론 — 언제 쓰고 언제 안 쓰는가
먼저, 기존 렌더링 파이프라인의 병목을 짚어보자
GPU Resident Drawer의 가치를 이해하려면 먼저 전통적인 CPU 기반 렌더링이 왜 느려지는지를 알아야 합니다.
Unity의 기존 렌더링 흐름은 대략 이렇습니다:
1. CPU: 씬의 모든 Renderer를 순회하며 컬링 판단
2. CPU: 드로우 콜 목록 정리 (SRP Batcher, Static Batching 적용)
3. CPU → GPU: 드로우 명령(Draw Call) 전달
4. GPU: 실제 렌더링 수행
문제는 3번입니다. CPU가 매 프레임마다 “이걸 그려라, 저걸 그려라”를 GPU에 하나씩 전달하는 데 상당한 시간이 걸립니다. 씬에 오브젝트가 수천, 수만 개면 이 대화 자체가 병목이 됩니다. GPU는 이미 이전 작업을 마쳤는데, CPU가 다음 명령을 아직 준비 중인 상황이 발생합니다.
BatchRendererGroup — GPU Resident Drawer의 토대
GPU Resident Drawer는 BatchRendererGroup(BRG) API 위에 만들어졌습니다. BRG는 원래 DOTS(Entities) 그래픽스 패키지가 내부적으로 쓰던 저수준 API인데, Unity 6에서 일반 GameObjects에도 자동으로 적용되게 한 것이 GPU Resident Drawer입니다.
BRG의 핵심 아이디어는 드로우 명령의 주도권을 CPU에서 GPU로 넘기는 것입니다.
기존 방식은 CPU가 “오브젝트 A를 위치 X에 그려라”는 명령을 GPU에 전달하는 구조였다면, BRG 방식은 CPU가 인스턴스 데이터(위치, 회전, 스케일, 재질 정보 등)를 GPU 메모리에 올려두고 GPU가 직접 꺼내서 그리는 구조입니다.
기존:
CPU [오브젝트 순회 → 드로우콜 생성] → GPU [드로우콜 수신 → 렌더링]
BRG:
CPU [인스턴스 데이터를 GPU 메모리에 업로드] → GPU [데이터 직접 참조 → 렌더링]
이 구조 덕분에 CPU-GPU 간 통신 횟수가 대폭 줄어들고, GPU는 대기 없이 작업을 이어갈 수 있습니다.
GPU Instancing과 간접 렌더링(Indirect Rendering)
BRG가 GPU 메모리에 올려두는 데이터는 구조화된 버퍼(Structured Buffer) 형태입니다. 같은 메시를 쓰는 오브젝트들의 트랜스폼 정보가 배열로 묶여 GPU에 상주(Resident)합니다. 이것이 이름에 “Resident”가 붙은 이유이기도 합니다.
실제 그리기 명령은 DrawMeshInstancedIndirect 계열의 간접 렌더링 방식으로 실행됩니다. “Indirect”라는 이름처럼, CPU가 직접 “몇 개 그려라”를 지시하는 게 아니라 GPU가 버퍼를 보고 스스로 인스턴스 개수와 범위를 판단합니다.
기존 Graphics.RenderMeshInstanced()는 호출당 최대 1,023개까지만 인스턴스를 처리할 수 있었지만, GPU Resident Drawer는 이 제한이 없습니다. 내부적으로 배치를 자동 분할하기 때문에 이론상 인스턴스 수 제한이 없습니다.
GPU 오클루전 컬링 — 컬링도 GPU에서
컬링(Culling), 즉 화면에 보이지 않는 오브젝트를 렌더링 대상에서 제외하는 작업도 기존에는 CPU 몫이었습니다. GPU Resident Drawer는 이것까지 GPU로 넘깁니다.
방식은 이전 프레임의 깊이 버퍼(Depth Buffer) 활용입니다:
1. 이전 프레임 렌더링 완료 → Depth Buffer 보존
2. 현재 프레임 시작 시, Compute Shader가 각 인스턴스의 바운딩 박스를 Depth Buffer에 대조
3. 가려진(occluded) 인스턴스를 드로우 목록에서 제외
4. 통과한 인스턴스만 렌더링
이 방식의 장점은 속도입니다. CPU 오클루전 컬링은 레이캐스팅이나 PVS(Potentially Visible Set) 구조를 CPU에서 계산해야 하는 반면, GPU 병렬 처리는 수천 개의 인스턴스를 동시에 판별합니다.
다만, 여기에는 하나의 단점이 존재합니다. 바로, 1프레임 지연입니다. 이전 프레임 기준으로 컬링하기 때문에, 카메라가 매우 빠르게 이동하거나 회전하면 컬링 오류(잘못 제외된 오브젝트가 한 프레임 깜박이는 현상)가 생길 수 있습니다. 고속 이동 카메라가 있는 게임이라면 이 부분을 테스트해볼 필요가 있습니다.
DOTS 인스턴싱 — 셰이더 조건
GPU Resident Drawer가 작동하려면 사용하는 셰이더가 DOTS 인스턴싱을 지원해야 합니다. URP/HDRP의 Lit, Unlit 등 Unity 내장 셰이더는 이미 지원하지만, 커스텀 셰이더는 확인이 필요합니다.
DOTS 인스턴싱 셰이더에서 인스턴스 데이터를 읽는 방식은 기존 GPU 인스턴싱과 다릅니다:
// 기존 GPU Instancing
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
// DOTS Instancing (BRG 방식)
#ifdef UNITY_DOTS_INSTANCING_ENABLED
UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
UNITY_DOTS_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)
#endif
커스텀 셰이더를 많이 쓰는 프로젝트라면 GPU Resident Drawer 적용 전에 셰이더 호환성 점검이 선행되어야 합니다.
이와 관련된 내용은 다음 커스텀 셰이더와 GPU Resident Drawer — DOTS 인스턴싱 완전 정복 포스팅에서 보다 자세히 다루고 있습니다.
실제 수치로 보는 효과
공개된 테스트 결과 중 인상적인 사례가 있습니다. 식생 오브젝트 35,000개 씬에서:
| 항목 | GPU Resident Drawer 비활성 | GPU Resident Drawer 활성 |
|---|---|---|
| 드로우 콜 수 | 43,500 | 128 |
| CPU 렌더링 부하 | 기준값 | 약 50% 감소 |
| GPU 메모리 사용량 | 기준값 | +약 100MB 증가 |
드로우 콜이 43,000개에서 128개로 줄었다는 수치는 꽤 충격적입니다. 반면 메모리는 약 100MB가 늘어납니다. GPU 메모리에 인스턴스 데이터를 상주시키는 비용이 그대로 메모리 오버헤드로 나타나는 겁니다.
메모리 예산이 빡빡한 저사양 타겟 프로젝트라면 활성화 전후를 반드시 Profiler로 측정해보고 사용 여부를 결정하세요.
활성화 방법 (URP 기준)
세 군데 설정을 바꿔야 합니다:
① Shader Stripping 설정
Project Settings → Graphics → Shader Stripping
– BatchRendererGroup Variants → Keep All
② URP Asset 설정
Project Settings → Graphics에서 활성 URP Asset 선택
– SRP Batcher → 활성화
– GPU Resident Drawer → Instanced Drawing
③ Universal Renderer 설정
Renderer List의 렌더러를 더블클릭
– Rendering Path → Forward+
빌드 시간이 늘어나는 점은 감수해야 합니다. BRG 셰이더 변형(Variant)을 모두 컴파일해야 하기 때문입니다. 에디터 Play Mode 진입 속도는 영향을 받지 않습니다.
호환되지 않는 경우
아래 조건 중 하나라도 해당하면 해당 GameObject는 GPU Resident Drawer에서 자동으로 제외됩니다:
| 제외 조건 | 이유 |
|---|---|
MaterialPropertyBlock API 사용 |
인스턴스별 CPU 콜백이 필요한 구조 |
OnRenderObject 등 per-instance 콜백 |
동일한 이유 |
| DOTS 인스턴싱 미지원 셰이더 | BRG 데이터 접근 불가 |
| Light Probes가 Proxy Volume 사용 | GPU 상주 구조와 충돌 |
| Skinned Mesh Renderer | 현재 미지원 |
위에서 LPPV(Light Probe Proxy Volume)와 충돌하는 이유는 데이터 흐름이 근본적으로 다르기 때문입니다. LPPV는 Renderer의 월드 위치를 기준으로 프록시 볼륨 내 어느 지점의 SH(Spherical Harmonics) 계수를 샘플링할지 CPU가 매 프레임 계산해서 인스턴스마다 다른 조명 데이터를 주입해야 합니다. 반면 GPU Resident Drawer는 인스턴스 데이터를 GPU 메모리 버퍼에 한 번 올려두고 CPU 개입 없이 GPU가 직접 읽는 구조입니다. LPPV가 요구하는 “매 프레임, 인스턴스별, CPU 주도 GI 데이터 갱신”이 이 GPU 상주 모델과 맞지 않아 BRG는 해당 오브젝트를 제외하는 쪽을 선택합니다. 라이팅 품질을 포기하지 않으면서 GPU Resident Drawer를 쓰려면 LPPV 대신 베이크된 Light Probe 또는 이 구조를 지원하도록 설계된 APV(Adaptive Probe Volumes)를 사용하는 것을 추천합니다.
위의 조건에는 해당하지 않지만, 특정 오브젝트를 명시적으로 제외하고 싶다면 Disallow GPU Driven Rendering 컴포넌트를 추가하면 됩니다.
디버깅 — 제대로 작동하고 있는지 확인하는 법
Frame Debugger에서 드로우 콜이 “Hybrid Batch Group” 이름으로 묶여 나타나면 GPU Resident Drawer가 해당 오브젝트를 처리하고 있다는 뜻입니다.

GPU 오클루전 컬링 확인은 Window → Analysis → Rendering Debugger → GPU Resident Drawer 탭 → Occlusion Test Overlay 체크박스를 켜면 컬링된 오브젝트를 씬 뷰에서 시각적으로 확인할 수 있습니다.

결론 — 언제 쓰고 언제 안 쓰는가
GPU Resident Drawer는 반복 메시가 많은 씬에서 극적인 효과를 냅니다. 도시 배경, 숲, 군중 등 동일 메시가 대량 분포하는 경우가 전형적인 수혜 케이스입니다.
반면 캐릭터처럼 Skinned Mesh가 많거나, MaterialPropertyBlock으로 개별 제어가 필요한 오브젝트가 많은 씬은 아직 효과를 온전히 받기 어렵습니다. 이런 경우라면 섣불리 전체 활성화보다는 구역별로 나눠 적용하고 Profiler로 검증하는 게 낫습니다.