US5L2——MaterialPropertyDrawer类

MaterialPropertyDrawer

MaterialPropertyDrawer​(材质属性绘制器)用于自定义材质属性在材质面板中的显示和交互方式的
材质属性通常在 Shader 中通过属性语句块定义。默认情况下,Unity 提供了一些基础的控件(如滑块、颜色选择器等)
通过继承 MaterialPropertyDrawer​(材质属性绘制器),你可以为自定义 Shader 属性创建更加灵活和直观的控件

MaterialPropertyDrawer 和 ShaderGUI 的区别

ShaderGUI​ 是用来自定义整个材质面板的,
MaterialPropertyDrawer​(材质属性绘制器)是用来自定义某一个属性的
相当于可以更加精细的来进行属性自定义显示封装

MaterialPropertyDrawer 类的声明

  1. 新建一个 C# 脚本,继承自 MaterialPropertyDrawer​ 类,类名最好以 Drawer 结尾

  2. 重写 void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)​ 方法

    也可以重写 void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)​ 方法
    区别只在于是否想让控件标签的又要显示字符串又要显示图片,详见:GUIContent​

  3. 在其中实现 UI 自定义布局

1
2
3
4
5
6
7
8
9
10
using UnityEditor;
using UnityEngine;

public class Lesson128 : MaterialPropertyDrawer
{
public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
{
base.OnGUI(position, prop, label, editor);
}
}

MaterialPropertyDrawer 类的使用

假设我们要实现一个自带最大值和最小值的滑动条,最大值和最小值通过构造函数由外部提供:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEditor;
using UnityEngine;

public class Lesson128_FloatPropertyDrawer : MaterialPropertyDrawer
{
private float _min;
private float _max;

// 使用构造函数初始化自定义的一些变量
public Lesson128_FloatPropertyDrawer(float min, float max)
{
_min = min;
_max = max;
}

public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
{
if (prop.type != MaterialProperty.PropType.Float)
{
EditorGUILayout.LabelField(label, "请使用float或者数值,否则无法使用此控件");
return;
}
EditorGUILayout.LabelField($"{label} ({_min}~{_max})");
prop.floatValue = EditorGUILayout.Slider(prop.floatValue, _min, _max);
}
}
  1. 配合 ShaderGUI​ 使用

    假设要对下面的 Shader 自定义材质 Inspector 界面,并且要自定义 Shader 的 float​ 类型的属性的绘制方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Shader "Unlit/CustomInspectorTest"
    {
    Properties
    {
    _MainTex("Texture", 2D) = "white"{}
    _TestFloat("TestFloat", Float) = 1
    }
    SubShader {/*...*/}

    CustomEditor "Lesson127_CustomShaderInspector"
    }

    以上节课实现的 ShaderGUI​ 派生类为例,代码详见:US5L1——ShaderGUI类
    要使用 MaterialPropertyDrawer​ 派生类,需要在类内部声明相应变量并实例化,
    然后再 ShaderGUI​ 派生类的 OnGUI​ 方法内调用 MaterialPropertyDrawer​ 派生类的 OnGUI​ 方法
    其中 MaterialPropertyDrawer​ 派生类的 OnGUI​ 需要传入绘制位置,MaterialProperty​ 属性,控件名,以及 MaterialEditor​ 参数

    关于绘制位置参数,如果是自动布局,可以直接使用 EditorGUILayout.GetControlRect()​

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    using UnityEditor;
    using UnityEngine;

    public class Lesson127_CustomShaderInspector : ShaderGUI
    {
    private bool isShow;
    private Lesson128 floatDrawer = new(-2, 2);

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
    {
    if (GUILayout.Button(isShow ? "隐藏所有属性设置" : "显示所有属性设置"))
    {
    isShow = !isShow;
    }

    // 获取当前材质球
    Material material = materialEditor.target as Material;

    if (GUILayout.Button("重置材质球属性"))
    {
    material.SetTexture("_MainTex", null);
    material.SetFloat("_TestFloat", 0);
    }

    if (isShow)
    {
    MaterialProperty property = FindProperty("_MainTex", properties);
    materialEditor.ShaderProperty(property, property.displayName);
    // 使用自定义的控件去设置TestFloat属性
    property = FindProperty("_TestFloat", properties);
    floatDrawer.OnGUI(EditorGUILayout.GetControlRect(), property, property.displayName, materialEditor);
    // 绘制渲染队列控件
    material.renderQueue = EditorGUILayout.IntField("渲染队列", material.renderQueue);
    }
    }
    }

    显示效果:

    image

  2. 独立使用

    注意!这个功能的 BUG 比较多,可能需要多调整以解决问题

    我们往往不会让 MaterialPropertyDrawer​ 派生类只配合 ShaderGUI​ 类使用,因为这些逻辑也可以在 ShaderGUI​ 类实现
    MaterialPropertyDrawer​ 派生类更多的时候会单独使用,我们可以直接在 Shader 内让某个特定属性使用这个 MaterialPropertyDrawer​ 派生类

    假设要对下面的 Shader 自定义 float​ 类型的属性的绘制方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Shader "Unlit/CustomInspectorTest"
    {
    Properties
    {
    _MainTex("Texture", 2D) = "white"{}
    _TestFloat("TestFloat", Float) = 1
    _TestFloat2("TestFloat2", Float) = 1
    }
    SubShader {/*...*/}
    }

    假设要让 _TestFloat2​ 这个属性使用自定义的 MaterialPropertyDrawer​ 派生类,
    直接在 _TestFloat2​ 使用 [MaterialPropertyDrawer派生类名(构造函数参数)]​ 即可,它的使用方法类似于 C# 的特性:CS4L21——特性,
    如果 MaterialPropertyDrawer​ 派生类类名由 Drawer​ 后缀,在 Shader 中使用是可以忽略的,就如同 C# 特性的 Attribute​ 后缀那样
    如果 MaterialPropertyDrawer​ 派生类拥有有参构造参数,则可以通过类名后边填入参数。

    警告!参数内不要填入 -​ ,即使要传递负数也不能这么填,如果要传递负数,可能需要寻求其他方式解决
    Unity Shader 会在例如 [Lesson128_FloatProperty(-3, 3)]​ 的语句上报错,报错原因不明!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Shader "Unlit/CustomInspectorTest"
    {
    Properties
    {
    _MainTex("Texture", 2D) = "white"{}
    _TestFloat("TestFloat", Float) = 1
    // [Lesson128_FloatProperty(-3.0, 3,0)] _TestFloat2("TestFloat2", Float) = 1 // 不能使用'-',否则会报错,原因不明
    [Lesson128_FloatProperty(0.0, 3.0)] _TestFloat2("TestFloat2", Float) = 1
    }
    SubShader {/*...*/}
    }

    显示效果:

    image