关于Shader KeyWord的整理
关于Shader KeyWord的整理
关于Shader KeyWord的整理。源自于挺久之前做的Demo,今天翻出来整理一下。
文章目录
- 关于Shader KeyWord的整理
- 前言
- 一、KeyWord
- 二、KeyWord查看
- 三、KeyWordDemo
- 1.multi_pile
- 2.shader_feature
- 四、变体收集器自动生成
前言
关于Shader KeyWord的整理。源自于挺久之前做的Demo,今天翻出来整理一下。
一、KeyWord
在shader 编写中,我们常常会遇到这样的问题。某些显示功能我们想通过代码实现手动控制打开关闭。由此 变体应运而生。Unity shader 的变体定义有
multi_pile
shader_feature
shader_feature_local unity 2019之后才有
以上都可以实现功能的开关功能。他们的作用域是不一样的。 multi_pile作用于全局也可作用于局部,可以使用Shader.EnableKeyord(“XXXX”),将作用于全局,使用meshRender.material.EnableKeyord (“XXXX”)则作用于局部。shader_feature也同理,但在打包上差别就很大,以下会说明。
在定义上multi_pile A 将只定义一个变体A,而且默认打开,无法关闭。所以我们需要添加下划线,代表关闭状态
multi_pile _ A。
而定义 shader_feature A 将会默认定义变体_ 以及变体A。
Local keyords:
shader_feature和multi_pile的主要缺点是,定义的所有关键字都限制了Unity的全局关键字数量(256个全局关键字,加上64个本地关键字)。为了避免这个问题,我们可以使用不同的着色器变体指令:shader_feature_local和multi_pile_local。
shader_feature_local: 与 shader_feature类似, 仅限本shader使用
multi_pile_local: 与multi_pile类似, 限本shader使用
我们可以点击这里切换Debug模式,查看材质球上缓存的变体。以及其他数据。
选择shader文件,点击keyord,可以显示shader定义的变体。
点击Compile And Sho Code的箭头可以查看变体组合
在frame debugger上,我们也能看到当前生效的变体。
我们先直接上我们的demo代码。定义一个shader 代码如下
Shader "Unlit/NeUnlitShader" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_pile MY_multi_1 #pragma multi_pile MY_multi_2 #include "UnityCG.cginc" struct vertOut { float4 pos : POSITION; }; vertOut vert(appdata_base v) { vertOut o; o.pos = UnityObjectToClipPos(v.vertex); return o; } float4 frag(vertOut i):COLOR { float4 c = float4(0, 0, 0, 0); #if defined (MY_multi_1) c = float4(0, 1, 0, 0);//输出绿色 #endif #if defined (MY_multi_2) c = float4(0, 0, 1, 0);//输出蓝色 #endif return c; } ENDCG } } }
代码如上,我们定义两个multi_pile全局变体 MY_multi_1, MY_multi_2。MY_multi_2的优先级高于MY_multi_1。当MY_multi_2 成立时,我们输出蓝色,当MY_multi_1 成立,并且MY_multi_2不成立时,我们输出绿色。当MY_multi_1, MY_multi_2都不生效时,我们直接输出黑色。
我们添加CS脚本,控制变体开关。
public class TestKeyWorld : MonoBehaviour { public bool multi_1; public MeshRenderer meshRender; public void OnChangeJJJJ() { multi_1 = !multi_1; if (multi_1) { Shader.EnableKeyord("MY_multi_1"); Shader.DisableKeyord("MY_multi_2"); //meshRender.material.EnableKeyord ("MY_multi_1"); //meshRender.material.DisableKeyord ("MY_multi_2"); } else { Shader.EnableKeyord("MY_multi_2"); Shader.DisableKeyord("MY_multi_1"); //meshRender.material.EnableKeyord ("MY_multi_2"); //meshRender.material.DisableKeyord ("MY_multi_1"); } } }
我们定义函数OnChangeJJJJ来控制变体的开关。
场景上我们定义两个面片A,B。 以及按钮。按钮的点击事件绑定我们的函数OnChangeJJJJ()
场景如下
我们可以看到面片已经显示蓝色了。这是因为我们变体定义#pragma multi_pile MY_multi_1,不含下划线。那他的默认值就是开启状态。并且函数OnChangeJJJJ()控制不了变体的开关。
这时候我们修改变体定义,添加关闭选项”_“
#pragma multi_pile _ MY_multi_1
//#pragma multi_pile _ MY_multi_2 (注释该变体)
可以看到他显示黑色,即MY_multi_1并没有开启。我们点击按钮就可以控制变体的开启与关闭了。
我们可以看到时两个面板一起变绿,因为我们用的是multi_pile全局变体。变体控制我们用的是 Shader.EnableKeyord(“MY_multi_1”);
如果我们想使用multi_pile控制单个材质球变体开关,我们可以使用meshRender.material.EnableKeyord (“MY_multi_1”)。我们修改代码shader
#pragma multi_pile _ MY_multi_1 #pragma multi_pile _ MY_multi_2
修改函数OnChangeJJJJ
public void OnChangeJJJJ() { multi_1 = !multi_1; if (multi_1) { //Shader.EnableKeyord("MY_multi_1"); //Shader.DisableKeyord("MY_multi_2"); meshRender.material.EnableKeyord("MY_multi_1"); meshRender.material.DisableKeyord("MY_multi_2"); } else { //Shader.EnableKeyord("MY_multi_2"); //Shader.DisableKeyord("MY_multi_1"); meshRender.material.EnableKeyord("MY_multi_2"); meshRender.material.DisableKeyord("MY_multi_1"); } }
运行游戏。我们可以看到右边的面板还是绿色,实际上他应该时黑色,应该时材质球的缓存导致的,当我们打包出来后或者重启Unity就会变成黑色的。左边面板功能正常。
我们打包试exe一下
可以看到功能正常。我们这里用的是multi_pile,打包时候会生成所有变体,无论当前没有用到。就是因为这东西会生成所有变体组合,当变体定义数量多时,变体组合成指数增长,内存会爆炸的。所以我们需要适当使用。
我们尝试使用 shader_feature 实现以上效果。修改shader 文件
#pragma shader_feature MY_multi_1 #pragma shader_feature MY_multi_2
修改cs函数
public void OnChangeJJJJ() { multi_1 = !multi_1; if (multi_1) { //Shader.EnableKeyord("MY_multi_1"); //Shader.DisableKeyord("MY_multi_2"); meshRender.material.EnableKeyord("MY_multi_1"); meshRender.material.DisableKeyord("MY_multi_2"); } else { //Shader.EnableKeyord("MY_multi_2"); //Shader.DisableKeyord("MY_multi_1"); meshRender.material.EnableKeyord("MY_multi_2"); meshRender.material.DisableKeyord("MY_multi_1"); } }
变体定义 #pragma shader_feature MY_multi_1 不需要携带_下划线。他会默认定义。
运行由此,我们发现没什么异常。
我们打个包看看
我们可以看到他默认显示黑色,并且按钮没有反应。因为shader_feature 变体打包时候只会打进已编译的变体。shader_feature 的默认值是”_”,默认是不开启的。
为解决以上问题ShaderVariants 变体收集器,应运而生。
Demo中选择后
放到这里预加载
我们再打一次包试试
功能正常了。
我们已经明白了使用变体收集器ShaderVariants配合使用shader_feature可以很好的控制变体组合生成,排除不需要的变体。
变体生成规则如下
shader_feature A
shader_feature B
变体Group如下
A,B,AB
直接贴代码。
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 : EditorWindo { static Dictionary> ShaderVariantDict = ne Dictionary >(); public static List GetAllRuntimeDirects() { //搜索所有资源 List directories = ne List (); directories.Add("Assets"); return directories; } private ShaderVariantCollection svc; readonly public static string ALL_SHADER_VARAINT_PATH = "Assets/AllShaders.shadervariants"; static List allShaderNameList = ne List (); [MenuItem("ShaderTool/AutoShaderVariants")] public static void GenShaderVariant() { ShaderVariantDict = ne Dictionary >(); //先搜集所有keyord到工具类SVC toolSVC = ne ShaderVariantCollection(); var shaders = AssetDatabase.FindAssets("t:Shader", ne string[] { "Assets", "Packages" }).ToList(); foreach (var shader in shaders) { ShaderVariantCollection.ShaderVariant sv = ne ShaderVariantCollection.ShaderVariant(); var shaderPath = AssetDatabase.GUIDToAssetPath(shader); sv.shader = AssetDatabase.LoadAssetAtPath (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 allMats = ne List (); //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 = ne 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 = ne int[] { }; public string[][] KeyWords = ne string[][] { }; public string[] ReMainingKeyWords = ne string[] { }; } //shader数据的缓存 static Dictionary ShaderDataDict = ne Dictionary (); //添加Material计算 static List passShaderList = ne List (); /// /// 添加到Dictionary /// /// 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 和 keyords sd = GetShaderKeyords(curMat.shader); ShaderDataDict[curMat.shader.name] = sd; } var kCount = sd.PassTypes.Length; if (kCount > 2000) { if (!passShaderList.Contains(curMat.shader.name)) { Debug.LogFormat("Shader【{0}】,变体数量:{1},不建议继续分析,后续也会跳过!", curMat.shader.name, kCount); passShaderList.Add(curMat.shader.name); } else { Debug.LogFormat("mat:{0} , shader:{1} ,keyordCount:{2}", curMat.name, curMat.shader.name, kCount); } return; } Listsvlist = null; if (!ShaderVariantDict.TryGetValue(curMat.shader.name, out svlist)) { svlist = ne List (); ShaderVariantDict[curMat.shader.name] = svlist; } //求所有mat的k for (int i = 0; i < sd.PassTypes.Length; i++) { // var pt = (PassType)sd.PassTypes[i]; ShaderVariantCollection.ShaderVariant? sv = null; try { string[] key_orlds = sd.KeyWords[i]; //变体交集 大于0 ,添加到 svcList sv = ne ShaderVariantCollection.ShaderVariant(curMat.shader, pt, key_orlds); SetShaderVariantKeyWorld(svlist, sv); } catch (Exception e) { Debug.LogErrorFormat("{0}-当前shader不存在变体(可以无视):{1}-{2}", curMat.name, pt, curMat.shaderKeyords.ToString()); continue; } } } static void SetShaderVariantKeyWorld(List 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.keyords, _sv.keyords)) { isContain = true; break; } } if (!isContain) { svlist.Add(_sv); } } } static MethodInfo GetShaderVariantEntries = null; static ShaderVariantCollection toolSVC = null; //获取shader的 keyords public static ShaderData GetShaderKeyords(Shader shader) { ShaderData sd = ne ShaderData(); GetShaderVariantEntriesFiltered(shader, ne string[] { }, out sd.PassTypes, out sd.KeyWords, out sd.ReMainingKeyWords); return sd; } /// /// 获取keyord /// /// /// /// /// /// static void GetShaderVariantEntriesFiltered(Shader shader, string[] filterKeyords, out int[] passTypes, out string[][] keyordLists, out string[] remainingKeyords) { //2019.3接口 // internal static void GetShaderVariantEntriesFiltered( // Shader shader, 0 // int maxEntries, 1 // string[] filterKeyords, 2 // ShaderVariantCollection excludeCollection, 3 // out int[] passTypes, 4 // out string[] keyordLists, 5 // out string[] remainingKeyords) 6 if (GetShaderVariantEntries == null) { GetShaderVariantEntries = typeof(ShaderUtil).GetMethod("GetShaderVariantEntriesFiltered", BindingFlags.NonPublic | BindingFlags.Static); } passTypes = ne int[] { }; keyordLists = ne string[][] { }; remainingKeyords = ne string[] { }; if (toolSVC != null) { var _passtypes = ne int[] { }; var _keyords = ne string[] { }; var _remainingKeyords = ne string[] { }; object[] args = ne object[] { shader, 256, filterKeyords, toolSVC, _passtypes, _keyords, _remainingKeyords }; GetShaderVariantEntries.Invoke(null, args); var passtypes = args[4] as int[]; passTypes = passtypes; //key ord keyordLists = ne string[passtypes.Length][]; var ks = args[5] as string[]; for (int i = 0; i < passtypes.Length; i++) { keyordLists[i] = ks[i].Split(' '); } //Remaning key ord var rnks = args[6] as string[]; remainingKeyords = rnks; } } }
点击这里自动收集变体组合。
我们还想在打包时候输出shader信息
通过实现接口IPreprocessShaders完成
直接贴代码
using System.Collections; using System.Collections.Generic; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.Rendering; using UnityEngine; using UnityEngine.Rendering; public class MyCustomBuildProcessor : IPreprocessShaders { ShaderKeyord m_Blue; public MyCustomBuildProcessor() { m_Blue = ne ShaderKeyord("_BLUE"); } public int callbackOrder { get { return 0; } } public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IListdata) { System.Text.StringBuilder sb = ne System.Text.StringBuilder(); sb.AppendFormat("shader={3}, passType={0}, passName={1}, shaderType={2}n", snippet.passType, snippet.passName, snippet.shaderType, shader.name); for (int i = 0; i < data.Count; ++i) { var pdata = data[i]; sb.AppendFormat("{0}.{1},{2}: ", i, pdata.graphicsTier, pdata.shaderCompilerPlatform); var ks = pdata.shaderKeyordSet.GetShaderKeyords(); foreach (var k in ks) { sb.AppendFormat("{0}, ", k.ToString()); } sb.Append("n"); } Debug.Log(sb.ToString()); } }
空调维修
- 海信电视维修站 海信电视维修站点
- 格兰仕空调售后电话 格兰仕空调维修售后服务电
- 家电售后服务 家电售后服务流程
- 华扬太阳能维修 华扬太阳能维修收费标准表
- 三菱电机空调维修 三菱电机空调维修费用高吗
- 美的燃气灶维修 美的燃气灶维修收费标准明细
- 科龙空调售后服务 科龙空调售后服务网点
- 华帝热水器维修 华帝热水器维修常见故障
- 康泉热水器维修 康泉热水器维修故障
- 华凌冰箱维修电话 华凌冰箱维修点电话
- 海尔维修站 海尔维修站点地址在哪里
- 北京海信空调维修 北京海信空调售后服务
- 科龙空调维修 科龙空调维修故障
- 皇明太阳能售后 皇明太阳能售后维修点
- 海信冰箱售后服务 海信冰箱售后服务热线电话
- 海尔热水器服务热线