为了让大家了解为何要减少变体,这里列出变体过多的缺点
由于项目没使用 SRP,还是 built-in 管线,那么要使用 built-in 的阴影就需要使用到 #pragma multi_compile_fwdbase
该内置的 pragma 会生存很多不需要的 multi_compile
的 keyword
,为了定制效果,让 shader 尽可能的小,那么我们可以这么整:将 #pragma multi_compile_fwdbase
和 multi_compile_fog
编译生存的 keywrod
都手动来定义需要的
如果我们只要
的功能
选中 shader 文件,点击 Inspector 视图中的按钮:Compile and show code
,如下图的:
生存的代码中搜索 Global Keywords
,就可以看到各个变体的代码
以上两种方式都可以查看变体情况
根据上面 搜索 Global Keywords 的方式,我们可以知道生存了很多不必要的变体代码
变体多的缺点 上面有提到
为了优化,可以这么做,上面也有提到,这里再次重新强调一下:#pragma multi_compile_fwdbase
和 multi_compile_fog
编译生存的 keywrod
都手动来定义需要的
如:
//#pragma multi_compile_fog
//#pragma multi_compile_fwdbase
#define FOG_LINEAR
#define DIRECTIONAL
#define SHADOWS_SCREEN
变体的数量剧减,有原来的 98 个变成了 6 个(还有一些自定义的 multi_compile,不然只有3个,而这三个都是内置生成的 tier:1,2,3的,这个 tier: 1,2,3暂时不知道如何删除(注:后续发现可以再 IPreprocessShaders
中删除),不然的话,只有1个变体,那么这个 shader 文件就会小的可怜,打包速度、占用内存都会极小)
但是这样优化后的了另一个问题:Unity Editor 下的 Prefab、Material 都无法预览正确的效果
所以我们可以写个工具,在:打包前,批量修改 shader 的 fwdbase, fog 的变体,在打包后,在恢复过来,这样,Unity Editor 下既可以正常预览 Prefab、Material 的渲染效果,也可以在打包后变体减少
public void BuildPackage()
{
// 打包资源前,先处理一波 shader 的 fwdbase, fog 变体替换
BeforeAndAfterBuildShadersHandler.BeforeBuildHandle();
// 正常 building 逻辑在此
...
// 打包资源后,恢复 shader 文件
BeforeAndAfterBuildShadersHandler.AfterBuldHandle();
}
#define __DATAFORM_TXT__ // 使用 文本来存 shader 数据
// jave.lin 2021/08/30
// 打包构建前、后 shader 的处理工具
// 因为使用的是 built-in 管线,基本很多光影都需要 fwdbase, fog 等 built-in 的变体
// 而这个工具是不使用 built-in 变体,改用手动的方式来定义需要的变体
// 所以会导致 unity 编辑器是对 material 的预览效果出问题
// 因为在发布程序前,可以使用会 fwdbase, fog 等 built-in 变体
// 但是在发布程序时,必须使用自己手动的方式来定义变体(可以减少很多变体的数量)
// 如果使用 SRP 的话,可以变体的把控会更容易
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
// jave.lin shader 文件信息
public class ShaderFileInfo
{
public string path;
public UnityEngine.Shader obj;
public string lowPath;
public string lowShaderName;
}
// jave.lin shader 在 building 处理的工具类
public class BeforeAndAfterBuildShadersEditorWindow : EditorWindow
{
private string defaultShadersPath = "Assets/GameAssets/shaders";
private List<ShaderFileInfo> shaderInfoList = new List<ShaderFileInfo>();
private List<Shader> shaderList = null;
// 根据 shader name 过滤
private bool shadeNameFilter = true;
private string shaderNameFilterContent = "";
// 根据 file name 过滤
private bool fileNameFilter = true;
private string fileNameFilterContent = "";
private Vector2 dragDropFileScrollViewPos;
[MenuItem("实用工具/资源工具/打包构建前、后 shader 的处理工具")]
public static void _Show()
{
var win = EditorWindow.GetWindow<BeforeAndAfterBuildShadersEditorWindow>();
win.titleContent = new GUIContent("打包构建前、后 shader 的处理工具");
win.Show();
}
private void OnGUI()
{
#if __DATAFORM_TXT__
if (shaderList == null)
{
shaderList = new List<Shader>();
BeforeAndAfterBuildShadersHandler.LoadDataFromTxt(BeforeAndAfterBuildShadersHandler.dataPath, shaderList);
}
#else
if (shaderList == null)
{
shaderList = new List<Shader>();
var data = AssetDatabase.LoadAssetAtPath<BeforeAndAfterBuildShadersHandleData>(BeforeAndAfterBuildShadersHandler.dataPath);
shaderList.AddRange(data.shaders);
}
#endif
var src_endabled = GUI.enabled;
if (BeforeAndAfterBuildShadersHandler.ShaderBackupCount() > 0) GUI.enabled = false;
if (GUILayout.Button("Building前 手动 处理"))
{
BeforeAndAfterBuildShadersHandler.BeforeBuildHandle();
}
GUI.enabled = src_endabled;
if (BeforeAndAfterBuildShadersHandler.ShaderBackupCount() == 0) GUI.enabled = false;
if (GUILayout.Button("Building后 手动 处理"))
{
BeforeAndAfterBuildShadersHandler.AfterBuldHandle();
}
GUI.enabled = src_endabled;
if (BeforeAndAfterBuildShadersHandler.IsMemoryBK() && GUILayout.Button("清空备份数据"))
{
BeforeAndAfterBuildShadersHandler.ClearBackupInfo();
}
if (GUILayout.Button("清理配置中不存在的shaders文件"))
{
BeforeAndAfterBuildShadersHandler.ClearNotExistsShaders();
}
DisplayCombineFileInfoList();
}
private void DisplayCombineFileInfoList()
{
EditorGUILayout.Space();
var srcCol = GUI.contentColor;
GUI.contentColor = Color.gray;
EditorGUILayout.LabelField("================================================ Data List ================================================");
GUI.contentColor = srcCol;
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Clear"))
{
shaderInfoList.Clear();
}
if (GUILayout.Button("Reload"))
{
RefreshFileListInfo();
}
if (GUILayout.Button("Save"))
{
SaveDataAsset();
}
if (GUILayout.Button("Select"))
{
SelectAssetList();
}
if (GUILayout.Button("LoadShadersFolder"))
{
LoadShadersFolder();
}
EditorGUILayout.EndHorizontal();
GUI.contentColor = Color.green;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"Shader Name Filter : ", GUILayout.Width(150));
shadeNameFilter = EditorGUILayout.Toggle(shadeNameFilter, GUILayout.Width(20));
shaderNameFilterContent = EditorGUILayout.TextField(shaderNameFilterContent);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"File Name Filter : ", GUILayout.Width(150));
fileNameFilter = EditorGUILayout.Toggle(fileNameFilter, GUILayout.Width(20));
fileNameFilterContent = EditorGUILayout.TextField(fileNameFilterContent);
EditorGUILayout.EndHorizontal();
GUI.contentColor = srcCol;
var lowShaderNameFilterContent = shaderNameFilterContent.ToLower();
var lowFileNameFilterContent = fileNameFilterContent.ToLower();
dragDropFileScrollViewPos = EditorGUILayout.BeginScrollView(dragDropFileScrollViewPos/*, GUILayout.Width(300), GUILayout.Width(300)*/);
for (int i = 0; i < shaderInfoList.Count; i++)
{
var remove = false;
var info = shaderInfoList[i];
if (shadeNameFilter && !string.IsNullOrEmpty(lowShaderNameFilterContent))
{
if (!info.lowShaderName.Contains(lowShaderNameFilterContent))
{
continue;
}
}
if (fileNameFilter && !string.IsNullOrEmpty(lowFileNameFilterContent))
{
if (!info.lowPath.Contains(lowFileNameFilterContent))
{
continue;
}
}
EditorGUILayout.BeginHorizontal(GUIStyle.none);
remove = GUILayout.Button("X", GUILayout.Width(20));
EditorGUILayout.ObjectField(info.obj, typeof(UnityEngine.Object), false, GUILayout.Width(200));
EditorGUILayout.TextField(info.path);
EditorGUILayout.EndHorizontal();
if (remove)
{
shaderInfoList.RemoveAt(i);
--i;
}
}
EditorGUILayout.EndScrollView();
}
private void RefreshFileListInfo(List<string> addList = null)
{
shaderInfoList.Clear();
foreach (var shader in shaderList)
{
var path = AssetDatabase.GetAssetPath(shader);
shaderInfoList.Add(new ShaderFileInfo
{
path = path,
obj = shader,
lowPath = path.ToLower(),
lowShaderName = shader.name.ToLower(),
});
}
if (shaderInfoList.Count > 1)
{
SortDataList();
}
}
private void SortDataList()
{
shaderInfoList.Sort((a, b) => { return string.Compare(a.path, b.path); });
}
private void SaveDataAsset()
{
if (shaderList != null)
{
shaderList.Clear();
foreach (var info in shaderInfoList)
{
if (info.obj == null)
{
// 中途被删除
continue;
}
shaderList.Add(info.obj);
}
BeforeAndAfterBuildShadersHandler.SaveAsset(shaderList);
}
}
private void SelectAssetList()
{
// jave.lin : method0, Selection.gameObjects Read Only
//var arr = new GameObject[combineFileInfoList.Count];
//for (int i = 0; i < arr.Length; i++)
//{
// arr[i] = combineFileInfoList[i].obj as GameObject;
//}
//Selection.gameObjects = arr; // read only
// jave.lin : method1 : reflection, 但是获取不了 add 方法
//var addFunc = typeof(Selection).GetMethod(
// "Add",
// new Type[]
// {
// typeof(UnityEngine.Object)
// },
// new ParameterModifier[] { new ParameterModifier(1) }
// );
//Debug.Log($"PrefabCombinerEditorWindow.SelectPrefabList addFunc : {addFunc}");
// jave.lin : method2 : Selection.objects, 后来发现此 API 可以 setter
var arr = new UnityEngine.Object[shaderInfoList.Count];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = shaderInfoList[i].obj;
}
Selection.objects = arr;
}
private void LoadShadersFolder()
{
shaderInfoList.Clear();
var shaderGUIDs = AssetDatabase.FindAssets("t:Shader", new string[] { defaultShadersPath });
foreach (var guid in shaderGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
shaderInfoList.Add(new ShaderFileInfo
{
path = path,
obj = shader,
lowPath = path.ToLower(),
lowShaderName = shader.name.ToLower(),
});
}
}
}
// jave.lin 文件恢复方式
public enum eFileRevertType
{
MEMORY,
SVN,
GIT,
}
// jave.lin shader 的内存备份数据
public struct ShaderBackupInfo
{
public string path;
public string source;
}
// jave.lin building 前后的处理器
public class BeforeAndAfterBuildShadersHandler
{
// 数据路径
#if __DATAFORM_TXT__
public static string dataPath = "Assets/Editor/Shaders/BeforeAndAfterBuildShadersHandleTextData.txt";
#else
public static string dataPath = "Assets/Editor/Shaders/BeforeAndAfterBuildShadersHandleData.asset";
#endif
// shader 内存的备份数据
private static List<ShaderBackupInfo> shaderBackInfoList = new List<ShaderBackupInfo>();
// jave.lin : 使用 svn 还是 git
private static eFileRevertType eFileRevertType = eFileRevertType.MEMORY;
// jave.lin : 替换并获取缩进组
private static Regex fogReplaceRegex = new Regex("\\n(\\s+)#pragma multi_compile_fog", RegexOptions.Compiled);
private static Regex fwdbaseReplaceRegex = new Regex("\\n(\\s+)#pragma multi_compile_fwdbase", RegexOptions.Compiled);
// jave.lin : 读写文件时 encoding 是否带 BOM
private static bool readAndWriteWithBOM = false;
// jave.lin : 换行是否 CRLF
private static bool endLineFlagWithCRLF = true;
private static List<Shader> shaderList = new List<Shader>();
public static int ShaderBackupCount() => shaderBackInfoList != null ? shaderBackInfoList.Count : 0;
// statics
public static string GetFullPath(string assetPath)
{
return $"{Application.dataPath.Replace("Assets", "")}/{assetPath}".Replace("//", "/");
}
public static bool IsMemoryBK()=> eFileRevertType == eFileRevertType.MEMORY;
public static void ClearBackupInfo()
{
shaderBackInfoList.Clear();
}
public static void LoadData(string assetPath, List<Shader> ret)
{
#if __DATAFORM_TXT__
LoadDataFromTxt(assetPath, ret);
#else
LoadDataFromAsset(assetPath, ret);
#endif
}
public static void LoadDataFromAsset(string assetPath, List<Shader> ret)
{
var data = AssetDatabase.LoadAssetAtPath<BeforeAndAfterBuildShadersHandleData>(assetPath);
if (data == null) return;
ret.AddRange(data.shaders);
}
public static void LoadDataFromTxt(string txtPath, List<Shader> ret)
{
string txtContent;
if (File.Exists(txtPath))
{
txtContent = File.ReadAllText(txtPath);
}
else
{
var fullPath = GetFullPath(txtPath);
if (!File.Exists(fullPath))
{
return;
}
txtContent = File.ReadAllText(fullPath);
}
if (string.IsNullOrEmpty(txtContent))
{
return;
}
var paths = txtContent.Split(new string[] { "\n" }, System.StringSplitOptions.RemoveEmptyEntries);
foreach (var path in paths)
{
var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
if (shader == null) continue;
ret.Add(shader);
}
}
public static void SaveAsset(List<Shader> shaders)
{
#if __DATAFORM_TXT__
var content = "";
foreach (var shader in shaders)
{
var shaderPath = AssetDatabase.GetAssetPath(shader);
content += $"{shaderPath}\n";
}
var fullPath = GetFullPath(dataPath);
File.WriteAllText(fullPath, content);
AssetDatabase.Refresh();
#else
var isNew = false;
var data = AssetDatabase.LoadAssetAtPath<BeforeAndAfterBuildShadersHandleData>(dataPath);
if (data == null)
{
data = ScriptableObject.CreateInstance<BeforeAndAfterBuildShadersHandleData>();
isNew = true;
}
var saveList = new List<Shader>();
foreach (var shader in shaders)
{
if (shader == null) continue; // 中途被删除
saveList.Add(shader);
}
data.shaders.Clear();
data.shaders.AddRange(saveList);
if (!isNew) data = data.Clone();
AssetDatabase.CreateAsset(data, dataPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
#endif
}
// 读取 *.asset 配置的数据
public static void BeforeBuildHandle()
{
// jave.lin : 遍历里面的 *.shader 逐一处理
/*
- 如果是shader代码使用内存恢复先备份到内存
- 如果是shader代码使用文件恢复先备份到临时文件
- 如果是shader代码使用svn/git的版本控制,不需要我们手动备份,恢复时直接使用svn/git来恢复即可
- #pragma multi_compile_fog 替换为:
- #define FOG_LINEAR
- #pragma multi_compile_fwdbase 替换为:
- #define DIRECTIONAL
- #define SHADOWS_SCREEN
- 将对应的 *.shader 文件标记为:EditorUtility.SetDirty(shaderObj);
- 然后 AssetsDatabase.SaveAssets(); AssetsDatabase.Refresh();
*/
shaderBackInfoList.Clear();
shaderList.Clear();
LoadData(dataPath, shaderList);
if (shaderList.Count > 0)
{
var encoding = new System.Text.UTF8Encoding(readAndWriteWithBOM);
var endLineStr = endLineFlagWithCRLF ? "\r\n" : "\n"; // Environment.NewLine;
var fogReplacmentStr = $"{endLineStr}$1#define FOG_LINEAR";
var fwdbaseReplacmentStr = $"{endLineStr}$1#define DIRECTIONAL{endLineStr}$1#define SHADOWS_SCREEN";
foreach (var shader in shaderList)
{
if (shader == null) continue;
var path = AssetDatabase.GetAssetPath(shader);
var fullPath = GetFullPath(path);
if (!File.Exists(fullPath)) continue;
try
{
var txt = File.ReadAllText(fullPath, encoding);
shaderBackInfoList.Add(new ShaderBackupInfo { path = fullPath, source = txt });
// jave.lin : 普通替换,缩进很难看
//txt = txt.Replace("#pragma multi_compile_fog", "#define FOG_LINEAR");
//txt = txt.Replace("#pragma multi_compile_fwdbase", "#define DIRECTIONAL\n#define SHADOWS_SCREEN");
// jave.lin : 使用正则来替换,缩进就可以还原来的
txt = fogReplaceRegex.Replace(txt, fogReplacmentStr);
txt = fwdbaseReplaceRegex.Replace(txt, fwdbaseReplacmentStr);
File.WriteAllText(fullPath, txt);
}
catch(System.Exception er)
{
Debug.LogError(er);
}
}
}
}
public static void AfterBuldHandle()
{
// jave.lin : 遍历里面的 *.shader 逐一处理
/*
// method 0:
- 使用从内存中恢复shader代码(如果shader代码过多,可以考虑先backup 到 disk,然后再从 disk 恢复
// method 1:
- #define FOG_LINEAR 替换为:
- #pragma multi_compile_fog
- #define DIRECTIONAL、#define SHADOWS_SCREEN 替换为:
- #pragma multi_compile_fwdbase
- 将对应的 *.shader 文件标记为:EditorUtility.SetDirty(shaderObj);
- 然后 AssetsDatabase.SaveAssets(); AssetsDatabase.Refresh();
// method 2:(更方便的方式)
- 使用 svn/git 命令行来还原打包后的 shaders 文件
*/
if (shaderBackInfoList.Count == 0)
{
return;
}
if (eFileRevertType == eFileRevertType.MEMORY)
{
var encoding = new System.Text.UTF8Encoding(readAndWriteWithBOM);
foreach (var info in shaderBackInfoList)
{
try
{
File.WriteAllText(info.path, info.source, encoding);
}
catch (System.Exception er)
{
Debug.LogError(er);
}
}
}
else
{
var revertFiles = new List<string>();
foreach (var info in shaderBackInfoList)
{
revertFiles.Add(info.path);
}
try
{
RevertFiles(revertFiles);
}
catch(System.Exception er)
{
Debug.LogError(er);
}
}
shaderBackInfoList.Clear();
}
public static void ClearNotExistsShaders()
{
shaderList.Clear();
LoadData(dataPath, shaderList);
if (shaderList.Count > 0)
{
for (int i = shaderList.Count - 1; i > -1; i--)
{
var shader = shaderList[i];
if (shader == null) continue;
var path = AssetDatabase.GetAssetPath(shader);
var fullPath = GetFullPath(path);
if (!File.Exists(fullPath))
{
shaderList.RemoveAt(i);
}
}
}
SaveAsset(shaderList);
}
private static void RevertFiles(List<string> files)
{
// TortoiseProc.exe /command:log /path:"H:\WorkFiles\ProjectArt\Assets\Art\(Temporary).meta" /closeonend:1
var revision = eFileRevertType.ToString().ToLower();
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(revision);
var revertFiles = string.Join<string>(" ", files);
if (eFileRevertType == eFileRevertType.SVN)
{
// 需要确保安装了 小乌龟 svn 命令行工具,可参考:https://blog.csdn.net/linjf520/article/details/119617076
// e.g.: svn revert "C:\\test1.txt" "C:\\test2.txt"
Debug.Log($"svn revert {revertFiles}");
info.Arguments = $" revert {revertFiles}";
}
else if (eFileRevertType == eFileRevertType.GIT)
{
// 需要确保安装了 git,使用 git checkout ...
// e.g.: git checkout "C:\\test1.txt" "C:\\test2.txt"
Debug.Log($"git checkout {revertFiles}");
info.Arguments = $" checkout {revertFiles}";
}
else if (eFileRevertType == eFileRevertType.MEMORY)
{
// nops
}
else
{
Debug.LogError($"unimplements eRevesionApp : {eFileRevertType} revert handle.");
return;
}
info.CreateNoWindow = false;
info.ErrorDialog = true;
info.UseShellExecute = true;
if (info.UseShellExecute)
{
info.RedirectStandardOutput = false;
info.RedirectStandardError = false;
info.RedirectStandardInput = false;
}
else
{
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.StandardOutputEncoding = System.Text.UTF8Encoding.UTF8;
info.StandardErrorEncoding = System.Text.UTF8Encoding.UTF8;
}
System.Diagnostics.Process process = System.Diagnostics.Process.Start(info);
if (!info.UseShellExecute)
{
Debug.Log(process.StandardOutput);
Debug.Log(process.StandardError);
}
process.WaitForExit();
process.Close();
}
}
// jave.lin building 前后需要特殊处理的 shader
[CreateAssetMenu(menuName = "Tools/Create BeforeAndAfterBuildShadersHandleData ")]
public class BeforeAndAfterBuildShadersHandleData : ScriptableObject
{
public List<Shader> shaders;
public BeforeAndAfterBuildShadersHandleData Clone()
{
var ret = ScriptableObject.CreateInstance<BeforeAndAfterBuildShadersHandleData>();
ret.shaders = new List<Shader>(this.shaders);
return ret;
}
}
另外,PlayerSettings/Other Settings/Rendering/Graphics API 去掉勾,只保留要发布的单个目标平台(切换到切他平台,再切到对应平台即可)
这个 shader 变体也可以减少
另外,如果是 surface shader 的话,很多的变体,可以通过 一些 surface shader 指令来删除,参考文档:Writing Surface Shaders - 搜索:Code generation options 部分
下面是 surface shader 中的 各种 noxxx 的删除变体、skip_variants 删除变体的示例:
//jave.lin - 测试删除 surface shader 的变体
#pragma surface surf Lambert fullforwardshadows addshadow alphatest:_Cutoff nodynlightmap nodirlightmap noforwardadd nolppv nofog noambient novertexlights
#pragma skip_variants SHADOWS_SCREEN LIGHTPROBE_SH VERTEXLIGHT_ON FOG_LINEAR DIRECTIONAL_COOKIE
#pragma skip_variants SPOT POINT POINT_COOKIE SHADOWS_CUBE SHADOWS_DEPTH FOG_EXP FOG_EXP2
#pragma skip_variants INSTANCING_ON DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON
#pragma skip_variants UNITY_HDR_ON EDITOR_VISUALIZATION
#pragma skip_variants SHADOWS_CUBE
#pragma fragmentoption ARB_precision_hint_fastest
Declaring and using shader keywords in HLSL - 查看:multi_compile shortcuts 部分
直接抄过来吧:
Unity provides several “shortcut” notations for declaring shader keywords.
The following shortcuts relate to light, shadow and lightmapping in the Built-in Render Pipeline:
DIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON SHADOWS_SCREEN SHADOWS_SHADOWMASK LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH
. These variants are needed by PassType.ForwardBase.DIRECTIONAL LIGHTMAP_ON DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_SHADOW_MIXING VERTEXLIGHT_ON LIGHTPROBE_SH
. These variants are needed by PassType.ForwardBase.POINT DIRECTIONAL SPOT POINT_COOKIE DIRECTIONAL_COOKIE
. These variants are needed by PassType.ForwardAdd.POINT DIRECTIONAL SPOT POINT_COOKIE DIRECTIONAL_COOKIE SHADOWS_DEPTH SHADOWS_SCREEN SHADOWS_CUBE SHADOWS_SOFT SHADOWS_SHADOWMASK LIGHTMAP_SHADOW_MIXING
. This is the same as multi_compile_fwdadd, but this adds the ability for the lights to have real-time shadows.POINT DIRECTIONAL SPOT POINT_COOKIE DIRECTIONAL_COOKIE SHADOWS_DEPTH SHADOWS_SCREEN SHADOWS_CUBE SHADOWS_SOFT SHADOWS_SHADOWMASK LIGHTMAP_SHADOW_MIXING
. This is effectively a catch-all shortcut for all functionality related to real-time light and shadows, other than Light Probes.SHADOWS_DEPTH SHADOWS_CUBE
. These variants are needed by PassType.ShadowCaster.SHADOWS_SPLIT_SPHERES SHADOWS_SINGLE_CASCADE
. It also compiles variants without any of these keywords. These variants are needed for screen-space shadows.LIGHTMAP_ON DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON UNITY_HDR_ON SHADOWS_SHADOWMASK LIGHTPROBE_SH
. It also compiles variants without any of these keywords. These variants are needed by PassType.LightPrePassFinal and PassType.Deferred.The following shortcuts relate to other settings:
SOFTPARTICLES_ON
. It also compiles variants without this keyword. For more information, see Built-in Particle System.FOG_LINEAR, FOG_EXP, FOG_EXP2
. It also compiles variants without any of these keywords. You can control this behavior in the Graphics settings window.INSTANCING_ON PROCEDURAL_ON
. Otherwise, it adds this keyword: INSTANCING_ON
. It also compiles variants without any of these keywords. You can control this behavior in the Graphics settings window.便于对需要的 shader 查找
或是 shader 变体的统计都可以使用下面自己写的工具来辅助加速分析
比如,我们可以 Write All Shader Variant Info Logs
后,会生成一份 csv ,然后使用 excel 打开,对 变体数量那列降序排序即可发现那个 shader 变体过多,但是这个 shader variant count 的统计感觉不太对,和 unity build asset bundle 后的变体数量差异有点大,应该是 unity API 的 BUG
// jave.lin 2022/12/19 shader 查找器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
public class ShaderFinderTools : EditorWindow
{
enum eConditionType
{
Single,
MultiAnd,
MultiOr,
}
enum eShaderFindType
{
ShaderName,
ShaderAssetName,
ShaderVariantCount,
}
enum eShaderVariantCountCompare
{
Equal,
Less,
LessEqual,
Greater,
GreaterEqual,
NotEqual,
}
[MenuItem("实用工具/Shader/Shader Finder")]
private static void _Show()
{
var win = EditorWindow.GetWindow<ShaderFinderTools>();
win.titleContent = new GUIContent("Shader Finder");
win.Init();
win.Show();
}
private void Init()
{
}
private eConditionType conditionType;
private eShaderFindType findType;
private string shader_name;
private string shader_asset_name;
private UInt64 shader_variant_count;
private eShaderVariantCountCompare variantCountCompare;
private bool usedBySceneOnly;
private string logsPath = "ShaderVariantInfoLogs.csv";
private Vector2 shaderVariantRectPos;
private bool showAllRet;
private int pageSize;
private int pageIndex;
private List<Shader> shader_filter_list_helper1 = new List<Shader>();
private List<Shader> shader_filter_list_helper2 = new List<Shader>();
private void OnGUI()
{
if (shader_filter_list_helper1 == null) shader_filter_list_helper1 = new List<Shader>();
if (shader_filter_list_helper2 == null) shader_filter_list_helper2 = new List<Shader>();
conditionType = (eConditionType)EditorGUILayout.EnumPopup("Condition Type", conditionType);
if (conditionType == eConditionType.Single)
{
findType = (eShaderFindType)EditorGUILayout.EnumPopup("Find Type", findType);
switch (findType)
{
case eShaderFindType.ShaderName:
shader_name = EditorGUILayout.TextField("Shader Name", shader_name);
break;
case eShaderFindType.ShaderAssetName:
shader_asset_name = EditorGUILayout.TextField("Shader Asset Name", shader_asset_name);
break;
case eShaderFindType.ShaderVariantCount:
shader_variant_count = (UInt64)EditorGUILayout.IntField("Shader Variant Count", (int)shader_variant_count);
variantCountCompare = (eShaderVariantCountCompare)EditorGUILayout.EnumPopup("Variant Count Compare", variantCountCompare);
usedBySceneOnly = EditorGUILayout.Toggle("Used By Scene Only", usedBySceneOnly);
break;
default:
break;
}
}
else
{
shader_name = EditorGUILayout.TextField("Shader Name", shader_name);
shader_asset_name = EditorGUILayout.TextField("Shader Asset Name", shader_asset_name);
shader_variant_count = (UInt64)EditorGUILayout.IntField("Shader Variant Count", (int)shader_variant_count);
variantCountCompare = (eShaderVariantCountCompare)EditorGUILayout.EnumPopup("Variant Count Compare", variantCountCompare);
usedBySceneOnly = EditorGUILayout.Toggle("Used By Scene Only", usedBySceneOnly);
}
if (GUILayout.Button("Find"))
{
shader_filter_list_helper1.Clear();
if (conditionType == eConditionType.Single)
{
switch (findType)
{
case eShaderFindType.ShaderName:
FilterByShaderName(shader_name, shader_filter_list_helper1);
break;
case eShaderFindType.ShaderAssetName:
FilterByShaderAssetName(shader_asset_name, shader_filter_list_helper1);
break;
case eShaderFindType.ShaderVariantCount:
FilterByShaderVariantCount(shader_variant_count, usedBySceneOnly, variantCountCompare, shader_filter_list_helper1);
break;
default:
break;
}
}
else if (conditionType == eConditionType.MultiOr)
{
FilterByShaderName(shader_name, shader_filter_list_helper1);
FilterByShaderAssetName(shader_asset_name, shader_filter_list_helper1);
FilterByShaderVariantCount(shader_variant_count, usedBySceneOnly, variantCountCompare, shader_filter_list_helper1);
}
else
{
FilterByMultiAnd(
shader_name,
shader_asset_name,
shader_variant_count, usedBySceneOnly, variantCountCompare,
shader_filter_list_helper1
);
}
shader_filter_list_helper2.Clear();
shader_filter_list_helper2.AddRange(shader_filter_list_helper1.Distinct());
}
EditorGUILayout.LabelField(new string('-', 200));
if (GUILayout.Button("Write All Shader Variant Info Logs"))
{
WriteAllShaderVariantInfoLogs();
}
if (GUILayout.Button("Open Shader Variant Info Logs Path"))
{
OpenAllShaderVariantInfoLogsPath();
}
EditorGUILayout.LabelField(new string('-', 200));
{
var srcEnabled = GUI.enabled;
GUI.enabled = false;
EditorGUILayout.LabelField($"Result Count : {shader_filter_list_helper2.Count}");
GUI.enabled = srcEnabled;
showAllRet = EditorGUILayout.Toggle("Show All", showAllRet);
if (!showAllRet)
{
pageSize = EditorGUILayout.IntSlider("Page Size", pageSize, 2, 100);
var pageTotal = (int)Mathf.Ceil((float)shader_filter_list_helper2.Count / pageSize);
if (pageTotal == 1)
{
GUI.enabled = false;
}
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("<", GUILayout.Width(30))) pageIndex--;
var textField_style = EditorStyles.textField;
var textField_style_srcAlign = textField_style.alignment;
textField_style.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.TextField(pageIndex.ToString(), EditorStyles.textField, GUILayout.Width(40));
textField_style.alignment = textField_style_srcAlign;
if (GUILayout.Button(">", GUILayout.Width(30))) pageIndex++;
EditorGUILayout.EndHorizontal();
pageIndex = (int)Mathf.Clamp(pageIndex, 0, pageTotal);
pageIndex = EditorGUILayout.IntSlider("Page Index", pageIndex, 0, pageTotal);
GUI.enabled = srcEnabled;
}
}
{
EditorGUILayout.LabelField(new string('-', 200));
shaderVariantRectPos = EditorGUILayout.BeginScrollView(shaderVariantRectPos);
if (showAllRet)
{
for (int i = 0; i < shader_filter_list_helper2.Count; i++)
{
EditorGUILayout.ObjectField("Shader:", shader_filter_list_helper2[i], typeof(Shader), false);
}
}
else
{
var start = pageIndex * pageSize;
var end = (int)(Mathf.Min(start + pageSize, shader_filter_list_helper2.Count));
for (int i = start; i < end; i++)
{
EditorGUILayout.ObjectField("Shader:", shader_filter_list_helper2[i], typeof(Shader), false);
}
}
EditorGUILayout.EndScrollView();
}
}
private List<string> FilterAllShaderGUIDs()
{
var ret = new List<string>();
var guids = AssetDatabase.FindAssets("t:shader");
//var guids1 = AssetDatabase.FindAssets("t:shader", new string[] { "Assets" });
jave.lin : Referring to package paths : "Packages//..."
jave.lin : ref : https://docs.unity3d.com/Manual/upm-assets.html
//var guids2 = AssetDatabase.FindAssets("t:shader", new string[] { "Packges/com.unity.postprocessing" });
//ret.AddRange(guids1);
//ret.AddRange(guids2);
ret.AddRange(guids);
return ret;
}
private void FilterByMultiAnd(
string target_shader_name,
string target_shader_asset_name,
UInt64 count, bool usedBySceneOnly, eShaderVariantCountCompare variantCountCompare,
List<Shader> shader_filter_list)
{
var method = typeof(ShaderUtil).GetMethod("GetVariantCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
target_shader_name = target_shader_name.ToLower();
target_shader_asset_name = target_shader_asset_name.ToLower();
var guidlist = FilterAllShaderGUIDs();
foreach (var guid in guidlist)
{
var shaderPath = AssetDatabase.GUIDToAssetPath(guid);
var shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
if (shader.name.ToLower().Contains(target_shader_name) &&
shaderPath.ToLower().Contains(target_shader_asset_name)
)
{
var variantCount = (UInt64)method.Invoke(null, new System.Object[] { shader, true });
var add = false;
switch (variantCountCompare)
{
case eShaderVariantCountCompare.Equal:
add = variantCount == count;
break;
case eShaderVariantCountCompare.Less:
add = variantCount < count;
break;
case eShaderVariantCountCompare.LessEqual:
add = variantCount <= count;
break;
case eShaderVariantCountCompare.Greater:
add = variantCount > count;
break;
case eShaderVariantCountCompare.GreaterEqual:
add = variantCount >= count;
break;
case eShaderVariantCountCompare.NotEqual:
add = variantCount != count;
break;
default:
break;
}
if (add)
{
shader_filter_list.Add(shader);
}
}
}
}
private void FilterByShaderName(string target_shader_name, List<Shader> shader_filter_list)
{
target_shader_name = target_shader_name.ToLower();
var guidlist = FilterAllShaderGUIDs();
foreach (var guid in guidlist)
{
var shaderPath = AssetDatabase.GUIDToAssetPath(guid);
var shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
if (shader.name.ToLower().Contains(target_shader_name))
{
shader_filter_list.Add(shader);
}
}
}
private void FilterByShaderAssetName(string target_shader_asset_name, List<Shader> shader_filter_list)
{
target_shader_asset_name = target_shader_asset_name.ToLower();
var guidlist = FilterAllShaderGUIDs();
foreach (var guid in guidlist)
{
var shaderPath = AssetDatabase.GUIDToAssetPath(guid);
if (shaderPath.ToLower().Contains(target_shader_asset_name))
{
shader_filter_list.Add(AssetDatabase.LoadAssetAtPath<Shader>(shaderPath));
}
}
}
private void FilterByShaderVariantCount(UInt64 count, bool usedBySceneOnly, eShaderVariantCountCompare variantCountCompare, List<Shader> shader_filter_list)
{
var method = typeof(ShaderUtil).GetMethod("GetVariantCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var guidlist = FilterAllShaderGUIDs();
foreach (var guid in guidlist)
{
var shaderPath = AssetDatabase.GUIDToAssetPath(guid);
var shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
var variantCount = (UInt64)method.Invoke(null, new System.Object[] { shader, true });
var add = false;
switch (variantCountCompare)
{
case eShaderVariantCountCompare.Equal:
add = variantCount == count;
break;
case eShaderVariantCountCompare.Less:
add = variantCount < count;
break;
case eShaderVariantCountCompare.LessEqual:
add = variantCount <= count;
break;
case eShaderVariantCountCompare.Greater:
add = variantCount > count;
break;
case eShaderVariantCountCompare.GreaterEqual:
add = variantCount >= count;
break;
case eShaderVariantCountCompare.NotEqual:
add = variantCount != count;
break;
default:
break;
}
if (add)
{
shader_filter_list.Add(shader);
}
}
}
private void WriteAllShaderVariantInfoLogs()
{
//logsPath
var method = typeof(ShaderUtil).GetMethod("GetVariantCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var sb = new StringBuilder();
var guidlist = FilterAllShaderGUIDs();
foreach (var guid in guidlist)
{
var shaderPath = AssetDatabase.GUIDToAssetPath(guid);
var shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
var variantCount = (UInt64)method.Invoke(null, new System.Object[] { shader, true });
sb.AppendLine($"shaderPath:{shaderPath},shaderName:{shader.name},variantCount:{variantCount}");
}
System.IO.File.WriteAllText(logsPath, sb.ToString());
}
private void OpenAllShaderVariantInfoLogsPath()
{
var selectPath = logsPath.Replace("/", "\\");
System.Diagnostics.Process.Start("explorer.exe", $"/select,{selectPath}");
}
}
这种方式不用 shader variant collection 的方式来收集,因为 shader variant collection 不容易维护
然后多增加变体或是漏了变体
所以我在项目中使用的这种方式:
具体工具代码如下:
// jave.lni 2022/12/28 T2M shader 变体识别包打包工具
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class T2M_Shader_Variant_PackerTools : EditorWindow
{
[MenuItem("实用工具/Shader/T2M shader 变体识别包打包工具")]
private static void _Show()
{
var win = EditorWindow.GetWindow<T2M_Shader_Variant_PackerTools>();
win.titleContent = new GUIContent("T2M shader 变体识别包打包工具");
win.Init();
win.Show();
}
private void Init()
{
}
private void OnGUI()
{
if (GUILayout.Button("打包T2M识别包"))
{
Shader_Variant_Analysic_Pack_Handle();
}
if (GUILayout.Button("清理Ref_Mats纹理"))
{
ClearRefMatsTexs();
}
}
private void TexIfExsitPushToList(Material mat, string tex_name, HashSet<string> ret)
{
var tex = mat.HasProperty(tex_name) ? mat.GetTexture(tex_name) : null;
if (tex != null)
{
var tex_asset_path = AssetDatabase.GetAssetPath(tex);
if (!ret.Contains(tex_asset_path)) ret.Add(tex_asset_path);
}
}
private void TexIfExsitClear(Material mat, string tex_name)
{
if (mat.HasProperty(tex_name))
{
mat.SetTexture(tex_name, null);
}
}
private void Shader_Variant_Analysic_Pack_Handle()
{
var shader_name_hashset = new HashSet<string>();
var shader_asset_path_list = new List<string>();
var shader_tex_name_list = new List<string>();
// 参考生成变体的 material 路径
const string ref_mat_path = "Assets/Art/Shaders/T2M_Ref_Mats";
// variant 包名
const string variant_ab_name = "t2m_variant";
// shader 名字集合
shader_name_hashset.Add("Amazing Assets/Terrain To Mesh/Splatmap");
// shader 文件集合
shader_asset_path_list.Add("Assets/Amazing Assets/Terrain To Mesh/Shaders/Splatmap/Splatmap.shader");
// shader texture name 集合
shader_tex_name_list.Add("_T2M_SplatMap_0");
shader_tex_name_list.Add("_T2M_Layer_0_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_0_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_0_Mask");
shader_tex_name_list.Add("_T2M_Layer_1_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_1_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_1_Mask");
shader_tex_name_list.Add("_T2M_Layer_2_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_2_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_2_Mask");
shader_tex_name_list.Add("_T2M_Layer_3_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_3_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_3_Mask");
shader_tex_name_list.Add("_T2M_SplatMap_1");
shader_tex_name_list.Add("_T2M_Layer_4_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_4_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_4_Mask");
shader_tex_name_list.Add("_T2M_Layer_5_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_5_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_5_Mask");
shader_tex_name_list.Add("_T2M_Layer_6_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_6_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_6_Mask");
shader_tex_name_list.Add("_T2M_Layer_7_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_7_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_7_Mask");
shader_tex_name_list.Add("_T2M_SplatMap_2");
shader_tex_name_list.Add("_T2M_Layer_8_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_8_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_8_Mask");
shader_tex_name_list.Add("_T2M_Layer_9_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_9_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_9_Mask");
shader_tex_name_list.Add("_T2M_Layer_10_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_10_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_10_Mask");
shader_tex_name_list.Add("_T2M_Layer_11_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_11_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_11_Mask");
shader_tex_name_list.Add("_T2M_SplatMap_3");
shader_tex_name_list.Add("_T2M_Layer_12_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_12_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_12_Mask");
shader_tex_name_list.Add("_T2M_Layer_13_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_13_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_13_Mask");
shader_tex_name_list.Add("_T2M_Layer_14_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_14_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_14_Mask");
shader_tex_name_list.Add("_T2M_Layer_15_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_15_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_15_Mask");
shader_tex_name_list.Add("_T2M_SplatMaps2DArray");
shader_tex_name_list.Add("_T2M_DiffuseMaps2DArray");
shader_tex_name_list.Add("_T2M_NormalMaps2DArray");
shader_tex_name_list.Add("_T2M_MaskMaps2DArray");
shader_tex_name_list.Add("_T2M_HolesMap");
shader_tex_name_list.Add("_MainTex");
shader_tex_name_list.Add("_BumpMap");
var tex_asset_path_hashset = new HashSet<string>();
var mat_counter = 0;
var all_mat_guids = AssetDatabase.FindAssets("t:material", new string[] { "Assets" });
var sn = 0;
if (!System.IO.Directory.Exists(ref_mat_path))
{
System.IO.Directory.CreateDirectory(ref_mat_path);
}
try
{
foreach (var mat_guid in all_mat_guids)
{
var mat_asset_path = AssetDatabase.GUIDToAssetPath(mat_guid);
if (mat_asset_path.Contains("Editor") ||
mat_asset_path.StartsWith("Assets/Test/") ||
mat_asset_path.Contains("Ref_Mats")
)
{
continue;
}
var mat = AssetDatabase.LoadAssetAtPath<Material>(mat_asset_path);
// 过滤 shader
if (mat != null && shader_name_hashset.Contains(mat.shader.name))
{
var new_mat_asset_path = $"{ref_mat_path}/ref_mat_{sn++}.mat";
var new_mat = AssetDatabase.LoadAssetAtPath<Material>(new_mat_asset_path);
if (new_mat != null)
{
System.IO.File.Copy(mat_asset_path, new_mat_asset_path, true);
}
else
{
if (!AssetDatabase.CopyAsset(mat_asset_path, new_mat_asset_path))
{
System.IO.File.Copy(mat_asset_path, new_mat_asset_path, true);
}
new_mat = AssetDatabase.LoadAssetAtPath<Material>(new_mat_asset_path);
}
var src_ai = AssetImporter.GetAtPath(mat_asset_path);
var new_ai = AssetImporter.GetAtPath(new_mat_asset_path);
// 设置 src, new 材质 pbr_variant ab
//src_ai.assetBundleName = string.Empty;
new_ai.assetBundleName = variant_ab_name;
mat_counter++;
}
}
}
catch
{
}
Debug.Log($"variant sn : {sn}");
// 清理 材质中的纹理
all_mat_guids = AssetDatabase.FindAssets("t:material", new string[] { ref_mat_path });
foreach (var mat_guid in all_mat_guids)
{
var mat_asset_path = AssetDatabase.GUIDToAssetPath(mat_guid);
var mat = AssetDatabase.LoadAssetAtPath<Material>(mat_asset_path);
foreach (var tex_name in shader_tex_name_list)
{
TexIfExsitClear(mat, tex_name);
//TexIfExsitPushToList(new_mat, tex_name, tex_asset_path_hashset);
}
}
// 设置 shader pbr_variant ab
{
foreach (var item in shader_asset_path_list)
{
var ai = AssetImporter.GetAtPath(item);
if (ai != null) ai.assetBundleName = variant_ab_name;
}
}
//ScalableBufferManager
//QualitySettings.resolutionScalingFixedDPIFactor
//GraphicsSettings.reso
// jave.lin : 先取消掉之前的纹理的 ab 信息
设置 每个 pbr 材质使用到的纹理的 ab
//foreach (var tex_asset_path in tex_asset_path_hashset)
//{
// var ai = AssetImporter.GetAtPath(tex_asset_path);
// if (ai != null)
// {
// //ai.assetBundleName = tex_asset_path.ToLower().Replace("Assets/", "");
// //ai.assetBundleName = tex_asset_path.Substring(tex_asset_path.IndexOf("/") + 1);
// ai.assetBundleName = string.Empty;
// //ai.assetBundleName = tex_asset_path;
// }
//}
AssetDatabase.Refresh();
AssetDatabase.SaveAssets();
Debug.Log($"Variant AB识别包,已处理, shader : {shader_asset_path_list.Count}个, mat : {mat_counter}个, tex : {tex_asset_path_hashset.Count}个");
}
private void ClearRefMatsTexs()
{
var shader_asset_path_list = new List<string>();
var shader_tex_name_list = new List<string>();
// 参考生成变体的 material 路径
const string ref_mat_path = "Assets/Art/Shaders/T2M_Ref_Mats";
// shader texture name 集合
shader_tex_name_list.Add("_T2M_SplatMap_0");
shader_tex_name_list.Add("_T2M_Layer_0_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_0_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_0_Mask");
shader_tex_name_list.Add("_T2M_Layer_1_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_1_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_1_Mask");
shader_tex_name_list.Add("_T2M_Layer_2_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_2_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_2_Mask");
shader_tex_name_list.Add("_T2M_Layer_3_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_3_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_3_Mask");
shader_tex_name_list.Add("_T2M_SplatMap_1");
shader_tex_name_list.Add("_T2M_Layer_4_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_4_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_4_Mask");
shader_tex_name_list.Add("_T2M_Layer_5_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_5_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_5_Mask");
shader_tex_name_list.Add("_T2M_Layer_6_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_6_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_6_Mask");
shader_tex_name_list.Add("_T2M_Layer_7_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_7_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_7_Mask");
shader_tex_name_list.Add("_T2M_SplatMap_2");
shader_tex_name_list.Add("_T2M_Layer_8_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_8_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_8_Mask");
shader_tex_name_list.Add("_T2M_Layer_9_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_9_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_9_Mask");
shader_tex_name_list.Add("_T2M_Layer_10_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_10_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_10_Mask");
shader_tex_name_list.Add("_T2M_Layer_11_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_11_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_11_Mask");
shader_tex_name_list.Add("_T2M_SplatMap_3");
shader_tex_name_list.Add("_T2M_Layer_12_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_12_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_12_Mask");
shader_tex_name_list.Add("_T2M_Layer_13_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_13_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_13_Mask");
shader_tex_name_list.Add("_T2M_Layer_14_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_14_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_14_Mask");
shader_tex_name_list.Add("_T2M_Layer_15_Diffuse");
shader_tex_name_list.Add("_T2M_Layer_15_NormalMap");
shader_tex_name_list.Add("_T2M_Layer_15_Mask");
shader_tex_name_list.Add("_T2M_SplatMaps2DArray");
shader_tex_name_list.Add("_T2M_DiffuseMaps2DArray");
shader_tex_name_list.Add("_T2M_NormalMaps2DArray");
shader_tex_name_list.Add("_T2M_MaskMaps2DArray");
shader_tex_name_list.Add("_T2M_HolesMap");
shader_tex_name_list.Add("_MainTex");
shader_tex_name_list.Add("_BumpMap");
var all_mat_guids = AssetDatabase.FindAssets("t:material", new string[] { ref_mat_path });
foreach (var mat_guid in all_mat_guids)
{
var mat_asset_path = AssetDatabase.GUIDToAssetPath(mat_guid);
var mat = AssetDatabase.LoadAssetAtPath<Material>(mat_asset_path);
foreach (var tex_name in shader_tex_name_list)
{
TexIfExsitClear(mat, tex_name);
//TexIfExsitPushToList(new_mat, tex_name, tex_asset_path_hashset);
}
}
AssetDatabase.Refresh();
AssetDatabase.SaveAssets();
}
}
// jave.lin ref : https://blog.unity.com/technology/stripping-scriptable-shader-variants
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using static UnityEditor.Progress;
//public static class TestBuildAB
//{
// [MenuItem("Assets/BuildABs")]
// public static void _TestBuild()
// {
// var path = "Assets/StreamingAssets/game_assets";
// if (!System.IO.Directory.Exists(path))
// {
// System.IO.Directory.CreateDirectory(path);
// }
// BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.Android);
// }
//}
// Simple example of stripping of a debug build configuration
class ShaderBuildProcessor : IPreprocessShaders
{
private ShaderKeyword[] skipArr = new ShaderKeyword[]
{
new ShaderKeyword("DYNAMICLIGHTMAP_ON"),
//new ShaderKeyword("DIRLIGHTMAP_COMBINED"), // javelin : indirect.diffuse 有使用到
//new ShaderKeyword("LIGHTPROBE_SH"), // jave.lin : reflection sh 需要
//new ShaderKeyword("LIGHTMAP_SHADOW_MIXING"), // jave.lin : distance shadow mask 会使用到
new ShaderKeyword("FOG_EXP"),
new ShaderKeyword("FOG_EXP2"), //new ShaderKeyword("SHADOWS_DEPTH"),
new ShaderKeyword("DEBUG"),
//new ShaderKeyword("SHADOWS_DEPTH"), // jave.lin : 主城水体的效果需要
new ShaderKeyword("SHADOWS_CUBE"),
new ShaderKeyword("SPOT"),
//new ShaderKeyword("POINT"), // jave.lin : 角色实时补光需要使用到
new ShaderKeyword("DIRECTIONAL_COOKIE"),
new ShaderKeyword("POINT_COOKIE"),
};
public ShaderBuildProcessor()
{
}
// Multiple callback may be implemented.
// The first one executed is the one where callbackOrder is returning the smallest number.
public int callbackOrder { get { return 0; } }
private bool ShouldSkipVariant(Shader shader, ShaderCompilerData data)
{
foreach (var item in skipArr)
{
if (data.shaderKeywordSet.IsEnabled(item))
{
//Debug.Log($"shader.name : {shader.name} skip variant:{ShaderKeyword.GetGlobalKeywordName(item)}");
return true;
}
}
if (data.graphicsTier == GraphicsTier.Tier2)
{
//Debug.Log($"shader.name : {shader.name} skip tier 2");
return true;
}
if (data.graphicsTier == GraphicsTier.Tier3)
{
//Debug.Log($"shader.name : {shader.name} skip tier 3");
return true;
}
return false;
}
public void OnProcessShader(
Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> shaderCompilerData)
{
In development, don't strip debug variants
//if (EditorUserBuildSettings.development)
// return;
for (int i = 0; i < shaderCompilerData.Count; ++i)
{
if (ShouldSkipVariant(shader, shaderCompilerData[i]))
{
shaderCompilerData.RemoveAt(i);
--i;
}
}
}
}
角色展示界面内存:1.3GB
其中比较大块的是:
角色展示界面内存:398.4 MB
其中比较大块的是:
特别明显的是 PBR Uber Shader 和 T2M shader 的优化前后
就是之前说的:UBer Shader 拆分为 #define + 少量 #multi_compile 的多份 shader 优化实践结果
比如 Uber Shader 中的某个 Pass
如果在 unity editor 下让美术直接使用这个优化过的 shader ,那么会导致美术所见即所得很不方便,比如:要烘焙后,才能显示正常
因此我们的解决方法是: 美术在平时开发的时候,仍然是使用 UBer Shader
然后在 游戏发布 AssetBundle (AB资源)前,我们将所有的使用 UBer Shader 的材质,判断变体开关情况、目录情况,替换为对应的 拆分的变体,这样,让 2^N 的变体指数增长的变体复杂度大大减低
比如:
BeforeBuildMatShaderHandleTools.ReplaceAllMatShader(); // jave.lin : build ab 前材质的替换
ClearUpMatPropKWTools.ClearAllMat(); // jave.lin : 强制执行一下所有材质瘦身
BuildPipeline.BuildAssetBundles(xxxxx); // jave.lin : build ab
shader 内存将:基本上原来 190 MB 的 UBer ,直接降到: 2~4 MB
容易导致 Unity Editor 下 的渲染和发布后的效果不同,比如:真机上可能需要 Shader.globalMaximumLOD
去切换一下值,才能正常显示 (下面有些解决方法,UNITY 的 BUG 吗?他就不会自动 fallback 到可以低级别的 lod shader 吗?),部分计算会多余浪费掉,比如:强制#define FOG_LINEAR 的shader 比如让 场景的 fog 开启线性雾,否则会有雾效显示异常的情况
比如,出现真机上的这些 BUG:
GLSL link error: The number of fragment samplers (17) is greater than the maximum number allowed (16).
Note: Creation of internal variant of shader 'Amazing Assets/Terrain To Mesh/Splatmap_layertex_5' failed.
这类问题解决方法也简单,将 sampler (纹理采样单元) 数量降低即可解决 (修改 shader,或是有一些效果,该删的删)
这也是为何之前说的 Shader.globalMaximumLOD
切换一下就好了,因为低级别一些的 LOD,使用的 sampler 是没有超出 16 个的
后续补充的踩坑敬仰:Unity - 记一次非正规变体优化带来的兼容性导致部分手机卡死的问题