UnityShader自定义属性特性

前言:

在编写UnityShader时,我们常常会使用特性来更换材质球面板的属性外观,除此之外,还可以使用自定义的扩展脚本来实现自定义的材质球界面,参考我之前的文章UnityShaderUI编辑器扩展

但是自定义扩展每次都要单独写一个Editor脚本来扩展对应的Shader,而不能像特性那样,只要加一个[HDR],[Range]就能更改材质面板的界面。

那么我们如何方便又快速的创建自定义的特性,方便我们只要写一个全功能脚本,就能快速自定义扩展不同的Shader呢?

原理:

如图,只需要编写脚本,类继承MaterialPropertyDrawer就可以了,类名的命名方式是特性名+Drawer,如:SingleLineTextureDrawer,或者不加Drawer也可以,Unity会自动添加并识别。

在Shader中使用:

写法:[特性类名(不加Drawer)]

材质面板显示:

自定义Group自动分组功能:

构想:一个特性Group,在Shader属性前添加时,作为折叠栏功能,如[Group]_group1表示一个折叠栏,如[Group(_MainTex,_IntTest)]_group2表示group2折叠栏下包含属性_MainTex与_IntTest属性的绘制或者不绘制。

如图:

效果:

实现:

在我们的构想中,Group可以包含子属性,也可以不包含,包含的子属性的数量不定,所以构建构造函数:

启用keys数组用来存放传递的命名,这些命名作为我们用来分组和查询属性的必要条件。

重写OnGUI函数:

这其中有一个GUIData调用,这是用来存放Shader的属性是否应该被绘制的数据类,如果不这样做,你就会发现就算使用了自定义的特性,这个属性还会被绘制一次,因为我们的实现逻辑需要在Group中处理其子属性的可见性。

GUIData类与自定义方法:

很简单,立面有一个字典,这个字典存放了属性的可见性,同样有两个方法,一个方法设置属性的可见性,一个方法获取属性的可见性。

然后,需要写一个自定义脚本,继承ShaderGUI并且在Shader中使用CustomEditor调用,我们自主实现Shader的OnGUI逻辑,如图:

为什么还要这样自定义ShaderGUI呢?因为上述说过,为了实现Group分组效果,需要避免其子属性不管有没有特性,都应该被Group折叠栏决定显影,若是不自主实现,你就会发现你自定义绘制了一遍,这些子属性不管有没有特性,也会再被绘制一遍,因为没有处理Unity默认的绘制逻辑,所以需要我们自定义一个ShaderGUI实现。

全部代码:

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;


namespace CustomShaderPropertyDrawer
{
    #region Data
    public class GUIData
    {
        private static Dictionary<string, bool> propertyShow = new Dictionary<string, bool>();

        public static void SetPropertyVisible(string property, bool visiable)
        {
            if (propertyShow.ContainsKey(property))
                propertyShow[property] = visiable;
            else
                propertyShow.Add(property, visiable);
        }

        public static bool GetPropertyVisible(string property)
        {
            if (propertyShow.ContainsKey(property))
                return propertyShow[property];
            else
                return false;
        }
    }

    #endregion
    public class GUIShow : ShaderGUI
    {
        internal MaterialProperty[] Pops { get; private set; }


        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            Pops = properties;
            var material = materialEditor.target as Material;
            var shader = material.shader;
            for (int i = 0; i < properties.Length; i++)
            {
                if (System.String.Concat(shader.GetPropertyAttributes(i)).Contains("Group"))
                {
                    materialEditor.ShaderProperty(properties[i], properties[i].displayName);
                }
                else
                {
                    if (GUIData.GetPropertyVisible(properties[i].name))
                        materialEditor.ShaderProperty(properties[i], properties[i].displayName);
                }
            }
        }

    }

    #region MaterialPropertyDrawer
    /// <summary>
    /// Group分组,采样Flodout形式,可使用自定义GUIStyle
    /// </summary>
    public class GroupDrawer : MaterialPropertyDrawer
    {
        readonly string[] keys;
        public GroupDrawer() { }
        public GroupDrawer(params string[] keys)
        {
            this.keys = keys;
        }

        public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
        {
            if (editor.customShaderGUI == null || !(editor.customShaderGUI is GUIShow))
                editor.DefaultShaderProperty(prop, prop.displayName);
            else
            {
                if (prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Int)
                {
                    bool group_foldout = false;
                    switch (prop.type)
                    {
                        case MaterialProperty.PropType.Float:
                            group_foldout = prop.floatValue == 1.0f ? true : false;
                            EditorGUI.BeginChangeCheck();
                            group_foldout = EditorGUI.Foldout(position, group_foldout, prop.displayName);
                            if (EditorGUI.EndChangeCheck())
                            {
                                prop.floatValue = group_foldout ? 1f : 0f;
                                if (keys != null && keys.Length > 0)
                                {
                                    foreach (var key in keys)
                                    {
                                        GUIData.SetPropertyVisible(key, group_foldout);
                                    }
                                }
                            }
                            break;
                        case MaterialProperty.PropType.Int:
                            group_foldout = prop.intValue == 1 ? true : false;
                            EditorGUI.BeginChangeCheck();
                            group_foldout = EditorGUI.Foldout(position, group_foldout, prop.displayName);
                            if (EditorGUI.EndChangeCheck())
                            {
                                prop.intValue = group_foldout ? 1 : 0;
                                if (keys != null && keys.Length > 0)
                                {
                                    foreach (var key in keys)
                                    {
                                        GUIData.SetPropertyVisible(key, group_foldout);
                                    }
                                }
                            }
                            break;
                    }
                }
            }

        }
    }

    /// <summary>
    /// 单线贴图样式
    /// </summary>
    public class SingleLineTexture : MaterialPropertyDrawer
    {
        public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor)
        {
            return 0;
        }
        public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
        {
            if (prop.type == MaterialProperty.PropType.Texture)
            {
                editor.TexturePropertySingleLine(label, prop);
            }
        }
    }

    #endregion
}

注意:官方说明,为了性能,EditorGUILayout不能和MaterialPropertyDrawers一起使用,所以最好还是使用EditorGUI

更多内容可以参考官方MaterialEditor,MaterialPropertyDrawer,ShaderOnGUI,EditorGUI等编辑器内容,同时可以阅读我的编辑器扩展基础知识:Unity拓展编辑器基础知识

相关推荐
DARLING Zero two♡13 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
Gu Gu Study15 分钟前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
芊寻(嵌入式)37 分钟前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_1 小时前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_20131 小时前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑1 小时前
php 使用qrcode制作二维码图片
开发语言·php
夜雨翦春韭1 小时前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
何曾参静谧1 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++