MaterialEditor GUI重写的歪门邪道
MaterialEditor GUI重写的歪门邪道
序言
以前有人在群里问过怎么过滤掉unity里面的其他没有用的Shader,或者重新将工程里面的Shader列表重新排序。
反编译
这个经过反编译代码,我找到了欺骗世界的方法。(
其实也比较简单,找到绘制下拉窗口的GUI函数调用位置。然后修改过滤方法就行了。
然鹅,比较尴尬的问题是,有的函数是Internal的,需要用歪门邪道才能调用。
先用ILspy找到可以调用Editor里面Internal函数的dll,然后新建一个package,找一个不会用到的dll欺骗引擎,然后就可以在这个package里面调用Internal相关函数了。
也可以看这一篇的详细解释:https://blog.csdn.net/znybn1/article/details/77008982
开始重写
太长不看版:
重写MaterialEditor,
修改OnHeaderControlsGUI,
自定义m_ShaderPopup函数,
用AdvancedDropdown修改过滤逻辑
MaterialEditor
新建完之后,我们需要重写MaterialEditor。也就是需要继承MaterialEditor,并且加上[CustomEditor(typeof(Material))]的Attribute Tag。
[CanEditMultipleObjects]
[CustomEditor(typeof(Material))]
public class CustomMaterialEditor : MaterialEditor
{
private ShaderGUI m_CustomEditorShaderGUI;
public Shader m_CustomEditorShader;
GUIContent noShaderContent = new GUIContent("No Shader Selected");
}
然后要重写OnHeaderControlsGUI这个函数。在Rider里面反编译可以看到下拉框的函数调用是在this.ShaderPopup((GUIStyle) "MiniPulldown");
internal override void OnHeaderControlsGUI()
{
this.serializedObject.Update();
float labelWidth = EditorGUIUtility.labelWidth;
using (new EditorGUI.DisabledScope(!this.IsEnabled()))
{
EditorGUIUtility.labelWidth = 50f;
//替换this.ShaderPopup((GUIStyle) "MiniPulldown");
this.m_ShaderPopup((GUIStyle) "MiniPulldown");
if ((UnityEngine.Object) ((Material) this.target).shader != (UnityEngine.Object) null && !this.HasMultipleMixedShaderValues() && (((Material) this.target).shader.hideFlags & HideFlags.DontSave) == HideFlags.None)
{
if (GUILayout.Button("Edit...", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
AssetDatabase.OpenAsset((UnityEngine.Object) ((Material) this.target).shader);
}
}
EditorGUIUtility.labelWidth = labelWidth;
}
接下来就是写一个一样的函数进行替换就行了。
private void m_ShaderPopup(GUIStyle style)
{
m_VisibleAllShader &= GUIPreferences.guiDebug;
bool m_enabled = GUI.enabled;
Rect rect = EditorGUI.PrefixLabel(EditorGUILayout.GetControlRect(), 47385, EditorGUIUtility.TempContent("Shader"));
EditorGUI.showMixedValue = this.HasMultipleMixedShaderValues();
GUIContent content = EditorGUIUtility.TempContent((UnityEngine.Object) ((Material) this.target).shader != (UnityEngine.Object) null ? ((Material) this.target).shader.name : "No Shader Selected");
if (EditorGUI.DropdownButton(rect, content, FocusType.Keyboard, style))
//替换new MaterialEditor.ShaderSelectionDropdown
new CustomShaderSelectionDropDown(((Material) this.target).shader, new Action<object>(this.OnSelectedShaderPopup), m_VisibleAllShader).Show(rect);
EditorGUI.showMixedValue = false;
GUI.enabled = m_enabled;
}
跳转到这个函数可以看到负责Shader过滤的逻辑具体是在new MaterialEditor.ShaderSelectionDropdown里面。所以我们再如法炮制,重写多一个自定义的ShaderSelectionDropdown。
/// <summary>
/// 重写了MaterialEditor.ShaderSelectionDropdown
/// </summary>
private class CustomShaderSelectionDropDown : AdvancedDropdown
{
private Action<object> m_OnSelectedShaderPopup;
private Shader m_CurrentShader;
//控制是否显示所有Shader的开关
private bool m_VisibleAllShder;
//Copy
private AdvancedDropdownItem FindOrCreateChild(
AdvancedDropdownItem parent,
string path)
{
...
}
public CustomShaderSelectionDropDown(Shader shader, Action<object> onSelectedShaderPopup, bool visibleAllShader)
: base(new AdvancedDropdownState())
{
...
//控制是否显示所有Shader的开关
this.m_VisibleAllShder = visibleAllShader;
}
//Copy
private class ShaderDropdownItem : AdvancedDropdownItem
{
...
}
//Copy
private void AddShaderToMenu(
string prefix,
AdvancedDropdownItem parent,
string fullShaderName,
string shaderName)
{
....
}
protected override void ItemSelected(AdvancedDropdownItem item) => this.m_OnSelectedShaderPopup((object) ((ShaderDropdownItem) item).fullName);
//过滤Shader的函数
protected override AdvancedDropdownItem BuildRoot()
{
AdvancedDropdownItem root = new AdvancedDropdownItem("Shaders");
ShaderInfo[] allShaderInfo = ShaderUtil.GetAllShaderInfo();
List<string> stringList = new List<string>();
List<string> source1 = new List<string>();
List<string> source2 = new List<string>();
List<string> source3 = new List<string>();
foreach (ShaderInfo shaderInfo in allShaderInfo)
{
//主要的过滤逻辑
if (!shaderInfo.name.StartsWith("Deprecated") && !shaderInfo.name.StartsWith("Hidden"))
{
//全部显示
if (m_VisibleAllShder)
{
if (shaderInfo.hasErrors)
source3.Add(shaderInfo.name);
else if (!shaderInfo.supported)
source2.Add(shaderInfo.name);
else if (shaderInfo.name.StartsWith("Legacy Shaders/"))
source1.Add(shaderInfo.name);
else
stringList.Add(shaderInfo.name);
}
else
{
if (shaderInfo.hasErrors)
source3.Add(shaderInfo.name);
else if (!shaderInfo.supported)
source2.Add(shaderInfo.name);
// else if (shaderInfo.name.StartsWith("Legacy Shaders/"))
// source1.Add(shaderInfo.name);
else if (shaderInfo.name.StartsWith("SRP/"))
stringList.Add(shaderInfo.name);
}
}
}
//最后排序进行把ShaderName加入到Menu中
stringList.Sort((Comparison<string>) ((s1, s2) =>
{
int num = s2.Count<char>((Func<char, bool>) (c => c == '/')) - s1.Count<char>((Func<char, bool>) (c => c == '/'));
if (num == 0)
num = s1.CompareTo(s2);
return num;
}));
source1.Sort();
source2.Sort();
source3.Sort();
stringList.ForEach((Action<string>) (s => this.AddShaderToMenu("", root, s, s)));
if (source1.Any<string>() || source2.Any<string>() || source3.Any<string>())
root.AddSeparator();
source1.ForEach((Action<string>) (s => this.AddShaderToMenu("", root, s, s)));
source2.ForEach((Action<string>) (s => this.AddShaderToMenu("Not supported/", root, s, "Not supported/" + s)));
source3.ForEach((Action<string>) (s => this.AddShaderToMenu("Failed to compile/", root, s, "Failed to compile/" + s)));
return root;
}
}
具体的过滤代码是在BuildRoot函数里。可以看到有一个ShaderInfo.name的判断。判断Shader是否报错,过时,支持。
我们就按照具体项目里面的Shader的名字进行过滤,然后将Shader的Name加入到stringList里面就可以了。(stringList里面才是最终项目里面用的Shader)
还有一件事
然后还有另外一个问题,就是这样美术使用是没什么问题的。但是开发的时候,我们想要临时修改过滤设置怎么办。
这个我当时是在Editor写了一个SettingsProvider自己加了多一个Panel控制这个Editor的显示逻辑。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
public static class GUIPreferences
{
static bool m_Loaded = false;
private static bool s_GUIDebugMode = false;
private static bool s_MaterialEditorRenderPreviewBall = true;
public static bool guiDebug
{
get => s_GUIDebugMode;
set
{
if (s_GUIDebugMode == value)
return;
s_GUIDebugMode = value;
}
}
public static bool renderPreViewBall
{
get => s_MaterialEditorRenderPreviewBall;
set
{
if (s_MaterialEditorRenderPreviewBall == value)
return;
s_MaterialEditorRenderPreviewBall = value;
}
}
[SettingsProvider]
static SettingsProvider PreferenceGUI()
{
return new SettingsProvider("Preferences/GUI Preferences", SettingsScope.User)
{
guiHandler = searchContext =>
{
if (!m_Loaded)
Load();
EditorGUIUtility.labelWidth = 300;
guiDebug = EditorGUILayout.Toggle("GUI Debug Mode", guiDebug);
renderPreViewBall = EditorGUILayout.Toggle("Material Editor Render Preview Ball", renderPreViewBall);
}
};
}
static GUIPreferences()
{
Load();
}
static void Load()
{
s_GUIDebugMode = false;
s_MaterialEditorRenderPreviewBall = true;
m_Loaded = true;
}
}
#endif
切换需要调用加了ContextMenu Attribute一样的函数。为了让这个MaterialEditor的那个HeaderHelpAndSettingsGUI显示跟普通的MonoScript一样,需要我们重写DrawHeaderHelpAndSettingsGUI。
需要把传入的Objects数组变成只有当前Material Object。(因为DisplayObjectContextMenu函数需要数组长度为一,才能够显示ContextMenu的函数调用窗口。)
[ContextMenu("SwitchOn")]
void SetVisibleAllShader()
{
m_VisibleAllShader = !m_VisibleAllShader;
Debug.Log($"Visible All Shader :{m_VisibleAllShader}");
}
internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r)
{
Vector2 vector2 = EditorStyles.iconButton.CalcSize(EditorGUI.GUIContents.titleSettingsIcon);
float x = vector2.x;
Rect position = new Rect(r.xMax - x, r.y + 5f, vector2.x, vector2.y);
bool enabled = GUI.enabled;
GUI.enabled = true;
bool flag = EditorGUI.DropdownButton(position, GUIContent.none, FocusType.Passive, EditorStyles.optionsButtonStyle);
GUI.enabled = enabled;
//需要数组长度为一
UnityEngine.Object[] objects;
if (GUIPreferences.guiDebug)
{
objects = new UnityEngine.Object[] {this};
}
else
{
objects = this.targets;
}
if (flag)
EditorUtility.DisplayObjectContextMenu(position, objects, 0);
float num = x + vector2.x;
return EditorGUIUtility.DrawEditorHeaderItems(new Rect(r.xMax - num, r.y + 5f, vector2.x, vector2.y), this.targets);
}
另外一个问题就是以前项目有时会因为这个MaterialPreview窗口导致卡顿,这个当时也可以通过重写OnHeaderIconGUI进行控制Preview窗口是否显示。(其实我觉得这个preview窗口没啥用,而且这个preview窗口也会进入管线渲染流程,会导致额外的渲染bug。CameraType还得做额外的判断处理,实在是麻烦至极。)
internal override void OnHeaderIconGUI(Rect iconRect)
{
if (GUIPreferences.renderPreViewBall)
base.OnHeaderIconGUI(iconRect);
}
全部代码
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace UnityEditor.InternalVisibleEditor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(Material))]
public class CustomMaterialEditor : MaterialEditor
{
private ShaderGUI m_CustomEditorShaderGUI;
public Shader m_CustomEditorShader;
GUIContent noShaderContent = new GUIContent("No Shader Selected");
private bool m_VisibleAllShader = false;
[ContextMenu("SwitchOn")]
void SetVisibleAllShader()
{
m_VisibleAllShader = !m_VisibleAllShader;
Debug.Log($"Visible All Shader :{m_VisibleAllShader}");
}
internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r)
{
Vector2 vector2 = EditorStyles.iconButton.CalcSize(EditorGUI.GUIContents.titleSettingsIcon);
float x = vector2.x;
Rect position = new Rect(r.xMax - x, r.y + 5f, vector2.x, vector2.y);
bool enabled = GUI.enabled;
GUI.enabled = true;
bool flag = EditorGUI.DropdownButton(position, GUIContent.none, FocusType.Passive, EditorStyles.optionsButtonStyle);
GUI.enabled = enabled;
UnityEngine.Object[] objects;
if (GUIPreferences.guiDebug)
{
objects = new UnityEngine.Object[] {this};
}
else
{
objects = this.targets;
}
if (flag)
EditorUtility.DisplayObjectContextMenu(position, objects, 0);
float num = x + vector2.x;
return EditorGUIUtility.DrawEditorHeaderItems(new Rect(r.xMax - num, r.y + 5f, vector2.x, vector2.y), this.targets);
}
internal override void OnHeaderIconGUI(Rect iconRect)
{
if (GUIPreferences.renderPreViewBall)
base.OnHeaderIconGUI(iconRect);
}
internal override void OnHeaderControlsGUI()
{
this.serializedObject.Update();
float labelWidth = EditorGUIUtility.labelWidth;
using (new EditorGUI.DisabledScope(!this.IsEnabled()))
{
EditorGUIUtility.labelWidth = 50f;
this.m_ShaderPopup((GUIStyle) "MiniPulldown");
if ((UnityEngine.Object) ((Material) this.target).shader != (UnityEngine.Object) null && !this.HasMultipleMixedShaderValues() &&
(((Material) this.target).shader.hideFlags & HideFlags.DontSave) == HideFlags.None)
{
if (GUILayout.Button("Edit...", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
AssetDatabase.OpenAsset((UnityEngine.Object) ((Material) this.target).shader);
}
}
EditorGUIUtility.labelWidth = labelWidth;
}
private bool HasMultipleMixedShaderValues()
{
bool flag = false;
Shader shader = (this.targets[0] as Material).shader;
for (int index = 1; index < this.targets.Length; ++index)
{
if ((UnityEngine.Object) shader != (UnityEngine.Object) (this.targets[index] as Material).shader)
{
flag = true;
break;
}
}
return flag;
}
private void m_ShaderPopup(GUIStyle style)
{
m_VisibleAllShader &= GUIPreferences.guiDebug;
bool m_enabled = GUI.enabled;
Rect rect = EditorGUI.PrefixLabel(EditorGUILayout.GetControlRect(), 47385, EditorGUIUtility.TempContent("Shader"));
EditorGUI.showMixedValue = this.HasMultipleMixedShaderValues();
GUIContent content =
EditorGUIUtility.TempContent((UnityEngine.Object) ((Material) this.target).shader != (UnityEngine.Object) null ? ((Material) this.target).shader.name : "No Shader Selected");
if (EditorGUI.DropdownButton(rect, content, FocusType.Keyboard, style))
new CustomShaderSelectionDropDown(((Material) this.target).shader, new Action<object>(this.OnSelectedShaderPopup), m_VisibleAllShader).Show(rect);
EditorGUI.showMixedValue = false;
GUI.enabled = m_enabled;
}
/// <summary>
/// 重写了MaterialEditor.ShaderSelectionDropdown
/// </summary>
private class CustomShaderSelectionDropDown : AdvancedDropdown
{
private Action<object> m_OnSelectedShaderPopup;
private Shader m_CurrentShader;
private bool m_VisibleAllShder;
private AdvancedDropdownItem FindOrCreateChild(
AdvancedDropdownItem parent,
string path)
{
string name = path.Split('/')[0];
foreach (AdvancedDropdownItem child in parent.children)
{
if (child.name == name)
return child;
}
AdvancedDropdownItem child1 = new AdvancedDropdownItem(name);
parent.AddChild(child1);
return child1;
}
public CustomShaderSelectionDropDown(Shader shader, Action<object> onSelectedShaderPopup, bool visibleAllShader)
: base(new AdvancedDropdownState())
{
this.minimumSize = new Vector2(270f, 308f);
this.m_CurrentShader = shader;
this.m_OnSelectedShaderPopup = onSelectedShaderPopup;
this.m_VisibleAllShder = visibleAllShader;
}
private class ShaderDropdownItem : AdvancedDropdownItem
{
private string m_FullName;
private string m_Prefix;
public string fullName => this.m_FullName;
public string prefix => this.m_Prefix;
public ShaderDropdownItem(string prefix, string fullName, string shaderName)
: base(shaderName)
{
this.m_FullName = fullName;
this.m_Prefix = prefix;
this.id = (prefix + fullName + shaderName).GetHashCode();
}
}
private void AddShaderToMenu(
string prefix,
AdvancedDropdownItem parent,
string fullShaderName,
string shaderName)
{
string[] strArray = shaderName.Split('/');
if (strArray.Length > 1)
{
this.AddShaderToMenu(prefix, this.FindOrCreateChild(parent, shaderName), fullShaderName, shaderName.Substring(strArray[0].Length + 1));
}
else
{
ShaderDropdownItem shaderDropdownItem = new ShaderDropdownItem(prefix, fullShaderName, shaderName);
parent.AddChild((AdvancedDropdownItem) shaderDropdownItem);
if ((UnityEngine.Object) this.m_CurrentShader != (UnityEngine.Object) null && this.m_CurrentShader.name == fullShaderName)
this.m_DataSource.selectedIDs.Add(shaderDropdownItem.id);
}
}
protected override void ItemSelected(AdvancedDropdownItem item) => this.m_OnSelectedShaderPopup((object) ((ShaderDropdownItem) item).fullName);
protected override AdvancedDropdownItem BuildRoot()
{
AdvancedDropdownItem root = new AdvancedDropdownItem("Shaders");
ShaderInfo[] allShaderInfo = ShaderUtil.GetAllShaderInfo();
List<string> stringList = new List<string>();
List<string> source1 = new List<string>();
List<string> source2 = new List<string>();
List<string> source3 = new List<string>();
foreach (ShaderInfo shaderInfo in allShaderInfo)
{
if (!shaderInfo.name.StartsWith("Deprecated") && !shaderInfo.name.StartsWith("Hidden"))
{
if (m_VisibleAllShder)
{
if (shaderInfo.hasErrors)
source3.Add(shaderInfo.name);
else if (!shaderInfo.supported)
source2.Add(shaderInfo.name);
else if (shaderInfo.name.StartsWith("Legacy Shaders/"))
source1.Add(shaderInfo.name);
else
stringList.Add(shaderInfo.name);
}
else
{
if (shaderInfo.hasErrors)
source3.Add(shaderInfo.name);
else if (!shaderInfo.supported)
source2.Add(shaderInfo.name);
// else if (shaderInfo.name.StartsWith("Legacy Shaders/"))
// source1.Add(shaderInfo.name);
else if (shaderInfo.name.StartsWith("SRP/"))
stringList.Add(shaderInfo.name);
}
}
}
stringList.Sort((Comparison<string>) ((s1, s2) =>
{
int num = s2.Count<char>((Func<char, bool>) (c => c == '/')) - s1.Count<char>((Func<char, bool>) (c => c == '/'));
if (num == 0)
num = s1.CompareTo(s2);
return num;
}));
source1.Sort();
source2.Sort();
source3.Sort();
stringList.ForEach((Action<string>) (s => this.AddShaderToMenu("", root, s, s)));
if (source1.Any<string>() || source2.Any<string>() || source3.Any<string>())
root.AddSeparator();
source1.ForEach((Action<string>) (s => this.AddShaderToMenu("", root, s, s)));
source2.ForEach((Action<string>) (s => this.AddShaderToMenu("Not supported/", root, s, "Not supported/" + s)));
source3.ForEach((Action<string>) (s => this.AddShaderToMenu("Failed to compile/", root, s, "Failed to compile/" + s)));
return root;
}
}
}
}