Files
Vision/Assets/Shaders/Editor/UIMaskDrawers.cs
T
2026-10-12 14:26:05 +08:00

232 lines
8.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<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 + " 处占用";
}
}