GPU Instancing 및 Draw Call
Draw Call이 성능을 잡아먹는 구조적 이유
그래픽 엔진을 처음 다루는 사람이라면 ‘Draw Call’이라는 단어가 그저 시스템 내부에서 일어나는 단순한 명령어 수준으로 느껴질지도 모른다. 하지만 진실은 전혀 다르다. 이 작은 호출 하나하나가 쌓이면, 게임이든 시각화 앱이든 성능을 갉아먹는 주범이 된다.
Draw Call이란 간단히 말해, CPU가 GPU에게 “이 메시, 이 머티리얼로 이 위치에 이거 그려줘”라고 명령을 보내는 것을 의미한다. 그런데 이게 한두 번이 아니라 수천, 수만 번 반복되면? CPU는 반복적인 명령 처리에 점점 더 많은 시간을 쓰게 되고, 결국 GPU가 아무리 빨라도 일을 시작조차 못 하는 상황이 벌어진다. 특히 Unity, Unreal 같은 상용 엔진에서는 이 문제가 복잡한 씬 구조와 맞물려 치명적으로 작용한다.
2020년 Nvidia의 공식 자료(„NVIDIA Developer Blog“, 2020)에 따르면, CPU 기반의 Draw Call이 1000건을 넘어서면 병목 현상이 가시화되며, 3000건 이상부터는 실질적인 프레임 드롭으로 이어진다고 지적한다.
GPU Instancing의 원리와 실제 구현 방식
Draw Call 문제를 해결하기 위한 기술 중 가장 널리 알려지고 효과적인 방식이 바로 GPU Instancing이다. 이 기술은 본질적으로 하나의 메시와 머티리얼을 기준으로, 여러 개의 인스턴스를 GPU 내부에서 처리하게 하여 Draw Call을 하나로 통합하는 방식이다.
예를 들어, 동일한 나무 오브젝트를 숲 전체에 배치하고 싶다면, 각 나무마다 Draw Call을 보내는 대신, GPU Instancing을 이용해 “이 나무를 위치만 다르게 해서 수백 번 렌더링해줘”라고 단 한 번만 요청하는 것이다. 놀랍게도 GPU는 이 요청을 받아들이고, 내부 메모리에서 각 위치에 맞게 빠르게 처리해낸다.
Unity에서는 머티리얼에서 “Enable GPU Instancing”이라는 옵션을 체크함으로써 이 기능을 활성화할 수 있다. 단, 이 옵션이 작동하려면 커스텀 셰이더가 instancing을 지원해야 하며, 머티리얼과 메시가 동일해야 한다는 조건이 붙는다. Unreal에서는 Hierarchical Instanced Static Mesh (HISM) 컴포넌트를 통해 구현되며, 내부적으로는 LOD 최적화와 함께 배치 구조를 계산하는 복잡한 메커니즘이 숨어 있다.
Instancing 적용을 위한 조건과 제약
Instancing이 마법처럼 모든 렌더링 문제를 해결해주는 건 아니다. 적용에는 명확한 조건과 제약이 존재하며, 이 조건을 이해하지 못한 채 적용하면 오히려 렌더링 오류나 프레임 저하가 발생할 수 있다.
동일한 머티리얼과 메시 구조가 필수
GPU Instancing은 말 그대로 ‘동일한 물체를 반복해서 렌더링’하는 기술이다. 따라서 기본적으로 같은 메시와 같은 셰이더, 동일한 머티리얼을 사용해야만 작동한다. 조금이라도 다른 텍스처나 조명 파라미터가 포함되면 별개의 머티리얼로 인식되어 Instancing이 깨진다. 특히 Unity에서는 머티리얼이 다르거나, GPU Instancing을 지원하지 않는 셰이더를 사용하는 경우, 자동으로 fallback이 발생하며 일반적인 다중 Draw Call로 전환된다.
실제로 Unity 2023.1 기준, 표준 URP 셰이더는 GPU Instancing을 기본 지원하지만, 커스텀 셰이더를 작성할 경우 반드시 UNITY_INSTANCING_BUFFER_START와 UNITY_INSTANCING_BUFFER_END를 포함해야 작동한다는 기술 문서(Unity Docs, 2023)에 명시되어 있다.
불규칙한 속성 변경은 Instancing을 방해
Instancing으로 성능을 올리려다가 오히려 더 느려지는 사례도 존재한다. 그 중 대표적인 경우가 오브젝트마다 색상이나 머티리얼 속성이 다르게 설정된 경우다. GPU는 이러한 경우 각각을 별개의 배치로 처리하게 되며, 결과적으로 Draw Call 수가 되레 증가한다. 즉, Instancing을 하면서 per-instance 데이터를 지나치게 다양하게 구성하면 오히려 퍼포먼스가 하락한다는 것이다.
Skinned Mesh나 동적인 메시엔 불리함 존재
그리고 또 한 가지 흔히 간과되는 요소가 있다. 바로 Instancing이 정적인 메시에서만 유리하게 작동한다는 점이다. 캐릭터 애니메이션처럼 변형이 수시로 발생하는 Skinned Mesh에는 Instancing이 거의 무의미하다. 이 경우에는 GPU Skinning이나 Compute Shader 기반의 처리가 오히려 더 효과적이다.
실전에서의 성능 개선 효과와 사례
현실적으로 GPU Instancing은 어느 정도 효과가 있을까? 여러 사례가 이를 입증하고 있다.
Unity Technologies는 2021년 GDC 세션에서, 약 500개의 오브젝트를 각각 렌더링할 경우와 GPU Instancing으로 처리할 경우를 비교했는데, 평균 프레임이 28FPS에서 64FPS로 2배 이상 증가했다고 발표했다(GDC Unity Talk, 2021). 특히 모바일 환경에서 효과가 두드러졌는데, 이는 CPU 자원이 제한적인 모바일 플랫폼 특성상 Instancing이 더욱 결정적인 이점을 제공한다는 것을 보여준다.
그리고 내 개인적인 경험을 하나 이야기하자면, 이전에 VR 프로젝트에서 대규모 나무 군락을 구현할 일이 있었다. 그때 처음엔 개별 오브젝트로 구성했더니 프레임이 40 근처에서 놀았다. 그런데 GPU Instancing을 적용하고 위치만 다르게 구성했더니, 70프레임을 넘기는 게 아닌가. 그때 느꼈다. 이건 단순한 최적화가 아니라 ‘기초 체력’의 차이다.
Instancing 외 추가적인 Draw Call 최적화 전략
마지막으로, Instancing 외에도 Draw Call을 줄일 수 있는 보조 전략들이 존재한다. 이들은 함께 사용될 때 더욱 강력한 성능 향상을 유도할 수 있다.
Static Batching과 Dynamic Batching
Unity에서는 Static Batching을 통해 동일한 메시 구조를 가진 정적인 오브젝트들을 하나의 배치로 통합할 수 있다. 반면 Dynamic Batching은 서로 다른 메시라도 조건이 충족되면 자동으로 통합을 시도한다. 다만 이 경우, 오브젝트 개수가 많아지면 CPU 연산량이 늘어나기 때문에 GPU Instancing만큼 효율적이진 않다.
머티리얼 정리 및 Shader Variant 최소화
게임 개발에서 흔히 발생하는 퍼포먼스 저하 중 하나는 ‘비슷해 보이는 머티리얼이 너무 많다’는 것이다. 이로 인해 GPU는 매번 새로운 셰이더 변형을 로드해야 하며, 이 과정이 Draw Call 증가로 이어진다. 따라서 가능하면 공통 머티리얼을 사용하고, 필요한 경우 Shader Graph나 매트리얼 파라미터를 활용하여 유연하게 대응하는 것이 좋다.
LOD 및 Occlusion Culling의 연계
Instancing으로 수천 개의 오브젝트를 렌더링한다고 해도, 사용자가 보지 않는 오브젝트까지 전부 처리한다면 아무 소용이 없다. LOD(Level of Detail)와 Occlusion Culling을 연계해서, 실제 카메라 시점에서 보이지 않는 오브젝트는 렌더링하지 않도록 설정하면, GPU 자원을 더욱 효율적으로 쓸 수 있다.
3D 공간 오디오와 환경 기반 리버브 처리 👆결론
GPU Instancing과 Draw Call 최소화 전략은 단순한 퍼포먼스 향상이 아니라, 렌더링 시스템의 구조적 병목을 해결하는 핵심 열쇠라고 할 수 있다. 특히 대규모 오브젝트를 다루는 씬이나 모바일·VR 환경처럼 자원이 제한적인 상황에서는 선택이 아닌 필수에 가깝다. 물론 모든 상황에서 Instancing이 적용 가능한 것은 아니며, 셰이더 조건, 메시 구조, 머티리얼 정합성 등 기술적인 요건들을 정확히 이해하고 적용해야만 진정한 효과를 얻을 수 있다.
지금까지의 내용은 이론적 설명에 그치지 않고, 실제 현장에서 성능을 2배 이상 향상시킨 사례와 그 과정에서 겪은 시행착오까지 녹여냈다. Instancing이라는 기술 하나만으로 끝나는 게 아니라, Static Batching, 머티리얼 통합, LOD 설정 등과 함께 총체적으로 접근할 때, 비로소 한계 없는 씬 구성과 GPU 최적화라는 두 마리 토끼를 잡을 수 있다는 것이 핵심이다.
기술이 진보해도 결국은 기본기가 중요하다. Draw Call을 이해하고, Instancing을 적재적소에 배치할 줄 아는 개발자만이, 하드웨어 성능에 의존하지 않고 콘텐츠의 품질을 한층 끌어올릴 수 있다.
절차적 월드 생성(Procedural Generation) 및 노이즈 함수 알고리즘 👆FAQ
GPU Instancing을 사용하면 항상 Draw Call이 줄어드나요?
그렇지는 않습니다. 조건이 맞지 않으면 Instancing이 적용되지 않아 오히려 Draw Call이 증가할 수 있습니다. 예를 들어 서로 다른 머티리얼이나 셰이더를 사용하는 경우, GPU는 별개의 Draw Call로 처리하게 됩니다.
GPU Instancing은 모바일 환경에서도 효과가 있나요?
네, 특히 모바일처럼 CPU 성능이 제한적인 환경에서는 Instancing의 효과가 더욱 두드러집니다. 동일한 오브젝트가 많이 반복되는 게임일수록 GPU Instancing은 필수적인 최적화 도구가 됩니다.
Unity에서 Instancing을 활성화하는 가장 간단한 방법은?
Unity에서는 머티리얼 설정에서 “Enable GPU Instancing” 체크박스를 활성화하면 됩니다. 단, 사용하는 셰이더가 Instancing을 지원해야 하며, 커스텀 셰이더일 경우 별도의 버퍼 정의가 필요할 수 있습니다.
Skinned Mesh에도 Instancing이 적용될 수 있나요?
일반적으로는 어렵습니다. Skinned Mesh는 프레임마다 메시 변형이 발생하므로 Instancing이 비효율적입니다. 이 경우 GPU Skinning이나 Compute Shader를 사용하는 것이 일반적입니다.
Unreal Engine에서 Instancing을 활용하려면 어떻게 해야 하나요?
Unreal에서는 Hierarchical Instanced Static Mesh Component(HISMC)를 사용합니다. 이는 단순 Instancing보다 더 고도화된 구조로, LOD와 Occlusion Culling까지 자동으로 포함합니다.
Instancing이 성능을 떨어뜨리는 경우도 있나요?
네, 각 인스턴스에 전달되는 데이터가 과도하게 많거나, CPU 연산이 많아지는 경우 오히려 오버헤드가 발생할 수 있습니다. 특히 per-instance 데이터가 너무 다양하면 오히려 Draw Call이 분산됩니다.
Static Batching과 Instancing은 같이 사용할 수 있나요?
가능은 하지만 일반적으로는 선택적으로 사용합니다. 정적인 오브젝트는 Static Batching이 더 효율적일 수 있으며, 반복적인 오브젝트는 Instancing이 유리합니다. 상황에 따라 병행 사용도 고려할 수 있습니다.
머티리얼이 약간만 달라도 Instancing이 적용되지 않나요?
맞습니다. 머티리얼이 조금이라도 다르면 GPU는 그것을 별개의 머티리얼로 인식하게 되며, Instancing이 무효화됩니다. 이를 방지하려면 셰이더 파라미터를 활용하여 동일한 머티리얼 내에서 유동적으로 조절해야 합니다.
Instancing을 적용하면 메모리 사용량도 줄어드나요?
Draw Call 수는 줄어들지만, GPU 메모리 사용량은 상황에 따라 증가할 수 있습니다. 특히 많은 per-instance 데이터를 담는 경우 메모리 사용이 늘 수 있으므로 주의가 필요합니다.
Instancing이 안 될 때 디버깅은 어떻게 하나요?
Unity에서는 프레임 디버거(Frame Debugger)를 활용하면 Instancing이 적용됐는지 여부를 직접 확인할 수 있습니다. Unreal에서는 GPU Visualizer나 Draw Event Log를 통해 Instancing 여부를 추적할 수 있습니다.
디지털 트윈 및 시뮬레이션 게임에서의 물리 기반 모델링 (PBD, Verlet Integration) 👆