콘텐츠로 이동

Unity Shader Variants & 키워드 관리

Unity 셰이더에서 #pragma multi_compile이나 #pragma shader_feature를 사용할 때마다 GPU 플랫폼별로 별도 컴파일된 셰이더 변형(variant)이 생성됩니다. 프로젝트 규모가 커질수록 배리언트 수가 기하급수적으로 늘어 빌드 시간과 메모리를 잡아먹는 “배리언트 폭발” 문제가 발생합니다.


지시문빌드 포함런타임 전환주요 용도
multi_compile항상 모든 배리언트가능전역 기능 토글
shader_feature머티리얼에서 사용된 것만가능 (단, 미포함 배리언트 없음)머티리얼별 기능
multi_compile_local항상 포함, 로컬 스코프가능로컬 효과
shader_feature_local사용된 것만, 로컬 스코프가능머티리얼별 로컬
// 전역 키워드 — 모든 셰이더에서 공유 (최대 384개 제한)
#pragma multi_compile __ FOG_LINEAR FOG_EXP2
// 머티리얼 로컬 키워드 — 해당 셰이더에서만 (최대 64개)
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _EMISSION
#pragma shader_feature_local _ALPHATEST_ON

핵심 규칙: 머티리얼 Inspector에서 토글하는 기능은 shader_feature_local, 카메라·라이트·글로벌 효과는 multi_compile을 사용합니다.


키워드 집합마다 2^n개의 배리언트가 생성됩니다.

// 아래 조합은 2 × 3 × 2 = 12개 배리언트
#pragma multi_compile __ SHADOWS_ENABLED // 2가지
#pragma multi_compile FOG_OFF FOG_LINEAR FOG_EXP // 3가지
#pragma shader_feature_local _NORMALMAP // 2가지

플랫폼(PC, Android, iOS, WebGL…)을 곱하면 수천 개가 순식간에 생깁니다.


// Editor 스크립트로 배리언트 수 확인
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public class VariantCounter {
[MenuItem("Tools/Count Shader Variants")]
static void Count() {
var shader = Shader.Find("Custom/MyShader");
// ShaderUtil API로 배리언트 수 출력
int count = ShaderUtil.GetVariantCount(shader, true);
Debug.Log($"Variant count: {count}");
}
}
#endif

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
class CustomShaderStripper : IPreprocessShaders {
public int callbackOrder => 0;
public void OnProcessShader(
Shader shader,
ShaderSnippetData snippet,
IList<ShaderCompilerData> data) {
// 특정 셰이더가 아니면 스킵
if (shader.name != "Custom/MyShader") return;
var keyword = new ShaderKeyword("_EXPENSIVE_EFFECT");
// 릴리스 빌드에서 고비용 효과 배리언트 제거
for (int i = data.Count - 1; i >= 0; i--) {
if (data[i].shaderKeywordSet.IsEnabled(keyword)) {
data.RemoveAt(i);
}
}
}
}
#endif

Edit > Project Settings > Graphics > Shader Stripping에서:

  • Instancing Variants: Strip Unused
  • Fog Modes: 사용하지 않는 fog 모드 제거
  • LOD Cross-fade: Strip Unused

using UnityEngine;
public class MaterialKeywordController : MonoBehaviour {
private Material _mat;
void Start() {
_mat = GetComponent<Renderer>().material;
}
public void EnableNormalMap(bool enable) {
if (enable)
_mat.EnableKeyword("_NORMALMAP");
else
_mat.DisableKeyword("_NORMALMAP");
}
// shader_feature_local은 이 방식으로 제어
public void SetEmission(bool enable) {
_mat.SetKeyword(new LocalKeyword(_mat.shader, "_EMISSION"), enable);
}
}
// 모든 셰이더에 영향 (multi_compile 전용)
Shader.EnableKeyword("FOG_LINEAR");
Shader.DisableKeyword("FOG_EXP2");
// C# 8+ LocalKeyword API (Unity 2021.2+)
var globalKeyword = new GlobalKeyword("SHADOWS_ENABLED");
Shader.SetKeyword(globalKeyword, true);

URP는 자체 키워드 세트를 대량 사용합니다. 커스텀 셰이더 작성 시 URP 키워드와 충돌을 피해야 합니다.

// URP 내장 Include 사용
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// URP 이미 제공하는 키워드는 직접 선언하지 말 것
// 예: _MAIN_LIGHT_SHADOWS, _ADDITIONAL_LIGHTS, _SHADOWS_SOFT 등
// 커스텀 기능은 _local 접두사 사용
#pragma shader_feature_local _MY_CUSTOM_EFFECT
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class MyRenderPass : ScriptableRenderPass {
private static readonly GlobalKeyword s_MyKeyword
= GlobalKeyword.Create("_MY_EFFECT_ENABLED");
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData) {
var cmd = CommandBufferPool.Get("MyPass");
cmd.SetKeyword(s_MyKeyword, true);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}

자주 사용하는 배리언트를 미리 워밍업해 첫 렌더링 시 스터터링을 방지합니다.

using UnityEngine;
public class ShaderWarmup : MonoBehaviour {
[SerializeField] private ShaderVariantCollection _collection;
void Start() {
// 컬렉션에 포함된 모든 배리언트 미리 컴파일
_collection.WarmUp();
}
}

ShaderVariantCollectionEdit > Project Settings > Graphics > Preloaded Shaders에 등록하거나, 위처럼 코드로 워밍업합니다.


[ ] shader_feature_local vs multi_compile 올바르게 선택
[ ] 전역 키워드는 프로젝트 전체에서 최대 384개 내로 유지
[ ] IPreprocessShaders로 사용하지 않는 배리언트 스트리핑
[ ] Project Settings > Graphics > Shader Stripping 설정 확인
[ ] 빌드 로그에서 배리언트 수 모니터링
[ ] ShaderVariantCollection으로 중요 배리언트 워밍업
[ ] URP 내장 키워드와 커스텀 키워드 충돌 점검

  • shader_feature_local: 머티리얼별 기능 토글, 빌드 크기 최소화
  • multi_compile: 런타임 전환이 필요한 전역 효과
  • 배리언트 폭발 → IPreprocessShaders로 불필요한 조합 제거
  • 로컬 키워드 API(LocalKeyword, GlobalKeyword)로 타입 안전한 제어
  • URP 커스텀 셰이더에서는 _local 키워드 우선 사용