关于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;
}
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());
}
}
空调维修
- 我的世界电脑版运行身份怎么弄出来(我的世界
- 空调抽湿是什么意思,设置抽湿的温度有什么意
- 方太燃气灶有一个打不着火 怎么修复与排查方法
- 夏季免费清洗汽车空调的宣传口号
- 清洗完空调后出现漏水现象
- iphone6能玩什么游戏(iphone6游戏)
- 如何设置电脑密码锁屏(如何设置电脑密码锁屏
- win10删除开机密码提示不符合密码策略要求
- 电脑w7显示不是正版(w7不是正版怎么解决)
- 万家乐z8热水器显示e7解决 怎么修复与排查方法
- 1匹空调多少瓦数(1匹空调多少瓦)
- 安卓手机连接电脑用什么软件好(关于安卓手机
- 电脑网页看视频卡是什么原因(爱拍看视频卡)
- 华帝燃气灶点火器一直响然后熄火怎么办:问题
- 电脑壁纸怎么换(关于电脑壁纸怎么换的介绍)
- 冬天空调的出风口应该朝什么方向(冬天空调风