• 프로젝트에서 사용하는 특정 쉐이더의 키워드 및 Variants 숫자를 리스트로 정렬해주는 툴
  • 사용되는 키워드 분량, 필요 없는 키워드를 판별하는 용도로 사용
  • 글로벌로 제어되는 multi_compile 키워드는 카운팅되지 않음. (예 : _MAIN_LIGHT_SHWDOWS)
    • 정확히 Material에 저장된 Keyword만 카운트됨.

 

 

 

using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;

// Ref - https://github.com/needle-tools/shader-variant-explorer

public class ShaderVariantChecker : EditorWindow
{
    private static MethodInfo GetVariantCount, GetShaderGlobalKeywords, GetShaderLocalKeywords;

    private Shader _shader;
 
    private ulong _variantCount = 0;
    private ulong _usedVariantCount = 0;
    private int _keywordCount = 0;
    private int _materialCount = 0;
    private Dictionary<string, int> _materialKeywords = new Dictionary<string, int>();
    private bool _isChecked = false;

    private Vector2 scrollPosition;

    [MenuItem("CustomTool/ShaderVariantChecker")]
    private static void ShowWindow()
    {
        GetWindow<ShaderVariantChecker>().Show();
    }



    public void OnGUI()
    {
        _shader = EditorGUILayout.ObjectField("Shader", _shader, typeof(Shader), true) as Shader;
        if(GUILayout.Button("Check"))
        {
            if (!_shader) return;
            Process(_shader);
        }

        if (!_isChecked) return;
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("Variant Count : " + _variantCount);
        EditorGUILayout.LabelField("Used Variant Count : " + _usedVariantCount);
        EditorGUILayout.LabelField("Keyword Count : " + _keywordCount);

        EditorGUILayout.EndHorizontal();

        EditorGUILayout.LabelField("Used Material Count : " + _materialCount);
        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

        Color defaultColor = GUI.color;
        foreach (KeyValuePair<string, int> keyword in _materialKeywords)
        {
            EditorGUILayout.BeginHorizontal();
            bool isValid = keyword.Value > 0;
            if (isValid) GUI.color = Color.red;
            EditorGUILayout.TextField(keyword.Key);
            EditorGUILayout.TextField(keyword.Value.ToString(), GUILayout.Width(30));

            GUI.color = defaultColor;
            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndScrollView();
    }

    private void Process(Shader shader)
    {
        if (!shader) return;

   
        GetShaderDetails(shader, out var variantCount, out var usedVariantCount, out string[] localKeywords, out string[] globalKeywords);
        
        _materialKeywords.Clear();
        for (int i = 0; i < localKeywords.Length; i++)
        {
            _materialKeywords.Add(localKeywords[i], 0);
        }
        for (int i = 0; i < globalKeywords.Length; i++)
        {
            if (_materialKeywords.ContainsKey(globalKeywords[i])) continue;
            _materialKeywords.Add(globalKeywords[i], 0);
        }
   
        _variantCount = variantCount;
        _usedVariantCount = usedVariantCount;
        _keywordCount = _materialKeywords.Count;

        // Debug.Log("variantCount : " + variantCount + ", usedVariantCount : " + usedVariantCount + ", keywordCount : " + keywordTotalCount);

        List<Material> materials = FindMaterialsUsingShader(shader);
        _materialCount = materials.Count;
        for (int i=0; i< materials.Count; i++)
        {
            Material mat = materials[i];
            if (!mat) continue;
            string[] keywords = mat.shaderKeywords;
            if(keywords == null || keywords.Length ==0) continue;

            for(int j=0; j< keywords.Length; j++)
            {
                if (!_materialKeywords.ContainsKey(keywords[j])) continue;
                _materialKeywords[keywords[j]]++;
            }
        }

        _isChecked = true;
    }

    void GetShaderDetails(Shader requestedShader, out ulong shaderVariantCount, out ulong usedShaderVariantCount, out string[] localKeywords, out string[] globalKeywords)
    {
        if (GetVariantCount == null) GetVariantCount = typeof(ShaderUtil).GetMethod("GetVariantCount", (BindingFlags)(-1));
        if (GetShaderGlobalKeywords == null) GetShaderGlobalKeywords = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords", (BindingFlags)(-1));
        if (GetShaderLocalKeywords == null) GetShaderLocalKeywords = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords", (BindingFlags)(-1));

        if (GetVariantCount == null || GetShaderGlobalKeywords == null || GetShaderLocalKeywords == null)
        {
            shaderVariantCount = 0;
            usedShaderVariantCount = 0;
            localKeywords = null;
            globalKeywords = null;
            return;
        }

        shaderVariantCount = (ulong)GetVariantCount.Invoke(null, new object[] { requestedShader, false });
        usedShaderVariantCount = (ulong)GetVariantCount.Invoke(null, new object[] { requestedShader, true });
        localKeywords = (string[])GetShaderLocalKeywords.Invoke(null, new object[] { requestedShader });
        globalKeywords = (string[])GetShaderGlobalKeywords.Invoke(null, new object[] { requestedShader });

        // var name = $"{requestedShader.name}: ({shaderVariantCount} variants, {localKeywords.Length} local, {globalKeywords.Length} global)";
    }

    public static List<Material> FindMaterialsUsingShader(Shader shader)
    {
        List<Material> materialsUsingShader = new List<Material>();
        string[] materialAssetGUIDs = AssetDatabase.FindAssets("t:Material");
        foreach (var guid in materialAssetGUIDs)
        {
            string assetPath = AssetDatabase.GUIDToAssetPath(guid);
            Material material = AssetDatabase.LoadAssetAtPath<Material>(assetPath);

            if (material != null && material.shader != null)
            { 
                if (material.shader.name == shader.name)
                {
                    materialsUsingShader.Add(material);
                }
            }
        }

        return materialsUsingShader;
    }

}

WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,