找回密码
 立即注册
查看: 270|回复: 0

Unity Shader资源(二)项目措置

[复制链接]
发表于 2024-7-15 18:34 | 显示全部楼层 |阅读模式
一、 打包与变体收集

之前章节介绍了Shader变体的生成原因,为了让项目能够正常流畅运行,就需要考虑运行时的Shader资源打包问题。首先需要保证Shader变体不会丢掉,其次包体要尽可能小。对于Shader资源的打包很容易想到以下几种方案
生成方式长处错误谬误
Shader跟随Material不会丢掉变体极大增加资源冗余
Shader单独打包,全部使用multi_compile不会丢掉变体存在不用的变体
Shader单独打包,自主收集变体信息极简的包体容易丢掉包体
出于性能考虑,凡是会选择第三种方案,收集变体数据就是此中关键。常见的思路就是遍历所有打包资源,提取所有Scene、Prefab所引用的Material对其进行变体收集。ShaderVariantCollection除了能够保证变体数据不会丢掉,凡是还会在游戏启动时进行WarmUp。因此最好将mutli的变体数据也写入此中。(打包Shader时不需要手动引用HLSL文件,打包编译Shader时Unity会自动措置)
思路理清楚了,下面就是实现问题了。在Unity 2022中引入了Material Variants,能够便利得措置变体收集。但在之前的版本只能在了解源码的情况下,使用反射获取Unity内置方式来措置。
只给到关键的Material收集数据方式。核心思路是收集所有变体数据后,将材质的keyword与Shader的keyword求交集。Material中的Keyword可能并不是Shader中使用的(凡是由于切换Shader、Toogle生成当地Keyword造成),因此需要进行过滤。
  1.     //shader数据的缓存
  2.     static Dictionary<string, ShaderData> ShaderDataDict = new Dictionary<string, ShaderData>();
  3.     static Dictionary<string, List<ShaderVariantCollection.ShaderVariant>> ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
  4.     //添加Material计算
  5.     static List<string> passShaderList = new List<string>();
  6.     static MethodInfo GetShaderVariantEntries = null;
  7.     public struct ShaderVariantData
  8.     {
  9.         public int[] passTypes;
  10.         public string[] keywordLists;
  11.         public string[] remainingKeywords;
  12.     }
  13.     /// <summary>
  14.     /// 收集单个材质
  15.     /// </summary>
  16.     /// <param name=”curMat”></param>
  17.     static void AddToDict(Material curMat)
  18.     {
  19.         Shader shader = curMat.shader;
  20.         if (!curMat || !curMat.shader) return;
  21.         string shaderName = shader.name;
  22.         if (!ShaderDataDict.TryGetValue(shaderName, out ShaderData sd))
  23.         {
  24.             sd = GetShaderKeywords(shader);
  25.             ShaderDataDict[shaderName] = sd;
  26.         }
  27.         var passTypes = sd.PassTypes;
  28.         if (!ShaderVariantDict.TryGetValue(shaderName, out var svlist))
  29.         {
  30.             svlist = new List<ShaderVariantCollection.ShaderVariant>();
  31.             ShaderVariantDict[shaderName] = svlist;
  32.         }
  33.         // 过滤有效keywords
  34.         List<string> voildkeywords = new List<string>();
  35.         List<string> allkeywords = new List<string>();
  36.         foreach (string keyword in curMat.shaderKeywords)
  37.         {
  38.             foreach (var KeyWords in sd.KeyWords)
  39.             {
  40.                 allkeywords = allkeywords.Union(KeyWords).ToList();
  41.                 if (KeyWords.Contains(keyword) && !voildkeywords.Contains(keyword))
  42.                     voildkeywords.Add(keyword);
  43.             }
  44.         }
  45.         Debug.Log($”Shader:{curMat.shader.name}\n\rallkeywords:{allkeywords.ListToString(” ”)}\n\rvoildkeywords:{voildkeywords.ListToString(” ”)}”);
  46.         // 材质与Shader的keyword求交
  47.         foreach (PassType o in Enum.GetValues(typeof(PassType)))
  48.         {
  49.             var pt = o;
  50.             ShaderVariantCollection.ShaderVariant? sv = null;
  51.             try
  52.             {
  53.                 if (curMat.shaderKeywords.Length > 0)
  54.                 {
  55.                     sv = new ShaderVariantCollection.ShaderVariant(shader, pt, curMat.shaderKeywords);
  56.                 }
  57.                 else
  58.                 {
  59.                     sv = new ShaderVariantCollection.ShaderVariant(shader, pt);
  60.                 }
  61.             }
  62.             catch (Exception e)
  63.             {
  64.                 //Debug.LogErrorFormat(”{0}-当前shader不存在变体(可以无视):{1}-{2}”, curMat.name, pt, JsonMapper.ToJson(curMat.shaderKeywords));
  65.                 continue;
  66.             }
  67.             //判断sv 是否存在,不存在则添加
  68.             if (sv != null)
  69.             {
  70.                 bool isContain = false;
  71.                 var _sv = (ShaderVariantCollection.ShaderVariant)sv;
  72.                 foreach (var val in svlist)
  73.                 {
  74.                     if (val.passType == _sv.passType && System.Linq.Enumerable.SequenceEqual(val.keywords, _sv.keywords))
  75.                     {
  76.                         isContain = true;
  77.                         break;
  78.                     }
  79.                 }
  80.                 if (!isContain)
  81.                 {
  82.                     svlist.Add(_sv);
  83.                 }
  84.             }
  85.         }
  86.     }
  87.     /// <summary>
  88.     /// 获取所有Shader变体数据
  89.     /// </summary>
  90.     static void GetShaderVariantEntriesFiltered(Shader shader, string[] filterKeywords, out int[] passTypes, out string[][] keywordLists, out string[] remainingKeywords)
  91.     {
  92.         //2019.3接口
  93.         //            internal static void GetShaderVariantEntriesFiltered(
  94.         //                Shader                  shader,                     0
  95.         //                int                     maxEntries,                 1
  96.         //                string[]                filterKeywords,             2
  97.         //                ShaderVariantCollection excludeCollection,          3
  98.         //                out int[]               passTypes,                  4
  99.         //                out string[]            keywordLists,               5
  100.         //                out string[]            remainingKeywords)          6
  101.         if (GetShaderVariantEntries == null)
  102.         {
  103.             GetShaderVariantEntries = typeof(ShaderUtil).GetMethod(”GetShaderVariantEntriesFiltered”, BindingFlags.NonPublic | BindingFlags.Static);
  104.         }
  105.         passTypes = new int[] { };
  106.         keywordLists = new string[][] { };
  107.         remainingKeywords = new string[] { };
  108.         if (toolSVC != null)
  109.         {
  110.             var _passtypes = new int[] { };
  111.             var _keywords = new string[] { };
  112.             var _remainingKeywords = new string[] { };
  113.             object[] args = new object[] { shader, 1024, filterKeywords, toolSVC, _passtypes, _keywords, _remainingKeywords };
  114.             GetShaderVariantEntries.Invoke(null, args);
  115.             var passtypes = args[4] as int[];
  116.             passTypes = passtypes;
  117.             //key word
  118.             keywordLists = new string[passtypes.Length][];
  119.             var kws = args[5] as string[];
  120.             for (int i = 0; i < passtypes.Length; i++)
  121.             {
  122.                 keywordLists[i] = kws[i].Split(&#39; &#39;);
  123.             }
  124.             //Remaning key word
  125.             var rnkws = args[6] as string[];
  126.             remainingKeywords = rnkws;
  127.         }
  128.     }
复制代码


二、 Shader变体剔除

2.1 Editor Shader Stripping



默认情况下,Unity会对没有使用Shader变体进行剔除,若某些变体是运行时代码措置的(代码开启keyword,但建议这种情况不要使用feature),这样在打包时可能就会呈现变体丢掉问题。
Unity对于Shader变体区分了三种情况Lightmap、Fog、以及其它。对于Lightmap、Fog给出更加细致的剔除设置。其它的Shader变体也给到了三种措置方式:Strip Unused(默认)、Strip All(剔除所有变体)、 Keep All(保留所有变体),凡是这部门不用改动。


2.2 Script Shader Stripping

构建Shader会将Shader变体数据传入IPreprocessShaders.OnProcessShader,可以按照keyword手动剔除不想使用的变体,这样可以减少编译时间和包体大小。分歧项目的都有各自的需求,官方给到一个例子:按照方针平台设置决定是否启用某些keyword,某些效果是用来查看衬着数据的就可以这样操作。
  1. using System.Collections.Generic;
  2. using UnityEditor;
  3. using UnityEngine;
  4. using UnityEngine.Rendering;
  5. using UnityEditor.Build;
  6. using UnityEditor.Rendering;
  7. class ShaderDebugBuildPreprocessor : IPreprocessShaders
  8. {
  9.     ShaderKeyword m_KeywordToStrip;
  10.     public ShaderDebugBuildPreprocessor()
  11.     {
  12.         m_KeywordToStrip = new ShaderKeyword(”DEBUG”);
  13.     }
  14.     // Use callbackOrder to set when Unity calls this shader preprocessor. Unity starts with the preprocessor that has the lowest callbackOrder value.
  15.     public int callbackOrder { get { return 0; } }
  16.     public void OnProcessShader(
  17.         Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
  18.         {
  19.         for (int i = 0; i < data.Count; ++i)
  20.         {
  21.             if (data[i].shaderKeywordSet.IsEnabled(m_KeywordToStrip) && !EditorUserBuildSettings.development)
  22.             {
  23.                 var foundKeywordSet = string.Join(” ”, data[i].shaderKeywordSet.GetShaderKeywords());
  24.                 Debug.Log(”Found keyword DEBUG in variant ” + i + ” of shader ” + shader);
  25.                 Debug.Log(”Keyword set: ” + foundKeywordSet);
  26.                 data.RemoveAt(i);
  27.                 --i;
  28.             }
  29.         }
  30.     }
  31. }
复制代码
三、 优化建议

将不需要的Shader变体从编译中剔除(Strip)能够优化项目性能。如果剔除了运行时需要的变体,Unity会加载较为接近的变体。为了避免变体被不测剔除,需要注意以下几点:

  • 使用shader_feature声明的宏,不要在运行时对其进行控制(建议查抄所有cs中措置的keyword,有些坑很难在Frame Debug中发现 )
  • 可以使用ShaderVariantCollection对Shader变体进行收集,并一起打包,避免变体被丢弃。
  • 同一KeyWord在分歧Pass中要统必然义方式
在声明宏时,需要注意以下几点以优化变体:

  • shader_feature按照资源引用进行变体收集,对比 multi_compile有更少的变体。功能允许的情况下,优先使用shader_feature
  • 不要定义未使用的keyword
  • 针对shader stage声明宏
  • 可以使用preprocessor macros针对分歧方针平台进行措置
  1. #if SHADER_API_DESKTOP
  2.    #pragma multi_compile SHADOWS_LOW SHADOWS_HIGH
  3.    #pragma multi_compile REFLECTIONS_LOW REFLECTIONS_HIGH
  4.    #pragma multi_compile CAUSTICS_LOW CAUSTICS_HIGH
  5. #elif SHADER_API_MOBILE
  6.    #pragma multi_compile QUALITY_LOW QUALITY_HIGH
  7.    #pragma shader_feature CAUSTICS // Uses shader_feature, so Unity strips variants that use CAUSTICS if there are no Materials that use the keyword at build time.
  8. #endif
复制代码

  • 开启Shader变体的强制匹配,便于发现Shader变体丢掉问题:PlayerSettings.strictShaderVariantMatching
  • 启动时执行变体预热,避免初度加载Shader时造成卡顿体验(新版本才有异步接口,略坑)
  • 技术条件允许的情况下,不要使用Unity内置的尺度Shader,例如”Universal Render Pipeline/Lit”之类的,变体数量极大。

参考


  • Shaders core concepts
  • Shader Stripping
  • 【Unity3D】Shader变体打点流程-变体剔除
  • 【Unity3D】Shader变体打点流程2-变体收集
  • 张鑫:一种Shader变体收集和打包编译优化的思路
  • lfh:Unity SVC的一种收集方案

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 16:25 , Processed in 0.101639 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表