最近遇到一个问题,在editor中场景渲染正确,打包android之后,渲染异常。
经过排查得出原因:工程把所有shader单独打包Assetbundle,editor打包ab包的时候,未收集到正确的shader变体,未将场景中使用的shader变体打包到ab包中,所以发布apk之后,渲染异常。
什么是shader变体:ShaderVariant变体_Sevol_Y的博客-CSDN博客
在网上猛搜了一天,找到很多解决方法,也测试了很多方法:
- 方案1,测试可行。将shader加入到editor设置中,ProjectSetting-->Graphics-->AlwaysIncludedShaders。所有加入设置的shader都将编译全部shader变体。如果你知道是哪一个shader出现异常,加入进设置列表中,应该能解决问题。注意注意!:如果你的shader变体有很多,类似unity内置的standard,那么千万不要加入,这会导致你的出包巨慢,而且包内有很多用不到的shader变体。还有就是我出现问题的shader,包含的总变体有6M+个(百万个),这时候editor就会提醒你,亲,你的变体个数实在太多,这边不建议你加入列表,直接阻止你的操作。
- 方案2,未测试。将shader放入Resources文件夹下,等同方案一效果。
- 方案3,测试可行,将出现异常的mat和shader放在同一文件夹下,效果正常。
- 方案4,测试可行。使用unity自动收集shader变体文件功能。先点击Clear,清除已经收集到的变体,然后直接运行游戏,将游戏的犄角旮哪都跑一遍,或者你也可以运行到出现异常的地方(不过这样不全,只会解决你现目前发现问题的地方),然后点击SaveToAsset,保存shader变体文件到Shader assetbundle包内,效果正常。
- 方案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,自动收集变体代码2,测试。这里留一篇文章,我觉得很对,思路很清晰,易懂,可以看看GitHub - lujian101/ShaderVariantCollector
- 方案7,手撸Shader Variant Collection文件,同样不全,还不咋好用。
最后贴几篇参考:
【Unity3D】Shader变体管理流程2-变体收集 - 简书
GitHub - lujian101/ShaderVariantCollector
爬坑篇(3)之 Unity2019 keyword坑(又名:性感程序化身名侦探) - 知乎