using UnityEditor; using UnityEngine; // UIMask 材质面板的"下拉框 -> 隐藏属性"绘制器基类。 // ShaderLab 的 Blend / Cull 只接受数字属性,没法用枚举直接切换, // 所以由绘制器把下拉框的选项写进隐藏属性。 // 每次绘制面板都会同步一次,所以从其它 shader(例如 URP/Lit 也有同名的 // _SrcBlend / _DstBlend / _Cull)切过来的材质会被自动纠正。 // 注意:attribute 的名字必须是子类的简单类名,Unity 按类名查找绘制器。 public abstract class UIMaskEnumDrawer : MaterialPropertyDrawer { protected abstract string[] Options { get; } // 把下拉框当前选项同步到材质上的隐藏属性 protected abstract void Apply(MaterialEditor editor, int index); public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) { EditorGUI.BeginChangeCheck(); int index = Mathf.Clamp(Mathf.RoundToInt(prop.floatValue), 0, Options.Length - 1); index = EditorGUI.Popup(position, label, index, Options); if (EditorGUI.EndChangeCheck()) { prop.floatValue = index; } Apply(editor, index); } public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) { return EditorGUIUtility.singleLineHeight; } protected void SetValue(MaterialEditor editor, string propertyName, float value) { foreach (Object target in editor.targets) { var material = target as Material; if (material == null || !material.HasProperty(propertyName)) { continue; } if (!Mathf.Approximately(material.GetFloat(propertyName), value)) { material.SetFloat(propertyName, value); } } } } // 混合模式:Additive / Alpha / Alpha Premultiplied / Opaque // 数值对应 UnityEngine.Rendering.BlendMode:SrcAlpha=5, One=1, OneMinusSrcAlpha=10, Zero=0 // Opaque 会同时打开 ZWrite(不透明需要写深度),其余模式 ZWrite = 0 public class UIMaskBlendMode : UIMaskEnumDrawer { private static readonly string[] Items = { "Additive", "Alpha", "Alpha Premultiplied", "Opaque" }; protected override string[] Options => Items; protected override void Apply(MaterialEditor editor, int index) { const float srcAlpha = (float)UnityEngine.Rendering.BlendMode.SrcAlpha; // 5 const float one = (float)UnityEngine.Rendering.BlendMode.One; // 1 const float oneMinusSrcAlpha = (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha; // 10 const float zero = (float)UnityEngine.Rendering.BlendMode.Zero; // 0 switch (index) { case 1: // Alpha:标准透明混合 SetValue(editor, "_SrcBlend", srcAlpha); SetValue(editor, "_DstBlend", oneMinusSrcAlpha); SetValue(editor, "_ZWrite", 0f); break; case 2: // Alpha Premultiplied:颜色已预乘 alpha,源因子用 One SetValue(editor, "_SrcBlend", one); SetValue(editor, "_DstBlend", oneMinusSrcAlpha); SetValue(editor, "_ZWrite", 0f); break; case 3: // Opaque:不混合 + 写深度 SetValue(editor, "_SrcBlend", one); SetValue(editor, "_DstBlend", zero); SetValue(editor, "_ZWrite", 1f); break; default: // Additive:SrcAlpha + One SetValue(editor, "_SrcBlend", srcAlpha); SetValue(editor, "_DstBlend", one); SetValue(editor, "_ZWrite", 0f); break; } } } // 渲染面:只渲染前面 / 只渲染后面 / 双面 // ShaderLab 的 Cull 取值:Off=0(双面)、Front=1(剔除正面,即只渲染后面)、Back=2(剔除背面,即只渲染前面) public class UIMaskRenderFace : UIMaskEnumDrawer { private const float CullOff = 0f; private const float CullFront = 1f; private const float CullBack = 2f; private static readonly string[] Items = { "前面", "背面", "双面" }; protected override string[] Options => Items; protected override void Apply(MaterialEditor editor, int index) { float cull = index == 0 ? CullBack : (index == 1 ? CullFront : CullOff); SetValue(editor, "_Cull", cull); } } // 贴图通道:R / G / B / A(值直接被 shader 采样时使用,不需要写隐藏属性) // 默认 A(index = 3) public class UIMaskChannel : UIMaskEnumDrawer { private static readonly string[] Items = { "R 通道", "G 通道", "B 通道", "A 通道" }; protected override string[] Options => Items; protected override void Apply(MaterialEditor editor, int index) { // 下拉框的值本身就是要采样的通道号,无需同步到别的属性 } } // 通用模块:粒子自定义数据通道下拉框(shader 里写 [CustomDataChannel]) // 选项 9 项:null / Custom1.xyzw / Custom2.xyzw;值 0=null,1~4=Custom1,5~8=Custom2 // 右侧会提示该通道是否已被同一材质上的其它参数占用 //(约定:属性名里含 "CustomChannel" 的都会被统计,以后新增参数只要按这个命名即可自动纳入) // shader 侧配套函数:CustomDataChannelValue(custom1, custom2, channel) → 取对应通道的值 public class CustomDataChannel : MaterialPropertyDrawer { private static readonly string[] Items = { "null", "Custom1.x", "Custom1.y", "Custom1.z", "Custom1.w", "Custom2.x", "Custom2.y", "Custom2.z", "Custom2.w" }; private const float HintWidth = 150f; public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) { return EditorGUIUtility.singleLineHeight; } public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) { Rect popupRect = new Rect(position.x, position.y, Mathf.Max(60f, position.width - HintWidth - 4f), position.height); Rect hintRect = new Rect(position.xMax - HintWidth, position.y, HintWidth, position.height); int index = Mathf.Clamp(Mathf.RoundToInt(prop.floatValue), 0, Items.Length - 1); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop.hasMixedValue; index = EditorGUI.Popup(popupRect, label, index, Items); EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { prop.floatValue = index; } bool occupied; string hint = DescribeOccupation(editor, prop, index, out occupied); var hintContent = new GUIContent(hint, "数据来自粒子系统的「自定义数据」模块;" + "还需要在粒子渲染器的「自定义顶点流」里按 UV、Custom1.xyzw、Custom2.xyzw 的顺序勾选对应通道。"); Color oldColor = GUI.color; if (index == 0) { GUI.color = new Color(0.7f, 0.7f, 0.7f); } else { GUI.color = occupied ? new Color(1f, 0.72f, 0.25f) : new Color(0.55f, 0.85f, 0.55f); } EditorGUI.LabelField(hintRect, hintContent, EditorStyles.miniLabel); GUI.color = oldColor; } private static string DescribeOccupation(MaterialEditor editor, MaterialProperty prop, int index, out bool occupied) { occupied = false; if (index == 0) { return "未使用"; } var material = editor.targets.Length > 0 ? editor.targets[0] as Material : null; if (material == null) { return string.Empty; } Shader shader = material.shader; int count = ShaderUtil.GetPropertyCount(shader); var owners = new System.Collections.Generic.List(); for (int i = 0; i < count; i++) { string name = ShaderUtil.GetPropertyName(shader, i); if (name == prop.name || !name.Contains("CustomChannel")) { continue; } if (!material.HasProperty(name) || Mathf.RoundToInt(material.GetFloat(name)) != index) { continue; } string description = ShaderUtil.GetPropertyDescription(shader, i); owners.Add(string.IsNullOrEmpty(description) ? name : description); } if (owners.Count == 0) { return "未占用"; } occupied = true; return owners.Count == 1 ? "已被 " + owners[0] + " 占用" : "已被 " + owners[0] + " 等 " + owners.Count + " 处占用"; } }