Unity Shader Variants & 키워드 관리
Unity 셰이더에서 #pragma multi_compile이나 #pragma shader_feature를 사용할 때마다 GPU 플랫폼별로 별도 컴파일된 셰이더 변형(variant)이 생성됩니다. 프로젝트 규모가 커질수록 배리언트 수가 기하급수적으로 늘어 빌드 시간과 메모리를 잡아먹는 “배리언트 폭발” 문제가 발생합니다.
shader_feature vs multi_compile
섹션 제목: “shader_feature vs multi_compile”| 지시문 | 빌드 포함 | 런타임 전환 | 주요 용도 |
|---|---|---|---|
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_EDITORusing 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배리언트 스트리핑
섹션 제목: “배리언트 스트리핑”IPreprocessShaders 인터페이스
섹션 제목: “IPreprocessShaders 인터페이스”#if UNITY_EDITORusing 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); } } }}#endifProject Settings 스트리핑
섹션 제목: “Project Settings 스트리핑”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는 자체 키워드 세트를 대량 사용합니다. 커스텀 셰이더 작성 시 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_EFFECTRenderFeature에서 키워드 설정
섹션 제목: “RenderFeature에서 키워드 설정”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); }}Shader Variant Collection
섹션 제목: “Shader Variant Collection”자주 사용하는 배리언트를 미리 워밍업해 첫 렌더링 시 스터터링을 방지합니다.
using UnityEngine;
public class ShaderWarmup : MonoBehaviour { [SerializeField] private ShaderVariantCollection _collection;
void Start() { // 컬렉션에 포함된 모든 배리언트 미리 컴파일 _collection.WarmUp(); }}ShaderVariantCollection은 Edit > 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키워드 우선 사용