2026년 GDC와 Unite를 관통한 키워드를 하나만 꼽으라면 단연 GPU 드리븐 렌더링(GPU driven rendering) 이다. CPU가 오브젝트를 하나씩 순회하며 컬링하고 드로우콜을 쏘던 전통적인 방식은, 오픈월드의 풀 수십만 포기나 군중 수천 명 앞에서 금방 무너진다. 정작 GPU는 놀고 있는데 렌더 스레드가 병목인 상황이 너무 흔해졌다.
해결의 방향은 분명하다. "무엇을 그릴지" 정하는 일까지 GPU로 넘기는 것이다. 컬링도 GPU에서, 드로우 인자 생성도 GPU에서. CPU는 "이 메시 이만큼 그려" 명령 하나만 던지고 빠진다. 이번 세대 엔진들이 약속이라도 한 듯 같은 길로 움직이는 이유다. Unreal은 Nanite를 포함해 이미 GPU 드리븐이 기본이고, Unity도 6에서 같은 무기를 손에 쥐었다.
이 글에서는 Unity 6 기준으로 두 갈래를 정리한다. 코드 한 줄 없이 설정만으로 켜는 GPU Resident Drawer, 그리고 직접 제어가 필요할 때 꺼내는 RenderMeshIndirect API다.
전통 파이프라인의 병목 지점
기존 방식에서 드로우콜 비용은 오브젝트 수에 거의 비례한다. SRP Batcher가 셰이더 상태 전환을 줄여주긴 하지만, 결국 "오브젝트마다 CPU가 한 번씩 손을 댄다"는 구조 자체는 그대로다. 풀, 돌, 나뭇잎처럼 같은 메시가 수만 개 깔리는 씬에서 이 모델은 렌더 스레드를 그대로 태워버린다.
GPU 드리븐은 발상을 뒤집는다. 인스턴스 데이터를 큰 버퍼 하나에 올려두고, 컬링과 드로우 인자 계산을 GPU가 처리한다. 오브젝트가 1만 개든 20만 개든 CPU가 던지는 명령 수는 거의 상수에 머문다.
Unity 6 GPU Resident Drawer, 코드 없이 켜기
가장 손쉬운 입구다. URP Asset의 Rendering 섹션에서 GPU Resident Drawer를 Instanced Drawing으로 바꾸기만 하면 된다. 호환되는 MeshRenderer들을 엔진이 자동으로 GPU 인스턴싱 배치로 묶어준다. 같은 메시와 머티리얼을 쓰는 오브젝트 수천 개가 드로우콜 몇 개로 접힌다.
전제 조건이 몇 가지 있다. Universal Renderer의 Rendering Path가 Forward+ 여야 하고, SRP Batcher가 켜져 있어야 하며, 빌트인 렌더 파이프라인(BiRP)에서는 동작하지 않는다. 머티리얼도 SRP Batcher 호환이어야 한다. 매 프레임 Transform이 바뀌는 동적 오브젝트는 이득이 적으니, 정적인 대량 배치에 먼저 적용하는 게 정석이다.
직접 제어가 필요하면 RenderMeshInstanced
자동화로 부족하거나 런타임에 직접 그리고 싶다면 API로 내려간다. 예전의 Graphics.DrawMeshInstanced는 deprecated 되었고, 이제는 RenderParams 기반의 RenderMeshInstanced가 표준이다(2022.1 도입, Unity 6 정식). 인스턴스 데이터 구조체는 첫 필드가 반드시 오브젝트에서 월드로 가는 행렬이어야 한다.
using UnityEngine;
using UnityEngine.Rendering;
public class InstancedRocks : MonoBehaviour
{
[SerializeField] Mesh mesh;
[SerializeField] Material material;
[SerializeField] int count = 1000;
struct InstanceData
{
public Matrix4x4 objectToWorld; // 첫 필드는 반드시 오브젝트->월드 행렬
public uint renderingLayerMask;
}
InstanceData[] instances;
void Start()
{
instances = new InstanceData[count];
for (int i = 0; i < count; i++)
{
var pos = new Vector3(Random.Range(-50f, 50f), 0f, Random.Range(-50f, 50f));
instances[i].objectToWorld = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
instances[i].renderingLayerMask = 1;
}
}
void Update()
{
var rp = new RenderParams(material)
{
worldBounds = new Bounds(Vector3.zero, Vector3.one * 200f),
shadowCastingMode = ShadowCastingMode.On
};
Graphics.RenderMeshInstanced(rp, mesh, 0, instances);
}
}
한 번의 호출로 올릴 수 있는 인스턴스 수는 상수 버퍼 크기에 묶여 무한하지 않다. 수만 개를 넘기면 청크로 나눠 여러 번 호출하거나, 아래의 indirect 경로로 갈아타야 한다.
진짜 대량은 RenderMeshIndirect
RenderMeshIndirect는 드로우 인자(인덱스 수, 인스턴스 수 등)를 GraphicsBuffer에 담아 GPU에 넘긴다. 핵심은 이 instanceCount를 컴퓨트 셰이더가 채우게 만들 수 있다는 점이다. 즉 GPU에서 프러스텀 컬링을 돌린 뒤 살아남은 개수만 그리는, 완전한 GPU 드리븐 파이프라인이 여기서 완성된다.
using UnityEngine;
using UnityEngine.Rendering;
public class IndirectGrass : MonoBehaviour
{
[SerializeField] Mesh mesh;
[SerializeField] Material material; // 위치를 StructuredBuffer로 읽는 셰이더
[SerializeField] int instanceCount = 200000;
GraphicsBuffer argsBuffer;
GraphicsBuffer.IndirectDrawIndexedArgs[] args =
new GraphicsBuffer.IndirectDrawIndexedArgs[1];
void Start()
{
argsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments,
1, GraphicsBuffer.IndirectDrawIndexedArgs.size);
args[0].indexCountPerInstance = mesh.GetIndexCount(0);
args[0].instanceCount = (uint)instanceCount;
argsBuffer.SetData(args);
}
void Update()
{
var rp = new RenderParams(material)
{
worldBounds = new Bounds(Vector3.zero, Vector3.one * 1000f)
};
Graphics.RenderMeshIndirect(rp, mesh, argsBuffer);
}
void OnDestroy()
{
argsBuffer?.Release();
argsBuffer = null;
}
}
인스턴스별 위치나 회전은 별도 ComputeBuffer에 올려두고, 셰이더가 SV_InstanceID로 읽어가도록 짠다. 이러면 20만 포기 풀밭도 드로우콜 한 개로 끝난다.
GPU Occlusion Culling과 검증
GPU Resident Drawer를 켜면 그 아래 GPU Occlusion Culling 토글이 활성화된다. 다른 물체에 완전히 가려진 오브젝트를 GPU 단계에서 걸러내, 밀집된 도심이나 실내처럼 오버드로우가 심한 씬에서 특히 효과가 크다.
다만 모든 최적화는 측정이 먼저다. Frame Debugger로 배치가 실제로 묶이는지 눈으로 확인하고, Rendering Statistics 창에서 적용 전후의 Batches와 SetPass Calls를 비교하자. Profiler의 Render Thread 시간이 줄었는지가 최종 판정 기준이다. 숫자 없이 "켰으니 빨라졌겠지"로 넘어가면 머티리얼 비호환 탓에 배치가 하나도 안 묶이는 함정을 못 본다.
정리
세 가지만 기억하면 된다. 첫째, 2026년 렌더링 트렌드의 큰 줄기는 컬링과 드로우 결정을 GPU로 넘기는 GPU 드리븐이다. 둘째, Unity 6에서는 GPU Resident Drawer를 Forward+ 위에서 켜는 것만으로 별도 코드 없이 그 흐름에 무료 입장할 수 있다. 셋째, 자동화로 모자란 대량 식생이나 군중은 RenderMeshIndirect로 직접 짜되, 위치 데이터는 버퍼에 올리고 가능하면 컬링까지 GPU로 밀어 넣는다. 어떤 길을 택하든 Frame Debugger와 Profiler로 배치 수를 직접 확인하는 습관이 진짜 차이를 만든다.
'게임 산업 트렌드' 카테고리의 다른 글
| Unreal Engine 5.7 Substrate 실전 전환: 젖은 표면 레이어와 GBuffer 비용 관리 (0) | 2026.07.11 |
|---|---|
| 2026 게임 AI 트렌드: AI Native Game은 챗봇이 아니라 상태 머신이다 (0) | 2026.07.04 |
| 2026 멀티플레이어 트렌드: 서버 권위로의 회귀, Unity로 구현하는 클라이언트 예측과 재조정 (1) | 2026.06.20 |
| Unreal Fest 2025 핵심: Mass Entity + ZoneGraph으로 오픈월드 NPC 수천 명을 ECS로 돌리기 (0) | 2026.06.13 |
| GDC 2026 게임 트렌드: 언리얼 엔진 NNE로 구현하는 온디바이스 AI 캐릭터 제어 (1) | 2026.06.06 |