---
title: "합치지 않고도 빨라지는 법"
description: "SRP Batcher 는 메시를 안 합칩니다. 머티리얼 값을 GPU 에 상주시켜 두고 오프셋만 바꿉니다. 그래서 배치 조건이 머티리얼이 아니라 셰이더 변형입니다."
author: "유니얼"
pubDate: 2026-09-09
category: "game-dev/unity/rendering"
tags: ["Unity", "최적화", "Batching"]
canonical: https://blog.unialgames.com/posts/unity-srp-batcher/
---
메시를 합치는 게 비싸다면 안 합치면 됩니다.
문제는 드로우 콜이 아니라 그 앞에 붙는 상태 변경이었으니, 상태를 안 바꾸면 됩니다.

SRP Batcher 가 하는 일이 정확히 그것입니다.

## 값을 GPU 에 두고 온다

머티리얼마다 색, 타일링, 커스텀 프로퍼티 같은 값이 있습니다.
기존 방식은 오브젝트를 그릴 때마다 이 값들을 CPU 에서 GPU 로 올려 보냈습니다.
SRP Batcher 는 이걸 **미리 GPU 쪽 상수 버퍼에 올려 두고, 바뀔 때만 갱신** 합니다.

<figure class="diagram">
<svg viewBox="0 0 680 250" role="img" aria-label="머티리얼 값을 GPU 상수 버퍼에 상주시켜 두고 드로우 콜마다 오프셋만 바꾸는 구조 그림">
<text x="20" y="24" font-size="13" fill="var(--text-muted)">기존 방식</text>
<g stroke="var(--border)" fill="var(--bg)">
<rect x="20" y="38" width="150" height="26" rx="3"></rect>
<rect x="20" y="70" width="150" height="26" rx="3"></rect>
<rect x="20" y="102" width="150" height="26" rx="3"></rect>
</g>
<text x="30" y="56" font-size="12" fill="var(--warn)">상태 세팅 + 값 업로드</text>
<text x="30" y="88" font-size="12" fill="var(--warn)">상태 세팅 + 값 업로드</text>
<text x="30" y="120" font-size="12" fill="var(--warn)">상태 세팅 + 값 업로드</text>
<text x="180" y="56" font-size="12" fill="var(--text-muted)">Draw</text>
<text x="180" y="88" font-size="12" fill="var(--text-muted)">Draw</text>
<text x="180" y="120" font-size="12" fill="var(--text-muted)">Draw</text>
<text x="20" y="160" font-size="13" fill="var(--text-muted)">SRP Batcher</text>
<g stroke="var(--accent)" fill="var(--accent-soft)">
<rect x="20" y="174" width="150" height="26" rx="3"></rect>
</g>
<text x="30" y="192" font-size="12" fill="var(--accent)">상태 세팅 1회</text>
<g stroke="var(--border)" fill="var(--bg)">
<rect x="20" y="206" width="46" height="22" rx="3"></rect>
<rect x="72" y="206" width="46" height="22" rx="3"></rect>
<rect x="124" y="206" width="46" height="22" rx="3"></rect>
</g>
<text x="27" y="221" font-size="11" fill="var(--text-muted)">Draw</text>
<text x="79" y="221" font-size="11" fill="var(--text-muted)">Draw</text>
<text x="131" y="221" font-size="11" fill="var(--text-muted)">Draw</text>
<g stroke="var(--border)" fill="none">
<rect x="380" y="150" width="150" height="86" rx="4"></rect>
</g>
<text x="390" y="170" font-size="12" fill="var(--text-muted)">GPU 상수 버퍼</text>
<g stroke="var(--accent)" fill="var(--accent-soft)">
<rect x="390" y="180" width="130" height="14"></rect>
<rect x="390" y="198" width="130" height="14"></rect>
<rect x="390" y="216" width="130" height="14"></rect>
</g>
<text x="540" y="192" font-size="11" fill="var(--accent)">머티리얼 A</text>
<text x="540" y="210" font-size="11" fill="var(--accent)">머티리얼 B</text>
<text x="540" y="228" font-size="11" fill="var(--accent)">머티리얼 C</text>
<g stroke="var(--accent)" fill="none">
<path d="M180 214 L376 190"></path>
</g>
<text x="200" y="206" font-size="11" fill="var(--accent)">오프셋만 바꿔 가며</text>
<text x="380" y="56" font-size="13" fill="var(--text-muted)">값이 GPU 에 이미 있으니</text>
<text x="380" y="76" font-size="13" fill="var(--text-muted)">드로우 콜마다 올릴 것이 없습니다</text>
<text x="380" y="106" font-size="13" fill="var(--text-muted)">그래서 드로우 콜 수는 그대로거나</text>
<text x="380" y="126" font-size="13" fill="var(--text-muted)">늘어나는데 총 CPU 시간은 줄어듭니다</text>
</svg>
<figcaption>메시는 200개 그대로입니다. 줄어든 것은 드로우 콜 하나하나에 붙는 CPU 비용입니다.</figcaption>
</figure>

그래서 앞의 실측에서 드로우 콜이 524 에서 765 로 **늘었는데** SetPass Call 이
517 에서 300 으로 떨어졌던 것입니다. 그리기 명령은 오히려 잘게 나뉘었지만
그 앞에서 하던 일이 사라졌습니다.

## 배치 조건이 다르다

여기가 다이나믹 배칭과 갈리는 지점이고, 실무에서 가장 헷갈리는 자리입니다.

| | 배치가 이어지는 조건 |
| --- | --- |
| 다이나믹 배칭 | **같은 머티리얼** (같은 텍스처, 같은 값) |
| SRP Batcher | **같은 셰이더 변형** (값은 달라도 됨) |

SRP Batcher 에서는 **머티리얼이 달라도 됩니다.** 빨간 적과 파란 적이
같은 셰이더를 쓰고 값만 다르면 배치가 안 끊깁니다. 값은 어차피 버퍼에 다 있고
오프셋만 바꾸면 되니까요.

반대로 값이 완전히 같아도 **셰이더가 다르면 끊깁니다.**
`Sprite-Default` 와 `Sprite-Lit-Default` 를 섞어 쓰면 그 경계마다 끊깁니다.
커스텀 셰이더를 하나 추가할 때마다 배치가 하나씩 더 쪼개집니다.

> 다이나믹 배칭에서는 "머티리얼 종류를 줄여라" 가 맞고,
> SRP Batcher 에서는 "**셰이더 종류를 줄여라**" 가 맞습니다.
> 조언이 서로를 대체하지 않습니다.

## 셰이더 쪽 조건

SRP Batcher 가 걸리려면 셰이더가 머티리얼 프로퍼티를 `UnityPerMaterial` 이라는
상수 버퍼 블록 안에 선언해야 합니다. URP 내장 셰이더는 전부 그렇게 돼 있습니다.

직접 쓴 셰이더나 오래된 에셋에서 가져온 셰이더는 아닐 수 있습니다.
Shader Graph 로 만든 것은 자동으로 맞춰집니다.
셰이더 인스펙터를 열면 **SRP Batcher 호환 여부가 한 줄로 적혀 있고**,
호환이 아니면 그 이유까지 적혀 있습니다. 먼저 여기를 봅니다.

## 켜는 것만으로는 절반이다

Unity 6 에서 SRP Batcher 는 기본으로 켜져 있습니다.
그래서 "이미 켜져 있는데 왜 안 빨라지지" 가 자주 나옵니다.

켜는 것은 조건을 만들어 준 것뿐입니다.
**배치가 실제로 이어지려면 콘텐츠 구조가 받쳐 줘야 합니다.**
셰이더가 몇 종류인지, 정렬 레이어를 얼마나 쪼갰는지, 반투명 순서가 교차하는지가
전부 여기에 걸립니다.

그러니 켰다면 그다음 할 일은 Frame Debugger 를 열어
**어디서 몇 번 끊겼는지** 세는 것입니다.

## 6.3 의 3D Mesh in 2D 는 예외다

Unity 6.3 부터 3D 메시를 2D 정렬 체계에 넣을 수 있게 됐습니다.
예전에는 스프라이트 뒤에 3D 오브젝트를 자연스럽게 끼우려면 카메라 트릭이 필요했는데,
이제는 Mesh 2D 계열 셰이더에 Sorting Group 컴포넌트를 붙이면 됩니다.

**단 이 셰이더 계열은 현재 SRP Batcher 와 호환되지 않습니다.**
3D 와 2D 를 자주 번갈아 배치하면 배치가 그때마다 끊깁니다.

쓸 거라면 레이어 구조를 단순하게 유지하는 편이 낫습니다.
배경, 게임플레이, 전경 정도로 층을 나누고 그 안에서 섞지 않는 식입니다.
이건 버전에 따라 바뀔 수 있으니, 쓰는 Unity 6.x 패치 릴리스 노트를 확인해 보세요.