204 lines
7.4 KiB
C#
204 lines
7.4 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 混合模式:加色 / 普通混合
|
||
// 数值对应 UnityEngine.Rendering.BlendMode:SrcAlpha=5, One=1, OneMinusSrcAlpha=10
|
||
public class UIMaskBlendMode : UIMaskEnumDrawer
|
||
{
|
||
private static readonly string[] Items = { "Additive", "AlphaBlend" };
|
||
|
||
protected override string[] Options => Items;
|
||
|
||
protected override void Apply(MaterialEditor editor, int index)
|
||
{
|
||
SetValue(editor, "_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
||
SetValue(editor, "_DstBlend", index == 0
|
||
? (float)UnityEngine.Rendering.BlendMode.One
|
||
: (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
||
}
|
||
}
|
||
|
||
// 渲染面:只渲染前面 / 只渲染后面 / 双面
|
||
// 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<string>();
|
||
|
||
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 + " 处占用";
|
||
}
|
||
}
|