Unity MaterialPropertyBlock과 GPU Instancing 충돌 조건을 자동 진단하는 에디터 툴
Unity에서 MaterialPropertyBlock이 GPU Instancing을 깨뜨리는 조건을 자동 점검하는 EditorWindow를 구현합니다. 셰이더 인스턴싱 선언과 Material.enableInstancing, SRP Batcher 주의점을 함께 진단합니다.

핵심 요약
MaterialPropertyBlock 자체가 GPU Instancing을 항상 깨뜨리는 것은 아닙니다. 다만 셰이더가 인스턴싱 변형을 생성하지 않았거나 MPB로 설정한 프로퍼티가 인스턴스 프로퍼티로 선언되지 않았으면 인스턴싱을 기대할 수 없습니다. 또한 URP와 HDRP의 SRP Batcher에서는 MPB를 사용하는 렌더러가 배처 호환성을 잃을 수 있으므로 별도로 진단해야 합니다.
이 글에서는 Material.enableInstancing, 셰이더의 multi_compile_instancing, MPB 사용 여부를 검사하는 Unity EditorWindow를 구현합니다.
Unity GPU Instancing과 MaterialPropertyBlock의 관계
GPU Instancing은 같은 메시와 머티리얼을 사용하는 여러 오브젝트를 한 번의 드로우 콜로 처리하는 기능입니다. 오브젝트마다 색상이나 위치 보정값이 달라도 셰이더가 해당 값을 인스턴스 데이터로 읽도록 구성되어 있다면 인스턴싱을 유지할 수 있습니다.
일반적인 조건은 다음과 같습니다.
| 검사 항목 | 정상 조건 | 문제가 생기는 경우 |
|---|---|---|
| Material 설정 | Material.enableInstancing == true | 머티리얼에서 GPU Instancing이 꺼져 있음 |
| 셰이더 변형 | #pragma multi_compile_instancing 포함 | 인스턴싱 셰이더 변형이 빌드되지 않음 |
| 프로퍼티 선언 | UNITY_DEFINE_INSTANCED_PROP 사용 | MPB 값을 일반 머티리얼 프로퍼티로만 선언함 |
| 프로퍼티 접근 | UNITY_ACCESS_INSTANCED_PROP 사용 | 셰이더가 인스턴스 버퍼를 읽지 않음 |
| 렌더 상태 | 메시, 패스, 머티리얼 상태가 동일함 | 셰이더 패스나 렌더 상태가 서로 다름 |
| SRP Batcher | MPB 대신 머티리얼 상수 버퍼 사용 | MPB 사용으로 SRP Batcher 호환성이 사라짐 |
핵심은 MPB 값이 인스턴스 버퍼로 전달되는지입니다. 셰이더는 다음과 같은 형태로 인스턴스 프로퍼티를 선언해야 합니다.
#pragma multi_compile_instancing
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(Props)
void surf(Input IN, inout SurfaceOutputStandard o)
{
o.Albedo = UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor).rgb;
}
Shader Graph를 사용한다면 해당 프로퍼티를 인스턴스별로 사용할 수 있도록 Per Instance 설정을 확인해야 합니다.
MaterialPropertyBlock이 GPU Instancing을 깨뜨리는 조건은 무엇인가?
1. Material.enableInstancing이 꺼져 있는 경우
셰이더가 인스턴싱을 지원하더라도 머티리얼의 Enable GPU Instancing 옵션이 꺼져 있으면 인스턴싱 드로우가 생성되지 않습니다.
if (!renderer.sharedMaterial.enableInstancing)
{
Debug.LogWarning("GPU Instancing이 비활성화된 머티리얼입니다.");
}
2. 셰이더에 인스턴싱 변형이 없는 경우
커스텀 셰이더는 #pragma multi_compile_instancing을 포함해야 합니다. 이 선언이 없으면 GPU Instancing용 셰이더 키워드가 생성되지 않습니다.
Surface Shader는 일부 코드를 자동 생성하지만 커스텀 버텍스·프래그먼트 셰이더는 직접 선언하는 편이 안전합니다.
3. MPB 프로퍼티가 인스턴스 프로퍼티로 선언되지 않은 경우
다음 코드는 렌더러마다 다른 값을 설정하지만 셰이더가 _BaseColor를 인스턴스 버퍼에서 읽는다는 보장이 없습니다.
var block = new MaterialPropertyBlock();
block.SetColor("_BaseColor", color);
renderer.SetPropertyBlock(block);
셰이더에서 _BaseColor를 UNITY_DEFINE_INSTANCED_PROP로 선언하고 UNITY_ACCESS_INSTANCED_PROP로 접근해야 합니다. 그렇지 않으면 MPB 값이 인스턴스 데이터 경로와 맞지 않아 배칭 결과가 예상과 달라질 수 있습니다.
4. 머티리얼과 셰이더 패스가 서로 다른 경우
GPU Instancing은 단순히 같은 셰이더 이름을 사용하는지만 비교하지 않습니다. 메시, 서브메시, 머티리얼, 셰이더 패스, 렌더 상태가 함께 맞아야 합니다.
드로우 그룹을 다음 키로 생각하면 원인을 추적하기 쉽습니다.
이 키의 구성 요소가 다르면 같은 인스턴스 드로우로 묶일 수 없습니다.
5. SRP Batcher와 혼동하는 경우
URP와 HDRP에서는 GPU Instancing과 SRP Batcher가 서로 다른 최적화 경로입니다. MPB를 사용한 렌더러는 SRP Batcher 호환성을 잃을 수 있지만 이것이 곧 GPU Instancing이 항상 깨졌다는 뜻은 아닙니다.
따라서 다음 두 지표를 분리해서 확인해야 합니다.
- Frame Debugger에서 실제 인스턴스 드로우가 생성되는가?
- SRP Batcher 표시가 사라졌는가?
자동 진단 툴은 어떻게 구현할까?
진단기는 다음 순서로 동작합니다.
- 활성 씬의
MeshRenderer와SkinnedMeshRenderer를 검색합니다. - 각 렌더러의 머티리얼에서
enableInstancing을 확인합니다. GetPropertyBlock으로 MPB가 비어 있는지 확인합니다.- 셰이더 소스에서 인스턴싱 키워드와 인스턴스 프로퍼티 선언을 검사합니다.
- 의심 항목을 경고와 오류로 나누어 콘솔에 출력합니다.
아래 스크립트를 Assets/Editor/GpuInstancingMpbWindow.cs에 저장합니다.
using System;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GpuInstancingMpbWindow : EditorWindow
{
[MenuItem("Tools/Rendering/MPB Instancing Diagnosis")]
private static void Open()
{
GetWindow<GpuInstancingMpbWindow>("MPB Instancing");
}
private void OnGUI()
{
EditorGUILayout.HelpBox(
"활성 씬의 MPB 사용 여부와 GPU Instancing 설정을 검사합니다.",
MessageType.Info);
if (GUILayout.Button("Scan Active Scene", GUILayout.Height(30)))
{
ScanScene();
}
}
private static void ScanScene()
{
var report = new StringBuilder();
var rendererCount = 0;
var warningCount = 0;
foreach (var root in SceneManager.GetActiveScene().GetRootGameObjects())
{
foreach (var renderer in root.GetComponentsInChildren<Renderer>(true))
{
if (!(renderer is MeshRenderer) &&
!(renderer is SkinnedMeshRenderer))
{
continue;
}
rendererCount++;
InspectRenderer(renderer, report, ref warningCount);
}
}
report.Insert(0,
$"[MPB Instancing] Renderers: {rendererCount}, Warnings: {warningCount}\n");
Debug.Log(report.ToString());
}
private static void InspectRenderer(
Renderer renderer,
StringBuilder report,
ref int warningCount)
{
var block = new MaterialPropertyBlock();
renderer.GetPropertyBlock(block);
foreach (var material in renderer.sharedMaterials)
{
if (material == null || material.shader == null)
{
warningCount++;
report.AppendLine($"[Error] {renderer.name}: 머티리얼 또는 셰이더가 없습니다.");
continue;
}
if (!material.enableInstancing)
{
warningCount++;
report.AppendLine(
$"[Warning] {renderer.name}: {material.name}의 GPU Instancing이 꺼져 있습니다.");
}
if (!SystemInfo.supportsInstancing)
{
warningCount++;
report.AppendLine(
$"[Warning] {renderer.name}: 현재 그래픽스 장치가 Instancing을 지원하지 않습니다.");
}
if (block.isEmpty)
{
continue;
}
warningCount++;
report.AppendLine(
$"[Warning] {renderer.name}: MaterialPropertyBlock을 사용 중입니다. SRP Batcher 호환성과 인스턴스 프로퍼티 선언을 확인하세요.");
var source = ReadShaderSource(material.shader);
if (source != null &&
!source.Contains("#pragma multi_compile_instancing"))
{
warningCount++;
report.AppendLine(
$"[Warning] {material.shader.name}: multi_compile_instancing 선언을 찾지 못했습니다.");
}
if (source != null &&
!source.Contains("UNITY_DEFINE_INSTANCED_PROP"))
{
warningCount++;
report.AppendLine(
$"[Warning] {material.shader.name}: 인스턴스 프로퍼티 선언을 찾지 못했습니다.");
}
}
}
private static string ReadShaderSource(Shader shader)
{
var path = AssetDatabase.GetAssetPath(shader);
if (string.IsNullOrEmpty(path) ||
!path.EndsWith(".shader", StringComparison.OrdinalIgnoreCase))
{
return null;
}
try
{
return File.ReadAllText(path);
}
catch (IOException)
{
return null;
}
}
}
진단 결과를 해석하는 방법
이 도구가 MPB 사용을 경고하는 이유는 MPB가 무조건 잘못되었기 때문이 아닙니다. MaterialPropertyBlock API는 내부 프로퍼티 목록을 직접 열거하는 기능이 제한적이므로 위 스크립트는 MPB가 비어 있지 않다는 사실과 셰이더의 기본 선언만 검사합니다.
특정 프로퍼티가 문제인지 확인하려면 MPB 설정 코드를 래핑해 프로퍼티 ID를 기록하는 것이 좋습니다.
public sealed class TrackedMpb
{
public readonly MaterialPropertyBlock Block = new MaterialPropertyBlock();
public readonly HashSet<int> PropertyIds = new HashSet<int>();
public void SetColor(int propertyId, Color value)
{
PropertyIds.Add(propertyId);
Block.SetColor(propertyId, value);
}
public void SetFloat(int propertyId, float value)
{
PropertyIds.Add(propertyId);
Block.SetFloat(propertyId, value);
}
}
기록된 ID를 셰이더의 프로퍼티 목록과 비교하고 각 프로퍼티가 인스턴스 버퍼에 선언되었는지 검증하면 정적 진단의 정확도를 높일 수 있습니다.
성능 검증은 무엇으로 해야 하는가?
자동 진단은 가능성이 높은 원인을 찾는 단계입니다. 최종 결과는 다음 도구로 확인해야 합니다.
- Frame Debugger: 인스턴스 드로우인지 개별 드로우인지 확인합니다.
- Profiler:
Batches,SetPass Calls, 렌더 스레드 시간을 비교합니다. - Rendering Debugger: URP/HDRP에서 SRP Batcher 상태를 확인합니다.
- 실제 빌드: 에디터와 플랫폼별 셰이더 변형 차이를 검증합니다.
특히 오브젝트마다 서로 다른 MPB 값을 넣은 뒤 드로우 콜이 증가했다고 해서 즉시 GPU Instancing 실패로 단정하면 안 됩니다. 셰이더 변형, 머티리얼 설정, 렌더 파이프라인의 배칭 경로를 함께 확인해야 합니다.
자주 묻는 질문 (FAQ)
MaterialPropertyBlock을 사용하면 GPU Instancing이 항상 깨지나요?
아닙니다. 셰이더가 인스턴스 프로퍼티를 올바르게 선언하고 접근하면 MPB로 오브젝트별 값을 전달하면서 GPU Instancing을 유지할 수 있습니다.
MPB와 SRP Batcher 중 무엇을 사용해야 하나요?
오브젝트별 머티리얼 값을 자주 바꿔야 한다면 GPU Instancing과 인스턴스 프로퍼티를 검토합니다. 머티리얼 단위의 상수 데이터가 중심이라면 SRP Batcher가 더 적합할 수 있습니다. 두 경로는 Frame Debugger와 Profiler로 비교해야 합니다.
셰이더 소스만 검사하면 인스턴싱 성공 여부를 알 수 있나요?
아닙니다. 소스 검사는 필요한 선언을 빠르게 찾는 정적 검사입니다. 실제 배칭 여부는 셰이더 변형, 플랫폼 지원, 머티리얼 상태, 렌더 패스에 따라 달라지므로 Frame Debugger에서 확인해야 합니다.
정리
MaterialPropertyBlock은 GPU Instancing의 적이 아니라 오브젝트별 데이터를 전달하는 수단입니다. 문제는 enableInstancing 설정, 인스턴싱 셰이더 변형, UNITY_DEFINE_INSTANCED_PROP 선언, 실제 렌더 패스가 서로 맞지 않을 때 발생합니다.
EditorWindow로 MPB 사용 여부와 기본 셰이더 조건을 자동 점검하고 Frame Debugger로 최종 드로우 결과를 검증하면 렌더링 최적화 문제를 훨씬 빠르게 좁힐 수 있습니다.



