Unity - 搬砖日志 - MatierlaPropertyDrawer 中的参数如何匹配 - 自定义 Attribute 的参数提取


环境

Unity : 2020.3.37f1


搬一下砖,并记录,免得后续重新搬砖


完成的测试shader

Shader "Unlit/TestMyEnuMatAttributeShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        [MyEnumMatAttribute] _TestProp ("MyPropDraw no args", Float) = 1
        [MyEnumMatAttribute(Arg1)] _TestProp1 ("MyPropDraw with arg1", Float) = 1
        [MyEnumMatAttribute(Arg1,1,Arg2,2)] _TestProp2 ("MyPropDraw with multi args1", Float) = 1
        [MyEnumMatAttribute(Arg1,1.0,Arg2,2.0)] _TestProp3 ("MyPropDraw with multi args2", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2)] _TestProp4 ("MyPropDraw with multi args3", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2,Arg3)] _TestProp5 ("MyPropDraw with multi args4", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2,Arg3,Arg4)] _TestProp6 ("MyPropDraw with multi args5", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2,Arg3,Arg4, Arg5)] _TestProp7 ("MyPropDraw with multi args6", Float) = 1
        [MyEnumMatAttribute(1,2,3,4,5)] _TestProp8 ("MyPropDraw with multi args7", Float) = 1
        [MyEnumMatAttribute(1.0,2.0)] _TestProp9 ("MyPropDraw with multi args8", 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
        }
    }
}


完成的测试csharp代码

// jave.lin 2022/12/17 自定义 Enum 材质 attribute 的绘制
// 并测试 自定义的 attribute 中含带多个 参数的功能

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

public class MyEnumMatAttributeDrawer : MaterialPropertyDrawer
{
    private List<string> args = new List<string>();
    private List<float> vals = new List<float>();
    private string GetArgsValsStr()
    {
        return $"args:{string.Join(",", args)}, vals:{string.Join(",", vals)}";
    }
    public MyEnumMatAttributeDrawer()
    {
        Debug.Log($"MyEnumMatAttributeDrawer 0, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(string arg1) : base()
    {
        args.Add(arg1);
        Debug.Log($"MyEnumMatAttributeDrawer 1, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(string arg1, string arg2)
    {
        args.Add(arg1);
        args.Add(arg2);
        Debug.Log($"MyEnumMatAttributeDrawer 2, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(string arg1, string arg2, string arg3)
    {
        args.Add(arg1);
        args.Add(arg2);
        args.Add(arg3);
        Debug.Log($"MyEnumMatAttributeDrawer 3, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(string arg1, string arg2, string arg3, string arg4)
    {
        args.Add(arg1);
        args.Add(arg2);
        args.Add(arg3);
        args.Add(arg4);
        Debug.Log($"MyEnumMatAttributeDrawer 4, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(string args1, int val1, string args2, int val2)
    {
        args.Add(args1);
        args.Add(args2);
        vals.Add(val1);
        vals.Add(val2);
        // jave.lin : 注意这个重载是死活都进不来,原因:
        // jave.lin : 参考这里:https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/MaterialPropertyDrawer.cs#L96

        /**
         * jave.lin : 可以看到 CreatePropertyDraw 调用的是 .net 
         * Activator.CreateInstance(string func_name, object[] args) 的方式来查找 匹配的 函数签名
         * 所以我们的 int 类型都是识别不了的,只能有 string, 或 float 能识别
        private static MaterialPropertyDrawer CreatePropertyDrawer(Type klass, string argsText)
        {
            // no args -> default constructor
            if (string.IsNullOrEmpty(argsText))
                return Activator.CreateInstance(klass) as MaterialPropertyDrawer;

            // split the argument list by commas
            string[] argStrings = argsText.Split(',');
            var args = new object[argStrings.Length];
            for (var i = 0; i < argStrings.Length; ++i)
            {
                float f;
                string arg = argStrings[i].Trim();

                // if can parse as a float, use the float; otherwise pass the string
                if (float.TryParse(arg, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture.NumberFormat, out f))
                {
                    args[i] = f;
                }
                else
                {
                    args[i] = arg;
                }
            }
            return Activator.CreateInstance(klass, args) as MaterialPropertyDrawer;
        }
         */


        Debug.Log($"MyEnumMatAttributeDrawer 4.1, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(string args1, float val1, string args2, float val2)
    {
        // jave.lin : 根据 官方代码 https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/MaterialPropertyDrawer.cs#L96
        // jave.lin : 能匹配 string 和 float
        args.Add(args1);
        args.Add(args2);
        vals.Add(val1);
        vals.Add(val2);
        Debug.Log($"MyEnumMatAttributeDrawer 4.2, GetArgsValsStr():{GetArgsValsStr()}");
    }
    public MyEnumMatAttributeDrawer(params string[] args)
    {
        // jave.lin : 可以进入,所以如果要写一个通用的 不定长 string 处理,可以使用这个
        this.args.AddRange(args);
        Debug.Log($"MyEnumMatAttributeDrawer 5, GetArgsValsStr():{GetArgsValsStr()}");
    }

    public MyEnumMatAttributeDrawer(params object[] args)
    {
        // jave.lin : 不会进入
        for (int i = 0; i < args.Length; i++)
        {
            this.args.Add(args[i].ToString());
        };
        Debug.Log($"MyEnumMatAttributeDrawer 6, GetArgsValsStr():{GetArgsValsStr()}");
    }

    public MyEnumMatAttributeDrawer(params float[] vals)
    {
        // jave.lin : 全是数值的时候会进入(注意如果是别的不是 float 意外的数值类型将不会匹配上)
        for (int i = 0; i < vals.Length; i++)
        {
            this.vals.Add(vals[i]);
        };
        Debug.Log($"MyEnumMatAttributeDrawer 7, GetArgsValsStr():{GetArgsValsStr()}");
    }

    public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor)
    {
        base.OnGUI(position, prop, label, editor);

        //Debug.Log($"MyEnumMatAttribute draw, label : {label}, prop.dname:{prop.displayName}, prop.name:{prop.name}, with args:{string.Join(",", args)}, vals:{string.Join(",", vals)}");

        // jave.lin : 这里该怎么画,就怎么画

        // jave.lin : 但是这里有一个致命的设计,再 shader gui 中无法获取 drawer 对象,也就无法获取一些 drawer 中的参数、值,就无法制作一些高级的功能效果
    }

}

如果我们想要自定义 材质中的属性外观,就只能重写这个 MaterialPropertyDrawer 的派生类

无参数的还好

直接无参构造函数就完事

如果你想要提取 attribute 中的参数
那么就得有参数

这里值得注意的是,参数的类型声明,有一定规则,具体可以参考:https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/MaterialPropertyDrawer.cs#L96

可以看到,我们定义了一堆测试的 properties

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        [MyEnumMatAttribute] _TestProp ("MyPropDraw no args", Float) = 1
        [MyEnumMatAttribute(Arg1)] _TestProp1 ("MyPropDraw with arg1", Float) = 1
        [MyEnumMatAttribute(Arg1,1,Arg2,2)] _TestProp2 ("MyPropDraw with multi args1", Float) = 1
        [MyEnumMatAttribute(Arg1,1.0,Arg2,2.0)] _TestProp3 ("MyPropDraw with multi args2", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2)] _TestProp4 ("MyPropDraw with multi args3", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2,Arg3)] _TestProp5 ("MyPropDraw with multi args4", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2,Arg3,Arg4)] _TestProp6 ("MyPropDraw with multi args5", Float) = 1
        [MyEnumMatAttribute(Arg1,Arg2,Arg3,Arg4, Arg5)] _TestProp7 ("MyPropDraw with multi args6", Float) = 1
        [MyEnumMatAttribute(1,2,3,4,5)] _TestProp8 ("MyPropDraw with multi args7", Float) = 1
        [MyEnumMatAttribute(1.0,2.0)] _TestProp9 ("MyPropDraw with multi args8", Float) = 1
    }

但是能匹配的函数签名有下面几个:
在这里插入图片描述

匹配不上的可以参考代码注意,写的很详细:


        /**
         * jave.lin : 可以看到 CreatePropertyDraw 调用的是 .net 
         * Activator.CreateInstance(string func_name, object[] args) 的方式来查找 匹配的 函数签名
         * 所以我们的 int 类型都是识别不了的,只能有 string, 或 float 能识别
        private static MaterialPropertyDrawer CreatePropertyDrawer(Type klass, string argsText)
        {
            // no args -> default constructor
            if (string.IsNullOrEmpty(argsText))
                return Activator.CreateInstance(klass) as MaterialPropertyDrawer;

            // split the argument list by commas
            string[] argStrings = argsText.Split(',');
            var args = new object[argStrings.Length];
            for (var i = 0; i < argStrings.Length; ++i)
            {
                float f;
                string arg = argStrings[i].Trim();

                // if can parse as a float, use the float; otherwise pass the string
                if (float.TryParse(arg, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture.NumberFormat, out f))
                {
                    args[i] = f;
                }
                else
                {
                    args[i] = arg;
                }
            }
            return Activator.CreateInstance(klass, args) as MaterialPropertyDrawer;
        }
         */

我们也可以看到官方有类似的写法应用在:KeywordEnum, Enum 之类的 attribute
在这里插入图片描述

在这里插入图片描述


注意问题 - MaterialPropertyDraw 不生效

解决方法就是重新 Reimport 一下对应的 Editor 脚本就完事
在这里插入图片描述


Unity MaterialPropertyDrawer 的设计不友好问题

// jave.lin : 但是这里有一个致命的设计,再 shader gui 中无法获取 drawer 对象,也就无法获取一些 drawer 中的参数、值,就无法制作一些高级的功能效果
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值