您现在的位置是:首页 >技术杂谈 >Unity ShaderVariant 变体收集方案分析网站首页技术杂谈

Unity ShaderVariant 变体收集方案分析

Sevol_Y 2023-05-20 08:00:02
简介unity 收集变体

最近遇到一个问题,在editor中场景渲染正确,打包android之后,渲染异常。

经过排查得出原因:工程把所有shader单独打包Assetbundle,editor打包ab包的时候,未收集到正确的shader变体,未将场景中使用的shader变体打包到ab包中,所以发布apk之后,渲染异常。

什么是shader变体:ShaderVariant变体_Sevol_Y的博客-CSDN博客

在网上猛搜了一天,找到很多解决方法,也测试了很多方法:

  1. 方案1,测试可行。将shader加入到editor设置中,ProjectSetting-->Graphics-->AlwaysIncludedShaders。所有加入设置的shader都将编译全部shader变体。如果你知道是哪一个shader出现异常,加入进设置列表中,应该能解决问题。注意注意!:如果你的shader变体有很多,类似unity内置的standard,那么千万不要加入,这会导致你的出包巨慢,而且包内有很多用不到的shader变体。还有就是我出现问题的shader,包含的总变体有6M+个(百万个),这时候editor就会提醒你,亲,你的变体个数实在太多,这边不建议你加入列表,直接阻止你的操作。
  2. 方案2,未测试。将shader放入Resources文件夹下,等同方案一效果。
  3. 方案3,测试可行,将出现异常的mat和shader放在同一文件夹下,效果正常。
  4. 方案4,测试可行。使用unity自动收集shader变体文件功能。先点击Clear,清除已经收集到的变体,然后直接运行游戏,将游戏的犄角旮哪都跑一遍,或者你也可以运行到出现异常的地方(不过这样不全,只会解决你现目前发现问题的地方),然后点击SaveToAsset,保存shader变体文件到Shader  assetbundle包内,效果正常。
  5. 方案5,自动收集变体代码1,测试,未解决我的问题。自动收集变体文件ShaderVariant ,网上流传的脚本,放入工程测试了,不过未解决我的问题,不知道什么原因,这是BDFramework的框架内容,大家可以放到自己工程试试。GitHub - yimengfan/BDFramework.Core: Simple and powerful Unity3d game workflow! 简单、高效、高度工业化的商业级unity3d 工作流。
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor;
    using UnityEngine;
    using System.IO;
    using System.Reflection;
    using System;
    using UnityEngine.Rendering;
    using System.Linq;
    
    public class ShaderCollection : EditorWindow
    {
        static Dictionary<string, List<ShaderVariantCollection.ShaderVariant>> ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
        public static List<string> GetAllRuntimeDirects()
        {
            //搜索所有资源
            List<string> directories = new List<string>();
            directories.Add("Assets");
            return directories;
        }
        private ShaderVariantCollection svc;
        readonly public static string ALL_SHADER_VARAINT_PATH = "Assets/AllShaders.shadervariants";
    
        static List<string> allShaderNameList = new List<string>();
    
        [MenuItem("ShaderTool/AutoShaderVariants")]
        public static void GenShaderVariant()
        {
            ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
            //先搜集所有keyword到工具类SVC
            toolSVC = new ShaderVariantCollection();
            var shaders = AssetDatabase.FindAssets("t:Shader", new string[] { "Assets", "Packages" }).ToList();
            foreach (var shader in shaders)
            {
                ShaderVariantCollection.ShaderVariant sv = new ShaderVariantCollection.ShaderVariant();
                var shaderPath = AssetDatabase.GUIDToAssetPath(shader);
                sv.shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
                toolSVC.Add(sv);
                //
                allShaderNameList.Add(shaderPath);
            }
    
            var toolsSVCpath = "Assets/Tools.shadervariants";
            //防空
    
            File.WriteAllText(toolsSVCpath, "");
            AssetDatabase.DeleteAsset(toolsSVCpath);
            AssetDatabase.CreateAsset(toolSVC, toolsSVCpath);
    
            //搜索所有Mat
            var paths = GetAllRuntimeDirects().ToArray();
            var assets = AssetDatabase.FindAssets("t:Prefab", paths).ToList();
            var assets2 = AssetDatabase.FindAssets("t:Material", paths);
            assets.AddRange(assets2);
            List<string> allMats = new List<string>();
    
            //GUID to assetPath
            for (int i = 0; i < assets.Count; i++)
            {
                var p = AssetDatabase.GUIDToAssetPath(assets[i]);
                //获取依赖中的mat
                var dependenciesPath = AssetDatabase.GetDependencies(p, true);
                var mats = dependenciesPath.ToList().FindAll((dp) => dp.EndsWith(".mat"));
                allMats.AddRange(mats);
            }
    
            //处理所有的 material
            allMats = allMats.Distinct().ToList();
    
            float count = 1;
            foreach (var mat in allMats)
            {
                var obj = AssetDatabase.LoadMainAssetAtPath(mat);
                if (obj is Material)
                {
                    var _mat = obj as Material;
                    EditorUtility.DisplayProgressBar("处理mat", string.Format("处理:{0} - {1}", Path.GetFileName(mat), _mat.shader.name), count / allMats.Count);
                    AddToDict(_mat);
                }
    
                count++;
            }
    
            EditorUtility.ClearProgressBar();
            //所有的svc
            ShaderVariantCollection svc = new ShaderVariantCollection();
            foreach (var item in ShaderVariantDict)
            {
                foreach (var _sv in item.Value)
                {
                    svc.Add(_sv);
                }
            }
    
            AssetDatabase.DeleteAsset(ALL_SHADER_VARAINT_PATH);
            AssetDatabase.CreateAsset(svc, ALL_SHADER_VARAINT_PATH);
            AssetDatabase.Refresh();
    
        }
        public class ShaderData
        {
            public int[] PassTypes = new int[] { };
            public string[][] KeyWords = new string[][] { };
            public string[] ReMainingKeyWords = new string[] { };
        }
    
        //shader数据的缓存
        static Dictionary<string, ShaderData> ShaderDataDict = new Dictionary<string, ShaderData>();
    
    
    
        //添加Material计算
        static List<string> passShaderList = new List<string>();
    
        /// <summary>
        /// 添加到Dictionary
        /// </summary>
        /// <param name="curMat"></param>
        static void AddToDict(Material curMat)
        {
            if (!curMat || !curMat.shader) return;
    
            var path = AssetDatabase.GetAssetPath(curMat.shader);
            if (!allShaderNameList.Contains(path))
            {
                Debug.LogError("不存在shader:" + curMat.shader.name);
                Debug.Log(path);
                return;
            }
    
            ShaderData sd = null;
            ShaderDataDict.TryGetValue(curMat.shader.name, out sd);
            if (sd == null)
            {
                //一次性取出所有的 passtypes 和  keywords
                sd = GetShaderKeywords(curMat.shader);
                ShaderDataDict[curMat.shader.name] = sd;
            }
    
            var kwCount = sd.PassTypes.Length;
            if (kwCount > 2000)
            {
                if (!passShaderList.Contains(curMat.shader.name))
                {
                    Debug.LogFormat("Shader【{0}】,变体数量:{1},不建议继续分析,后续也会跳过!", curMat.shader.name, kwCount);
                    passShaderList.Add(curMat.shader.name);
                }
                else
                {
                    Debug.LogFormat("mat:{0} , shader:{1} ,keywordCount:{2}", curMat.name, curMat.shader.name, kwCount);
                }
    
                return;
            }
    
    
            List<ShaderVariantCollection.ShaderVariant> svlist = null;
            if (!ShaderVariantDict.TryGetValue(curMat.shader.name, out svlist))
            {
                svlist = new List<ShaderVariantCollection.ShaderVariant>();
                ShaderVariantDict[curMat.shader.name] = svlist;
            }
    
            //求所有mat的kw
            for (int i = 0; i < sd.PassTypes.Length; i++)
            {
                //
                var pt = (PassType)sd.PassTypes[i];
                ShaderVariantCollection.ShaderVariant? sv = null;
                try
                {
                    string[] key_worlds = sd.KeyWords[i];
    
                    //变体交集 大于0 ,添加到 svcList
                    sv = new ShaderVariantCollection.ShaderVariant(curMat.shader, pt, key_worlds);
                    SetShaderVariantKeyWorld(svlist, sv);
                }
                catch (Exception e)
                {
                    Debug.LogErrorFormat("{0}-当前shader不存在变体(可以无视):{1}-{2}", curMat.name, pt, curMat.shaderKeywords.ToString());
                    continue;
                }
    
    
            }
        }
    
        static void SetShaderVariantKeyWorld(List<ShaderVariantCollection.ShaderVariant> svlist, ShaderVariantCollection.ShaderVariant? sv)
        {
            //判断sv 是否存在,不存在则添加
            if (sv != null)
            {
                bool isContain = false;
                var _sv = (ShaderVariantCollection.ShaderVariant)sv;
                foreach (var val in svlist)
                {
                    if (val.passType == _sv.passType && System.Linq.Enumerable.SequenceEqual(val.keywords, _sv.keywords))
                    {
                        isContain = true;
                        break;
                    }
                }
    
                if (!isContain)
                {
                    svlist.Add(_sv);
                }
            }
        }
    
    
        static MethodInfo GetShaderVariantEntries = null;
    
        static ShaderVariantCollection toolSVC = null;
    
        //获取shader的 keywords
        public static ShaderData GetShaderKeywords(Shader shader)
        {
            ShaderData sd = new ShaderData();
            GetShaderVariantEntriesFiltered(shader, new string[] { }, out sd.PassTypes, out sd.KeyWords, out sd.ReMainingKeyWords);
            return sd;
        }
    
        /// <summary>
        /// 获取keyword
        /// </summary>
        /// <param name="shader"></param>
        /// <param name="filterKeywords"></param>
        /// <param name="passTypes"></param>
        /// <param name="keywordLists"></param>
        /// <param name="remainingKeywords"></param>
        static void GetShaderVariantEntriesFiltered(Shader shader, string[] filterKeywords, out int[] passTypes, out string[][] keywordLists, out string[] remainingKeywords)
        {
            //2019.3接口
            //            internal static void GetShaderVariantEntriesFiltered(
            //                Shader                  shader,                     0
            //                int                     maxEntries,                 1
            //                string[]                filterKeywords,             2
            //                ShaderVariantCollection excludeCollection,          3
            //                out int[]               passTypes,                  4
            //                out string[]            keywordLists,               5
            //                out string[]            remainingKeywords)          6
            if (GetShaderVariantEntries == null)
            {
                GetShaderVariantEntries = typeof(ShaderUtil).GetMethod("GetShaderVariantEntriesFiltered", BindingFlags.NonPublic | BindingFlags.Static);
            }
    
            passTypes = new int[] { };
            keywordLists = new string[][] { };
            remainingKeywords = new string[] { };
            if (toolSVC != null)
            {
                var _passtypes = new int[] { };
                var _keywords = new string[] { };
                var _remainingKeywords = new string[] { };
                object[] args = new object[] { shader, 256, filterKeywords, toolSVC, _passtypes, _keywords, _remainingKeywords };
                GetShaderVariantEntries.Invoke(null, args);
    
                var passtypes = args[4] as int[];
                passTypes = passtypes;
                //key word
                keywordLists = new string[passtypes.Length][];
                var kws = args[5] as string[];
                for (int i = 0; i < passtypes.Length; i++)
                {
                    keywordLists[i] = kws[i].Split(' ');
                }
    
                //Remaning key word
                var rnkws = args[6] as string[];
                remainingKeywords = rnkws;
            }
        }
    }
  6. 方案6,自动收集变体代码2,测试。这里留一篇文章,我觉得很对,思路很清晰,易懂,可以看看GitHub - lujian101/ShaderVariantCollector
  7. 方案7,手撸Shader Variant Collection文件,同样不全,还不咋好用。

 最后贴几篇参考:

【Unity3D】Shader变体管理流程2-变体收集 - 简书

GitHub - lujian101/ShaderVariantCollector

爬坑篇(3)之 Unity2019 keyword坑(又名:性感程序化身名侦探) - 知乎

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。