콘텐츠로 이동

Unity GPU Instancing과 Draw Call 최적화

동일한 메시와 머티리얼을 가진 오브젝트를 하나의 Draw Call로 렌더링하는 기법입니다. CPU→GPU 통신 횟수를 줄여 CPU 병목을 해소합니다.

// Surface Shader에서 GPU Instancing 활성화
Shader "Custom/InstancedShader" {
Properties { _Color("Color", Color) = (1,1,1,1) }
SubShader {
CGPROGRAM
#pragma surface surf Standard
#pragma multi_compile_instancing // 필수
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
struct Input { float2 uv_MainTex; };
void surf(Input IN, inout SurfaceOutputStandard o) {
o.Albedo = UNITY_ACCESS_INSTANCED_PROP(Props, _Color).rgb;
}
ENDCG
}
}

머티리얼 인스펙터에서 Enable GPU Instancing 체크박스를 활성화합니다.

MaterialPropertyBlock으로 인스턴스별 색상

섹션 제목: “MaterialPropertyBlock으로 인스턴스별 색상”

Material을 직접 수정하면 새 머티리얼이 생성되어 인스턴싱이 깨집니다. MaterialPropertyBlock을 사용하세요.

MaterialPropertyBlock _mpb;
MeshRenderer _renderer;
void Start() {
_mpb = new MaterialPropertyBlock();
_renderer = GetComponent<MeshRenderer>();
}
public void SetColor(Color color) {
_mpb.SetColor("_Color", color);
_renderer.SetPropertyBlock(_mpb); // 머티리얼 공유 유지
}

GameObject 없이 메시를 대량으로 렌더링합니다.

public class InstancedRenderer : MonoBehaviour {
[SerializeField] Mesh mesh;
[SerializeField] Material material;
[SerializeField] int count = 1000;
Matrix4x4[] _matrices;
MaterialPropertyBlock _mpb;
static readonly int ColorID = Shader.PropertyToID("_Color");
void Start() {
_matrices = new Matrix4x4[count];
var colors = new Vector4[count];
_mpb = new MaterialPropertyBlock();
var rand = new System.Random(42);
for (int i = 0; i < count; i++) {
_matrices[i] = Matrix4x4.TRS(
new Vector3(rand.Next(-50, 50), 0, rand.Next(-50, 50)),
Quaternion.Euler(0, rand.Next(360), 0),
Vector3.one);
colors[i] = new Vector4(
(float)rand.NextDouble(), (float)rand.NextDouble(),
(float)rand.NextDouble(), 1f);
}
_mpb.SetVectorArray(ColorID, colors);
}
void Update() {
// DrawMeshInstanced는 최대 1023개
for (int i = 0; i < count; i += 1023) {
int batch = Mathf.Min(1023, count - i);
var slice = new Matrix4x4[batch];
System.Array.Copy(_matrices, i, slice, 0, batch);
Graphics.DrawMeshInstanced(mesh, 0, material, slice, batch, _mpb);
}
}
}

Graphics.DrawMeshInstancedIndirect (무제한)

섹션 제목: “Graphics.DrawMeshInstancedIndirect (무제한)”

GPU 버퍼를 사용해 1023개 제한 없이 렌더링합니다.

ComputeBuffer _argsBuffer;
ComputeBuffer _dataBuffer;
void InitBuffers() {
uint[] args = { mesh.GetIndexCount(0), (uint)count, 0, 0, 0 };
_argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint),
ComputeBufferType.IndirectArguments);
_argsBuffer.SetData(args);
_dataBuffer = new ComputeBuffer(count, sizeof(float) * 16);
_dataBuffer.SetData(_matrices);
material.SetBuffer("_DataBuffer", _dataBuffer);
}
void Update() {
Graphics.DrawMeshInstancedIndirect(mesh, 0, material,
new Bounds(Vector3.zero, Vector3.one * 1000), _argsBuffer);
}
void OnDestroy() {
_argsBuffer?.Release();
_dataBuffer?.Release();
}

URP/HDRP에서는 SRP Batcher가 먼저 동작합니다.

조건배칭 방식
동일 셰이더 변형SRP Batcher (다른 머티리얼도 OK)
동일 메시 + 동일 머티리얼GPU Instancing
정적 오브젝트Static Batching

GPU Instancing은 동적 오브젝트(나무, 풀, 파티클 등)에 특히 효과적입니다.

  • 셰이더에 #pragma multi_compile_instancing + 머티리얼 설정 필수
  • 인스턴스별 프로퍼티는 MaterialPropertyBlock으로 전달 (머티리얼 직접 수정 금지)
  • DrawMeshInstanced는 최대 1023개, DrawMeshInstancedIndirect는 무제한
  • SRP Batcher → GPU Instancing → Static Batching 순으로 우선 적용