MaterialEditor GUI重写的歪门邪道

MaterialEditor GUI重写的歪门邪道

序言

以前有人在群里问过怎么过滤掉unity里面的其他没有用的Shader,或者重新将工程里面的Shader列表重新排序,避免美术使用乱七八糟项目以外的Shader。

反编译

这个经过反编译代码,我找到了欺骗世界的方法。(
其实也比较简单,找到绘制下拉窗口的GUI函数调用位置。然后修改过滤方法就行了。

然鹅,比较尴尬的问题是,有的函数是Internal的,需要用歪门邪道才能调用。

先用ILspy找到可以调用Editor里面Internal函数的dll,然后新建一个package,找一个不会用到的dll欺骗引擎,然后就可以在这个package里面调用Internal相关函数了。
也可以看这一篇的详细解释:https://blog.csdn.net/znybn1/article/details/77008982
图 1
image.png

开始重写

太长不看版:
重写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;
            }
        }
    }
}
posted @ 2024-03-26 19:11  凶恶的真实  阅读(47)  评论(0编辑  收藏  举报