环境
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 中的参数、值,就无法制作一些高级的功能效果