2.MaterialPropertyDrawer

2.自定义材质面板-MaterialPropertyDrawer类


2.1 知识点

MaterialPropertyDrawer类是用来做什么的

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

MaterialPropertyDrawer和ShaderGUI的区别

ShaderGUI 用来自定义整个材质面板,可以理解为以 Shader 材质球为一个整体单位;
而 MaterialPropertyDrawer(材质属性绘制器)是用来自定义某一个属性的,以属性为一个整体单位,
相当于可以更加精细地进行属性自定义显示封装。

MaterialPropertyDrawer类的声明

主要步骤

  • 新建一个 C# 脚本,继承自 MaterialPropertyDrawer 类
  • 重写 void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) 方法
  • 在其中实现 UI 自定义布局

声明

using UnityEditor;
using UnityEngine;

public class Lesson02_MaterialPropertyDrawer : MaterialPropertyDrawer
{
    public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
    {
        base.OnGUI(position, prop, label, editor);
        // 自定义布局逻辑
    }
}

MaterialPropertyDrawer类的使用

配合ShaderGUI使用

材质编辑器绘制指定为当前ShaderGUI的代码
Shader "Unlit/Lesson02_TestShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TestFloat("TestFloat", Float) = 1
    }
    SubShader
    {
       // ...
    }

    CustomEditor "Lesson02_CustomShaderInspector"
}
定义材质绘制器,指定绘制Float类型的属性
using UnityEditor;
using UnityEngine;

// 自定义材质属性绘制器,继承自 MaterialPropertyDrawer
// 用于在材质面板中创建带范围限制的滑动条控件
public class Lesson02_MaterialPropertyDrawer : MaterialPropertyDrawer
{
    private float min; // 滑动条最小值
    private float max; // 滑动条最大值

    /// <summary>
    /// 构造函数(用于初始化滑动条范围)
    /// </summary>
    /// <param name="min">滑动条最小值</param>
    /// <param name="max">滑动条最大值</param>
    public Lesson02_MaterialPropertyDrawer(float min, float max)
    {
        this.min = min;
        this.max = max;
    }

    /// <summary>
    /// 重写GUI绘制方法
    /// </summary>
    /// <param name="position">控件位置</param>
    /// <param name="prop">材质属性</param>
    /// <param name="label">属性显示名称</param>
    /// <param name="editor">材质编辑器实例</param>
    public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
    {
        // base.OnGUI(position, prop, label, editor); // 注释掉默认绘制逻辑

        // 类型校验:仅支持 Float 类型属性
        if (prop.type != MaterialProperty.PropType.Float)
        {
            // 显示错误提示(当属性类型不匹配时)
            EditorGUILayout.LabelField(label, "请使用float或者数值 不然无法使用该控件");
            return;
        }

        // 创建带范围限制的滑动条控件
        prop.floatValue = EditorGUILayout.Slider(
            label,           // 属性显示名称
            prop.floatValue, // 当前属性值
            min,             // 滑动条最小值
            max              // 滑动条最大值
        );
    }
}
复制上节课的ShaderGUI代码,初始实例化材质绘制器floatMaterialPropertyDrawer且区间设置为-50到50,绘制属性名为TestFloat时调用绘制器的绘制方法进行绘制
using UnityEditor;
using UnityEngine;

// 自定义Shader检查器类,继承自 ShaderGUI
public class Lesson02_CustomShaderInspector : ShaderGUI
{
    private bool isShow; // 控制属性面板的显示状态
    // private float value; // _TestFloat属性的值 其实没必要 直接用 materialProperty.floatValue 就行

    private Lesson02_MaterialPropertyDrawer floatMaterialPropertyDrawer = new Lesson02_MaterialPropertyDrawer(-50, 50);

    // 重写 ShaderGUI 的 OnGUI 方法,绘制自定义界面
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] materialProperties)
    {
        // base.OnGUI(materialEditor, properties); // 注释掉默认的界面绘制

        // 显示/隐藏属性设置的切换按钮
        if (GUILayout.Button(isShow ? "隐藏所有属性设置" : "显示所有属性设置"))
            isShow = !isShow;

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

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

        // 直接设置材质的渲染队列值
        material.renderQueue = EditorGUILayout.IntField("渲染队列", material.renderQueue);

        // 当显示属性时绘制所有属性
        if (isShow)
        {
            //...
            // 遍历所有材质属性的方式绘制属性编辑器
            foreach (var materialProperty in materialProperties)
            {
                if (materialProperty.displayName == "TestFloat")
                {
                    //...
                    // 调用 floatMaterialPropertyDrawer 的 OnGUI 方法,用于在 Unity 编辑器的 Inspector 面板中绘制自定义的 GUI 控件
                    // 此方法会处理特定材质属性(MaterialProperty)的显示和交互逻辑
                    // 参数说明:
                    // 1. EditorGUILayout.GetControlRect():获取一个用于绘制 GUI 控件的矩形区域,
                    //    这个矩形区域定义了在 Inspector 面板中显示该控件的位置和大小
                    // 2. materialProperty:要处理的材质属性对象,
                    //    它包含了材质属性的各种信息,如属性值、类型等
                    // 3. materialProperty.displayName:材质属性的显示名称,
                    //    这个名称会显示在 Inspector 面板中,用于标识该属性
                    // 4. materialEditor:当前的材质编辑器对象,
                    //    它提供了与材质编辑相关的上下文和功能
                    floatMaterialPropertyDrawer.OnGUI(EditorGUILayout.GetControlRect(), materialProperty,
                        materialProperty.displayName, materialEditor);
                }
                else
                    // 利用获取到的每个材质属性,采用 Unity 自带的 Inspector 窗口 UI 显示方式展示这些属性
                    // 效果与默认绘制相同
                    materialEditor.ShaderProperty(materialProperty, materialProperty.displayName);
            }
        }
    }
}
可以看到效果,我们设置的 -50 到 50 生效了

独立使用

注释掉 CustomEditor,不指定 ShaderGUI

在 Shader 文件中添加一个 TestFloat2 属性,属性前使用 [] 传入绘制类名(不需要添加 Drawer 后缀,和特性类似)

Shader "Unlit/Lesson02_TestShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TestFloat("TestFloat", Float) = 1
        [Lesson02_MaterialProperty(-6.6,6.6)]_TestFloat2("TestFloat2", Float) = 1
    }
    SubShader
    {
        //...
    }

    //    CustomEditor "Lesson02_CustomShaderInspector"
}
由于 Unity 的 bug 可能看不到效果

这无伤大雅,因为实际上很少直接继承 MaterialPropertyDrawer,Unity 已经提供了很多写好的基础 MaterialPropertyDrawer 类。


2.2 知识点代码

Lesson02_TestShader.shader

Shader "Unlit/Lesson02_TestShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TestFloat("TestFloat", Float) = 1
//        [Lesson02_MaterialProperty(-6.6,6.6)]_TestFloat2("TestFloat2", Float) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o, o.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }

    CustomEditor "Lesson02_CustomShaderInspector"
}

Lesson02_CustomShaderInspector.cs

using UnityEditor;
using UnityEngine;

// 自定义Shader检查器类,继承自ShaderGUI
public class Lesson02_CustomShaderInspector : ShaderGUI
{
    private bool isShow; // 控制属性面板的显示状态
    // private float value; // _TestFloat属性的值 其实没必要 直接用materialProperty.floatValue就行

    private Lesson02_MaterialPropertyDrawer floatMaterialPropertyDrawer = new Lesson02_MaterialPropertyDrawer(-50, 50);

    // 重写ShaderGUI的OnGUI方法,绘制自定义界面
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] materialProperties)
    {
        // base.OnGUI(materialEditor, properties); // 注释掉默认的界面绘制

        // 显示/隐藏属性设置的切换按钮
        if (GUILayout.Button(isShow ? "隐藏所有属性设置" : "显示所有属性设置"))
            isShow = !isShow;

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

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

        // 直接设置材质的渲染队列值
        material.renderQueue = EditorGUILayout.IntField("渲染队列", material.renderQueue);

        // 当显示属性时绘制所有属性
        if (isShow)
        {
            // //单独获取某一个属性的方式绘制属性编辑器
            // //找到_TestFloat属性
            // MaterialProperty testFloatMaterialProperty = FindProperty("_TestFloat", materialProperties);
            // //自定义一个拖动条去设置TestFloat属性
            // //这种单独获取某一个属性的方式 就不需要使用中间变量 以及 对应的材质球设置了
            // testFloatMaterialProperty.floatValue =
            //     EditorGUILayout.Slider("单独获取某一个属性的自定义float属性", testFloatMaterialProperty.floatValue, -1, 1);
            //
            // // 找到_MainTex属性
            // MaterialProperty mainTexMaterialProperty = FindProperty("_MainTex", materialProperties);
            // materialEditor.ShaderProperty(mainTexMaterialProperty, mainTexMaterialProperty.displayName);


            //----------------------------------------------------------------------------

            // 遍历所有材质属性的方式绘制属性编辑器
            foreach (var materialProperty in materialProperties)
            {
                if (materialProperty.displayName == "TestFloat")
                {
                    // //自定义一个拖动条去设置TestFloat属性
                    // materialProperty.floatValue =
                    //     EditorGUILayout.Slider("遍历所有材质属性的自定义float属性", materialProperty.floatValue, -1, 1);
                    //
                    // // _TestFloat属性的值 其实没必要 直接用materialProperty.floatValue就行
                    // // material.SetFloat("_TestFloat", value);

                    // 调用 floatMaterialPropertyDrawer 的 OnGUI 方法,用于在 Unity 编辑器的 Inspector 面板中绘制自定义的 GUI 控件
                    // 此方法会处理特定材质属性(MaterialProperty)的显示和交互逻辑
                    // 参数说明:
                    // 1. EditorGUILayout.GetControlRect():获取一个用于绘制 GUI 控件的矩形区域
                    //    这个矩形区域定义了在 Inspector 面板中显示该控件的位置和大小
                    // 2. materialProperty:要处理的材质属性对象
                    //    它包含了材质属性的各种信息,如属性值、类型等
                    // 3. materialProperty.displayName:材质属性的显示名称
                    //    这个名称会显示在 Inspector 面板中,用于标识该属性
                    // 4. materialEditor:当前的材质编辑器对象
                    //    它提供了与材质编辑相关的上下文和功能
                    floatMaterialPropertyDrawer.OnGUI(EditorGUILayout.GetControlRect(), materialProperty,
                        materialProperty.displayName, materialEditor);
                }
                else
                    //利用获取到的一个个的材质属性 利用Unity自带的Inspector窗口UI显示方式去显示这些属性
                    //等于和默认绘制没区别
                    materialEditor.ShaderProperty(materialProperty, materialProperty.displayName);
            }
        }
    }
}

Lesson02_MaterialPropertyDrawer.cs

using UnityEditor;
using UnityEngine;

// 自定义材质属性绘制器,继承自MaterialPropertyDrawer
// 用于在材质面板中创建带范围限制的滑动条控件
public class Lesson02_MaterialPropertyDrawer : MaterialPropertyDrawer
{
    private float min; // 滑动条最小值
    private float max; // 滑动条最大值

    /// <summary>
    /// 构造函数(用于初始化滑动条范围)
    /// </summary>
    /// <param name="min">滑动条最小值</param>
    /// <param name="max">滑动条最大值</param>
    public Lesson02_MaterialPropertyDrawer(float min, float max)
    {
        this.min = min;
        this.max = max;
    }

    /// <summary>
    /// 重写GUI绘制方法
    /// </summary>
    /// <param name="position">控件位置</param>
    /// <param name="prop">材质属性</param>
    /// <param name="label">属性显示名称</param>
    /// <param name="editor">材质编辑器实例</param>
    public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
    {
        // base.OnGUI(position, prop, label, editor); // 注释掉默认绘制逻辑

        // 类型校验:仅支持Float类型属性
        if (prop.type != MaterialProperty.PropType.Float)
        {
            // 显示错误提示(当属性类型不匹配时)
            EditorGUILayout.LabelField(label, "请使用float或者数值 不然无法使用该控件");
            return;
        }

        // 创建带范围限制的滑动条控件
        prop.floatValue = EditorGUILayout.Slider(
            label, // 属性显示名称
            prop.floatValue, // 当前属性值
            min, // 滑动条最小值
            max // 滑动条最大值
        );
    }
}

Lesson02_自定义材质面板_MaterialPropertyDrawer类.cs

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

public class Lesson02_自定义材质面板_MaterialPropertyDrawer类 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 MaterialPropertyDrawer类是用来做什么的

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

        #endregion

        #region 知识点二 MaterialPropertyDrawer和ShaderGUI的区别

        //ShaderGUI是用来自定义整个材质面板的 可以理解为以Shader材质球为一个整体单位
        //而MaterialPropertyDrawer(材质属性绘制器) 是用来自定义某一个属性的 以属性为一个整体单位
        //相当于可以更加精细的来进行属性自定义显示封装

        #endregion

        #region 知识点三 MaterialPropertyDrawer类的声明

        //1.新建一个C#脚本,继承自MaterialPropertyDrawer类
        //2.重写void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) 方法
        //3.在其中实现UI自定义布局

        #endregion

        #region 知识点四 MaterialPropertyDrawer类的使用

        //1.配合ShaderGUI使用
        //2.独立使用

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏