├── .gitattributes
├── SocoShaderVariantsStripper
├── Images
│ ├── 1.开启界面.png
│ ├── 10.优先级.png
│ ├── 3.全局设置.png
│ ├── 6.添加条件.png
│ ├── 8.修改条件.png
│ ├── 12.剔除检查.png
│ ├── 13.添加条件3.png
│ ├── 2.创建配置文件.png
│ ├── 7.添加条件2.png
│ ├── 4.选择Shader1.png
│ ├── 5.添加Shader2.png
│ ├── 11.读写Json序列化文件.png
│ └── 9.当包含keyword时.png
├── ShaderVariantsStripperCondition.True.cs
├── Editor
│ ├── ShaderVariantsStripperConditionWindow.cs
│ ├── ShaderVariantsStripperConfig.cs
│ ├── ShaderVariantsStripperCode.cs
│ └── ShaderVariantsStripperWindow.cs
├── ShaderVariantsStripperCondition.PassTypeIs.cs
├── ShaderVariantsStripperCondition.cs
├── ShaderVariantsStripperCondition.PassName.cs
├── ShaderVariantsStripperCondition.ShaderName.cs
├── stats_keywords.py
├── ShaderVariantsData.cs
├── README.md
├── ShaderVariantsStripperCondition.AExistAndBNotExist.cs
├── ShaderVariantsStripperCondition.MultiCondition.cs
└── ShaderVariantsStripperCondition.HasKeywordCombination.cs
├── SocoShaderVariantsCollection
├── ..Images
│ ├── 1.开启界面.png
│ ├── 10.排除变体.png
│ ├── 2.功能选择.png
│ ├── 3.快速浏览.png
│ ├── 6.合并文件.png
│ ├── 7.执行器列表.png
│ ├── 9.分割文件.png
│ ├── 4.材质收集器列表.png
│ ├── 5.变体过滤器列表.png
│ └── 8.变体声明组合.png
├── Editor
│ ├── Executable
│ │ ├── IExecutable.cs
│ │ ├── InvalidVariantStrip.cs
│ │ └── VariantKeywordCombination.cs
│ ├── VariantFilter
│ │ ├── IVariantFilter.cs
│ │ ├── VariantFilter_PassStrip.cs
│ │ └── VariantFilter_Shader.cs
│ ├── MaterialCollection
│ │ ├── IMaterialCollection.cs
│ │ ├── MaterialCollection_AssignMaterial.cs
│ │ ├── MaterialCollection_SceneDependency.cs
│ │ └── MaterialCollection_TotalMaterial.cs
│ ├── MaterialFilter
│ │ ├── IMaterialFilter.cs
│ │ └── TestMaterialFilter.cs
│ ├── SerializableShaderVariant.cs
│ ├── ShaderVariantCollectionToolConfig.cs
│ ├── ShaderVariantCollectionAddVariantWindow.cs
│ ├── ShaderVariantCollectionMaterialVariantConverter.cs
│ └── ShaderVariantCollectionMapper.cs
└── README.md
├── ..ProjectFile
├── README.md
└── MaterialCollection_ProjectDependRes.cs
├── ..Associate
├── README.md
└── SocoVariantStripAssociate.cs
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/1.开启界面.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/1.开启界面.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/10.优先级.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/10.优先级.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/3.全局设置.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/3.全局设置.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/6.添加条件.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/6.添加条件.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/8.修改条件.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/8.修改条件.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/12.剔除检查.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/12.剔除检查.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/13.添加条件3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/13.添加条件3.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/2.创建配置文件.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/2.创建配置文件.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/7.添加条件2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/7.添加条件2.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/1.开启界面.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/1.开启界面.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/10.排除变体.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/10.排除变体.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/2.功能选择.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/2.功能选择.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/3.快速浏览.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/3.快速浏览.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/6.合并文件.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/6.合并文件.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/7.执行器列表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/7.执行器列表.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/9.分割文件.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/9.分割文件.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/4.选择Shader1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/4.选择Shader1.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/5.添加Shader2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/5.添加Shader2.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/4.材质收集器列表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/4.材质收集器列表.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/5.变体过滤器列表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/5.变体过滤器列表.png
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/..Images/8.变体声明组合.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsCollection/..Images/8.变体声明组合.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/11.读写Json序列化文件.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/11.读写Json序列化文件.png
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Images/9.当包含keyword时.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossous/SocoTools/HEAD/SocoShaderVariantsStripper/Images/9.当包含keyword时.png
--------------------------------------------------------------------------------
/..ProjectFile/README.md:
--------------------------------------------------------------------------------
1 | # 项目文件
2 |
3 | 这个文件夹内,存放项目依赖相关脚本,直接使用肯定会报错,请按照项目需求修改脚本,或直接删除脚本。
4 | 当前项目依赖组件包括:
5 | > 1. MaterialCollection_ProjectDependRes: 变体收集工具中,获取项目资源表依赖
--------------------------------------------------------------------------------
/..Associate/README.md:
--------------------------------------------------------------------------------
1 | # 联动组件
2 |
3 | 这个文件夹内,存放多个SocoTools工具的联动功能,如果只使用了部分工具,可能会导致这里的脚本报错,请添加被引用的工具,或删除这个脚本。
4 | 当前联动组件包括:
5 | > 1. SocoVariantStripAssociate: 变体收集与剔除工具的联动脚本,可在收集工具中运行变体剔除判断。
6 |
7 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/Executable/IExecutable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace Soco.ShaderVariantsCollection
6 | {
7 | public abstract class IExecutable : ScriptableObject
8 | {
9 | public abstract void Execute(ShaderVariantCollectionMapper mapper);
10 | }
11 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/VariantFilter/IVariantFilter.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace Soco.ShaderVariantsCollection
4 | {
5 | public abstract class IVariantFilter : ScriptableObject
6 | {
7 | //return true will save and false will strip
8 | public abstract bool Filter(ShaderVariantCollection.ShaderVariant variant);
9 | }
10 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/MaterialCollection/IMaterialCollection.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 |
4 | namespace Soco.ShaderVariantsCollection
5 | {
6 | public abstract class IMaterialCollector : ScriptableObject
7 | {
8 | public abstract void AddMaterialBuildDependency(IList buildDependencyList);
9 | }
10 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/MaterialFilter/IMaterialFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 |
4 | namespace Soco.ShaderVariantsCollection
5 | {
6 | public abstract class IMaterialFilter : ScriptableObject
7 | {
8 | //return true will save and false will strip
9 | public abstract bool Filter(Material material, List collections);
10 | }
11 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SocoTools
2 |
3 | 个人开发的Unity工具,提供部分需求的通用解决方案
4 |
5 | 当前包括:
6 |
7 | 1. 变体剔除工具[SocoShaderVariantsStripper](https://github.com/crossous/SocoTools/tree/main/SocoShaderVariantsStripper)
8 | 2. 变体收集工具[SocoShaderVariantsCollection](https://github.com/crossous/SocoTools/tree/main/SocoShaderVariantsCollection)
9 |
10 | 除此外,Associate目录包含工具之间的联动组件,这些组件导入工程需保证相关工具已被导入工程; ProjectFile目录包含项目依赖文件,是提供项目依赖逻辑的简单框架,导入项目要根据需要改写或删除脚本。
11 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/MaterialFilter/TestMaterialFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 |
4 | namespace Soco.ShaderVariantsCollection
5 | {
6 | public sealed class TestMaterialFilter : IMaterialFilter
7 | {
8 | public override bool Filter(Material material, List collections)
9 | {
10 | //nothing to do
11 | return true;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/VariantFilter/VariantFilter_PassStrip.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Linq;
3 | using UnityEngine.Rendering;
4 |
5 | namespace Soco.ShaderVariantsCollection
6 | {
7 | public class VariantFilter_PassStrip : IVariantFilter
8 | {
9 | public PassType[] mStripPasses = new PassType[0];
10 |
11 | public override bool Filter(ShaderVariantCollection.ShaderVariant variant)
12 | {
13 | return !mStripPasses.Contains(variant.passType);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/MaterialCollection/MaterialCollection_AssignMaterial.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using UnityEngine;
4 |
5 | namespace Soco.ShaderVariantsCollection
6 | {
7 | // 这个是指定具体某些材质收集进来
8 | // 一般用于Debug
9 | public class MaterialCollection_AssignMaterial : IMaterialCollector
10 | {
11 | public Material[] materials = new Material[0];
12 |
13 | public override void AddMaterialBuildDependency(IList buildDependencyList)
14 | {
15 | materials.ToList().ForEach(buildDependencyList.Add);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/MaterialCollection/MaterialCollection_SceneDependency.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace Soco.ShaderVariantsCollection
7 | {
8 | //用于收集所有打包场景依赖的材质
9 | public class MaterialCollection_SceneDependency : IMaterialCollector
10 | {
11 | //是否只收集在EditorBuildSettings中enable的场景
12 | public bool collectOnlyEnable = true;
13 | public override void AddMaterialBuildDependency(IList buildDependencyList)
14 | {
15 | var sceneDependencyMaterials = EditorBuildSettings.scenes
16 | .Where(scene => !collectOnlyEnable || scene.enabled)
17 | .SelectMany(scene => AssetDatabase.GetDependencies(scene.path))
18 | .Where(dependencyAsset => dependencyAsset.EndsWith(".mat"))
19 | .Distinct()
20 | .Select(matPath => AssetDatabase.LoadAssetAtPath(matPath));
21 |
22 | foreach (Material m in sceneDependencyMaterials)
23 | buildDependencyList.Add(m);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.True.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEngine.Rendering;
3 | using UnityEditor;
4 | using UnityEngine.Scripting.APIUpdating;
5 |
6 | namespace Soco.ShaderVariantsStripper
7 | {
8 | public class ShaderVariantsStripperConditionTrue : ShaderVariantsStripperCondition
9 | {
10 | public bool Completion(Shader shader, ShaderVariantsData data)
11 | {
12 | return true;
13 | }
14 |
15 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
16 | {
17 | return true;
18 | }
19 |
20 | #if UNITY_EDITOR
21 | public string Overview()
22 | {
23 | return "恒定满足条件";
24 | }
25 |
26 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
27 | {
28 | EditorGUILayout.BeginVertical();
29 |
30 | EditorGUILayout.LabelField("恒定满足条件");
31 |
32 | EditorGUILayout.EndVertical();
33 | }
34 |
35 | public string GetName()
36 | {
37 | return "恒定满足条件";
38 | }
39 | #endif
40 | }
41 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Editor/ShaderVariantsStripperConditionWindow.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace Soco.ShaderVariantsStripper
7 | {
8 | public class ShaderVariantsStripperConditionWindow : EditorWindow
9 | {
10 | private static ShaderVariantsStripperConditionWindow m_window;
11 | public static ShaderVariantsStripperConditionWindow Window
12 | {
13 | get
14 | {
15 | if (m_window == null)
16 | {
17 | m_window = EditorWindow.GetWindow("ShaderVariantsStripperConditionWindow");
18 | m_window.minSize = new Vector2(480, 320);
19 | }
20 | return m_window;
21 | }
22 | }
23 |
24 | public ShaderVariantsStripperCondition mCondition;
25 | public ShaderVariantsStripperConditionOnGUIContext mContext;
26 |
27 | public void OnGUI()
28 | {
29 | if (mCondition != null)
30 | {
31 | //mContext.window = this;
32 | mCondition.OnGUI(mContext);
33 | }
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/SerializableShaderVariant.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 | using UnityEngine.Rendering;
4 |
5 | namespace Soco.ShaderVariantsCollection
6 | {
7 | [Serializable]
8 | public struct SerializableShaderVariant
9 | {
10 | public Shader shader;
11 | public PassType passType;
12 | public string[] keywords;
13 |
14 | public SerializableShaderVariant(ShaderVariantCollection.ShaderVariant variant)
15 | {
16 | shader = variant.shader;
17 | passType = variant.passType;
18 | keywords = variant.keywords;
19 | }
20 |
21 | public ShaderVariantCollection.ShaderVariant Deserialize()
22 | {
23 | //这样初始化的原因是,假如变体无效不会报错
24 | return new ShaderVariantCollection.ShaderVariant()
25 | {
26 | shader = shader,
27 | passType = passType,
28 | keywords = keywords
29 | };
30 | }
31 |
32 | public bool IsValid()
33 | {
34 | try
35 | {
36 | new ShaderVariantCollection.ShaderVariant(shader, passType, keywords);;
37 | return true;
38 | }
39 | catch (Exception e)
40 | {
41 | return false;
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.PassTypeIs.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEngine.Rendering;
3 | using UnityEditor;
4 |
5 | namespace Soco.ShaderVariantsStripper
6 | {
7 | public class ShaderVariantsStripperConditionPassTypeIs : ShaderVariantsStripperCondition
8 | {
9 | public PassType passType = PassType.Normal;
10 |
11 | public bool Completion(Shader shader, ShaderVariantsData data)
12 | {
13 | return data.passType == passType;
14 | }
15 |
16 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
17 | {
18 | return other.GetType() == typeof(ShaderVariantsStripperConditionPassTypeIs) &&
19 | (other as ShaderVariantsStripperConditionPassTypeIs).passType == this.passType;
20 | }
21 |
22 | #if UNITY_EDITOR
23 | public string Overview()
24 | {
25 | return $"当Pass类型是{passType}时";
26 | }
27 |
28 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
29 | {
30 | EditorGUILayout.BeginVertical();
31 |
32 | passType = (PassType)EditorGUILayout.EnumPopup("PassType", passType);
33 |
34 | EditorGUILayout.EndVertical();
35 | }
36 |
37 | public string GetName()
38 | {
39 | return "PassType是";
40 | }
41 | #endif
42 | }
43 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/MaterialCollection/MaterialCollection_TotalMaterial.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace Soco.ShaderVariantsCollection
7 | {
8 | //这是一个编写示例
9 | //将收集目录下所有材质
10 | //实际项目不建议这样做
11 | public sealed class MaterialCollection_TotalMaterial : IMaterialCollector
12 | {
13 | public enum PathMode
14 | {
15 | Asset,
16 | String
17 | }
18 | //private readonly string[] cIncludePath = { "Assets", "Packages" };
19 | [Tooltip("路径选择模式,以文件夹资产的方式,或以字符串路径")]
20 | public PathMode pathMode = PathMode.String;
21 | public string[] mIncludePath = { "Assets", "Packages" };
22 | public DefaultAsset[] mFolders = new DefaultAsset[0];
23 |
24 | public override void AddMaterialBuildDependency(IList buildDependencyList)
25 | {
26 | string[] includePath = mFolders.Select(f => AssetDatabase.GetAssetPath(f)).ToArray();
27 | includePath = pathMode == PathMode.Asset ? includePath : mIncludePath;
28 | var materialsGUID = AssetDatabase.FindAssets("t:Material", includePath);
29 |
30 | foreach (Material m in materialsGUID.Select(
31 | guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))
32 | ))
33 | {
34 | buildDependencyList.Add(m);
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Editor/ShaderVariantsStripperConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace Soco.ShaderVariantsStripper
6 | {
7 | [System.Serializable]
8 | public struct ConditionPair
9 | {
10 | [SerializeReference]
11 | public ShaderVariantsStripperCondition condition;
12 | public bool strip;
13 | public uint priority;
14 | }
15 |
16 | [System.Serializable]
17 | public class ShaderVariantsItem
18 | {
19 | public bool applyGlobalConfig;
20 | public List conditionPairs;
21 | }
22 |
23 | //可序列化字典
24 | [System.Serializable]
25 | public class ShaderConditionsDictionary : Dictionary, ISerializationCallbackReceiver
26 | {
27 | [SerializeField] private List _keys = new List();
28 | [SerializeField] private List _values = new List();
29 |
30 | public void OnBeforeSerialize()
31 | {
32 | _keys.Clear();
33 | _values.Clear();
34 |
35 | foreach (var kvp in this)
36 | {
37 | _keys.Add(kvp.Key);
38 | _values.Add(kvp.Value);
39 | }
40 | }
41 |
42 | public void OnAfterDeserialize()
43 | {
44 | Clear();
45 |
46 | for (var i = 0; i != Mathf.Min(_keys.Count, _values.Count); i++)
47 | {
48 | Add(_keys[i], _values[i]);
49 | }
50 | }
51 | }
52 |
53 | [CreateAssetMenu(menuName = "Soco/ShaderVariantsStripper/Create Config")]
54 | public class ShaderVariantsStripperConfig : ScriptableObject
55 | {
56 | public bool mEnable = true;
57 | public bool mIsWhiteList = false;
58 |
59 | public List mGlobalConditions = new List();
60 | public ShaderConditionsDictionary mShaderConditions = new ShaderConditionsDictionary();
61 | }
62 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using UnityEditor;
5 | using UnityEngine;
6 | using UnityEngine.Rendering;
7 |
8 | namespace Soco.ShaderVariantsStripper
9 | {
10 | #if UNITY_EDITOR
11 | public struct ShaderVariantsStripperConditionOnGUIContext
12 | {
13 | public Shader shader;
14 |
15 |
16 | //ShaderVariantsStripperConditionWindow window;
17 | internal string[] globalKeywords;
18 | internal string[] localKeywords;
19 |
20 | public void Init()
21 | {
22 | if (shader != null)
23 | {
24 | InitGetKeywordMethod();
25 |
26 | globalKeywords = sGetShaderGlobalKeywordsMethod.Invoke(null, new object[] { shader }) as string[];
27 | localKeywords = sGetShaderLocalKeywordsMethod.Invoke(null, new object[] { shader }) as string[];
28 | }
29 | }
30 |
31 | private static MethodInfo sGetShaderGlobalKeywordsMethod = null;
32 | private static MethodInfo sGetShaderLocalKeywordsMethod = null;
33 |
34 | private static void InitGetKeywordMethod()
35 | {
36 | if (sGetShaderGlobalKeywordsMethod == null)
37 | sGetShaderGlobalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords",
38 | BindingFlags.NonPublic | BindingFlags.Static);
39 |
40 | if (sGetShaderLocalKeywordsMethod == null)
41 | sGetShaderLocalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords",
42 | BindingFlags.NonPublic | BindingFlags.Static);
43 | }
44 | }
45 | #endif
46 |
47 | public interface ShaderVariantsStripperCondition
48 | {
49 | bool Completion(Shader shader, ShaderVariantsData data);
50 |
51 | bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData);
52 |
53 | #if UNITY_EDITOR
54 | string Overview();
55 | void OnGUI(ShaderVariantsStripperConditionOnGUIContext context);
56 | string GetName();
57 | #endif
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/Executable/InvalidVariantStrip.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace Soco.ShaderVariantsCollection
7 | {
8 | public class InvalidVariantStrip : IExecutable
9 | {
10 | public override void Execute(ShaderVariantCollectionMapper mapper)
11 | {
12 | var collection = mapper.mCollection;
13 |
14 | SerializedObject serializedObject = new UnityEditor.SerializedObject(collection);
15 | //serializedObject.Update();
16 | SerializedProperty m_Shaders = serializedObject.FindProperty("m_Shaders");
17 |
18 | for (int i = 0; i < m_Shaders.arraySize; ++i)
19 | {
20 | SerializedProperty pair = m_Shaders.GetArrayElementAtIndex(i);
21 |
22 | SerializedProperty first = pair.FindPropertyRelative("first");
23 | SerializedProperty second = pair.FindPropertyRelative("second");//ShaderInfo
24 |
25 | Shader shader = first.objectReferenceValue as Shader;
26 |
27 | if (shader == null)
28 | continue;
29 |
30 | SerializedProperty variants = second.FindPropertyRelative("variants");
31 | for (var vi = variants.arraySize - 1; vi >= 0 ; --vi)
32 | {
33 | SerializedProperty variantInfo = variants.GetArrayElementAtIndex(vi);
34 | ShaderVariantCollection.ShaderVariant variant = ShaderVariantCollectionMapper.PropToVariantObject(shader, variantInfo);
35 |
36 | if (!IsValid(variant))
37 | variants.DeleteArrayElementAtIndex(vi);
38 | }
39 | }
40 |
41 | serializedObject.ApplyModifiedProperties();
42 | mapper.Refresh();
43 |
44 | }
45 |
46 | private bool IsValid(ShaderVariantCollection.ShaderVariant variant)
47 | {
48 | try
49 | {
50 | new ShaderVariantCollection.ShaderVariant(variant.shader, variant.passType, variant.keywords);
51 | return true;
52 | }
53 | catch
54 | {
55 | return false;
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/..ProjectFile/MaterialCollection_ProjectDependRes.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Assets.Code.Editor.Res;
5 | using Newtonsoft.Json.Utilities;
6 | using UnityEditor;
7 | using UnityEngine;
8 |
9 | public sealed class MaterialCollection_ProjectDependRes : Soco.ShaderVariantsCollection.IMaterialCollector
10 | {
11 | public override void AddMaterialBuildDependency(IList buildDependencyList)
12 | {
13 | //获取资源表中所有资源
14 | var resList = ResConfigFileEnter.GetConfigFile();
15 |
16 | int resIndex = 0;
17 | foreach (string res in resList)
18 | {
19 | EditorUtility.DisplayProgressBar("材质收集", $"正在收集直接资源",
20 | (float)resIndex++ / (float)resList.Count);
21 | //如果资源本身是材质,则直接添加到列表中
22 | if (res.EndsWith(".mat"))
23 | {
24 | Material mat = AssetDatabase.LoadAssetAtPath(res);
25 | if(mat != null)
26 | buildDependencyList.Add(mat);
27 | }
28 | //如果不是材质,则找到资源所引用的材质添加到列表中
29 | //这样会导致大量申请小数组的GC,因此用下面的API
30 | // else
31 | // {
32 | // foreach (string depRes in AssetDatabase.GetDependencies(res))
33 | // {
34 | // if (depRes.EndsWith(".mat"))
35 | // {
36 | // Material mat = AssetDatabase.LoadAssetAtPath(depRes);
37 | // if(mat != null)
38 | // buildDependencyList.Add(mat);
39 | // }
40 | // }
41 | // }
42 | }
43 |
44 | //获取资源间接引用的资源
45 | EditorUtility.DisplayProgressBar("材质收集", "正在获取间接资源,这可能需要一段时间", 0);
46 | resIndex = 0;
47 | var indirectDependRes = AssetDatabase.GetDependencies(resList.ToArray());
48 |
49 | foreach (string res in indirectDependRes)
50 | {
51 | EditorUtility.DisplayProgressBar("材质收集", $"正在收集间接依赖资源",
52 | (float)resIndex++ / (float)indirectDependRes.Length);
53 | if (res.EndsWith(".mat"))
54 | {
55 | Material mat = AssetDatabase.LoadAssetAtPath(res);
56 | if(mat != null)
57 | buildDependencyList.Add(mat);
58 | }
59 | }
60 |
61 | EditorUtility.ClearProgressBar();
62 | }
63 | }
--------------------------------------------------------------------------------
/..Associate/SocoVariantStripAssociate.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using Soco.ShaderVariantsCollection;
4 | using UnityEditor;
5 | using UnityEditor.Rendering;
6 | using UnityEngine.Rendering;
7 |
8 | // 用于联动变体收集 和 变体剔除工具
9 | // 如果没有使用Soco变体剔除工具,此执行器应删除
10 | public class SocoVariantStripAssociate : Soco.ShaderVariantsCollection.IExecutable
11 | {
12 | public override void Execute(ShaderVariantCollectionMapper mapper)
13 | {
14 | var m_ShaderType = typeof(ShaderSnippetData).GetField("m_ShaderType", BindingFlags.Instance | BindingFlags.NonPublic);
15 | var m_PassType = typeof(ShaderSnippetData).GetField("m_PassType", BindingFlags.Instance | BindingFlags.NonPublic);
16 | var m_PassName = typeof(ShaderSnippetData).GetField("m_PassName", BindingFlags.Instance | BindingFlags.NonPublic);
17 |
18 | var stripConfigs =
19 | com.cyou.soco.ShaderVariantsStripper.ShaderVariantsStripperCode.LoadConfigs();
20 |
21 | foreach (var shader in mapper.shaders)
22 | {
23 | int variantIndex = 0;
24 | var variants = mapper.GetShaderVariants(shader).ToArray();
25 | foreach (var variant in variants)
26 | {
27 | EditorUtility.DisplayProgressBar("变体剔除", $"正在处理{shader.name} {variantIndex}/{variants.Length}变体",
28 | (float)variantIndex / (float)variants.Length);
29 | variantIndex++;
30 |
31 | ShaderSnippetData snipperData = new ShaderSnippetData();
32 |
33 | //这里没对RayTracing、Mesh Shader的剔除做相关处理,默认所有Shader Pass都有VertexShader
34 | m_ShaderType.SetValueDirect(__makeref(snipperData), ShaderType.Vertex);
35 | m_PassType.SetValueDirect(__makeref(snipperData), variant.passType);
36 | m_PassName.SetValueDirect(__makeref(snipperData), "");
37 |
38 | ShaderCompilerData data = new ShaderCompilerData();
39 | data.shaderKeywordSet = new ShaderKeywordSet();
40 |
41 | foreach (var keyword in variant.keywords)
42 | {
43 | data.shaderKeywordSet.Enable(new ShaderKeyword(keyword));
44 | }
45 |
46 | if (com.cyou.soco.ShaderVariantsStripper.ShaderVariantsStripperCode.IsVariantStrip(
47 | shader, snipperData, data, stripConfigs))
48 | {
49 | mapper.RemoveVariant(variant);
50 | }
51 | }
52 | }
53 |
54 | EditorUtility.ClearProgressBar();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.PassName.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 | using UnityEngine;
3 | using UnityEngine.Rendering;
4 | using UnityEditor;
5 | using UnityEngine.Scripting.APIUpdating;
6 |
7 | namespace Soco.ShaderVariantsStripper
8 | {
9 | public class ShaderVariantsStripperConditionPassName : ShaderVariantsStripperCondition
10 | {
11 | public enum MatchMode
12 | {
13 | AllMatch,
14 | StartsWith,
15 | EndsWith,
16 | Regex
17 | }
18 |
19 | public MatchMode mode;
20 | public string passName = "";
21 | public RegexOptions regexOptions;
22 |
23 | public bool Completion(Shader shader, ShaderVariantsData data)
24 | {
25 | switch (mode)
26 | {
27 | case MatchMode.AllMatch:
28 | return data.passName == passName;
29 | case MatchMode.StartsWith:
30 | return data.passName.StartsWith(passName);
31 | case MatchMode.EndsWith:
32 | return data.passName.EndsWith(passName);
33 | case MatchMode.Regex:
34 | return Regex.IsMatch(data.passName, passName, regexOptions);
35 | }
36 |
37 | return false;
38 | }
39 |
40 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
41 | {
42 | return other.GetType() == typeof(ShaderVariantsStripperConditionPassName) &&
43 | (other as ShaderVariantsStripperConditionPassName).mode == this.mode &&
44 | (other as ShaderVariantsStripperConditionPassName).passName == this.passName &&
45 | (this.mode != MatchMode.Regex || (other as ShaderVariantsStripperConditionPassName).regexOptions == this.regexOptions);
46 | }
47 |
48 | #if UNITY_EDITOR
49 |
50 | public string Overview()
51 | {
52 | return $"当Pass名称或模式<{passName}>符合条件<{mode}>时";
53 | }
54 |
55 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
56 | {
57 | EditorGUILayout.BeginVertical();
58 |
59 | mode = (MatchMode)EditorGUILayout.EnumPopup("MatchMode", mode);
60 |
61 | string label = mode == MatchMode.Regex ? "pattern" : "pass name";
62 | passName = EditorGUILayout.TextField(label, passName);
63 |
64 | if (mode == MatchMode.Regex)
65 | regexOptions = (RegexOptions)EditorGUILayout.EnumFlagsField("Regex Options", regexOptions);
66 |
67 | EditorGUILayout.EndVertical();
68 | }
69 |
70 | public string GetName()
71 | {
72 | return "Pass名称匹配";
73 | }
74 | #endif
75 | }
76 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.ShaderName.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 | using UnityEngine;
3 | using UnityEngine.Rendering;
4 | using UnityEditor;
5 | using UnityEngine.Scripting.APIUpdating;
6 |
7 | namespace Soco.ShaderVariantsStripper
8 | {
9 | public class ShaderVariantsStripperConditionShaderName : ShaderVariantsStripperCondition
10 | {
11 | public enum MatchMode
12 | {
13 | AllMatch,
14 | StartsWith,
15 | EndsWith,
16 | Regex
17 | }
18 |
19 | public MatchMode mode;
20 | public string shaderName;
21 | public RegexOptions regexOptions;
22 |
23 | public bool Completion(Shader shader, ShaderVariantsData data)
24 | {
25 | switch (mode)
26 | {
27 | case MatchMode.AllMatch:
28 | return shader.name == shaderName;
29 | case MatchMode.StartsWith:
30 | return shader.name.StartsWith(shaderName);
31 | case MatchMode.EndsWith:
32 | return shader.name.EndsWith(shaderName);
33 | case MatchMode.Regex:
34 | return Regex.IsMatch(shader.name, shaderName);
35 | }
36 |
37 | return false;
38 | }
39 |
40 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
41 | {
42 | return other.GetType() == typeof(ShaderVariantsStripperConditionShaderName) &&
43 | (other as ShaderVariantsStripperConditionShaderName).mode == this.mode &&
44 | (other as ShaderVariantsStripperConditionShaderName).shaderName == this.shaderName &&
45 | (this.mode != MatchMode.Regex || (other as ShaderVariantsStripperConditionShaderName).regexOptions == this.regexOptions);
46 | }
47 |
48 | #if UNITY_EDITOR
49 |
50 | public string Overview()
51 | {
52 | return $"当Shader名称或模式<{shaderName}>符合条件<{mode}>时";
53 | }
54 |
55 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
56 | {
57 | EditorGUILayout.BeginVertical();
58 |
59 | mode = (MatchMode)EditorGUILayout.EnumPopup("MatchMode", mode);
60 |
61 | string label = mode == MatchMode.Regex ? "pattern" : "shader name";
62 | shaderName = EditorGUILayout.TextField(label, shaderName);
63 |
64 | if (mode == MatchMode.Regex)
65 | regexOptions = (RegexOptions)EditorGUILayout.EnumFlagsField("Regex Options", regexOptions);
66 |
67 | EditorGUILayout.EndVertical();
68 | }
69 |
70 | public string GetName()
71 | {
72 | return "Shader名称匹配";
73 | }
74 | #endif
75 | }
76 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/ShaderVariantCollectionToolConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace Soco.ShaderVariantsCollection
7 | {
8 | [System.Serializable]
9 | public class ToggleObject
10 | {
11 | [SerializeReference]
12 | public ScriptableObject obj;
13 | public bool use;
14 | }
15 |
16 | //解决嵌套list序列化问题
17 | [System.Serializable]
18 | public struct ListWrapper
19 | {
20 | public List list;
21 | public ListWrapper(List list) => this.list = list;
22 | }
23 |
24 | [CreateAssetMenu(menuName = "Soco/ShaderVariantCollectionTools/Create Config")]
25 | public class ShaderVariantCollectionToolConfig : ScriptableObject, ISerializationCallbackReceiver
26 | {
27 | private Dictionary> mToggleObjects = new Dictionary>();
28 |
29 | [SerializeField] private List mKeys = new List();
30 | [SerializeField] private List mValues = new List();
31 | public void OnBeforeSerialize()
32 | {
33 | mKeys.Clear();
34 | mValues.Clear();
35 |
36 | foreach (var kvp in mToggleObjects)
37 | {
38 | mKeys.Add(kvp.Key.AssemblyQualifiedName);
39 | mValues.Add(new ListWrapper(kvp.Value));
40 | }
41 | }
42 | public void OnAfterDeserialize()
43 | {
44 | mToggleObjects.Clear();
45 | for (int i = 0; i < mKeys.Count; ++i)
46 | {
47 | System.Type type = System.Type.GetType(mKeys[i]);
48 | if (type != null)
49 | {
50 | mToggleObjects.Add(type, mValues[i].list);
51 | }
52 | }
53 | }
54 |
55 | public List GetToggleObjectList(System.Type type)
56 | {
57 | if (!mToggleObjects.TryGetValue(type, out var list))
58 | {
59 | list = new List();
60 | mToggleObjects.Add(type, list);
61 | }
62 |
63 | return list;
64 | }
65 |
66 | public void AddToggleObject(ToggleObject obj)
67 | {
68 | GetToggleObjectList(obj.obj.GetType().BaseType).Add(obj);
69 | }
70 |
71 | public void RemoveToggleObject(ToggleObject obj)
72 | {
73 | GetToggleObjectList(obj.obj.GetType().BaseType).Remove(obj);
74 | }
75 | }
76 |
77 | public class ToolDefaultEditor : Editor
78 | {
79 | public override void OnInspectorGUI()
80 | {
81 | base.OnInspectorGUI();
82 | }
83 | }
84 |
85 | public abstract class ShaderVariantCollectionToolEditor : Editor
86 | {
87 | }
88 |
89 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
90 | public sealed class ShaderVariantCollectionToolEditorAttribute : Attribute
91 | {
92 | public readonly Type componentType;
93 |
94 | public ShaderVariantCollectionToolEditorAttribute(Type componentType)
95 | {
96 | this.componentType = componentType;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/VariantFilter/VariantFilter_Shader.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 | using System.Linq;
4 | using UnityEditor;
5 |
6 | namespace Soco.ShaderVariantsCollection
7 | {
8 |
9 |
10 | public class VariantFilter_Shader : IVariantFilter
11 | {
12 | public enum Mode
13 | {
14 | Strip,
15 | OnlyReserveContains
16 | }
17 |
18 | public Mode mode = Mode.Strip;
19 | public Shader[] mShaders = new Shader[0];
20 |
21 | public override bool Filter(ShaderVariantCollection.ShaderVariant variant)
22 | {
23 | bool containShader = mShaders.Contains(variant.shader);
24 | return mode == Mode.Strip ? !containShader : containShader;
25 | }
26 | }
27 |
28 | [ShaderVariantCollectionToolEditor(typeof(VariantFilter_Shader))]
29 | class VariantFilter_ShaderEditor : ShaderVariantCollectionToolEditor
30 | {
31 | private bool foldoutValue = false;
32 | private int mOperatorIndex1 = 0;
33 | private int mOperatorIndex2 = 0;
34 | private Shader mOperatorShader = null;
35 |
36 | public override void OnInspectorGUI()
37 | {
38 | base.OnInspectorGUI();
39 |
40 | foldoutValue = EditorGUILayout.Foldout(foldoutValue, "添加删除交换操作");
41 |
42 | if (foldoutValue)
43 | {
44 | EditorGUILayout.BeginVertical();
45 |
46 | EditorGUILayout.BeginHorizontal();
47 | mOperatorIndex1 = EditorGUILayout.IntField("操作索引1", mOperatorIndex1);
48 | mOperatorIndex2 = EditorGUILayout.IntField("操作索引2", mOperatorIndex2);
49 | EditorGUILayout.EndHorizontal();
50 | mOperatorShader = EditorGUILayout.ObjectField("添加Shader", mOperatorShader, typeof(Shader)) as Shader;
51 |
52 | var targetObj = target as VariantFilter_Shader;
53 |
54 | EditorGUILayout.BeginHorizontal();
55 | if (GUILayout.Button("添加至操作索引1"))
56 | {
57 | if (mOperatorIndex1 >= 0 && mOperatorIndex1 <= targetObj.mShaders.Length)
58 | {
59 | Undo.RecordObject(targetObj, "VariantFilter_Shader insert element from list");
60 | List temp = new List(targetObj.mShaders);
61 | temp.Insert(mOperatorIndex1, mOperatorShader);
62 | targetObj.mShaders = temp.ToArray();
63 | }
64 | }
65 |
66 | if (GUILayout.Button("删除操作索引1"))
67 | {
68 | if (mOperatorIndex1 >= 0 && mOperatorIndex1 < targetObj.mShaders.Length)
69 | {
70 | Undo.RecordObject(targetObj, "VariantFilter_Shader delete shader from list");
71 | List temp = new List(targetObj.mShaders);
72 | temp.RemoveAt(mOperatorIndex1);
73 | targetObj.mShaders = temp.ToArray();
74 | }
75 | }
76 |
77 | if (GUILayout.Button("交换操作索引"))
78 | {
79 | if (mOperatorIndex1 >= 0 && mOperatorIndex1 < targetObj.mShaders.Length
80 | && mOperatorIndex2 >= 0 && mOperatorIndex2 < targetObj.mShaders.Length)
81 | {
82 | Undo.RecordObject(targetObj, "VariantFilter_Shader swap element from list");
83 | (targetObj.mShaders[mOperatorIndex1], targetObj.mShaders[mOperatorIndex2]) = (targetObj.mShaders[mOperatorIndex2], targetObj.mShaders[mOperatorIndex1]);
84 | }
85 | }
86 | EditorGUILayout.EndHorizontal();
87 |
88 | EditorGUILayout.EndVertical();
89 | }
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/stats_keywords.py:
--------------------------------------------------------------------------------
1 | #用于统计bundle中当前shader的变体
2 | #使用方法:
3 | #1. 用asset studio打开asset bundle
4 | #2. 在asset list中找到shader
5 | #3. 将shader复制保存到文本中
6 | #4. 编写此脚本ShaderVariants.from_file输入文本路径
7 |
8 | import re
9 |
10 | ParseState = {}
11 | ParseState['None'] = 0
12 | ParseState['SubProgram'] = 1
13 | ParseState['Pass'] = 2
14 | ParseState['PassFind'] = 3
15 | ParseState['Keywords'] = 4
16 | ParseState['Local Keywords'] = 5
17 |
18 | class ShaderVariants:
19 |
20 | def __init(self):
21 | pass
22 |
23 | #total_variants: dict key-pass, value-list of variants
24 |
25 | @staticmethod
26 | def from_file(filename):
27 |
28 | sv = ShaderVariants()
29 | sv.total_variants = {}
30 |
31 | with open(filename, 'r') as f:
32 |
33 | current_state = ParseState['None']
34 | current_keywords = []
35 | current_pass = ""
36 |
37 | for line in f:
38 |
39 | if current_state == ParseState['None']:
40 | if len(re.findall('Pass \{', line)) > 0:
41 | current_state = ParseState['Pass']
42 |
43 | elif current_state == ParseState['Pass']:
44 | current_pass = re.findall('Name "(\w+)"', line)[0]
45 | sv.total_variants[current_pass] = set()
46 | current_state = ParseState['PassFind']
47 |
48 | elif current_state == ParseState['PassFind']:
49 | if len(re.findall('SubProgram "gles3 " \{', line)) > 0:
50 | current_state = ParseState['SubProgram']
51 | current_keywords = []
52 | elif len(re.findall('Pass \{', line)) > 0:
53 | current_state = ParseState['Pass']
54 |
55 | elif current_state == ParseState['SubProgram']:
56 | if line.startswith("Keywords"):
57 | current_state = ParseState['Keywords']
58 | current_keywords = re.findall('"(\w+)"', line)
59 | elif line.startswith("Local Keywords"):
60 | current_state = ParseState['Local Keywords']
61 | current_keywords += re.findall('"(\w+)"', line)
62 | else:
63 | current_state = ParseState['PassFind']
64 | sv.total_variants[current_pass].add(" ".join(current_keywords))
65 |
66 | elif current_state == ParseState['Keywords']:
67 | if line.startswith("Local Keywords"):
68 | current_state = ParseState['Local Keywords']
69 | current_keywords += re.findall('"(\w+)"', line)
70 | else:
71 | current_state = ParseState['PassFind']
72 | sv.total_variants[current_pass].add(" ".join(current_keywords))
73 |
74 | elif current_state == ParseState['Local Keywords']:
75 | current_state = ParseState['PassFind']
76 | sv.total_variants[current_pass].add(" ".join(current_keywords))
77 |
78 | return sv
79 |
80 | # filename = "PFLitTreeOri.shader"
81 | # filename2 = "PFLitTreeSoco.shader"
82 |
83 | # sv1 = ShaderVariants.from_file(filename)
84 | # sv2 = ShaderVariants.from_file(filename2)
85 |
86 | # for k, v in sv1.total_variants.items():
87 | # print(f'pass:{k}, len:{len(v)}')
88 |
89 | # print('------')
90 | # for k, v in sv2.total_variants.items():
91 | # print(f'pass:{k}, len:{len(v)}')
92 |
93 | # var1=sv1.total_variants['ForwardLit']
94 | # var2=sv2.total_variants['ForwardLit']
95 |
96 | # var1 = dict.fromkeys(var1)
97 |
98 | # for k in var2:
99 | # if var1.get(k, None) == None:
100 | # print(k)
101 |
102 | filename = "PFLitSoco.shader"
103 | sv1 = ShaderVariants.from_file(filename)
104 |
105 | for k, v in sv1.total_variants.items():
106 | for i in v:
107 | print(i)
108 |
109 | print('---------')
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 | using UnityEngine.Rendering;
6 |
7 | #if UNITY_EDITOR
8 | using UnityEditor.Rendering;
9 | #endif
10 |
11 | namespace Soco.ShaderVariantsStripper
12 | {
13 | public enum ShaderVariantsDataShaderType
14 | {
15 | Vertex = 1,
16 | Fragment = 2,
17 | Geometry = 3,
18 | Hull = 4,
19 | Domain = 5,
20 | Surface = 6,
21 | Count = 7,
22 | RayTracing = 7,
23 | }
24 |
25 | public struct ShaderVariantsData
26 | {
27 | //ShaderSnipperData
28 | public ShaderVariantsDataShaderType shaderType;
29 | public UnityEngine.Rendering.PassType passType;
30 | public string passName;
31 |
32 | public bool inStripCallback;
33 |
34 | //ShaderCompilerData
35 | private UnityEngine.Rendering.ShaderKeywordSet shaderKeywordSet;
36 | private UnityEngine.Rendering.PlatformKeywordSet platformKeywordSet;
37 |
38 | private List _shaderKeywordList;
39 |
40 | private List shaderKeywordList
41 | {
42 | get
43 | {
44 | if (_shaderKeywordList == null)
45 | _shaderKeywordList = new List();
46 | return _shaderKeywordList;
47 | }
48 | }
49 |
50 | #if UNITY_EDITOR
51 | public static ShaderVariantsData GetDefaultShaderVariantsData()
52 | {
53 | return new ShaderVariantsData()
54 | {
55 | inStripCallback = false,
56 | passName = ""
57 | };
58 | }
59 |
60 | public static ShaderVariantsData GetShaderVariantsData(ShaderSnippetData shaderSnippetData,
61 | ShaderCompilerData data)
62 | {
63 | return new ShaderVariantsData()
64 | {
65 | shaderType = (ShaderVariantsDataShaderType)shaderSnippetData.shaderType,
66 | passType = shaderSnippetData.passType,
67 | passName = shaderSnippetData.passName,
68 | shaderKeywordSet = data.shaderKeywordSet,
69 | platformKeywordSet = data.platformKeywordSet,
70 | inStripCallback = true
71 | };
72 | }
73 | #endif
74 |
75 | public void EnableKeyword(ShaderKeyword keyword)
76 | {
77 | if (inStripCallback)
78 | shaderKeywordSet.Enable(keyword);
79 | else
80 | {
81 | if (shaderKeywordList.FindIndex(k => k == keyword.GetKeywordName()) < 0)
82 | {
83 | shaderKeywordList.Add(keyword.GetKeywordName());
84 | shaderKeywordList.Sort();
85 | }
86 | }
87 | }
88 |
89 | public void DisableKeyword(ShaderKeyword keyword)
90 | {
91 | if (inStripCallback)
92 | shaderKeywordSet.Disable(keyword);
93 | else
94 | shaderKeywordList.Remove(keyword.GetKeywordName());
95 | }
96 |
97 | public void DisableKeyword(string keyword)
98 | {
99 | if (inStripCallback)
100 | {
101 | shaderKeywordSet.Disable(new ShaderKeyword(keyword));
102 | }
103 | else
104 | shaderKeywordList.Remove(keyword);
105 | }
106 |
107 | public bool IsKeywordEnabled(ShaderKeyword keyword)
108 | {
109 | if (inStripCallback)
110 | return shaderKeywordSet.IsEnabled(keyword);
111 | else
112 | return shaderKeywordList.FindIndex(k => k == keyword.GetKeywordName()) >= 0;
113 | }
114 |
115 | public bool IsKeywordEnabled(string keyword, Shader shader = null)
116 | {
117 | if (inStripCallback)
118 | return shaderKeywordSet.IsEnabled(new ShaderKeyword(keyword)) ||
119 | (shader != null && shaderKeywordSet.IsEnabled(new ShaderKeyword(shader, keyword)));
120 | else
121 | return shaderKeywordList.FindIndex(k => k == keyword) >= 0;
122 | }
123 |
124 | public string[] GetShaderKeywords()
125 | {
126 | if (inStripCallback)
127 | return (from sk in shaderKeywordSet.GetShaderKeywords() select sk.GetKeywordName()).ToArray();
128 | else
129 | return shaderKeywordList.ToArray();
130 | }
131 |
132 | public ShaderKeyword[] GetShaderKeywordsObjectArray()
133 | {
134 | if (inStripCallback)
135 | return shaderKeywordSet.GetShaderKeywords();
136 | else
137 | {
138 | Debug.LogError("ShaderKeywordSet cannot be used outside of IPreprocessShaders");
139 | return null;
140 | }
141 | }
142 | }
143 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/README.md:
--------------------------------------------------------------------------------
1 | # 变体剔除工具
2 |
3 | ### 1. 开启界面
4 |
5 | 菜单栏Tools/Soco/ShaderVariantsStripper/OpenStripperWindow打开窗口。
6 |
7 | 
8 |
9 | ### 2. 创建配置文件
10 |
11 | 在Project窗口右键Create/Soco/ShaderVariantsStripper/Create Config创建配置文件,根据需要可以创建若干了,例如我创建了Global(对全局应用)、Effect(特效Shader)、Scene(场景Shader)、Role(角色Shader)、General(其他Shader)。
12 |
13 | 
14 |
15 | ### 3. 全局配置
16 |
17 | 选择好配置文件后,左侧Global Setting View和Shader View出现内容。点击Global Setting View中的全局设置按钮,右侧会出现全局设置栏,可添加全局条件。
18 |
19 | 
20 |
21 | 此工具的设计中,当前配置文件中Shader View没有Shader时,全局设置应用于项目所有Shader;当前配置文件中Shader View有Shader时,全局设置应用于当前配置文件中Shader View中的Shader。如果需要修改逻辑,可以查看ShaderVariantsStripperCode.cs
22 |
23 | ### 4. Shader配置
24 |
25 | 左侧下方可单独对shader配置,首先添加shader,可以用直接选择Shader,然后点添加按钮。
26 |
27 | 
28 |
29 | 为了方便,也可以通过输入ShaderName添加Shader。
30 |
31 | 
32 |
33 | 点击Shader后,右侧会出现当前Shader的剔除or保留条件。
34 |
35 | ### 5. 条件窗口
36 |
37 | ##### 5.1 条件类型
38 | ###### 5.1.1 PassType是
39 |
40 | 条件窗口中可选择和添加条件,例如我们选 __“PassType是”__ 这一条件,然后选择添加:
41 |
42 | 
43 | 
44 |
45 | 在新出现的选项中点击绿色的 __“保留”__ 按钮,变成红色的 __“剔除”__ 按钮,然后点击中间最大的 __“当Pass类型是Normal时”__ 按钮,在弹出的新窗口中,将PassType选择为 __“Deferred”__ 。
46 | 
47 |
48 | 现在的含义是,当满足pass是Deferred这一条件时,剔除当前变体。
49 |
50 | ###### 5.1.2 包含Keyword或集合
51 |
52 | 可以选择包含或不包含,也可以限定PassType。
53 |
54 | 除此外还有其他条件,包含Keyword或集合,例如我们明确打包的物体不需要支持Instance,即可添加一个条件,当包含INSTANCING_ON这一keyword时,直接剔除变体:
55 |
56 | 
57 |
58 | 点击“当前keyword”下方的按钮可删除keyword。
59 |
60 | 此条件可以添加多个Keyword,用于满足同时存在多个Keyword变体的情况,例如如果我们希望“中质量”关键字和“法线纹理”关键字不会同时存在,则将两个关键字都加入到条件中,然后选择剔除。
61 |
62 | ###### 5.1.3 多个条件
63 |
64 | 
65 |
66 | 同时满足多个条件才会生效的复合条件;
67 |
68 | 比如确定GBufferPass不需要法线压缩,如果将条件分开为:当前Pass是GBufferPass和当前Pass有法线压缩,则无法判断上述情况,所以需要一个复合条件来判断。
69 |
70 | 复合条件可以有多个。
71 |
72 | ##### 5.2 优先级
73 |
74 | 如果按照上面说,将项目中包含instance关键字的变体全部剔除,但对于草的shader又希望保留,则利用优先级功能:
75 |
76 | 
77 |
78 | 剔除/保留按钮后面的数字就是优先级,默认是0,数字越大,优先级越高,优先级为5的shader设置覆盖了优先级为0的全局设置,使变体得以保留。
79 |
80 | 优先级只会覆盖相同的条件,而剔除时,只要存在有剔除的条件没有被保留覆盖,就会剔除掉变体。
81 |
82 | ### 6. 剔除检查
83 |
84 | 当配置和条件变得繁杂时,可能无法快速判断一个变体是否会被此工具剔除掉,因此提供这个功能。
85 |
86 | 当配置文件没有选中时,配置选择下方会出现 __“剔除检查”__ 按钮,单击后右侧会出现对应选项。
87 |
88 | 
89 |
90 | 选择好Shader、ShaderType、PassType,以及组成变体的Keyword,点击测试剔除,下方会显示所有配置中满足条件的项,点击后可以在工程中定位到Config文件。
91 |
92 | ### 7. 自定义条件
93 |
94 | 此工具的优势就是可拓展性,可以自定义条件。
95 |
96 | 需要继承自`ShaderVariantsStripperCondition`接口,实现以下方法:
97 |
98 |
99 |
100 | `public bool Completion(Shader shader, ShaderVariantsData data)`
101 |
102 | `Completion`方法用于判断当前变体是否满足条件,传入的`ShaderVariantsData`对象包含当前变体的信息。
103 |
104 | `public bool EqualTo(ShaderVariantsStripperCondition other)`
105 |
106 | `EqualTo`方法用于判断两个条件是否相同,用于做优先级覆盖。
107 |
108 | `public string Overview()`
109 |
110 | `Overview`方法用于概览条件信息,也就是条件窗口上按钮上显示的字样。
111 |
112 | `OnGUI(ShaderVariantsStripperConditionOnGUIContext context)`
113 |
114 | `OnGUI`方法用于点击按钮后出现的窗口如何显示UI。
115 |
116 | `public string GetName()`
117 |
118 | `GetName`方法用于,当选择添加条件时,出现的字样。
119 |
120 |
121 |
122 | 当实现上述接口后,重新打开窗口,编辑器会自动根据反射信息获取条件,出现在条件选择添加的下拉菜单中。
123 |
124 | ### 8. 类名、名空间、Assembly修改
125 |
126 | 有时开发者希望迁移代码,例如从Assets Package化,可能会修改`ShaderVariantsStripperCondition`及实现类的类名、修改namespace、改变Assembly。由于序列化接口实现对象需要用到`SerializeReference`,当修改这些后,已经编辑好的Config文件可能无法匹配前后的类型(至少Unity2019-2021都是如此),如果在编辑器Project窗口点击Config,甚至会导致编辑器卡死。
127 |
128 | 如果你的项目资产是文本类型,可以直接用文本方式编辑资产中记录的类名、namespace、Assembly,如果项目资产是二进制,工具提供配置导出、导入为Json功能:
129 |
130 | 
131 |
132 | 序列化Config会将Json序列化数据保存到Config同目录同名Json文件中。
133 |
134 | 选择读取路径后,下方会显示当前读取路径,选择读取Json会将Json反序列化到当前Config中,这是覆盖保存,所以注意提前备份好Config。
135 |
136 | 对于修改类名等情况,可以提前Json序列化好配置文件,Json中会保存`SerializeReference`对象的类型、名空间、Assembly,可以用修改Json文件中相关名称,然后修改Unity中类型名称,之后从Json文件中反序列化。
137 |
138 | ### 9. 优化注意事项
139 |
140 | 因为找不到Unity Build的事件函数,无法在Build之前只进行一次Config读取,因此每次进入`OnProcessShader`函数时,都会调用`LoadConfigs`,来保证功能不出错;这个方法的逻辑,是从硬盘中读取Config文件,消耗不大,但依旧会对打包时间造成不少影响。
141 |
142 | 如果你们有Build Bundle相关代码,可以将`LoadConfigs`从`OnProcessShader`中注释掉,然后在Build Bundle前,在外部调用`ShaderVariantsStripperCode.LoadConfigs()`方法。
143 |
144 | 需要注意的是,如果注释掉`LoadConfigs`,会导致工程用Unity默认的File>Build Settings>build不走这一套剔除程序,可以在`OnProcessShader`增加一些判断逻辑,比如`sConfigs`为空时LoadConfigs,这样直接Build不会实时更新config文件,但依旧可以正常走剔除。
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.AExistAndBNotExist.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEngine.Rendering;
3 | using UnityEditor;
4 |
5 | namespace Soco.ShaderVariantsStripper
6 | {
7 | public class ShaderVariantsStripperConditionAExistAndBNotExist : ShaderVariantsStripperCondition
8 | {
9 | private enum AccessLevel
10 | {
11 | Global,
12 | Local
13 | }
14 |
15 | public string keywordA;
16 | public string keywordB;
17 |
18 | private AccessLevel _accessLevel = AccessLevel.Global;
19 | private int _selectedKeywordIndex;
20 | private string _inputKeyword;
21 |
22 | public bool Completion(Shader shader, ShaderVariantsData data)
23 | {
24 | bool valid = keywordA != "" && keywordB != "";
25 |
26 | bool AExist = data.IsKeywordEnabled(keywordA, shader);
27 |
28 | bool BNotExist = !data.IsKeywordEnabled(keywordB, shader);
29 |
30 | return valid && AExist && BNotExist;
31 | }
32 |
33 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
34 | {
35 | return other.GetType() == typeof(ShaderVariantsStripperConditionAExistAndBNotExist) &&
36 | (other as ShaderVariantsStripperConditionAExistAndBNotExist).keywordA == this.keywordA &&
37 | (other as ShaderVariantsStripperConditionAExistAndBNotExist).keywordB == this.keywordB;
38 | }
39 |
40 | #if UNITY_EDITOR
41 | public string Overview()
42 | {
43 | return $"当Keyword<{keywordA}>存在,且<{keywordB}>不存在时";
44 | }
45 |
46 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
47 | {
48 | EditorGUILayout.BeginVertical();
49 | if (context.shader != null)
50 | {
51 | #region 选择添加
52 | EditorGUILayout.BeginHorizontal();
53 | float width =
54 | EditorGUIUtility.currentViewWidth * 0.25f;
55 |
56 | AccessLevel newAccessLevel = (AccessLevel)EditorGUILayout.Popup((int)_accessLevel, new string[] { "Global", "Local" }, GUILayout.Width(width));
57 |
58 | if (newAccessLevel != _accessLevel)
59 | {
60 | _selectedKeywordIndex = 0;
61 | _accessLevel = newAccessLevel;
62 | }
63 |
64 | if (_accessLevel == AccessLevel.Global && context.globalKeywords.Length > 0 ||
65 | _accessLevel == AccessLevel.Local && context.localKeywords.Length > 0)
66 | {
67 | _selectedKeywordIndex = EditorGUILayout.Popup(_selectedKeywordIndex,
68 | _accessLevel == AccessLevel.Global ? context.globalKeywords : context.localKeywords, GUILayout.Width(width));
69 |
70 | string selectedKeyword = _accessLevel == AccessLevel.Global ? context.globalKeywords[_selectedKeywordIndex] : context.localKeywords[_selectedKeywordIndex];
71 |
72 | if (GUILayout.Button("设置为A", GUILayout.Width(width)))
73 | {
74 | keywordA = selectedKeyword;
75 | }
76 | if (GUILayout.Button("设置为B", GUILayout.Width(width)))
77 | {
78 | keywordB = selectedKeyword;
79 | }
80 | }
81 | EditorGUILayout.EndHorizontal();
82 | #endregion
83 |
84 | EditorGUILayout.Space(20);
85 | }
86 |
87 | #region 输入添加
88 | EditorGUILayout.BeginHorizontal();
89 |
90 | _inputKeyword = EditorGUILayout.TextField("输入Keyword", _inputKeyword, GUILayout.Width(EditorGUIUtility.currentViewWidth * 0.5f));
91 | if (GUILayout.Button("设置为A", GUILayout.Width(EditorGUIUtility.currentViewWidth * 0.25f)) && _inputKeyword != null)
92 | {
93 | string[] inputKeywords = _inputKeyword.Split(' ');
94 |
95 | foreach (var keyword in inputKeywords)
96 | {
97 | if (keyword != "" || _inputKeyword == "")
98 | {
99 | keywordA = keyword;
100 | }
101 | }
102 | }
103 | if (GUILayout.Button("设置为B", GUILayout.Width(EditorGUIUtility.currentViewWidth * 0.25f)) && _inputKeyword != null)
104 | {
105 | string[] inputKeywords = _inputKeyword.Split(' ');
106 |
107 | foreach (var keyword in inputKeywords)
108 | {
109 | if (keyword != "" || _inputKeyword == "")
110 | {
111 | keywordB = keyword;
112 | }
113 | }
114 | }
115 |
116 | EditorGUILayout.EndHorizontal();
117 | #endregion
118 |
119 | EditorGUILayout.Space(20);
120 |
121 | #region 显示Keyword
122 | EditorGUILayout.LabelField($"当包含Keyword<{keywordA}>,却不包含Keyword<{keywordB}>时");
123 | #endregion
124 | EditorGUILayout.EndVertical();
125 | }
126 |
127 | public string GetName()
128 | {
129 | return "KeywordA 存在 且 KeywordB不存在";
130 | }
131 | #endif
132 | }
133 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.MultiCondition.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 | using UnityEngine;
6 | using UnityEngine.Rendering;
7 | using UnityEditor;
8 | using UnityEngine.Scripting.APIUpdating;
9 |
10 | namespace Soco.ShaderVariantsStripper
11 | {
12 | public class ShaderVariantsStripperConditionMultiCondition : ShaderVariantsStripperCondition
13 | {
14 | [System.Serializable]
15 | private struct CondtionWrap
16 | {
17 | public CondtionWrap(ShaderVariantsStripperCondition condition)
18 | {
19 | this.condition = condition;
20 | }
21 | [SerializeReference] public ShaderVariantsStripperCondition condition;
22 | }
23 |
24 | [SerializeField] private List conditionList = new List();
25 |
26 | public bool Completion(Shader shader, ShaderVariantsData data)
27 | {
28 | foreach (var condition in conditionList)
29 | {
30 | if (!condition.condition.Completion(shader, data))
31 | return false;
32 | }
33 |
34 | return true;
35 | }
36 |
37 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
38 | {
39 | if (other.GetType() != typeof(ShaderVariantsStripperConditionMultiCondition))
40 | return false;
41 |
42 | ShaderVariantsStripperConditionMultiCondition otherCondition =
43 | other as ShaderVariantsStripperConditionMultiCondition;
44 |
45 | if (otherCondition.conditionList.Count != this.conditionList.Count)
46 | return false;
47 |
48 | for (int i = 0; i < this.conditionList.Count; ++i)
49 | {
50 | if (!otherCondition.conditionList[i].condition.EqualTo(this.conditionList[i].condition, variantData))
51 | return false;
52 | }
53 |
54 | return true;
55 | }
56 |
57 | #if UNITY_EDITOR
58 |
59 | public string Overview()
60 | {
61 | if (conditionList.Count == 0)
62 | return "满足多个条件,当前条件为空";
63 | else if (conditionList.Count == 1)
64 | return $"满足多个条件,当前只有一个条件:{conditionList[0].condition.Overview()}";
65 | else
66 | return $"同时满足以下条件:{string.Join(", ", (from condition in conditionList select condition.condition.Overview()))}";
67 | }
68 |
69 | private const float DividerHeight = 2f;
70 | private static Color dividerColor = Color.gray;
71 | private void DrawDivider()
72 | {
73 | Rect rect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(DividerHeight));
74 |
75 | EditorGUI.DrawRect(rect, dividerColor);
76 | GUILayout.Space(DividerHeight);
77 | }
78 |
79 | private int mSelectedCondition = 0;
80 | private static Type[] _ConditionTypes = new Type[0];
81 | private static string[] _ConditionNames = null;
82 |
83 | private static Type[] sConditionTypes
84 | {
85 | get
86 | {
87 | if (_ConditionNames == null)
88 | SetupConditionType();
89 | return _ConditionTypes;
90 | }
91 | }
92 |
93 | private static string[] sConditionNames
94 | {
95 | get
96 | {
97 | if (_ConditionNames == null)
98 | SetupConditionType();
99 | return _ConditionNames;
100 | }
101 | }
102 | private static void SetupConditionType()
103 | {
104 | _ConditionTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(
105 | assembly => assembly.GetTypes()).Where(
106 | type => typeof(ShaderVariantsStripperCondition).IsAssignableFrom(type) && !type.IsAbstract
107 | ).ToArray();
108 |
109 | _ConditionNames = _ConditionTypes.Select(type => (System.Activator.CreateInstance(type) as ShaderVariantsStripperCondition).GetName()).ToArray();
110 | }
111 |
112 | private Vector2 mScrollPosition = Vector2.zero;
113 |
114 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
115 | {
116 | EditorGUILayout.BeginVertical();
117 |
118 | mScrollPosition = EditorGUILayout.BeginScrollView(mScrollPosition);
119 | int removeIndex = -1;
120 | for (int i = 0; i < conditionList.Count; ++i)
121 | {
122 | var condition = conditionList[i];
123 |
124 | EditorGUILayout.LabelField(condition.condition.Overview());
125 | condition.condition.OnGUI(context);
126 | if (GUILayout.Button("删除此条件") && EditorUtility.DisplayDialog("Confirm", "确定删除吗?", "Yes", "No"))
127 | {
128 | removeIndex = i;
129 | }
130 |
131 | DrawDivider();
132 | }
133 | if (removeIndex != -1)
134 | conditionList.RemoveAt(removeIndex);
135 | EditorGUILayout.EndScrollView();
136 |
137 | mSelectedCondition = EditorGUILayout.Popup(mSelectedCondition, sConditionNames);
138 | if (GUILayout.Button("添加条件"))
139 | {
140 | if (mSelectedCondition >= sConditionTypes.Length)
141 | {
142 | Debug.LogError("程序有改变,请重新打开窗口");
143 | return;
144 | }
145 |
146 | ShaderVariantsStripperCondition condition =
147 | Activator.CreateInstance(sConditionTypes[mSelectedCondition]) as
148 | ShaderVariantsStripperCondition;
149 | conditionList.Add(new CondtionWrap(condition));
150 | }
151 |
152 | EditorGUILayout.EndVertical();
153 | }
154 |
155 | public string GetName()
156 | {
157 | return "多个条件同时满足";
158 | }
159 | #endif
160 | }
161 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/ShaderVariantCollectionAddVariantWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using UnityEditor;
6 | using UnityEngine;
7 | using UnityEngine.Rendering;
8 |
9 | namespace Soco.ShaderVariantsCollection
10 | {
11 | public class ShaderVariantCollectionAddVariantWindow : EditorWindow
12 | {
13 | private static ShaderVariantCollectionAddVariantWindow m_window;
14 | public static ShaderVariantCollectionAddVariantWindow Window
15 | {
16 | get
17 | {
18 | if (m_window == null)
19 | {
20 | m_window = EditorWindow.GetWindow("AddVariantWindow");
21 | m_window.minSize = new Vector2(480, 320);
22 | }
23 | return m_window;
24 | }
25 | }
26 |
27 | private Shader mShader;
28 | private PassType mPassType;
29 | private ShaderVariantCollectionMapper mMapper;
30 |
31 | private static MethodInfo sGetShaderGlobalKeywordsMethod = null;
32 | private static MethodInfo sGetShaderLocalKeywordsMethod = null;
33 |
34 | private string[] mShaderKeywords;
35 | private List mSelectedShaderKeywords = new List();
36 | private int mSelectedShaderKeywordIndex = 0;
37 |
38 | private enum State
39 | {
40 | None,
41 | Success,
42 | Failure
43 | }
44 |
45 | private State mState;
46 | private string mMessage;
47 |
48 |
49 | private static void InitGetKeywordMethod()
50 | {
51 | if (sGetShaderGlobalKeywordsMethod == null)
52 | sGetShaderGlobalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords",
53 | BindingFlags.NonPublic | BindingFlags.Static);
54 |
55 | if (sGetShaderLocalKeywordsMethod == null)
56 | sGetShaderLocalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords",
57 | BindingFlags.NonPublic | BindingFlags.Static);
58 | }
59 |
60 | public void Setup(Shader shader, PassType passType, ShaderVariantCollectionMapper mapper)
61 | {
62 | mShader = shader;
63 | mPassType = passType;
64 | mMapper = mapper;
65 |
66 | InitGetKeywordMethod();
67 |
68 | string[] globalKeywords = sGetShaderGlobalKeywordsMethod.Invoke(null, new object[] { shader }) as string[];
69 | string[] localKeywords = sGetShaderLocalKeywordsMethod.Invoke(null, new object[] { shader }) as string[];
70 | mShaderKeywords = globalKeywords.Concat(localKeywords).ToArray();
71 |
72 | mSelectedShaderKeywords.Clear();
73 | mSelectedShaderKeywordIndex = 0;
74 | mState = State.None;
75 | }
76 |
77 | public void OnGUI()
78 | {
79 | EditorGUILayout.BeginVertical();
80 |
81 | EditorGUILayout.LabelField($"当前Shader: {mShader}");
82 | mPassType = (PassType)EditorGUILayout.EnumPopup("PassType", mPassType);
83 |
84 | #region 待添加keyword
85 | EditorGUILayout.LabelField($"待添加Keyword:");
86 |
87 | EditorGUILayout.BeginHorizontal();
88 | int i = 0;
89 | foreach (string keyword in mShaderKeywords)
90 | {
91 | if (i != 0 && i % 4 == 0)
92 | {
93 | EditorGUILayout.EndHorizontal();
94 | EditorGUILayout.BeginHorizontal();
95 | }
96 |
97 | if (!mSelectedShaderKeywords.Contains(keyword))
98 | {
99 | if (GUILayout.Button(keyword))
100 | {
101 | mSelectedShaderKeywords.Add(keyword);
102 | }
103 | i++;
104 | }
105 | }
106 | EditorGUILayout.EndHorizontal();
107 | #endregion
108 |
109 | #region 当前keyword
110 | EditorGUILayout.LabelField($"当前Keyword:");
111 |
112 | EditorGUILayout.BeginHorizontal();
113 | for (int keywordIndex = 0; keywordIndex < mSelectedShaderKeywords.Count; ++keywordIndex)
114 | {
115 | if (keywordIndex != 0 && keywordIndex % 4 == 0)
116 | {
117 | EditorGUILayout.EndHorizontal();
118 | EditorGUILayout.BeginHorizontal();
119 | }
120 |
121 | if (GUILayout.Button(mSelectedShaderKeywords[keywordIndex]))
122 | {
123 | mSelectedShaderKeywords.RemoveAt(keywordIndex);
124 | }
125 | }
126 | EditorGUILayout.EndHorizontal();
127 | #endregion
128 |
129 | if (GUILayout.Button("添加变体"))
130 | {
131 | bool newVariantSuccess = false;
132 | ShaderVariantCollection.ShaderVariant newVariant;
133 | string errorMessage = "";
134 |
135 | try
136 | {
137 | newVariant = new ShaderVariantCollection.ShaderVariant(mShader, mPassType,
138 | mSelectedShaderKeywords.ToArray());
139 | newVariantSuccess = true;
140 | }
141 | catch (Exception e)
142 | {
143 | newVariantSuccess = false;
144 | errorMessage = e.Message;
145 | }
146 |
147 | string keywordString = string.Join(", ", mSelectedShaderKeywords);
148 | if (newVariantSuccess)
149 | {
150 | newVariant = new ShaderVariantCollection.ShaderVariant(mShader, mPassType,
151 | mSelectedShaderKeywords.ToArray());//不重新new会因为变量可能没初始化导致编译错误
152 | if (mMapper.HasVariant(newVariant))
153 | {
154 | mState = State.Failure;
155 | mMessage = $"变体<{mPassType}>[{keywordString}]已存在";
156 | }
157 | else
158 | {
159 | ShaderVariantCollectionToolsWindow.Window.UndoShaderVariantCollectionTool();
160 | if (!mMapper.AddVariant(newVariant))
161 | {
162 | mState = State.Failure;
163 | mMessage = $"变体<{mPassType}>[{keywordString}]添加失败";
164 | }
165 | else
166 | {
167 | mState = State.Success;
168 | mMessage = $"变体<{mPassType}>[{keywordString}]添加成功";
169 | ShaderVariantCollectionToolsWindow.Window.RefreshPassKeywordMap(mShader);
170 | //ShaderVariantCollectionToolsWindow.Window.CollectPassKeywordMap(mMapper.GetShaderVariants(mShader));
171 | ShaderVariantCollectionToolsWindow.Window.Repaint();
172 | }
173 | }
174 | }
175 | else
176 | {
177 | mState = State.Failure;
178 | mMessage = $"变体<{mPassType}>[{keywordString}]创建失败,相关报错为:{errorMessage}";
179 | }
180 | }
181 |
182 | #region 消息显示
183 | if (mState != State.None)
184 | {
185 | Color oriColor = GUI.color;
186 | GUI.color = mState == State.Success ? Color.green : Color.red;
187 | GUI.color *= 0.5f;
188 |
189 | EditorGUILayout.LabelField(mMessage, EditorStyles.whiteLabel);
190 |
191 | GUI.color = oriColor;
192 | }
193 | #endregion
194 |
195 | EditorGUILayout.EndVertical();
196 |
197 |
198 | }
199 | }
200 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Editor/ShaderVariantsStripperCode.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEditor;
4 | using UnityEditor.Build;
5 | using UnityEditor.Rendering;
6 | using UnityEngine;
7 | using System.Linq;
8 |
9 | namespace Soco.ShaderVariantsStripper
10 | {
11 | public class ShaderVariantsStripperCode : IPreprocessShaders
12 | {
13 | public int callbackOrder { get { return 0; } }
14 |
15 | private static string[] sAllPath = { "Assets", "Packages" };
16 | private static ShaderVariantsStripperConfig[] sConfigs;
17 |
18 | private List<(ConditionPair conditionPair, ShaderVariantsStripperConfig config)> mConditionList = new List<(ConditionPair condition, ShaderVariantsStripperConfig config)>();
19 | public static ShaderVariantsStripperConfig[] LoadConfigs()
20 | {
21 | string[] guids = AssetDatabase.FindAssets("t:ShaderVariantsStripperConfig", sAllPath);
22 |
23 | sConfigs = (from guid in guids
24 | select AssetDatabase.LoadAssetAtPath(
25 | AssetDatabase.GUIDToAssetPath(guid)))
26 | .ToArray();
27 |
28 | return sConfigs;
29 | }
30 |
31 | public static void ClearConfigs()
32 | {
33 | sConfigs = null;
34 | }
35 |
36 | public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList data)
37 | {
38 | LoadConfigs();
39 |
40 | ShaderCompilerData workaround = data[0];
41 |
42 | int stripCount = 0;
43 |
44 | for (int i = data.Count - 1; i >= 0 ; --i)
45 | {
46 | mConditionList.Clear();
47 |
48 | StripVariant(shader, snippet, data[i], sConfigs, mConditionList);
49 |
50 | bool isStrip = false;
51 | foreach (var conditionPair_fromConfig in mConditionList)
52 | {
53 | isStrip |= conditionPair_fromConfig.conditionPair.strip;
54 | if (conditionPair_fromConfig.config.mIsWhiteList)
55 | {
56 | isStrip = false;
57 | break;
58 | }
59 | }
60 |
61 | if (isStrip)
62 | {
63 | data.RemoveAt(i);
64 | stripCount++;
65 | }
66 | }
67 |
68 | Debug.Log($"Shader:{shader.name} Pass:{snippet.passType} 剔除个数:{stripCount}");
69 |
70 | if (data.Count == 0)
71 | {
72 | Debug.Log($"Shader:{shader.name} Pass:{snippet.passType} 因剔除全部保留变体一个");
73 | data.Add(workaround);
74 | }
75 | }
76 |
77 | //对外开放接口,用于检查keyword是否需要被剔除
78 | private static List<(ConditionPair conditionPair, ShaderVariantsStripperConfig config)> sConditionList = new List<(ConditionPair condition, ShaderVariantsStripperConfig config)>();
79 | public static bool IsVariantStrip(Shader shader, ShaderSnippetData snippet, ShaderCompilerData data, ShaderVariantsStripperConfig[] configs)
80 | {
81 | sConditionList.Clear();
82 | StripVariant(shader, snippet, data, sConfigs, sConditionList);
83 |
84 | return sConditionList.Any(conditionPair_fromConfig =>
85 | conditionPair_fromConfig.conditionPair.strip)
86 | && sConditionList.All(conditionPair_fromConfig =>
87 | !conditionPair_fromConfig.config.mIsWhiteList);
88 | }
89 |
90 | public static void StripVariant(Shader shader, ShaderSnippetData snippet, ShaderCompilerData data,
91 | ShaderVariantsStripperConfig[] configs,
92 | List<(ConditionPair conditionPair, ShaderVariantsStripperConfig config)> conditionList)
93 | {
94 | StripVariant(shader, ShaderVariantsData.GetShaderVariantsData(snippet, data), configs, conditionList);
95 | }
96 |
97 | public static void StripVariant(Shader shader, ShaderVariantsData variantData, ShaderVariantsStripperConfig[] configs, List<(ConditionPair conditionPair, ShaderVariantsStripperConfig config)> conditionList)
98 | {
99 | int FindConditionEqual(ConditionPair pair, out int index)
100 | {
101 | for (int condList_i = 0; condList_i < conditionList.Count; ++condList_i)
102 | {
103 | if (pair.condition.EqualTo(conditionList[condList_i].conditionPair.condition, variantData))
104 | {
105 | index = condList_i;
106 | return condList_i;
107 | }
108 | }
109 |
110 | index = -1;
111 | return -1;
112 | }
113 |
114 | foreach (ShaderVariantsStripperConfig config in configs)
115 | {
116 | if (!config.mEnable)
117 | continue;
118 |
119 | bool applyGlobalConfig = true;
120 |
121 | // 如果这个配置文件中能找到当前shader,则应用配置文件中“应用global config选项”
122 | if (config.mShaderConditions.TryGetValue(shader, out ShaderVariantsItem item))
123 | applyGlobalConfig = item.applyGlobalConfig;
124 | // 如果Shader View中没有Shader,则Global Setting应用于全体Shader
125 | else if (config.mShaderConditions.Count == 0 && !config.mIsWhiteList)
126 | applyGlobalConfig = true;
127 | else
128 | applyGlobalConfig = false;
129 |
130 | //Global condition
131 | if (applyGlobalConfig)
132 | {
133 | foreach (ConditionPair pair in config.mGlobalConditions)
134 | {
135 | if (pair.condition.Completion(shader, variantData))
136 | {
137 | if (!config.mIsWhiteList)
138 | {
139 | //如果有相同的条件,
140 | if (FindConditionEqual(pair, out int findIndex) != -1)
141 | {
142 | //且优先级更高
143 | if(pair.priority > conditionList[findIndex].conditionPair.priority)
144 | conditionList[findIndex] = (pair, config);
145 | //优先级更低则直接丢弃
146 | }
147 | else//否则加入列表
148 | conditionList.Add((pair, config));
149 | }
150 | else//白名单变体无需判断优先级和相等条件
151 | {
152 | conditionList.Add((pair, config));
153 | }
154 | }
155 | }
156 | }
157 | //Shader local condition
158 | if (item != null)
159 | {
160 | foreach (ConditionPair pair in item.conditionPairs)
161 | {
162 | if (pair.condition.Completion(shader, variantData))
163 | {
164 | if (!config.mIsWhiteList)
165 | {
166 | if (FindConditionEqual(pair, out int findIndex) != -1)
167 | {
168 | if (pair.priority > conditionList[findIndex].conditionPair.priority)
169 | conditionList[findIndex] = (pair, config);
170 | }
171 | else
172 | conditionList.Add((pair, config));
173 | }
174 | else
175 | {
176 | conditionList.Add((pair, config));
177 | }
178 | }
179 | }
180 | }
181 | }
182 | }
183 | }
184 | }
185 |
186 |
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/ShaderVariantsStripperCondition.HasKeywordCombination.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 | using UnityEngine.Rendering;
4 | using UnityEditor;
5 |
6 |
7 | namespace Soco.ShaderVariantsStripper
8 | {
9 | public class ShaderVariantsStripperConditionHasKeywordCombination
10 | : ShaderVariantsStripperCondition
11 | {
12 |
13 | public bool include = true;
14 | public List keywords = new List();
15 |
16 | public bool constraintPassType = false;
17 | public PassType passType = PassType.Normal;
18 |
19 | private enum AccessLevel
20 | {
21 | Global,
22 | Local
23 | }
24 |
25 | private AccessLevel _accessLevel = AccessLevel.Global;
26 | private int _selectedKeywordIndex;
27 | private string _inputKeyword;
28 |
29 | public bool Completion(Shader shader, ShaderVariantsData data)
30 | {
31 | if (constraintPassType && passType != data.passType)
32 | return false;
33 |
34 | int combinationValue = 0;
35 | foreach (string keyword in keywords)
36 | {
37 | combinationValue += data.IsKeywordEnabled(keyword, shader)
38 | ? 1
39 | : 0;
40 | }
41 |
42 | return keywords.Count == 0
43 | || (include ? combinationValue == keywords.Count : combinationValue != keywords.Count);
44 | }
45 |
46 | public bool EqualTo(ShaderVariantsStripperCondition other, ShaderVariantsData variantData)
47 | {
48 | if (other.GetType() != typeof(ShaderVariantsStripperConditionHasKeywordCombination))
49 | {
50 | return false;
51 | }
52 |
53 | ShaderVariantsStripperConditionHasKeywordCombination otherCondition =
54 | other as ShaderVariantsStripperConditionHasKeywordCombination;
55 |
56 | //两个条件都未指定Pass
57 | bool nonConstraintPassType = this.constraintPassType == otherCondition.constraintPassType &&
58 | !this.constraintPassType;
59 |
60 | //两个条件都指定Pass,且Pass相等
61 | bool constraintPassTypeAndTypeEqual = this.constraintPassType == otherCondition.constraintPassType &&
62 | this.constraintPassType && this.passType == otherCondition.passType;
63 |
64 | //其中一个指定了Pass,另一个未指定,但当前环境下pass符合条件
65 | PassType constraintPassType = this.constraintPassType ? this.passType : otherCondition.passType;
66 | bool constraintPassTypeNotEqualButPassEqual =
67 | this.constraintPassType != otherCondition.constraintPassType &&
68 | constraintPassType == variantData.passType;
69 |
70 | //三个条件满足之一就算指定Pass的条件相等
71 | bool passEqualCondtion = nonConstraintPassType || constraintPassTypeAndTypeEqual ||
72 | constraintPassTypeNotEqualButPassEqual;
73 |
74 | if (this.keywords.Count != otherCondition.keywords.Count || !passEqualCondtion)
75 | {
76 | return false;
77 | }
78 |
79 | var set1 = new HashSet(this.keywords);
80 | var set2 = new HashSet(otherCondition.keywords);
81 | return set1.SetEquals(set2) && this.include == otherCondition.include;
82 | }
83 |
84 | #if UNITY_EDITOR
85 | public string Overview()
86 | {
87 | string passConstraint = constraintPassType ? $"({passType})" : "";
88 | string c = include ? "" : "不";
89 | string s = $"{passConstraint}当{c}包含Keyword<";
90 |
91 |
92 | for (int i = 0; i < keywords.Count; ++i)
93 | {
94 | s += keywords[i];
95 | if (i != keywords.Count - 1)
96 | s += ", ";
97 | }
98 |
99 | s += ">";
100 | if (keywords.Count > 1)
101 | {
102 |
103 | s += $"(共{keywords.Count}个)组合";
104 | }
105 |
106 |
107 | s += "时";
108 |
109 | return s;
110 | }
111 |
112 | public void OnGUI(ShaderVariantsStripperConditionOnGUIContext context)
113 | {
114 | EditorGUILayout.BeginVertical();
115 |
116 | #region 包含选项
117 | EditorGUILayout.BeginHorizontal();
118 |
119 | string c = include ? "" : "不";
120 | if (GUILayout.Button($"当{c}包含"))
121 | {
122 | include = !include;
123 | }
124 | EditorGUILayout.LabelField("下列keyword或keyword组合时");
125 |
126 | EditorGUILayout.EndHorizontal();
127 | #endregion
128 | EditorGUILayout.Space(20);
129 |
130 | if (context.shader != null)
131 | {
132 | #region 选择添加
133 | EditorGUILayout.BeginHorizontal();
134 | float width =
135 | EditorGUIUtility.currentViewWidth * 0.33f;
136 |
137 | AccessLevel newAccessLevel = (AccessLevel)EditorGUILayout.Popup((int)_accessLevel, new string[] { "Global", "Local" }, GUILayout.Width(width));
138 |
139 | if (newAccessLevel != _accessLevel)
140 | {
141 | _selectedKeywordIndex = 0;
142 | _accessLevel = newAccessLevel;
143 | }
144 |
145 | if (_accessLevel == AccessLevel.Global && context.globalKeywords.Length > 0 ||
146 | _accessLevel == AccessLevel.Local && context.localKeywords.Length > 0)
147 | {
148 | _selectedKeywordIndex = EditorGUILayout.Popup(_selectedKeywordIndex,
149 | _accessLevel == AccessLevel.Global ? context.globalKeywords : context.localKeywords, GUILayout.Width(width));
150 |
151 | string selectedKeyword = _accessLevel == AccessLevel.Global ? context.globalKeywords[_selectedKeywordIndex] : context.localKeywords[_selectedKeywordIndex];
152 |
153 | if (GUILayout.Button("添加", GUILayout.Width(width)) && !keywords.Contains(selectedKeyword))
154 | {
155 | keywords.Add(selectedKeyword);
156 | }
157 | }
158 | EditorGUILayout.EndHorizontal();
159 | #endregion
160 |
161 | EditorGUILayout.Space(20);
162 | }
163 |
164 | #region 输入添加
165 | EditorGUILayout.BeginHorizontal();
166 |
167 | _inputKeyword = EditorGUILayout.TextField("输入Keyword", _inputKeyword, GUILayout.Width(EditorGUIUtility.currentViewWidth * 0.66f));
168 | if (GUILayout.Button("添加", GUILayout.Width(EditorGUIUtility.currentViewWidth * 0.33f)) && _inputKeyword != null)
169 | {
170 | string[] inputKeywords = _inputKeyword.Split(' ');
171 |
172 | foreach (var keyword in inputKeywords)
173 | {
174 | if(keyword != "" && !keywords.Contains(keyword))
175 | keywords.Add(keyword);
176 | }
177 | }
178 |
179 | EditorGUILayout.EndHorizontal();
180 | #endregion
181 |
182 | EditorGUILayout.Space(20);
183 |
184 | #region 显示/删除Keyword
185 |
186 | EditorGUILayout.LabelField("当前Keyword:" + (keywords.Count == 0 ? " 无" : ""));
187 | EditorGUILayout.BeginHorizontal();
188 |
189 | const float itemWidth = 160.0f;
190 | float accumulationWidth = 0;
191 |
192 | for (int i = 0; i < keywords.Count; ++i)
193 | {
194 | if (accumulationWidth + itemWidth >
195 | EditorGUIUtility.currentViewWidth)
196 | {
197 | accumulationWidth = 0;
198 | EditorGUILayout.EndHorizontal();
199 | EditorGUILayout.BeginHorizontal();
200 | }
201 |
202 | if (GUILayout.Button(new GUIContent(keywords[i], keywords[i]), GUILayout.Width(itemWidth)))
203 | {
204 | keywords.RemoveAt(i);
205 | break;
206 | }
207 |
208 | accumulationWidth += itemWidth;
209 | }
210 |
211 | EditorGUILayout.EndHorizontal();
212 | #endregion
213 |
214 | #region 限定PassType
215 | EditorGUILayout.BeginHorizontal();
216 | constraintPassType = EditorGUILayout.ToggleLeft("条件限定指定PassType生效", constraintPassType);
217 | if (constraintPassType)
218 | {
219 | passType = (PassType)EditorGUILayout.EnumPopup("PassType", passType);
220 | }
221 | EditorGUILayout.EndHorizontal();
222 | #endregion
223 |
224 | EditorGUILayout.EndVertical();
225 | }
226 |
227 | public string GetName() => "包含Keyword或集合";
228 | #endif
229 | }
230 |
231 | }
232 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/README.md:
--------------------------------------------------------------------------------
1 | # 变体收集工具
2 |
3 | ### 1. 开启菜单
4 |
5 | 顶部菜单栏Tools/Soco/ShaderVariantCollectionTools/OpenWindow 开启窗口。
6 | 
7 | **变体收集文件**:Unity原生的变体收集文件,可以对Shader变体产生引用,使变体打入Bundle,可用于预热变体。在填入当前操作变体收集文件前,将显示红色。
8 | **工具配置文件**:当前工具的配置文件,方便保留材质收集、变体材质过滤等工具的参数,不需要每次打开都重新编写。工具打开时会自动寻找,且如果找不到时,会自动在工具目录下生成默认配置文件,并选择添加(上图的Default ShaderVariantCollection Tool Config)。
9 |
10 | ### 2. 功能选择
11 |
12 | 当变体收集文件不为空时,功能选择菜单启用。
13 | 当前工具暂时支持三类主要功能:**快速浏览、收集工具、批处理工具**。
14 |
15 | 
16 |
17 | **快速浏览**:浏览变体收集文件的内容;相比Unity原生的变体收集UI,工具的浏览能更快捷的定位shader、pass所拥有的变体,概览变体个数。
18 | **项目收集工具**:利用项目中材质对变体的引用,收集变体,在过程中过滤材质或变体。
19 | **批处理工具**:对收集完的变体收集文件批量处理,例如排列组合添加材质不会收集到的multi_compile变体。
20 |
21 | ### 3. 快速浏览
22 |
23 | 点击快速浏览后,次级功能选项会变成Shader View,可显示当前变体收集文件中包含的Shader。
24 | 通过选择项目Shader并点击添加,以及点击下方Shader名称后的减号,可以添加或删除Shader。通过“过滤”中的字符串,可以筛选Shader。
25 | 点击列表中的Shader名称,可以查看收集文件中,Shader拥有哪些变体。变体在右侧浏览窗口,通过PassType分好组。可以通过加号和减少增减变体。
26 |
27 | 
28 |
29 | ### 4. 收集工具
30 |
31 | #### 4.1 材质收集器
32 | 点击项目收集工具后,先进入材质收集器列表选项。
33 | 材质收集器是对象,对象的类需要实现`Soco.ShaderVariantsCollection.IMaterialCollection`接口,并实现`AddMaterialBuildDependency`这个方法,将打包所需要的材质添加到`AddMaterialBuildDependency`传入的`List`中。
34 | 下面是我所实现的事例`MaterialCollection_SceneDependency`,作用是获取所有打包场景依赖的材质:
35 | ```C#
36 | namespace Soco.ShaderVariantsCollection
37 | {
38 | //用于收集所有打包场景依赖的材质
39 | public class MaterialCollection_SceneDependency : IMaterialCollection
40 | {
41 | //是否只收集在EditorBuildSettings中enable的场景
42 | public bool collectOnlyEnable = true;
43 | public override void AddMaterialBuildDependency(IList buildDependencyList)
44 | {
45 | var sceneDependencyMaterials = EditorBuildSettings.scenes //所有场景
46 | .Where(scene => !collectOnlyEnable || scene.enabled) //是否enable
47 | .SelectMany(scene => AssetDatabase.GetDependencies(scene.path)) //获取场景依赖的所有资源
48 | .Where(dependencyAsset => dependencyAsset.EndsWith(".mat")) //获取资源中的材质
49 | .Distinct() //去重
50 | .Select(matPath => AssetDatabase.LoadAssetAtPath(matPath));
51 |
52 | buildDependencyList.AddRange(sceneDependencyMaterials);
53 | }
54 | }
55 | }
56 | ```
57 | 不同项目可按照需要实现接口,比如某些项目用资源表决定有哪些材质会打入包中,就可以实现一个类,专门读取资源表获取资源,然后获取资源引用的材质,或资源本身就是材质,下边是我们项目的实现:
58 | ```C#
59 | //用于获取资源表所引用的资源
60 | //注意,这个类并不在Soco.ShaderVariantsCollection名空间下,因为理想中,这个类是依赖于工程,而非工具的功能,所以继承时用到了类包含namespace的全名
61 | public sealed class MaterialCollection_DependRes : Soco.ShaderVariantsCollection.IMaterialCollection
62 | {
63 | public override void AddMaterialBuildDependency(IList buildDependencyList)
64 | {
65 | //获取资源表中所有资源
66 | List resList = ResConfigFileEnter.GetConfigFile();
67 | foreach (string res in resList)
68 | {
69 | //如果资源本身是材质,则直接添加到列表中
70 | if (res.EndsWith(".mat"))
71 | {
72 | Material mat = AssetDatabase.LoadAssetAtPath(res);
73 | if(mat != null)
74 | buildDependencyList.Add(mat);
75 | }
76 | //如果不是材质,则找到资源所引用的材质添加到列表中
77 | else
78 | {
79 | foreach (string depRes in AssetDatabase.GetDependencies(res))
80 | {
81 | if (depRes.EndsWith(".mat"))
82 | {
83 | Material mat = AssetDatabase.LoadAssetAtPath(depRes);
84 | if(mat != null)
85 | buildDependencyList.Add(mat);
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | ```
93 | 工具打开时会获取所有实现`IMaterialCollection`接口的类,可以通过点击“添加收集材质回调”按钮,实例化实现类,对象和对象的参数会保存在工具配置文件中。
94 | “**删除**”按钮可以删除收集器对象,**使用**表示下次点击“**收集材质**”时,是否会用到这个收集器对象。
95 | 下方绘制了实现类的成员,可通过修改数值使对象收集不同的材质,例如我的一个材质收集器实现类,可以收集某些路径下所有材质,成员变量是一个路径,通过修改成员的数值,就可以收集不同路径下的材质。
96 | 
97 |
98 | #### 4.2 收集变体
99 | 搞定好收集器后,先确认是否要覆盖原有文件,还是在原有文件上添加内容,可通过次级功能选项“**Collection View**”下的复选框修改。
100 | 收集变体分为三步:**收集材质、材质变体转化、写入收集文件**。在次级功能菜单**Collection View**中可以单步运行,也可以直接点击“一键变体收集”运行三步。
101 | **收集材质**是通过4.1描述的材质收集器,在项目中收集材质。
102 | **材质变体转化**是将材质存储的shader keyword,转换为一个或多个shader变体。
103 | **写入收集文件**是将转换得到的变体写入变体收集文件。
104 |
105 | #### 4.3 材质、变体过滤
106 | 
107 | 上述收集器会按照规则收集材质,然后转换变体,但有时会得到我们不希望得到的变体;
108 | 例如一个URP管线的项目,肯定不希望得到`Standard`这个Shader实例化出来的材质,如果按照引用规则收集,很多模型默认的内嵌材质会导致收集到。
109 | 或者一个Forward管线也可能收集到`Deferred`或`Meta`这些Pass的变体。
110 | 理所当然的,可以在收集器的逻辑中,屏蔽掉某个Shader的所有材质,但如果这样,需要在每个收集器的逻辑里添加屏蔽代码,需知材质收集器不止一种,屏蔽需求也不止一个,所以我添加了过滤器这一功能,将收集和过滤分离开来。
111 | 和收集器一样,需要实现接口来实现过滤器。材质过滤器需要继承`IMaterialFilter`接口,并实现`Filter`方法:
112 | ```C#
113 | public abstract class IMaterialFilter : ScriptableObject
114 | {
115 | //return true will save and false will strip
116 | public abstract bool Filter(Material material, List collections);
117 | }
118 | ```
119 | 方法将传入材质,以及收集到材质的收集器,方法需要返回bool类型参数,返回true这个材质将保留,false则会使材质被剔除。
120 | 类似的,变体的过滤器需要继承`IVariantFilter`接口,并实现`Filter`方法:
121 | ```C#
122 | public abstract class IVariantFilter : ScriptableObject
123 | {
124 | //return true will save and false will strip
125 | public abstract bool Filter(ShaderVariantCollection.ShaderVariant variant);
126 | }
127 | ```
128 | 工具自带实现了两个变体过滤器:可以剔除或只保留指定Shader的`VariantFilter_Shader`,可以剔除指定Pass的`VariantFilter_PassStrip`。
129 | 材质过滤器将在材质收集阶段后应用,变体过滤器将在材质-变体转换阶段后应用。
130 |
131 | 注: 不需要的变体自然能通过Unity的变体剔除来剔除,这里的做法只是为了保证变体收集文件的整洁性。
132 |
133 | #### 4.4 自带收集器与过滤器使用说明
134 | 材质收集器:
135 |
136 | ① `MaterialCollection_SceneDependency`获取所有在BuildSetting中的场景,所引用的材质。
137 | 成员`collectOnlyEnable`指定是否只收集在BuildSetting中打勾的场景。
138 |
139 | ② `MaterialCollection_TotalMaterial`获取指定目录下的所有材质。
140 | 有两种指定文件夹的形式,利用`pathMode`指定;
141 | 其一是Asset,可以拖动文件夹到`mFolders`数组中,这样的缺陷是,`Assets`和`Packages`目录无法拖动;
142 | 其二是String,可以将路径字符串指定到`mIncludePath`数组中,路径的起始从项目根路径开始,也就是类似`Assets\Res`这种形式。
143 |
144 | ③ `MaterialCollection_AssignMaterial`指定收集某几个材质
145 | 将材质拖到materials数组中即可
146 |
147 | 变体过滤器:
148 |
149 | ① `VariantFilter_Shaderp`剔除或只保留指定shader。
150 | 将需要操作的Shader指定到`mShaders`数组中,mode指定模式。
151 | 当模式为Strip时,将剔除收集到变体中,shader处于`mShaders`数组中的变体。
152 | 当模式为OnlyReserveContains时,将只保留shader处于`mShaders`数组中的变体。
153 | 只保留模式可以用来只收集某一shader的变体,这样不会减少收集时间,但非覆盖模式下,可以只收集几个Shader,应对需要的场景。
154 |
155 | ② `VariantFilter_PassStrip`剔除指定Pass。
156 | 将需要剔除的PassType指定到`mStripPasses`数组中即可
157 |
158 | 材质过滤器因没有需求,暂时没提供默认实现类。
159 |
160 | ### 5. 批处理工具
161 | 收集工具将变体收集、写入到变体文件中后,如果有对变体批量处理的需求,则会用到这一功能。
162 | 通过功能选择的批处理工具按钮会进入到批处理工具页面,当前有三个功能:**批处理执行器列表**、**合并文件**
163 |
164 | #### 5.1 批处理执行器列表
165 | 当需要自定义批处理功能时,可按照接口实现执行器。
166 | 这里举例一个使用场景:前面收集器收集到的是所有材质记录的keywords,这个参数是通过Material.EnableKeyword添加,Material.DisableKeyword去除,多数情况下,需要材质来开启关闭的keyword,用shader_feature声明,而雾效、阴影这些全局效果关键字,用multi_compile声明。
167 | 打包时的规则是,获取引用到的所有变体组合的shader_feature部分(材质和变体收集文件会对变体产生引用),然后和shader的multi_compile keyword排列组合;通过材质获取的keyword组合,能保证打包不会丢变体,但由于没有收集、组合multi_compile keyword,会导致无法正确预热。
168 | 此时就需要用批处理工具,将multi_compile部分与已经收集的变体排列组合,并添加到变体收集文件中,使变体正确预热。
169 |
170 | 和材质收集器、材质变体过滤器类似,批处理执行器需要实现`IExecutable`接口,并实现`Execute`方法。
171 | ```C#
172 | public abstract class IExecutable : ScriptableObject
173 | {
174 | public abstract void Execute(ShaderVariantCollectionMapper mapper);
175 | }
176 | ```
177 | 方法会将当前正在处理的变体收集文件的包装器传入(因为Unity原生的变体收集文件提供的接口太少了),这个包装器类提供了Shader、变体是否存在,以及添加、删除Shader和变体的接口。
178 | 
179 | 执行器的列表中,会多出“执行”和“全部执行”按钮;执行会执行当前执行器,不管执行器的“使用”选项是否勾选;而全部执行会执行所有勾选“使用”的执行器。
180 |
181 | #### 5.2 自带执行器使用说明
182 |
183 | ① `VariantKeywordCombination`
184 | 添加keyword声明组,排列组合后,与现有变体再组合,写入到变体收集文件中。
185 | 变体声明组就是Shader中的`#pragma multi_compile _ A B`,这样默认keyword`_`和`A`、`B`就是一个声明组。
186 | Shader参数指定需要组合的Shader。
187 | 添加keyword声明组可以通过最下面的加号`+`,然后会出现新的一行,包含`+`和`-`,`-`会去掉当前变体声明组,`+`会添加keyword。
188 | 添加的keyword会根据选择模式变化,选择模式共3个`Custom`、`Default`、`DeclareStatement`。
189 | 当模式为`Custom`时,点击`+`会将选择模式右侧选择的变体添加到当前声明组中。
190 | 当模式为`Default`时,点击`+`会将默认keyword`_`(代表全下划线keyword,无论shader中用了几个下划线声明都一样)添加到当前声明组中。
191 | 当模式为`DeclareStatement`时,右侧可以输入声明字符串(类似`#pragma multi_compile _ A B`),点击`+`会解析字符串,并将解析出的声明组添加到当前声明组,这一选项方便直接从shader复制语句。
192 | 声明字符串只支持`multi_compile`,无论是否有local,支持instance(`multi_compile_instancing`)、fog、particle这些build_in声明,暂不支持`multi_compile_fwdbase`等其他build_in声明,如需要可改代码。
193 | 当shader不为空时,可以通过“尝试收集声明组”按钮,读取shader文件尝试解析文件内容,获取声明组,这样能加快设置速度,但需要人工排查是否正确收集,否则很可能收集到不想要的声明组。
194 | 
195 |
196 | ② `SocoVariantStripAssociate`
197 | 与[Soco变体剔除工具](https://github.com/crossous/SocoTools/tree/main/SocoShaderVariantsStripper)联动,可以利用剔除工具的逻辑,将变体收集文件中,不会打入bundle的变体剔除掉,精简变体收集文件。
198 | 因为依赖于变体剔除工具,所以当变体剔除工具不存在时,可以将这个类去掉。
199 |
200 | #### 5.3 合并文件
201 | 
202 | 作用是将新放入的文件的内容,合并到左侧的变体收集文件中。
203 |
204 | #### 5.4 分割文件
205 | 
206 | 有时候会有需求将现有文件分割成多份,比如低端设备初次进入游戏,会在预热时卡住一段时间,希望能在预热时显示进度条,此时就会希望分割文件。
207 | 首先设置路径,默认是源文件位置。然后设置每个文件最多多少个变体。
208 | 然后选择切割模式,其一是每个文件固定变体数量。
209 | 选框可以决定分割的最小单位,如果可以分割Shader也可以分割Pass,则最小单位就是变体;否则如果可以分割Shader但不能分割Shader中的Pass,则最小分割单位是Pass;最后就是不可分割Shader,则不会分割Shader。
210 | 第二个切割模式是按照固定文件数量切割,变体均匀分布在每个文件中,这样方便配置资源表。
211 |
212 | #### 5.5 排除变体
213 | 
214 | 有时有需求跑变体收集,例如游戏需要先预热新手流程前30分钟的变体。
215 | 但Unity的变体收集会收集到不想要的变体,这时候可以先用标准流程收集到打包变体,然后切换收集到的变体为操作文件,利用排除变体功能,将不在标准收集文件中的变体删除掉,相当于做了一个交集(Intersection)操作,这样可以保证前30min收集到的变体都是干净的。
216 |
217 |
218 | ### 6. 自定义编辑器
219 | 可以发现材质收集器、材质过滤器、变体过滤器、执行器的界面大多相似,因为它们的实现方式都是继承自某个抽象类然后实现抽象方法。
220 | 界面都是绘制各个对象的成员,如果希望自定义编辑器,工具提供了相关抽象。例如上面的执行器自身的定义是这样:
221 | ```C#
222 | public class VariantKeywordCombination : IExecutable
223 | {
224 | //something
225 | }
226 | ```
227 | 那么自定义编辑器可以这样定义:
228 | ```C#
229 | [ShaderVariantCollectionToolEditor(typeof(VariantKeywordCombination))]
230 | class VariantKeywordCombinationEditor : ShaderVariantCollectionToolEditor
231 | {
232 | public void OnEnable()
233 | {
234 | //something
235 | }
236 |
237 | public override void OnInspectorGUI()
238 | {
239 | //something
240 | }
241 | }
242 | ```
243 | `ShaderVariantCollectionToolEditor`这个类继承自Unity的`Editor`类,所以相关的事件方法都可以实现,界面打开时会自动寻找有没有实现当前对象类的编辑器,如果没有就绘制类的成员,如果有则按照实现的编辑器代码绘制。
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/ShaderVariantCollectionMaterialVariantConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using Soco.ShaderVariantsCollection;
6 | using UnityEditor;
7 | using UnityEngine;
8 | using UnityEngine.Rendering;
9 |
10 | public class ShaderVariantCollectionMaterialVariantConverter
11 | {
12 | //Key-材质
13 | //Value-材质被哪个收集器采集
14 | private Dictionary> mMaterials = new Dictionary>();
15 |
16 | //用于缓存Shader包含哪些keyword
17 | private Dictionary mCachedKeywords = new Dictionary();
18 |
19 | //存储已经添加的变体
20 | //列表存放的是,去重、排序后组合成的字符串,当字符串加入到列表中,代表这一keyword序列所有可能的变体都被加入到变体列表中
21 | private Dictionary> mInsertedVariants = new Dictionary>();
22 |
23 | private List mVariants = new List();
24 |
25 | //获取shader keyword方法,需要反射获取
26 | private static MethodInfo _GetShaderGlobalKeywordsMethod = null;
27 | private static MethodInfo _GetShaderLocalKeywordsMethod = null;
28 |
29 | private static MethodInfo GetShaderGlobalKeywordsMethod
30 | {
31 | get
32 | {
33 | if (_GetShaderGlobalKeywordsMethod == null)
34 | _GetShaderGlobalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords",
35 | BindingFlags.NonPublic | BindingFlags.Static);
36 |
37 | return _GetShaderGlobalKeywordsMethod;
38 | }
39 | }
40 |
41 | private static MethodInfo GetShaderLocalKeywordsMethod
42 | {
43 | get
44 | {
45 | if (_GetShaderLocalKeywordsMethod == null)
46 | _GetShaderLocalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords",
47 | BindingFlags.NonPublic | BindingFlags.Static);
48 |
49 | return _GetShaderLocalKeywordsMethod;
50 | }
51 | }
52 |
53 | private static string[] GetShaderKeywords(Shader shader)
54 | {
55 | string[] globalKeywords = GetShaderGlobalKeywordsMethod.Invoke(null, new object[] { shader }) as string[];
56 | string[] localKeywords = GetShaderLocalKeywordsMethod.Invoke(null, new object[] { shader }) as string[];
57 |
58 | return globalKeywords.Concat(localKeywords).ToArray();
59 | }
60 |
61 | public bool CollectMaterial(IEnumerable collectors)
62 | {
63 | mMaterials.Clear();
64 | List materials = new List();
65 |
66 | bool success = false;
67 |
68 | try
69 | {
70 | int collectorIndex = 0;
71 | int collectorListCount = collectors.Count();
72 | foreach (IMaterialCollector collector in collectors)
73 | {
74 | EditorUtility.DisplayProgressBar("材质收集", $"正在处理第{collectorIndex + 1}/{collectorListCount}个 --{collector.name}",
75 | (float)collectorIndex / (float)collectorListCount);
76 | materials.Clear();
77 | collector.AddMaterialBuildDependency(materials);
78 | foreach (Material material in materials)
79 | {
80 | if (!mMaterials.TryGetValue(material, out List fromCollect))
81 | {
82 | fromCollect = new List();
83 | mMaterials.Add(material, fromCollect);
84 | }
85 |
86 | fromCollect.Add(collector);
87 | }
88 |
89 | collectorIndex++;
90 | }
91 |
92 | Debug.Log($"[材质收集]共收集到{mMaterials}个材质");
93 |
94 | success = true;
95 | }
96 | finally
97 | {
98 | EditorUtility.ClearProgressBar();
99 | }
100 |
101 | return success;
102 | }
103 |
104 | public bool FilterMaterial(IEnumerable materialFilters)
105 | {
106 | var deleteList = mMaterials
107 | .Where(kv => materialFilters.Any(mf => !mf.Filter(kv.Key, kv.Value)))
108 | .Select(kv => kv.Key);
109 |
110 | foreach (var deleteMat in deleteList)
111 | mMaterials.Remove(deleteMat);
112 |
113 | return true;
114 | }
115 |
116 | public bool FilterMaterial(IEnumerable variantFilters)
117 | {
118 | mVariants.RemoveAll(variant => variantFilters.Any(vf => !vf.Filter(variant)));
119 | return true;
120 | }
121 |
122 | //查看keyword是否属于shader,用于去除无效keyword
123 | private bool IsKeywordBelongToShader(Shader shader, string keyword)
124 | {
125 | if (!mCachedKeywords.TryGetValue(shader, out string[] keywords))
126 | {
127 | keywords = GetShaderKeywords(shader);
128 | mCachedKeywords.Add(shader, keywords);
129 | }
130 |
131 | return keywords.Contains(keyword);
132 | }
133 |
134 | private bool AddVariant(ShaderVariantCollection.ShaderVariant variant)
135 | {
136 | int findIndex = mVariants.FindIndex(v =>
137 | {
138 | return v.shader == variant.shader
139 | && v.passType == variant.passType
140 | && v.keywords.SequenceEqual(variant.keywords);
141 | });
142 |
143 | if (findIndex < 0)
144 | {
145 | mVariants.Add(variant);
146 | }
147 |
148 | return findIndex < 0;
149 | }
150 |
151 | private bool IsValidVariant(Shader shader, PassType passType, string[] keywords)
152 | {
153 | try
154 | {
155 | ShaderVariantCollection.ShaderVariant testVariant
156 | = new ShaderVariantCollection.ShaderVariant(shader, passType, keywords);
157 |
158 | return true;
159 | }
160 | catch
161 | {
162 | return false;
163 | }
164 | }
165 |
166 | private readonly string[] mSingleKeywords = new string[1];
167 | private bool IsKeywordInPass(Shader shader, PassType passType, string keyword)
168 | {
169 | mSingleKeywords[0] = keyword;
170 | return IsValidVariant(shader, passType, mSingleKeywords);
171 | }
172 |
173 | private readonly string[] mEmptyKeywords = new string[0];
174 | private bool IsPassExist(Shader shader, PassType passType)
175 | {
176 | return IsValidVariant(shader, passType, mEmptyKeywords);
177 | }
178 |
179 |
180 | private List mTempInsertKeywords = new List();
181 | private List mTempSortedKeywords = new List();
182 |
183 | private void AddVariantFromKeywords(Shader shader, List validKeywords)
184 | {
185 | //如果keyword为空,直接尝试加入变体
186 | if (validKeywords.Count == 0)
187 | {
188 | //循环尝试所有Pass是否能加入
189 | for (PassType passType = (PassType)Enum.GetValues(typeof(PassType)).Cast().Min();
190 | passType <= (PassType)Enum.GetValues(typeof(PassType)).Cast().Max();
191 | ++passType)
192 | {
193 | if (!IsPassExist(shader, passType))
194 | continue;
195 |
196 | AddVariant(new ShaderVariantCollection.ShaderVariant(shader, passType, mEmptyKeywords));
197 | }
198 |
199 | return;
200 | }
201 |
202 |
203 | foreach (string keyword in validKeywords)
204 | {
205 | for (PassType passType = (PassType)Enum.GetValues(typeof(PassType)).Cast().Min();
206 | passType <= (PassType)Enum.GetValues(typeof(PassType)).Cast().Max();
207 | ++passType)
208 | {
209 | //如果当前pass没有这个keyword,跳过
210 | if (!IsKeywordInPass(shader, passType, keyword))
211 | continue;
212 |
213 | mTempInsertKeywords.Clear();
214 | mTempInsertKeywords.Add(keyword);
215 |
216 | //遍历其他keyword,逐个添加进组合,找到对于这个keyword的最长的有效组合
217 | foreach (string currentKeyword in validKeywords)
218 | {
219 | if (currentKeyword == keyword)
220 | continue;
221 |
222 | mTempInsertKeywords.Add(currentKeyword);
223 |
224 | //需要有序来检查Sequence Equal
225 | mTempSortedKeywords.Clear();
226 | mTempSortedKeywords.AddRange(mTempInsertKeywords);
227 | mTempSortedKeywords.Sort();
228 |
229 | //如果加入当前keyword后,变体无效,则将当前keyword去掉
230 | if (!IsValidVariant(shader, passType, mTempSortedKeywords.ToArray()))
231 | mTempInsertKeywords.RemoveAt(mTempInsertKeywords.Count - 1);
232 | }
233 |
234 | mTempInsertKeywords.Sort();
235 | AddVariant(new ShaderVariantCollection.ShaderVariant(shader, passType, mTempInsertKeywords.ToArray()));
236 | }
237 | }
238 | }
239 |
240 | public bool CollectVariant()
241 | {
242 | mCachedKeywords.Clear();
243 | mInsertedVariants.Clear();
244 | mVariants.Clear();
245 |
246 | List validKeywords = new List();
247 |
248 | int materialIndex = 0;
249 |
250 | bool success = false;
251 |
252 | try
253 | {
254 | foreach (Material material in mMaterials.Keys)
255 | {
256 | EditorUtility.DisplayProgressBar("变体收集-材质与变体转换",
257 | $"正在处理第{materialIndex}/{mMaterials.Keys.Count}个材质 --{material.name}",
258 | (float)materialIndex / (float)mMaterials.Keys.Count);
259 |
260 | Shader shader = material.shader;
261 |
262 | validKeywords.Clear();
263 | validKeywords.AddRange(material.shaderKeywords.Distinct()); //去重
264 | validKeywords.RemoveAll(keyword => !IsKeywordBelongToShader(shader, keyword)); //去除没有的变体
265 | validKeywords.Sort();
266 |
267 | //检查keyword序列是否已添加,如果是则跳过
268 | string keywordsKey = string.Join(" ", validKeywords);
269 | if (mInsertedVariants.TryGetValue(shader, out List collectedVariant))
270 | {
271 | if (collectedVariant.Contains(keywordsKey))
272 | continue;
273 | }
274 | else
275 | {
276 | collectedVariant = new List();
277 | mInsertedVariants.Add(shader, collectedVariant);
278 | }
279 |
280 | AddVariantFromKeywords(shader, validKeywords);
281 | materialIndex++;
282 | }
283 |
284 | success = true;
285 | }
286 | catch (Exception e)
287 | {
288 | Debug.LogError(
289 | $"[材质变体转换]正在处理第{materialIndex}个材质{mMaterials.ElementAt(materialIndex).Key.name}\n报错信息为:{e.Message}\nStackTrace:{e.StackTrace}");
290 | }
291 | finally
292 | {
293 | EditorUtility.ClearProgressBar();
294 | }
295 |
296 | Debug.Log($"[材质变体转换]总共加入{mVariants.Count}个变体");
297 | return success;
298 | }
299 |
300 | public IEnumerable GetMaterials()
301 | {
302 | return mMaterials.Keys;
303 | }
304 |
305 | public int GetVariantCount()
306 | {
307 | return mVariants.Count;
308 | }
309 |
310 | public IEnumerable GetMaterialFrom(Material material)
311 | {
312 | if (mMaterials.TryGetValue(material, out var fromList))
313 | {
314 | return fromList;
315 | }
316 |
317 | return new IMaterialCollector[0];
318 | }
319 |
320 | public void WriteToShaderVariantCollectionFile(ShaderVariantCollection collection)
321 | {
322 | foreach (var variant in mVariants)
323 | {
324 | collection.Add(variant);
325 | }
326 | }
327 |
328 | public void GetKeywordFromMaterial(string keyword, IList matList, Shader shader = null)
329 | {
330 | foreach (Material mat in GetMaterials())
331 | {
332 | if (shader != null && mat.shader != shader)
333 | continue;
334 |
335 | if (mat.shaderKeywords.Contains(keyword) && !matList.Contains(mat))
336 | matList.Add(mat);
337 | }
338 | }
339 | }
340 |
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/ShaderVariantCollectionMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Reflection;
6 | using UnityEditor;
7 | using UnityEngine;
8 | using UnityEngine.Rendering;
9 |
10 | namespace Soco.ShaderVariantsCollection
11 | {
12 | //用于访问ShaderVariantCollection被隐藏的C++部分
13 | //继承UnityEngine.ScriptableObject, ISerializationCallbackReceiver是为了Undo
14 | public class ShaderVariantCollectionMapper : UnityEngine.ScriptableObject, ISerializationCallbackReceiver
15 | {
16 | [SerializeField]
17 | internal ShaderVariantCollection mCollection;
18 | private Dictionary> mMapper = new Dictionary>();
19 |
20 | private List mMapperSerializeKeys = new List();
21 | [System.Serializable]
22 | private struct ListWrapper
23 | {
24 | public List list;
25 |
26 | public ListWrapper(List list)
27 | {
28 | this.list = list;
29 | }
30 |
31 | // public List Deserialize()
32 | // {
33 | // return list.Select(variant => variant.Deserialize()).ToList();
34 | // }
35 | }
36 | private List mMapperSerializeValues = new List();
37 |
38 | [NonSerialized]
39 | private bool mSerializedFlag = false;
40 |
41 | private static MethodInfo sAddNewShaderToCollection = null;
42 |
43 | private static void InitShaderUtilMethod()
44 | {
45 | if (sAddNewShaderToCollection == null)
46 | sAddNewShaderToCollection = typeof(ShaderUtil).GetMethod("AddNewShaderToCollection",
47 | BindingFlags.NonPublic | BindingFlags.Static);
48 | }
49 |
50 | //当用CreateInstance时,调用这个方法
51 | public void Init(ShaderVariantCollection collection)
52 | {
53 | mCollection = collection;
54 |
55 | if (mCollection == null)
56 | {
57 | throw new Exception(
58 | "ShaderVariantCollectionMapper constructor get a null ShaderVariantCollection Object");
59 | }
60 |
61 | ReadFromFile();
62 | InitShaderUtilMethod();
63 | }
64 |
65 | public ShaderVariantCollectionMapper(ShaderVariantCollection collection)
66 | {
67 | Init(collection);
68 | }
69 |
70 | internal static ShaderVariantCollection.ShaderVariant PropToVariantObject(Shader shader, SerializedProperty variantInfo)
71 | {
72 | PassType passType = (PassType)variantInfo.FindPropertyRelative("passType").intValue;
73 | string keywords = variantInfo.FindPropertyRelative("keywords").stringValue;
74 | string[] keywordSet = keywords.Split(' ');
75 | keywordSet = (keywordSet.Length == 1 && keywordSet[0] == "") ? new string[0] : keywordSet;
76 |
77 | ShaderVariantCollection.ShaderVariant newVariant = new ShaderVariantCollection.ShaderVariant()
78 | {
79 | shader = shader,
80 | keywords = keywordSet,
81 | passType = passType
82 | };
83 |
84 | return newVariant;
85 | }
86 |
87 | private void ReadFromFile()
88 | {
89 | mMapper.Clear();
90 |
91 | SerializedObject serializedObject = new UnityEditor.SerializedObject(mCollection);
92 | //serializedObject.Update();
93 | SerializedProperty m_Shaders = serializedObject.FindProperty("m_Shaders");
94 |
95 | for (int i = 0; i < m_Shaders.arraySize; ++i)
96 | {
97 | SerializedProperty pair = m_Shaders.GetArrayElementAtIndex(i);
98 |
99 | SerializedProperty first = pair.FindPropertyRelative("first");
100 | SerializedProperty second = pair.FindPropertyRelative("second");//ShaderInfo
101 |
102 | Shader shader = first.objectReferenceValue as Shader;
103 |
104 | if (shader == null)
105 | continue;
106 |
107 | mMapper[shader] = new List();
108 |
109 | SerializedProperty variants = second.FindPropertyRelative("variants");
110 | for (var vi = 0; vi < variants.arraySize; ++vi)
111 | {
112 | SerializedProperty variantInfo = variants.GetArrayElementAtIndex(vi);
113 |
114 | ShaderVariantCollection.ShaderVariant variant = PropToVariantObject(shader, variantInfo);
115 | mMapper[shader].Add(new SerializableShaderVariant(variant));
116 | }
117 | }
118 | }
119 |
120 | public Dictionary>.KeyCollection shaders => mMapper.Keys;
121 |
122 | public ReadOnlyCollection GetShaderVariants(Shader shader) =>
123 | mMapper[shader].Select(v=>v.Deserialize()).ToList().AsReadOnly();
124 |
125 | public bool HasShader(Shader shader)
126 | {
127 | bool collectionHasShader = false;
128 | SerializedObject serializedObject = new UnityEditor.SerializedObject(mCollection);
129 | //serializedObject.Update();
130 | SerializedProperty m_Shaders = serializedObject.FindProperty("m_Shaders");
131 |
132 | for (int i = 0; i < m_Shaders.arraySize; ++i)
133 | {
134 | SerializedProperty pair = m_Shaders.GetArrayElementAtIndex(i);
135 |
136 | SerializedProperty first = pair.FindPropertyRelative("first");
137 | SerializedProperty second = pair.FindPropertyRelative("second"); //ShaderInfo
138 |
139 | Shader currentShader = first.objectReferenceValue as Shader;
140 |
141 | if (currentShader == shader)
142 | {
143 | collectionHasShader = true;
144 | break;
145 | }
146 | }
147 |
148 | bool mapperHasShader = mMapper.ContainsKey(shader);
149 |
150 | Debug.Assert(collectionHasShader == mapperHasShader, "map对象与源文件内容不符");
151 | return mapperHasShader;
152 | }
153 |
154 | public void AddShader(Shader shader)
155 | {
156 | if (!mMapper.ContainsKey(shader))
157 | {
158 | mMapper.Add(shader, new List());
159 | }
160 |
161 | sAddNewShaderToCollection.Invoke(null, new object[] { shader, mCollection });
162 | }
163 |
164 | public void RemoveShader(Shader shader)
165 | {
166 | if (mMapper.ContainsKey(shader))
167 | {
168 | mMapper.Remove(shader);
169 | }
170 |
171 | SerializedObject serializedObject = new UnityEditor.SerializedObject(mCollection);
172 | //serializedObject.Update();
173 | SerializedProperty m_Shaders = serializedObject.FindProperty("m_Shaders");
174 |
175 | for (int shaderIndex = 0; shaderIndex < m_Shaders.arraySize; ++shaderIndex)
176 | {
177 | SerializedProperty pair = m_Shaders.GetArrayElementAtIndex(shaderIndex);
178 |
179 | SerializedProperty first = pair.FindPropertyRelative("first");
180 |
181 | Shader currentShader = first.objectReferenceValue as Shader;
182 |
183 | if (currentShader == shader)
184 | {
185 | m_Shaders.DeleteArrayElementAtIndex(shaderIndex);
186 | break;
187 | }
188 | }
189 |
190 | serializedObject.ApplyModifiedProperties();
191 | }
192 |
193 | private bool VariantCompare(SerializableShaderVariant l, SerializableShaderVariant r)
194 | {
195 | if (l.shader != r.shader)
196 | return false;
197 |
198 | if (l.passType != r.passType)
199 | return false;
200 |
201 | if (l.keywords.Length != r.keywords.Length)
202 | return false;
203 |
204 | return l.keywords.OrderBy(s => s).SequenceEqual(r.keywords.OrderBy(t => t));
205 | }
206 |
207 | public bool HasVariant(ShaderVariantCollection.ShaderVariant variant)
208 | {
209 | bool collectionHasVariant = mCollection.Contains(variant);
210 |
211 | bool mapperHasVariant =
212 | mMapper.ContainsKey(variant.shader) && mMapper[variant.shader].FindIndex(v=>VariantCompare(new SerializableShaderVariant(variant), v)) != -1;
213 |
214 | Debug.Assert(collectionHasVariant == mapperHasVariant, "map对象与源文件内容不符");
215 | return collectionHasVariant;
216 | }
217 |
218 | public bool AddVariant(ShaderVariantCollection.ShaderVariant variant)
219 | {
220 | if (!mCollection.Add(variant))
221 | return false;
222 |
223 | if (!mMapper.ContainsKey(variant.shader))
224 | mMapper.Add(variant.shader, new List());
225 |
226 | mMapper[variant.shader].Add(new SerializableShaderVariant(variant));
227 |
228 | return true;
229 | }
230 |
231 | public bool RemoveVariant(ShaderVariantCollection.ShaderVariant variant)
232 | {
233 | if (!mCollection.Remove(variant))
234 | return false;
235 |
236 | if (mMapper.TryGetValue(variant.shader, out var variantList))
237 | {
238 | int index = variantList.FindIndex(v => VariantCompare(new SerializableShaderVariant(variant), v));
239 | Debug.Assert(index != -1, "map对象与源文件内容不符,无法在map对象中找到变体");
240 | variantList.RemoveAt(index);
241 |
242 | return true;
243 | }
244 |
245 | Debug.Assert(false, "map对象与源文件内容不符,无法在map对象中找到Shader");
246 | return false;
247 | }
248 |
249 | private ShaderVariantCollection.ShaderVariant PropToObject(Shader shader, SerializedProperty variantInfo)
250 | {
251 | PassType passType = (PassType)variantInfo.FindPropertyRelative("passType").intValue;
252 | string keywords = variantInfo.FindPropertyRelative("keywords").stringValue;
253 | string[] keywordSet = keywords.Split(' ');
254 | ShaderVariantCollection.ShaderVariant newVariant = new ShaderVariantCollection.ShaderVariant()
255 | {
256 | shader = shader,
257 | keywords = keywordSet,
258 | passType = passType
259 | };
260 |
261 | return newVariant;
262 | }
263 |
264 | public void Merge(ShaderVariantCollection otherFile)
265 | {
266 | if (otherFile == null)
267 | return;
268 |
269 | SerializedObject serializedOther = new UnityEditor.SerializedObject(otherFile);
270 | SerializedProperty m_Shaders = serializedOther.FindProperty("m_Shaders");
271 |
272 | for (int i = 0; i < m_Shaders.arraySize; ++i)
273 | {
274 | SerializedProperty pair = m_Shaders.GetArrayElementAtIndex(i);
275 |
276 | SerializedProperty first = pair.FindPropertyRelative("first");
277 | SerializedProperty second = pair.FindPropertyRelative("second");//ShaderInfo
278 |
279 | Shader shader = first.objectReferenceValue as Shader;
280 |
281 | SerializedProperty variants = second.FindPropertyRelative("variants");
282 | for (var vi = 0; vi < variants.arraySize; ++vi)
283 | {
284 | SerializedProperty variantInfo = variants.GetArrayElementAtIndex(vi);
285 |
286 | ShaderVariantCollection.ShaderVariant newVariant = PropToObject(shader, variantInfo);
287 | if (!mCollection.Contains(newVariant))
288 | {
289 | mCollection.Add(newVariant);
290 | }
291 | }
292 | }
293 |
294 | ReadFromFile();
295 | }
296 |
297 | public int Intersection(ShaderVariantCollection otherFile)
298 | {
299 | if (otherFile == null)
300 | return -1;
301 |
302 | ShaderVariantCollectionMapper otherMapper = new ShaderVariantCollectionMapper(otherFile);
303 | List excludeVariants = new List();
304 | foreach (KeyValuePair> pair in mMapper)
305 | {
306 | foreach (SerializableShaderVariant serializableVariant in pair.Value)
307 | {
308 | ShaderVariantCollection.ShaderVariant variant = serializableVariant.Deserialize();
309 | if (!otherMapper.HasVariant(variant))
310 | {
311 | excludeVariants.Add(variant);
312 | }
313 | }
314 | }
315 |
316 | foreach (ShaderVariantCollection.ShaderVariant variant in excludeVariants)
317 | {
318 | RemoveVariant(variant);
319 | }
320 |
321 | List excludeShaders = new List();
322 | foreach (KeyValuePair> pair in mMapper)
323 | {
324 | if (pair.Value.Count() == 0)
325 | excludeShaders.Add(pair.Key);
326 | }
327 |
328 | foreach (Shader shader in excludeShaders)
329 | {
330 | RemoveShader(shader);
331 | }
332 |
333 | ReadFromFile();
334 |
335 | return excludeVariants.Count();
336 | }
337 |
338 | public void Refresh() => ReadFromFile();
339 |
340 | public void SetSerializeFlag(bool serialize) => mSerializedFlag = serialize;
341 |
342 | public void OnBeforeSerialize()
343 | {
344 | if (!mSerializedFlag)
345 | {
346 | return;
347 | }
348 |
349 | if (mMapper == null)
350 | mMapper = new Dictionary>();
351 |
352 | mMapperSerializeKeys.Clear();
353 | mMapperSerializeValues.Clear();
354 |
355 | foreach (var kvp in mMapper)
356 | {
357 | mMapperSerializeKeys.Add(kvp.Key);
358 | //序列化与反序列化都需要浅拷贝一层引用,否则对map的value的list操作,依旧会改变序列化中的数值
359 | mMapperSerializeValues.Add(new ListWrapper(kvp.Value.ToList()));
360 | }
361 | }
362 |
363 | public void OnAfterDeserialize()
364 | {
365 | if (mMapper == null)
366 | mMapper = new Dictionary>();
367 |
368 | mMapper.Clear();
369 | for (int i = 0; i < mMapperSerializeKeys.Count; ++i)
370 | {
371 | mMapper.Add(mMapperSerializeKeys[i], mMapperSerializeValues[i].list.ToList());
372 | }
373 | }
374 | }
375 |
376 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsCollection/Editor/Executable/VariantKeywordCombination.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Text.RegularExpressions;
7 | using UnityEditor;
8 | using UnityEngine;
9 |
10 | namespace Soco.ShaderVariantsCollection
11 | {
12 | [System.Serializable]
13 | public struct KeywordDeclareGroup
14 | {
15 | public List keywords;
16 | }
17 |
18 | public class VariantKeywordCombination : IExecutable
19 | {
20 | public Shader mShader;
21 | public List mKeywordDeclareGroups = new List();
22 |
23 | public override void Execute(ShaderVariantCollectionMapper mapper)
24 | {
25 | if (mShader == null || mKeywordDeclareGroups.Count == 0)
26 | {
27 | Debug.LogError($"执行器{name}无法执行,原因是成员参数不符合规格,可能shader{mShader}为null,或keyword声明组长度为0(当前长度为{mKeywordDeclareGroups.Count})");
28 | return;
29 | }
30 |
31 | if (!mapper.HasShader(mShader))
32 | {
33 | Debug.LogError($"执行器{name}无法执行,原因是收集文件中不包含当前shader{mShader}");
34 | return;
35 | }
36 |
37 | var shaderVariants = mapper.GetShaderVariants(mShader);
38 | List combinationList = new List();
39 | combinationList.Add(new string[0]);
40 |
41 | foreach (KeywordDeclareGroup declareGroup in mKeywordDeclareGroups)
42 | {
43 | foreach (var keyword in declareGroup.keywords)
44 | {
45 | if (keyword == "_")
46 | {
47 | continue;
48 | }
49 | else
50 | {
51 | int combinationLimit = combinationList.Count;
52 | for (int i = 0; i < combinationLimit; ++i)
53 | {
54 | List combination = combinationList[i].ToList();
55 | if (!combination.Contains(keyword))
56 | combination.Add(keyword);
57 |
58 | combinationList.Add(combination.ToArray());
59 | }
60 | }
61 | }
62 | }
63 |
64 | foreach (ShaderVariantCollection.ShaderVariant variant in shaderVariants.ToArray())
65 | {
66 | //去除已有变体中包含的multi_compile声明
67 | var removeMultiCompileKeywordList = variant.keywords.Where((string k) =>
68 | {
69 | foreach (KeywordDeclareGroup declareGroup in mKeywordDeclareGroups)
70 | {
71 | foreach (var keyword in declareGroup.keywords)
72 | {
73 | if (k == keyword)
74 | return false;
75 | }
76 | }
77 |
78 | return true;
79 | });
80 |
81 | foreach (string[] combination in combinationList)
82 | {
83 | try
84 | {
85 | ShaderVariantCollection.ShaderVariant newVariant =
86 | new ShaderVariantCollection.ShaderVariant(variant.shader, variant.passType,
87 | removeMultiCompileKeywordList.Concat(combination).ToArray());
88 |
89 | mapper.AddVariant(newVariant);
90 | }
91 | catch (Exception e)
92 | {
93 | continue;
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | [ShaderVariantCollectionToolEditor(typeof(VariantKeywordCombination))]
101 | class VariantKeywordCombinationEditor : ShaderVariantCollectionToolEditor
102 | {
103 | private static MethodInfo _GetShaderGlobalKeywordsMethod = null;
104 | private static MethodInfo _GetShaderLocalKeywordsMethod = null;
105 |
106 | private static MethodInfo GetShaderGlobalKeywordsMethod
107 | {
108 | get
109 | {
110 | if(_GetShaderGlobalKeywordsMethod == null)
111 | _GetShaderGlobalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords",
112 | BindingFlags.NonPublic | BindingFlags.Static);
113 |
114 | return _GetShaderGlobalKeywordsMethod;
115 | }
116 | }
117 |
118 | private static MethodInfo GetShaderLocalKeywordsMethod
119 | {
120 | get
121 | {
122 | if(_GetShaderLocalKeywordsMethod == null)
123 | _GetShaderLocalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords",
124 | BindingFlags.NonPublic | BindingFlags.Static);
125 |
126 | return _GetShaderLocalKeywordsMethod;
127 | }
128 | }
129 |
130 | private string[] mShaderKeywords = new string[0];
131 | private int mSelectedKeywordIndex = 0;
132 |
133 | private enum KeywordMode
134 | {
135 | CustomKeyword,
136 | DefaultKeyword,
137 | DeclareStatement
138 | }
139 |
140 | private KeywordMode mKeywordMode = KeywordMode.CustomKeyword;
141 | private string mDeclareStatement = "";
142 |
143 | private const string cDeclarePattern = @"#pragma\s+(multi_compile(?:_\w+)?)+(?:\s+(\w+))*";
144 |
145 | public void OnEnable()
146 | {
147 | VariantKeywordCombination obj = target as VariantKeywordCombination;
148 |
149 | if (obj.mShader != null)
150 | {
151 | string[] globalKeywords = GetShaderGlobalKeywordsMethod.Invoke(null, new object[] { obj.mShader }) as string[];
152 | string[] localKeywords = GetShaderLocalKeywordsMethod.Invoke(null, new object[] { obj.mShader }) as string[];
153 | mShaderKeywords = globalKeywords.Concat(localKeywords).ToArray();
154 | Array.Sort(mShaderKeywords);
155 | mSelectedKeywordIndex = 0;
156 | }
157 | }
158 |
159 | public override void OnInspectorGUI()
160 | {
161 | VariantKeywordCombination obj = target as VariantKeywordCombination;
162 |
163 | EditorGUILayout.BeginVertical();
164 |
165 | EditorGUILayout.BeginHorizontal();
166 | EditorGUI.BeginChangeCheck();
167 | Shader newShader = EditorGUILayout.ObjectField("Shader", obj.mShader, typeof(Shader)) as Shader;
168 | if (EditorGUI.EndChangeCheck() && newShader != obj.mShader)
169 | {
170 | Undo.RecordObject(obj, $"{obj.GetType().Name} change shader");
171 |
172 | obj.mShader = newShader;
173 | if (obj.mShader == null)
174 | mShaderKeywords = new string[0];
175 | else
176 | {
177 | string[] globalKeywords = GetShaderGlobalKeywordsMethod.Invoke(null, new object[] { obj.mShader }) as string[];
178 | string[] localKeywords = GetShaderLocalKeywordsMethod.Invoke(null, new object[] { obj.mShader }) as string[];
179 | mShaderKeywords = globalKeywords.Concat(localKeywords).ToArray();
180 | Array.Sort(mShaderKeywords);
181 | }
182 | obj.mKeywordDeclareGroups.Clear();
183 |
184 | mSelectedKeywordIndex = 0;
185 | }
186 |
187 | if (obj.mShader != null && GUILayout.Button("尝试收集声明组"))
188 | {
189 | Undo.RecordObject(obj, $"{obj.GetType().Name} add keyword throw declare statement");
190 | obj.mKeywordDeclareGroups.Clear();
191 | TryParseShader(obj.mShader, obj.mKeywordDeclareGroups);
192 |
193 | }
194 | EditorGUILayout.EndHorizontal();
195 |
196 | EditorGUILayout.BeginHorizontal();
197 | mKeywordMode = (KeywordMode)EditorGUILayout.EnumPopup("选择模式", mKeywordMode);
198 | if (mKeywordMode == KeywordMode.CustomKeyword)
199 | {
200 | mSelectedKeywordIndex = EditorGUILayout.Popup(mSelectedKeywordIndex, mShaderKeywords);
201 | }
202 | else if (mKeywordMode == KeywordMode.DeclareStatement)
203 | {
204 | mDeclareStatement = EditorGUILayout.TextField("声明语句", mDeclareStatement);
205 | }
206 | EditorGUILayout.EndHorizontal();
207 |
208 | for (int i = 0; i < obj.mKeywordDeclareGroups.Count; ++i)
209 | {
210 | EditorGUILayout.BeginHorizontal();
211 |
212 | for (int j = 0; j < obj.mKeywordDeclareGroups[i].keywords.Count; ++j)
213 | {
214 | string keyword = obj.mKeywordDeclareGroups[i].keywords[j];
215 | if (GUILayout.Button(new GUIContent(keyword, keyword)))
216 | {
217 | Undo.RecordObject(obj, $"{obj.GetType().Name} delete declareGroup's keyword");
218 | obj.mKeywordDeclareGroups[i].keywords.RemoveAt(j);
219 | }
220 | }
221 |
222 | if (GUILayout.Button(new GUIContent("+", "将keyword添加到当前组合中"), GUILayout.Width(20)))
223 | {
224 | if (mKeywordMode == KeywordMode.CustomKeyword)
225 | {
226 | if (!obj.mKeywordDeclareGroups[i].keywords.Contains(mShaderKeywords[mSelectedKeywordIndex]))
227 | {
228 | Undo.RecordObject(obj, $"{obj.GetType().Name} add keyword to declareGroup");
229 | obj.mKeywordDeclareGroups[i].keywords.Add(mShaderKeywords[mSelectedKeywordIndex]);
230 | }
231 | }
232 | else if(mKeywordMode == KeywordMode.DefaultKeyword)
233 | {
234 | if (!obj.mKeywordDeclareGroups[i].keywords.Contains("_"))
235 | {
236 | Undo.RecordObject(obj, $"{obj.GetType().Name} add default keyword to declareGroup");
237 | obj.mKeywordDeclareGroups[i].keywords.Add("_");
238 | }
239 | }
240 | else if (mKeywordMode == KeywordMode.DeclareStatement)
241 | {
242 | Undo.RecordObject(obj, $"{obj.GetType().Name} add keyword throw declare statement");
243 | foreach (string keyword in ParseDeclareStatement(mDeclareStatement))
244 | {
245 | if (!obj.mKeywordDeclareGroups[i].keywords.Contains(keyword))
246 | obj.mKeywordDeclareGroups[i].keywords.Add(keyword);
247 | }
248 | }
249 | }
250 |
251 | if (GUILayout.Button(new GUIContent("-", "将当前变体组删除"), GUILayout.Width(20)))
252 | {
253 | Undo.RecordObject(obj, $"{obj.GetType().Name} delete declareGroup");
254 | obj.mKeywordDeclareGroups.RemoveAt(i);
255 | EditorGUILayout.EndHorizontal();
256 | break;
257 | }
258 |
259 | EditorGUILayout.EndHorizontal();
260 | }
261 |
262 | if (GUILayout.Button(new GUIContent("+", "添加变体声明组"), GUILayout.Width(20)))
263 | {
264 | Undo.RecordObject(obj, $"{obj.GetType().Name} add keywords");
265 | if (mKeywordMode == KeywordMode.DeclareStatement)
266 | {
267 | obj.mKeywordDeclareGroups.Add(new KeywordDeclareGroup(){keywords = ParseDeclareStatement(mDeclareStatement).ToList()});
268 | }
269 | else
270 | {
271 | obj.mKeywordDeclareGroups.Add(new KeywordDeclareGroup(){keywords = new List()});
272 | }
273 | }
274 |
275 | EditorGUILayout.EndVertical();
276 | }
277 |
278 | private void TryParseShader(Shader shader, List targetList)
279 | {
280 | string path = AssetDatabase.GetAssetPath(shader);
281 |
282 | if (!File.Exists(path))
283 | {
284 | Debug.LogError($"{shader}所在路径{path}不存在,可能Shader是Unity内置Shader");
285 | return;
286 | }
287 |
288 | using (var sr = new StreamReader(path))
289 | {
290 | string content = sr.ReadToEnd();
291 | //正则前加行首和空字符,这样去除注释的声明
292 | MatchCollection matches = Regex.Matches(content, @"^\s+"+cDeclarePattern, RegexOptions.Multiline);
293 |
294 | foreach (Match match in matches)
295 | {
296 | string[] declareGroup = ParseDeclareStatement(match.Groups[0].Value);
297 | if (declareGroup.Length == 0)
298 | continue;
299 |
300 | bool hasDeclared = targetList.FindIndex(
301 | kdg => kdg.keywords.OrderBy(k => k).SequenceEqual(declareGroup.OrderBy(k => k))) > 0;
302 |
303 | if (!hasDeclared)
304 | targetList.Add(new KeywordDeclareGroup(){keywords = declareGroup.ToList()});
305 | }
306 | }
307 | }
308 |
309 | private string[] ParseDeclareStatement(string statement)
310 | {
311 | MatchCollection matches = Regex.Matches(statement, cDeclarePattern);
312 |
313 | if (matches.Count == 0)
314 | {
315 | Debug.LogError($"当前声明语句{statement}无法匹配出字段");
316 | return new string[0];
317 | }
318 | else
319 | {
320 | string multiCompileDeclare = matches[0].Groups[1].Value;
321 | multiCompileDeclare = multiCompileDeclare.Replace("_vertex", "");
322 | multiCompileDeclare = multiCompileDeclare.Replace("_fragment", "");
323 | multiCompileDeclare = multiCompileDeclare.Replace("_hull", "");
324 | multiCompileDeclare = multiCompileDeclare.Replace("_domain", "");
325 | multiCompileDeclare = multiCompileDeclare.Replace("_geometry", "");
326 | multiCompileDeclare = multiCompileDeclare.Replace("_raytracing", "");
327 | multiCompileDeclare = multiCompileDeclare.Replace("_local", "");
328 |
329 | if (multiCompileDeclare == "multi_compile")
330 | {
331 | List keywords = new List();
332 | foreach (Capture capture in matches[0].Groups[2].Captures)
333 | //如果是全下划线
334 | if (capture.Value.All(cr=>cr=='_'))
335 | keywords.Add("_");
336 | else
337 | keywords.Add(capture.Value);
338 |
339 | if (keywords.Count == 1)
340 | {
341 | if (keywords[0] != "_")
342 | {
343 | keywords.Add("_");
344 | }
345 | else
346 | {
347 | Debug.LogError($"{statement}只声明了一个全下划线变体");
348 | return new string[0];
349 | }
350 | }
351 |
352 | return keywords.ToArray();
353 | }
354 | else if (multiCompileDeclare == "multi_compile_instancing")
355 | {
356 | return new string[] { "_", "INSTANCING_ON" };
357 | }
358 | else if (multiCompileDeclare == "multi_compile_particles")
359 | {
360 | return new string[] { "_", "SOFTPARTICLES_ON" };
361 | }
362 | else if (multiCompileDeclare == "multi_compile_fog")
363 | {
364 | return new string[] { "_", "FOG_LINEAR", "FOG_EXP", "FOG_EXP2" };
365 | }
366 | //TODO: Other BuildIn keyword declare, eg. multi_compile_fwdbase
367 | else
368 | {
369 | Debug.LogError($"未实现当前{multiCompileDeclare}变体声明,请根据需要到VariantKeywordCombination中实现");
370 | return new string[0];
371 | }
372 | }
373 |
374 | }
375 | }
376 | }
--------------------------------------------------------------------------------
/SocoShaderVariantsStripper/Editor/ShaderVariantsStripperWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using UnityEditor;
7 | using UnityEngine;
8 | using UnityEngine.Rendering;
9 | using Object = UnityEngine.Object;
10 |
11 | namespace Soco.ShaderVariantsStripper
12 | {
13 | public class ShaderVariantsStripperWindow : EditorWindow
14 | {
15 | private enum ContentState
16 | {
17 | None,
18 | StripCheck,
19 | GlobalSetting,
20 | ShaderSetting
21 | }
22 |
23 | private static ShaderVariantsStripperWindow m_window;
24 | private static ShaderVariantsStripperWindow Window
25 | {
26 | get
27 | {
28 | if (m_window == null)
29 | {
30 | m_window = EditorWindow.GetWindow("ShaderVariantsStripper");
31 | m_window.minSize = cMinWindowSize;
32 | }
33 | return m_window;
34 | }
35 | }
36 |
37 | private GUIStyle mBlackStyle, mItemStyle;
38 |
39 | private ShaderVariantsStripperConfig mConfig;
40 | private string mReadJsonPath = "";
41 |
42 | //右侧菜单滚动条、状态
43 | private ContentState mCurrentContentState = ContentState.None;
44 | private Vector2 mContentScrollViewPos = Vector2.zero;
45 |
46 | private int mSelectedCondition = 0;
47 | private Type[] mConditionTypes = new Type[0];
48 | private string[] mConditionNames = null;
49 |
50 | //Shader View变量
51 | private Vector2 mShaderViewScrollViewPos = Vector2.zero;
52 | private Shader mShaderViewSelectedShader = null;
53 | private bool mTextShaderInput = false;
54 | private string mTextShaderInputStr = "";
55 | private Shader mWillInsertShader = null;
56 | private string mFilterShaderName = "";
57 | private Dictionary mFilterShaders = new Dictionary();
58 |
59 | //剔除检查
60 | private Shader mStripCheckShader;
61 | private ShaderVariantsData mStripCheckData;
62 | private static MethodInfo sGetShaderGlobalKeywordsMethod = null;
63 | private static MethodInfo sGetShaderLocalKeywordsMethod = null;
64 | private string[] mStripCheckShaderGlobalKeywords;
65 | private string[] mStripCheckShaderLocalKeywords;
66 | private enum AccessLevel
67 | {
68 | Global,
69 | Local
70 | }
71 | private AccessLevel mStripCheckAccessLevel = AccessLevel.Global;
72 | private int mStripCheckSelectedKeywordIndex;
73 | private List<(ConditionPair conditionPair, ShaderVariantsStripperConfig config)> mStripCheckConditionList = new List<(ConditionPair condition, ShaderVariantsStripperConfig config)>();
74 | private Vector2 mStripCheckConditionListScrollViewPos = Vector2.zero;
75 |
76 | private static int cBorderWidth = 10;
77 | private static Vector2 cMinWindowSize = new Vector2(1200, 600);
78 | private static int cLeftWidth = 400;
79 | private static int cLeftTopHeight = 100;
80 | private static int cLeftMiddleHeight = 100;
81 | private static int cMiddleWidth = 20;
82 |
83 | [MenuItem("Tools/Soco/ShaderVariantsStripper/OpenStripperWindow", priority = 200)]
84 | public static void OpenWindow()
85 | {
86 | Window.Show();
87 | }
88 |
89 | public void OnGUI()
90 | {
91 | EditorGUILayout.Space(cBorderWidth);
92 | GUILayout.BeginHorizontal(GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
93 |
94 | #region 左半部分
95 | EditorGUILayout.BeginVertical(GUILayout.MinWidth(cLeftWidth));
96 |
97 | #region 上半部分 读取配置文件
98 | EditorGUILayout.BeginVertical(mBlackStyle, GUILayout.MinHeight(cLeftTopHeight));
99 | EditorGUILayout.LabelField("配置文件:");
100 | ShaderVariantsStripperConfig newConfig = EditorGUILayout.ObjectField(mConfig, typeof(ShaderVariantsStripperConfig), false) as ShaderVariantsStripperConfig;
101 |
102 | if (newConfig != mConfig)
103 | {
104 | SaveObject(mConfig);
105 | mConfig = newConfig;
106 | }
107 |
108 | if (mConfig != null)
109 | {
110 | EditorGUILayout.BeginHorizontal();
111 | Color oriColor = GUI.color;
112 | GUI.color = mConfig.mEnable ? Color.green : Color.red;
113 | if (GUILayout.Button(new GUIContent((mConfig.mEnable ? "已启用" : "已禁用"), "启用或禁用当前配置文件")))
114 | {
115 | mConfig.mEnable = !mConfig.mEnable;
116 | }
117 |
118 | GUI.color = mConfig.mIsWhiteList ? Color.white : Color.grey;
119 | if (GUILayout.Button(new GUIContent((mConfig.mIsWhiteList ? "白名单" : "剔除列表"), "当前配置文件是否为白名单")))
120 | {
121 | mConfig.mIsWhiteList = !mConfig.mIsWhiteList;
122 | }
123 | GUI.color = oriColor;
124 | EditorGUILayout.EndHorizontal();
125 |
126 | EditorGUILayout.BeginHorizontal();
127 | if (GUILayout.Button("序列化Config为Json", GUILayout.Width(cLeftWidth * 0.5f)))
128 | {
129 | string configPath = AssetDatabase.GetAssetPath(mConfig);
130 | string jsonPath = configPath.Replace(".asset", ".json");
131 |
132 | string json = JsonUtility.ToJson(mConfig);
133 |
134 | using (StreamWriter writer = new StreamWriter(jsonPath, false))
135 | {
136 | writer.Write(json);
137 | }
138 |
139 | ShowNotification(new GUIContent($"成功保存至{jsonPath}"));
140 | }
141 |
142 | if (GUILayout.Button("选择读取路径", GUILayout.Width(cLeftWidth * 0.25f)))
143 | {
144 | string configPath = AssetDatabase.GetAssetPath(mConfig);
145 |
146 | mReadJsonPath = EditorUtility.OpenFilePanel("选择JSON路径",
147 | (mReadJsonPath == "" ? Path.GetDirectoryName(configPath) : mReadJsonPath), "json");
148 | }
149 | if (GUILayout.Button("读取JSON", GUILayout.Width(cLeftWidth * 0.25f)) && mReadJsonPath != ""
150 | && EditorUtility.DisplayDialog("注意", "是否读取Json覆盖到当前配置文件,确认后不可恢复", "确认", "停,停!"))
151 | {
152 | if (System.IO.File.Exists(mReadJsonPath))
153 | {
154 | using (StreamReader reader = new StreamReader(mReadJsonPath))
155 | {
156 | string json = reader.ReadToEnd();
157 | JsonUtility.FromJsonOverwrite(json, mConfig);
158 | }
159 | }
160 | else
161 | Debug.LogError($"Json文件{mReadJsonPath}不存在");
162 | }
163 | EditorGUILayout.EndHorizontal();
164 | if (mReadJsonPath != "")
165 | {
166 | EditorGUILayout.LabelField(new GUIContent(mReadJsonPath, mReadJsonPath));
167 | }
168 | }
169 | else//mConfig == null
170 | {
171 | Color oriColor = GUI.color;
172 | GUI.color = mCurrentContentState == ContentState.StripCheck ? Color.green : oriColor;
173 | if (GUILayout.Button(new GUIContent("剔除检查", "给定一组变体和条件,检查该变体是否会被剔除")))
174 | {
175 | mCurrentContentState = ContentState.StripCheck;
176 | }
177 | GUI.color = oriColor;
178 | }
179 |
180 | EditorGUILayout.EndVertical();
181 | #endregion
182 |
183 | EditorGUILayout.Space(cBorderWidth);
184 |
185 | #region 中部 全局设置
186 | EditorGUILayout.BeginVertical(mBlackStyle, GUILayout.MinHeight(cLeftMiddleHeight));
187 | EditorGUILayout.LabelField("Global Setting View");
188 | if (mConfig != null)
189 | {
190 | string globalText = "全局设置" +
191 | (mConfig.mGlobalConditions.Count == 0 ? " " : $"({mConfig.mGlobalConditions.Count}条)");
192 |
193 | string applyText = "";
194 | if (mConfig.mIsWhiteList)
195 | {
196 | applyText = (mConfig.mShaderConditions.Count == 0 ? "(当前无效)" : "(应用于ShaderView中Shader)");
197 | }
198 | else
199 | {
200 | applyText = (mConfig.mShaderConditions.Count == 0 ? "(应用于全体Shader)" : "(应用于ShaderView中Shader)");
201 | }
202 |
203 | if (GUILayout.Button(new GUIContent(globalText + applyText, "浏览全局设置"), GUILayout.ExpandHeight(true),
204 | GUILayout.ExpandWidth(true)))
205 | {
206 | mCurrentContentState = ContentState.GlobalSetting;
207 | }
208 | }
209 | EditorGUILayout.EndVertical();
210 | #endregion
211 |
212 | EditorGUILayout.Space(cBorderWidth);
213 |
214 | #region 下半部分 选择Shader
215 | EditorGUILayout.BeginVertical(mBlackStyle, GUILayout.MinHeight(position.height - cLeftTopHeight - cLeftMiddleHeight - 4 * cBorderWidth));
216 | EditorGUILayout.LabelField("Shader View");
217 |
218 | if (mConfig != null)
219 | {
220 | EditorGUILayout.BeginHorizontal();
221 | if (mTextShaderInput)
222 | {
223 | mTextShaderInputStr = EditorGUILayout.TextField(mTextShaderInputStr);
224 | }
225 | else
226 | {
227 | mWillInsertShader = EditorGUILayout.ObjectField(mWillInsertShader, typeof(Shader)) as Shader;
228 | }
229 |
230 | Color oriColor = GUI.color;
231 | if (mTextShaderInput)
232 | GUI.color = Color.red;
233 |
234 | if (GUILayout.Button("文本输入"))
235 | {
236 | mTextShaderInput = !mTextShaderInput;
237 | }
238 |
239 | GUI.color = oriColor;
240 |
241 | if (GUILayout.Button("添加"))
242 | {
243 | if (mTextShaderInput)
244 | {
245 | mWillInsertShader = Shader.Find(mTextShaderInputStr);
246 | }
247 |
248 | if (mWillInsertShader != null && !mConfig.mShaderConditions.ContainsKey(mWillInsertShader))
249 | {
250 | var item = new ShaderVariantsItem();
251 | item.conditionPairs = new List();
252 | item.applyGlobalConfig = true;
253 | mConfig.mShaderConditions.Add(mWillInsertShader, item);
254 | if (mFilterShaderName != "" && mWillInsertShader.name.IndexOf(mFilterShaderName, StringComparison.OrdinalIgnoreCase) > 0)
255 | {
256 | mFilterShaders.Add(mWillInsertShader, item);
257 | }
258 | }
259 |
260 | }
261 | EditorGUILayout.EndHorizontal();
262 |
263 | string prevFilterShaderName = mFilterShaderName;
264 | mFilterShaderName = EditorGUILayout.TextField("过滤", mFilterShaderName);
265 | if (mFilterShaderName == "")
266 | {
267 | mFilterShaders.Clear();
268 | }
269 | else if (prevFilterShaderName != mFilterShaderName)
270 | {
271 | mFilterShaders.Clear();
272 | foreach (var kvp in mConfig.mShaderConditions)
273 | {
274 | Shader shader = kvp.Key;
275 | if (shader.name.IndexOf(mFilterShaderName, StringComparison.OrdinalIgnoreCase) > 0)
276 | {
277 | mFilterShaders.Add(kvp.Key, kvp.Value);
278 | }
279 | }
280 | }
281 |
282 | mShaderViewScrollViewPos = EditorGUILayout.BeginScrollView(mShaderViewScrollViewPos);
283 | Shader willRemoveInDict = null;
284 |
285 | Dictionary displayList =
286 | mFilterShaderName == "" ? mConfig.mShaderConditions : mFilterShaders;
287 |
288 | foreach (var kvp in displayList)
289 | {
290 | Shader shader = kvp.Key;
291 | ShaderVariantsItem item = kvp.Value;
292 |
293 | Color oriGUIColor = GUI.color;
294 | if (shader == mShaderViewSelectedShader)
295 | GUI.color = Color.green;
296 |
297 | EditorGUILayout.BeginHorizontal();
298 | if (GUILayout.Button(new GUIContent(shader.name, shader.name), GUILayout.MinWidth(cLeftWidth - 100)))
299 | {
300 | if (mShaderViewSelectedShader == shader)//选中状态下再次选择,在项目中定位
301 | {
302 | Selection.activeObject = shader;
303 | EditorGUIUtility.PingObject(shader);
304 | }
305 |
306 | mShaderViewSelectedShader = shader;
307 | mCurrentContentState = ContentState.ShaderSetting;
308 | }
309 |
310 | GUI.color = item.applyGlobalConfig ? Color.green : Color.red;
311 | if (GUILayout.Button(new GUIContent("G", "应用全局设置"), GUILayout.MinWidth(45)))
312 | {
313 | item.applyGlobalConfig =
314 | !item.applyGlobalConfig;
315 | }
316 |
317 | GUI.color = oriGUIColor;
318 | if (GUILayout.Button("删除", GUILayout.MinWidth(45)))
319 | {
320 | willRemoveInDict = shader;
321 | if (mCurrentContentState == ContentState.ShaderSetting && mShaderViewSelectedShader == shader)
322 | {
323 | mCurrentContentState = ContentState.None;
324 | mShaderViewSelectedShader = null;
325 | }
326 | }
327 | EditorGUILayout.EndHorizontal();
328 | }
329 |
330 | if (willRemoveInDict != null)
331 | {
332 | Undo.RegisterCompleteObjectUndo(mConfig, "ShaderVariantsStripper config delete shader");
333 | mConfig.mShaderConditions.Remove(willRemoveInDict);
334 | mFilterShaders.Remove(willRemoveInDict);
335 | }
336 |
337 |
338 | EditorGUILayout.EndScrollView();
339 | }
340 |
341 | EditorGUILayout.EndVertical();
342 | #endregion
343 |
344 | EditorGUILayout.EndVertical();
345 | #endregion
346 |
347 | #region 中间分隔线
348 | EditorGUILayout.BeginVertical(GUILayout.MinWidth(cMiddleWidth));
349 | EditorGUILayout.Space(10);
350 | EditorGUILayout.EndVertical();
351 | #endregion
352 |
353 | #region 右半部分
354 | int rightWidth = (int)(position.width - cLeftWidth - cMiddleWidth - 10);
355 | EditorGUILayout.BeginVertical(mBlackStyle, GUILayout.MinWidth(rightWidth), GUILayout.MinHeight(position.height - cBorderWidth * 2));
356 | if (mConfig != null)
357 | {
358 | #region 标题状态+获取列表
359 | List conditionList = null;
360 | switch (mCurrentContentState)
361 | {
362 | case ContentState.GlobalSetting:
363 | EditorGUILayout.LabelField("全局设置");
364 | conditionList = mConfig.mGlobalConditions;
365 | break;
366 | case ContentState.ShaderSetting:
367 | EditorGUILayout.LabelField($"Shader<{mShaderViewSelectedShader.name}>设置");
368 | if (mShaderViewSelectedShader != null && mConfig.mShaderConditions.TryGetValue(mShaderViewSelectedShader, out ShaderVariantsItem svi))
369 | {
370 | conditionList = svi.conditionPairs;
371 | }
372 | break;
373 | default:
374 | EditorGUILayout.LabelField("未选择,请选择<全局设置>或");
375 | break;
376 | }
377 | #endregion
378 |
379 | if (conditionList != null)
380 | {
381 | #region 添加条件
382 | EditorGUILayout.BeginHorizontal();
383 | mSelectedCondition = EditorGUILayout.Popup(mSelectedCondition, mConditionNames, GUILayout.MinWidth(rightWidth * 0.7f));
384 | if (GUILayout.Button("添加", GUILayout.MinWidth(rightWidth * 0.3f)))
385 | {
386 | if (mSelectedCondition >= mConditionTypes.Length)
387 | {
388 | Debug.LogError("程序有改变,请重新打开窗口");
389 | return;
390 | }
391 |
392 | ShaderVariantsStripperCondition condition =
393 | Activator.CreateInstance(mConditionTypes[mSelectedCondition]) as
394 | ShaderVariantsStripperCondition;
395 | conditionList.Add(new ConditionPair(){condition = condition, strip = false, priority = 0});
396 | }
397 | EditorGUILayout.EndHorizontal();
398 | #endregion
399 |
400 | #region 浏览条件
401 | mContentScrollViewPos = EditorGUILayout.BeginScrollView(mContentScrollViewPos);
402 |
403 | for (int i = 0; i < conditionList.Count; ++i)
404 | {
405 | //var (globalCondition, strip) = conditionList[i];
406 | ConditionPair conditionPair = conditionList[i];
407 | // var condition = conditionList[i].condition;
408 | // bool strip = conditionList[i].strip;
409 | // uint priority = conditionList[i].priority;
410 |
411 | EditorGUILayout.BeginHorizontal(mItemStyle, GUILayout.Width(rightWidth), GUILayout.Height(50));
412 |
413 | if (!mConfig.mIsWhiteList)
414 | {
415 | Color oriGUIColor = GUI.color;
416 | if (conditionPair.strip)
417 | GUI.color = Color.red;
418 | else
419 | GUI.color = Color.green;
420 | if (GUILayout.Button((conditionPair.strip ? "剔除" : "保留"), GUILayout.Width(50),
421 | GUILayout.Height(50)))
422 | {
423 | //conditionList[i] = new ConditionPair(){condition = condition, strip = !strip, priority = priority};
424 | conditionPair.strip = !conditionPair.strip;
425 | conditionList[i] = conditionPair;
426 | }
427 | GUI.color = oriGUIColor;
428 |
429 | uint newPriority = (uint)Mathf.Max(0,
430 | EditorGUILayout.IntField((int)conditionPair.priority, GUILayout.Width(50),
431 | GUILayout.Height(50)));
432 | if (newPriority != conditionPair.priority)
433 | {
434 | conditionPair.priority = newPriority;
435 | conditionList[i] = conditionPair;
436 | }
437 | }
438 |
439 | if (conditionPair.condition == null)
440 | conditionList.RemoveAt(i);
441 |
442 | if (GUILayout.Button(conditionPair.condition.Overview(), GUILayout.Width(rightWidth - 3 * 50 - 25),
443 | GUILayout.Height(50)))
444 | {
445 | ShaderVariantsStripperConditionWindow.Window.mCondition = conditionPair.condition;
446 | ShaderVariantsStripperConditionOnGUIContext context = new ShaderVariantsStripperConditionOnGUIContext()
447 | {
448 | shader = mCurrentContentState == ContentState.ShaderSetting ? mShaderViewSelectedShader : null
449 | };
450 | context.Init();
451 | ShaderVariantsStripperConditionWindow.Window.mContext = context;
452 |
453 | ShaderVariantsStripperConditionWindow.Window.Show();
454 | ShowNotification(new GUIContent("已打开条件窗口,如未发现请检查窗口是否被当前窗口覆盖"));
455 | }
456 |
457 | if (GUILayout.Button("删除", GUILayout.Width(50), GUILayout.Height(50)))
458 | {
459 | Undo.RegisterCompleteObjectUndo(mConfig, "ShaderVariantsStripper config delete condition");
460 | conditionList.RemoveAt(i);
461 | }
462 |
463 | EditorGUILayout.BeginVertical(GUILayout.Width(25), GUILayout.Height(50));
464 | if (GUILayout.Button("↑", GUILayout.Width(25), GUILayout.Height(25)) && i != 0)
465 | (conditionList[i - 1], conditionList[i]) = (conditionList[i], conditionList[i - 1]);
466 | if (GUILayout.Button("↓", GUILayout.Width(25), GUILayout.Height(25)) && i != conditionList.Count - 1)
467 | (conditionList[i + 1], conditionList[i]) = (conditionList[i], conditionList[i + 1]);
468 |
469 | EditorGUILayout.EndVertical();
470 |
471 | EditorGUILayout.EndHorizontal();
472 | EditorGUILayout.Space(5);
473 | }
474 | EditorGUILayout.EndScrollView();
475 | #endregion
476 | }
477 | }
478 | else// mConfig == null
479 | {
480 | #region 剔除检查
481 | if (mCurrentContentState == ContentState.StripCheck)
482 | {
483 | Shader nextStripCheckShader =
484 | EditorGUILayout.ObjectField("剔除检查目标Shader", mStripCheckShader, typeof(Shader)) as Shader;
485 |
486 | if (nextStripCheckShader != mStripCheckShader)
487 | {
488 | mStripCheckShader = nextStripCheckShader;
489 | mStripCheckData = ShaderVariantsData.GetDefaultShaderVariantsData();
490 | mStripCheckAccessLevel = AccessLevel.Global;
491 | mStripCheckSelectedKeywordIndex = 0;
492 | if (nextStripCheckShader != null)
493 | {
494 | mStripCheckShaderGlobalKeywords = sGetShaderGlobalKeywordsMethod.Invoke(null, new object[] { nextStripCheckShader }) as string[];
495 | mStripCheckShaderLocalKeywords = sGetShaderLocalKeywordsMethod.Invoke(null, new object[] { nextStripCheckShader }) as string[];
496 | }
497 | }
498 |
499 | if (mStripCheckShader != null)
500 | {
501 | EditorGUILayout.LabelField("变体条件");
502 | mStripCheckData.shaderType = (ShaderVariantsDataShaderType) EditorGUILayout.EnumPopup("ShaderType", mStripCheckData.shaderType);
503 | mStripCheckData.shaderType = (ShaderVariantsDataShaderType)Mathf.Clamp((int)mStripCheckData.shaderType,
504 | (int)ShaderVariantsDataShaderType.Vertex, (int)ShaderVariantsDataShaderType.RayTracing + 1);
505 | mStripCheckData.passType = (UnityEngine.Rendering.PassType) EditorGUILayout.EnumPopup("PassType", mStripCheckData.passType);
506 | mStripCheckData.passName = EditorGUILayout.TextField("PassName", mStripCheckData.passName);
507 |
508 | string[] keywords = mStripCheckData.GetShaderKeywords();
509 |
510 | EditorGUILayout.LabelField($"Keyword,共{keywords.Length}个");
511 |
512 | EditorGUILayout.BeginHorizontal();
513 | AccessLevel newAccessLevel = (AccessLevel)EditorGUILayout.Popup((int) mStripCheckAccessLevel, new string[] { "Global", "Local" }, GUILayout.Width(rightWidth * 0.3f));
514 | if (newAccessLevel != mStripCheckAccessLevel)
515 | {
516 | mStripCheckSelectedKeywordIndex = 0;
517 | mStripCheckAccessLevel = newAccessLevel;
518 | }
519 |
520 | if (mStripCheckAccessLevel == AccessLevel.Global && mStripCheckShaderGlobalKeywords.Length > 0 ||
521 | mStripCheckAccessLevel == AccessLevel.Local && mStripCheckShaderLocalKeywords.Length > 0)
522 | {
523 | string[] keywordList = mStripCheckAccessLevel == AccessLevel.Global
524 | ? mStripCheckShaderGlobalKeywords
525 | : mStripCheckShaderLocalKeywords;
526 | mStripCheckSelectedKeywordIndex = EditorGUILayout.Popup(mStripCheckSelectedKeywordIndex,
527 | keywordList, GUILayout.Width(rightWidth * 0.3f));
528 |
529 | string selectedKeyword = keywordList[mStripCheckSelectedKeywordIndex];
530 | UnityEngine.Rendering.ShaderKeyword newKeyword =
531 | mStripCheckAccessLevel == AccessLevel.Global
532 | ? new ShaderKeyword(selectedKeyword)
533 | : new ShaderKeyword(mStripCheckShader, selectedKeyword);
534 |
535 | if (GUILayout.Button("添加", GUILayout.Width(rightWidth * 0.3f)) && !mStripCheckData.IsKeywordEnabled(newKeyword))
536 | {
537 | mStripCheckData.EnableKeyword(newKeyword);
538 | }
539 | }
540 | EditorGUILayout.EndHorizontal();
541 |
542 | EditorGUILayout.BeginHorizontal();
543 | int keywordCount = 0;
544 | foreach (var keyword in keywords)
545 | {
546 | if (GUILayout.Button(new GUIContent(keyword, keyword), GUILayout.Width(rightWidth * 0.2f)))
547 | {
548 | mStripCheckData.DisableKeyword(keyword);
549 | }
550 | keywordCount++;
551 | if (keywordCount % 4 == 0)
552 | {
553 | EditorGUILayout.EndHorizontal();
554 | EditorGUILayout.BeginHorizontal();
555 | }
556 | else
557 | EditorGUILayout.Space();
558 | }
559 | EditorGUILayout.EndHorizontal();
560 |
561 | EditorGUILayout.BeginHorizontal();
562 | if (GUILayout.Button("测试剔除"))
563 | {
564 | mStripCheckConditionList.Clear();
565 |
566 | string[] guids = AssetDatabase.FindAssets("t:ShaderVariantsStripperConfig", new string[]{"Assets", "Packages"});
567 |
568 | ShaderVariantsStripperConfig[] allConfigs = (from guid in guids
569 | select AssetDatabase.LoadAssetAtPath(
570 | AssetDatabase.GUIDToAssetPath(guid)))
571 | .ToArray();
572 | ShaderVariantsStripperCode.StripVariant(mStripCheckShader, mStripCheckData, allConfigs, mStripCheckConditionList);
573 |
574 | bool strip = mStripCheckConditionList.Any(conditionPair_fromConfig =>
575 | conditionPair_fromConfig.conditionPair.strip);
576 | bool hasWhiteList = mStripCheckConditionList.Any(conditionPair_fromConfig =>
577 | conditionPair_fromConfig.config.mIsWhiteList);
578 | string res = hasWhiteList ? "白名单" : (strip ? "剔除" : "保留");
579 | ShowNotification(new GUIContent("检测结果为:" + res));
580 | mStripCheckConditionListScrollViewPos = Vector2.zero;
581 | }
582 |
583 | if (mStripCheckConditionList.Count != 0 && GUILayout.Button("清空结果"))
584 | {
585 | mStripCheckConditionList.Clear();
586 | }
587 | EditorGUILayout.EndHorizontal();
588 |
589 | if (mStripCheckConditionList.Count != 0)
590 | {
591 | bool strip = mStripCheckConditionList.Any(conditionPair_fromConfig =>
592 | conditionPair_fromConfig.conditionPair.strip);
593 |
594 | bool hasWhiteList = mStripCheckConditionList.Any(conditionPair_fromConfig =>
595 | conditionPair_fromConfig.config.mIsWhiteList);
596 |
597 | string res = hasWhiteList ? "白名单" : (strip ? "剔除" : "保留");
598 | EditorGUILayout.LabelField("检测结果为:" + res);
599 |
600 | mStripCheckConditionListScrollViewPos =
601 | EditorGUILayout.BeginScrollView(mStripCheckConditionListScrollViewPos);
602 |
603 | foreach (var conditionPair_fromConfig in mStripCheckConditionList)
604 | {
605 | string conditionRes = "";
606 | if (conditionPair_fromConfig.config.mIsWhiteList)
607 | {
608 | conditionRes = " 白名单";
609 | }
610 | else
611 | {
612 | conditionRes = (conditionPair_fromConfig.conditionPair.strip ? " 剔除" : " 保留") + $" 优先级:{conditionPair_fromConfig.conditionPair.priority}";
613 | }
614 |
615 | if (GUILayout.Button(conditionPair_fromConfig.config.name + conditionRes + $" 条件:{conditionPair_fromConfig.conditionPair.condition.Overview()}"))
616 | {
617 | Selection.activeObject = conditionPair_fromConfig.config;
618 | EditorGUIUtility.PingObject(conditionPair_fromConfig.config);
619 | }
620 | }
621 | EditorGUILayout.EndScrollView();
622 | }
623 | }
624 | }
625 | #endregion
626 | }
627 | EditorGUILayout.EndVertical();
628 | #endregion
629 |
630 | GUILayout.EndHorizontal();
631 | }
632 |
633 | void SaveObject(Object obj)
634 | {
635 | if (obj != null)
636 | {
637 | EditorUtility.SetDirty(obj);
638 | AssetDatabase.SaveAssets();
639 | AssetDatabase.Refresh();
640 | }
641 | }
642 |
643 | Texture2D MakeTex(int width, int height, Color col)
644 | {
645 | Color[] pix = new Color[width * height];
646 | for (int i = 0; i < pix.Length; i++)
647 | pix[i] = col;
648 | Texture2D result = new Texture2D(width, height);
649 | result.SetPixels(pix);
650 | result.Apply();
651 | return result;
652 | }
653 |
654 | private void SetupStyle()
655 | {
656 | if (mBlackStyle == null)
657 | {
658 | Color backColor = EditorGUIUtility.isProSkin ? new Color(0.18f, 0.18f, 0.18f) : new Color(0.7f, 0.7f, 0.7f);
659 | Texture2D _blackTexture;
660 | _blackTexture = MakeTex(4, 4, backColor);
661 | _blackTexture.hideFlags = HideFlags.DontSave;
662 | mBlackStyle = new GUIStyle();
663 | mBlackStyle.normal.background = _blackTexture;
664 | }
665 |
666 | if (mItemStyle == null)
667 | {
668 | Color itemColor = EditorGUIUtility.isProSkin ? new Color(0.3f, 0.3f, 0.3f) : new Color(0.9f, 0.9f, 0.9f);
669 | Texture2D _itemColorTexture;
670 | _itemColorTexture = MakeTex(4, 4, itemColor);
671 | _itemColorTexture.hideFlags = HideFlags.DontSave;
672 | mItemStyle = new GUIStyle();
673 | mItemStyle.normal.background = _itemColorTexture;
674 | }
675 | }
676 |
677 | private void SetupConditionType()
678 | {
679 | mConditionTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(
680 | assembly => assembly.GetTypes()).Where(
681 | type => typeof(ShaderVariantsStripperCondition).IsAssignableFrom(type) && !type.IsAbstract
682 | ).ToArray();
683 |
684 | mConditionNames = mConditionTypes.Select(type => (System.Activator.CreateInstance(type) as ShaderVariantsStripperCondition).GetName()).ToArray();
685 | }
686 |
687 | private static void InitGetKeywordMethod()
688 | {
689 | if (sGetShaderGlobalKeywordsMethod == null)
690 | sGetShaderGlobalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords",
691 | BindingFlags.NonPublic | BindingFlags.Static);
692 |
693 | if (sGetShaderLocalKeywordsMethod == null)
694 | sGetShaderLocalKeywordsMethod = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords",
695 | BindingFlags.NonPublic | BindingFlags.Static);
696 | }
697 |
698 | public void Awake()
699 | {
700 | SetupStyle();
701 | SetupConditionType();
702 | InitGetKeywordMethod();
703 | }
704 |
705 | public void OnDisable()
706 | {
707 | SaveObject(mConfig);
708 | ShaderVariantsStripperConditionWindow.Window.Close();
709 | }
710 | }
711 | }
--------------------------------------------------------------------------------