关于Shader KeyWord的整理

家电修理 2023-07-16 19:17www.caominkang.com电器维修

关于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使用

二、KeyWord查看

我们可以点击这里切换Debug模式,查看材质球上缓存的变体。以及其他数据。


选择shader文件,点击keyord,可以显示shader定义的变体。

点击Compile And Sho Code的箭头可以查看变体组合


在frame debugger上,我们也能看到当前生效的变体。

三、KeyWordDemo

我们先直接上我们的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()
场景如下

1.multi_pile

我们可以看到面片已经显示蓝色了。这是因为我们变体定义#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,打包时候会生成所有变体,无论当前没有用到。就是因为这东西会生成所有变体组合,当变体定义数量多时,变体组合成指数增长,内存会爆炸的。所以我们需要适当使用。

2.shader_feature

我们尝试使用 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;
  }

   
  List svlist = 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, IList data)
 {
  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());
 }
}

Copyright © 2016-2025 www.caominkang.com 曹敏电脑维修网 版权所有 Power by