> 文章列表 > Unity ShaderVariant 变体收集方案分析

Unity ShaderVariant 变体收集方案分析

Unity ShaderVariant 变体收集方案分析

最近遇到一个问题,在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到工具类SVCtoolSVC = 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);//搜索所有Matvar 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 assetPathfor (int i = 0; i < assets.Count; i++){var p = AssetDatabase.GUIDToAssetPath(assets[i]);//获取依赖中的matvar dependenciesPath = AssetDatabase.GetDependencies(p, true);var mats = dependenciesPath.ToList().FindAll((dp) => dp.EndsWith(".mat"));allMats.AddRange(mats);}//处理所有的 materialallMats = 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();//所有的svcShaderVariantCollection 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 和  keywordssd = 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的kwfor (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 ,添加到 svcListsv = 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的 keywordspublic 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)          6if (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 wordkeywordLists = 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 wordvar 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坑(又名:性感程序化身名侦探) - 知乎