NB_FX
This commit is contained in:
3
Packages/NB_FX/XuanXuanRenderUtility/Editor.meta
Normal file
3
Packages/NB_FX/XuanXuanRenderUtility/Editor.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd758a702a9040fcb8614b232817cff9
|
||||
timeCreated: 1721290722
|
||||
@@ -0,0 +1,146 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
[CustomEditor(typeof(AnimationSheetHelper))]
|
||||
public class AnimationSheetHelperEditor : Editor
|
||||
{
|
||||
SerializedProperty isParticleBaseShader;
|
||||
SerializedProperty isPostProcessShader;
|
||||
SerializedProperty postProcessingController;
|
||||
SerializedProperty propertyName;
|
||||
|
||||
SerializedProperty xSize;
|
||||
SerializedProperty ySize;
|
||||
|
||||
SerializedProperty manualPlay;
|
||||
SerializedProperty manualPlayePos;
|
||||
SerializedProperty speed;
|
||||
|
||||
SerializedProperty scaleOffset;
|
||||
SerializedProperty mat;
|
||||
SerializedProperty frameIndex;
|
||||
SerializedProperty frameCount;
|
||||
|
||||
private AnimationSheetHelper _target;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
_target = (AnimationSheetHelper)target;
|
||||
|
||||
isParticleBaseShader = serializedObject.FindProperty("isParticleBaseShader");
|
||||
// isPostProcessShader = serializedObject.FindProperty("isPostProcessShader");
|
||||
// postProcessingController = serializedObject.FindProperty("postProcessingController");
|
||||
propertyName = serializedObject.FindProperty("propertyName");
|
||||
|
||||
xSize = serializedObject.FindProperty("xSize");
|
||||
ySize = serializedObject.FindProperty("ySize");
|
||||
|
||||
manualPlay = serializedObject.FindProperty("manualPlay");
|
||||
manualPlayePos = serializedObject.FindProperty("manualPlayePos");
|
||||
speed = serializedObject.FindProperty("speed");
|
||||
|
||||
scaleOffset = serializedObject.FindProperty("scaleOffset");
|
||||
mat = serializedObject.FindProperty("mat");
|
||||
frameIndex = serializedObject.FindProperty("frameIndex");
|
||||
frameCount = serializedObject.FindProperty("frameCount");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawShaderSection();
|
||||
EditorGUILayout.Space(5);
|
||||
DrawAnimationSection();
|
||||
EditorGUILayout.Space(5);
|
||||
DrawRuntimeInfo();
|
||||
EditorGUILayout.Space(8);
|
||||
DrawInitButton();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
#region Sections
|
||||
|
||||
void DrawShaderSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("Shader 设置", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(isParticleBaseShader, new GUIContent("使用 NB Shader"));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
_target.InitParticleBaseShaderToggle();
|
||||
EditorUtility.SetDirty(_target);
|
||||
}
|
||||
|
||||
// EditorGUI.BeginChangeCheck();
|
||||
// EditorGUILayout.PropertyField(isPostProcessShader, new GUIContent("使用 后处理扭曲"));
|
||||
// if (EditorGUI.EndChangeCheck())
|
||||
// {
|
||||
// serializedObject.ApplyModifiedProperties();
|
||||
// // _target.InitPostProcessToggle();
|
||||
// EditorUtility.SetDirty(_target);
|
||||
// }
|
||||
|
||||
// if (isPostProcessShader.boolValue)
|
||||
// {
|
||||
// GUI.enabled = false;
|
||||
// EditorGUILayout.PropertyField(postProcessingController);
|
||||
// GUI.enabled = true;
|
||||
// }
|
||||
|
||||
EditorGUILayout.PropertyField(propertyName, new GUIContent("Shader 属性名"));
|
||||
}
|
||||
|
||||
void DrawAnimationSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("序列帧设置", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(xSize, new GUIContent("横向帧数量"));
|
||||
EditorGUILayout.PropertyField(ySize, new GUIContent("纵向帧数量"));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
_target.Init();
|
||||
EditorUtility.SetDirty(_target);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.PropertyField(manualPlay, new GUIContent("手动控制播放"));
|
||||
|
||||
if (manualPlay.boolValue)
|
||||
{
|
||||
EditorGUILayout.PropertyField(manualPlayePos, new GUIContent("手动播放位置"));
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.PropertyField(speed, new GUIContent("播放速度 fps"));
|
||||
}
|
||||
}
|
||||
|
||||
void DrawRuntimeInfo()
|
||||
{
|
||||
EditorGUILayout.LabelField("运行时信息", EditorStyles.boldLabel);
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.PropertyField(scaleOffset, new GUIContent("Tiling Offset"));
|
||||
EditorGUILayout.PropertyField(mat, new GUIContent("Material"));
|
||||
EditorGUILayout.PropertyField(frameIndex);
|
||||
EditorGUILayout.PropertyField(frameCount);
|
||||
GUI.enabled = true;
|
||||
}
|
||||
|
||||
void DrawInitButton()
|
||||
{
|
||||
if (GUILayout.Button("初始化", GUILayout.Height(28)))
|
||||
{
|
||||
_target.Init();
|
||||
EditorUtility.SetDirty(_target);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e1a5d6748bd4515869cb9c6ebd2b548
|
||||
timeCreated: 1769260616
|
||||
@@ -0,0 +1,103 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class BinaryIntAttribute : PropertyAttribute
|
||||
{
|
||||
public int binaryBits;
|
||||
public bool showInputFiled;
|
||||
public int tabNums;
|
||||
|
||||
|
||||
public BinaryIntAttribute(int Bits = 32, bool showInput = false, int tab = 0)
|
||||
{
|
||||
binaryBits = Bits;
|
||||
showInputFiled = showInput;
|
||||
tabNums = tab;
|
||||
}
|
||||
}
|
||||
|
||||
[CustomPropertyDrawer(typeof(BinaryIntAttribute))]
|
||||
public class BinaryIntDrawer : PropertyDrawer
|
||||
{
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
|
||||
GUIStyle richTextStyle = EditorStyles.label;
|
||||
richTextStyle.richText = true;
|
||||
BinaryIntAttribute binaryIntAttribute = (BinaryIntAttribute)attribute;
|
||||
// int value = property.intValue;
|
||||
// int largestBit = 0;
|
||||
// for (int i = 0; i < 32; i++)
|
||||
// {
|
||||
// if ((~value & (1 << i)) == 0)
|
||||
// {
|
||||
// largestBit = i;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// int addZeroCount = 0;
|
||||
// if (largestBit < binaryIntAttribute.binaryBits)
|
||||
// {
|
||||
// addZeroCount = binaryIntAttribute.binaryBits - largestBit - 1;
|
||||
// }
|
||||
//
|
||||
// string addZeroString = "";
|
||||
// for (int i = 0; i < addZeroCount; i++)
|
||||
// {
|
||||
// addZeroString += "0";
|
||||
// }
|
||||
// string binary = addZeroString+Convert.ToString(property.intValue, 2);
|
||||
string binary = DrawBinaryInt(property.intValue, binaryIntAttribute.binaryBits);
|
||||
|
||||
string tabs = "";
|
||||
for (int i = 0; i < binaryIntAttribute.tabNums; i++)
|
||||
{
|
||||
tabs += "\t";
|
||||
}
|
||||
|
||||
if (binaryIntAttribute.showInputFiled)
|
||||
{
|
||||
string labelText = property.displayName + tabs + "<mspace=1em>" + binary + "</mspace>";
|
||||
Rect intRect = EditorGUI.PrefixLabel(position, new GUIContent(labelText), richTextStyle);
|
||||
property.intValue = EditorGUI.IntField(intRect, property.intValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(property.displayName + tabs);
|
||||
EditorGUILayout.LabelField(binary);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DrawBinaryInt(int value, int binaryBits)
|
||||
{
|
||||
int largestBit = 0;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if ((~value & (1 << i)) == 0)
|
||||
{
|
||||
largestBit = i;
|
||||
}
|
||||
}
|
||||
|
||||
int addZeroCount = 0;
|
||||
if (largestBit < binaryBits)
|
||||
{
|
||||
addZeroCount = binaryBits - largestBit - 1;
|
||||
}
|
||||
|
||||
string addZeroString = "";
|
||||
for (int i = 0; i < addZeroCount; i++)
|
||||
{
|
||||
addZeroString += "0";
|
||||
}
|
||||
|
||||
string binary = addZeroString + Convert.ToString(value, 2);
|
||||
return binary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4517deaf5f31a5848b6a58304d1f516f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
//因为GradientPicker窗口,在窗口打开时使用的是Gradient对象的Cache。
|
||||
//如果有Undo操作,导致MaterialProperty更新了,但Gradient却更新不到ColorPicer里。所以在这里进行强制更新。
|
||||
//后续需要强制更新Picker的时候,也可以用这里。
|
||||
//DeepSeek写的。谨慎用。
|
||||
public static class GradientReflectionHelper
|
||||
{
|
||||
#region GradientPicker 相关
|
||||
|
||||
private static Type _gradientPickerType;
|
||||
|
||||
// SetCurrentGradient 方法
|
||||
private static MethodInfo _setGradientMethod;
|
||||
private static Action<Gradient> _setGradientDelegate;
|
||||
|
||||
// RefreshGradientData 方法
|
||||
private static MethodInfo _refreshMethod;
|
||||
private static Action _refreshDelegate;
|
||||
|
||||
#endregion
|
||||
|
||||
#region GradientPreviewCache 相关
|
||||
|
||||
private static Type _gradientCacheType;
|
||||
private static MethodInfo _clearCacheMethod;
|
||||
private static Action _clearCacheDelegate;
|
||||
|
||||
#endregion
|
||||
|
||||
// 初始化状态
|
||||
private static bool _initialized;
|
||||
private static bool _isValid;
|
||||
private static readonly object _initLock = new object();
|
||||
|
||||
#region 公共API
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void SetCurrentGradient(Gradient gradient)
|
||||
{
|
||||
ExecuteAction(() =>
|
||||
{
|
||||
if (_setGradientDelegate != null)
|
||||
_setGradientDelegate(gradient);
|
||||
else if (_setGradientMethod != null)
|
||||
_setGradientMethod.Invoke(null, new object[] { gradient });
|
||||
}, "SetCurrentGradient");
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void RefreshGradientData()
|
||||
{
|
||||
ExecuteAction(() =>
|
||||
{
|
||||
if (_refreshDelegate != null)
|
||||
_refreshDelegate();
|
||||
else if (_refreshMethod != null)
|
||||
_refreshMethod.Invoke(null, null);
|
||||
}, "RefreshGradientData");
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void ClearGradientCache()
|
||||
{
|
||||
ExecuteAction(() =>
|
||||
{
|
||||
if (_clearCacheDelegate != null)
|
||||
_clearCacheDelegate();
|
||||
else if (_clearCacheMethod != null)
|
||||
_clearCacheMethod.Invoke(null, null);
|
||||
}, "ClearGradientCache");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 核心实现
|
||||
|
||||
private static void ExecuteAction(Action action, string methodName)
|
||||
{
|
||||
if (!Application.isEditor) return;
|
||||
|
||||
EnsureInitialized();
|
||||
if (!_isValid) return;
|
||||
|
||||
try
|
||||
{
|
||||
action?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[GradientReflection] {methodName} failed: {ex.Message}");
|
||||
// 可选择性地禁用后续调用
|
||||
// _isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureInitialized()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
lock (_initLock)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 初始化 GradientPicker 类型
|
||||
_gradientPickerType = Type.GetType("UnityEditor.GradientPicker, UnityEditor");
|
||||
if (_gradientPickerType != null)
|
||||
{
|
||||
// 初始化 SetCurrentGradient
|
||||
_setGradientMethod = GetStaticMethod(
|
||||
_gradientPickerType,
|
||||
"SetCurrentGradient",
|
||||
new[] { typeof(Gradient) }
|
||||
);
|
||||
if (_setGradientMethod != null)
|
||||
{
|
||||
_setGradientDelegate = CreateActionDelegate<Gradient>(_setGradientMethod);
|
||||
}
|
||||
|
||||
// 初始化 RefreshGradientData
|
||||
_refreshMethod = GetStaticMethod(
|
||||
_gradientPickerType,
|
||||
"RefreshGradientData",
|
||||
Type.EmptyTypes
|
||||
);
|
||||
if (_refreshMethod != null)
|
||||
{
|
||||
_refreshDelegate = CreateActionDelegate(_refreshMethod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[GradientReflection] GradientPicker type not found");
|
||||
}
|
||||
|
||||
// 2. 初始化 GradientPreviewCache
|
||||
_gradientCacheType = Type.GetType("UnityEditorInternal.GradientPreviewCache, UnityEditor");
|
||||
if (_gradientCacheType != null)
|
||||
{
|
||||
_clearCacheMethod = GetStaticMethod(
|
||||
_gradientCacheType,
|
||||
"ClearCache",
|
||||
Type.EmptyTypes
|
||||
);
|
||||
if (_clearCacheMethod != null)
|
||||
{
|
||||
_clearCacheDelegate = CreateActionDelegate(_clearCacheMethod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[GradientReflection] GradientPreviewCache type not found");
|
||||
}
|
||||
|
||||
// 3. 验证初始化状态
|
||||
_isValid = (_setGradientMethod != null || _refreshMethod != null) &&
|
||||
_clearCacheMethod != null;
|
||||
|
||||
if (!_isValid)
|
||||
{
|
||||
Debug.LogWarning($"[GradientReflection] Initialization incomplete. " +
|
||||
$"SetCurrentGradient: {(_setGradientMethod != null ? "OK" : "Missing")}, " +
|
||||
$"RefreshGradientData: {(_refreshMethod != null ? "OK" : "Missing")}, " +
|
||||
$"ClearCache: {(_clearCacheMethod != null ? "OK" : "Missing")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Debug.Log("[GradientReflection] Initialized successfully");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[GradientReflection] Initialization failed: {e}");
|
||||
_isValid = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodInfo GetStaticMethod(Type type, string methodName, Type[] parameterTypes)
|
||||
{
|
||||
if (type == null) return null;
|
||||
|
||||
return type.GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
parameterTypes,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private static Action CreateActionDelegate(MethodInfo method)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Action)Delegate.CreateDelegate(typeof(Action), method);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[GradientReflection] Failed to create delegate for {method.Name}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Action<T> CreateActionDelegate<T>(MethodInfo method)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), method);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[GradientReflection] Failed to create delegate for {method.Name}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助功能
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否所有反射方法都可用
|
||||
/// </summary>
|
||||
public static bool IsFullySupported => _isValid;
|
||||
|
||||
/// <summary>
|
||||
/// 获取反射初始化状态
|
||||
/// </summary>
|
||||
public static string GetReflectionStatus()
|
||||
{
|
||||
if (!_initialized) return "Not Initialized";
|
||||
|
||||
return $"GradientPicker: {(_gradientPickerType != null ? "Found" : "Missing")}\n" +
|
||||
$"- SetCurrentGradient: {(_setGradientMethod != null ? "OK" : "Missing")}\n" +
|
||||
$"- RefreshGradientData: {(_refreshMethod != null ? "OK" : "Missing")}\n" +
|
||||
$"GradientPreviewCache: {(_gradientCacheType != null ? "Found" : "Missing")}\n" +
|
||||
$"- ClearCache: {(_clearCacheMethod != null ? "OK" : "Missing")}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebe6cf7ae30f4357bae940f8ce409628
|
||||
timeCreated: 1753589587
|
||||
@@ -0,0 +1,261 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using System;
|
||||
using NBShader;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
// #if UNITY_EDITOR
|
||||
[CustomEditor(typeof(MaterialPropertyAgent))]
|
||||
public class MaterialPropertyAgentInspector : UnityEditor.Editor
|
||||
{
|
||||
private MaterialPropertyAgent agent;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
agent = (MaterialPropertyAgent)target;
|
||||
}
|
||||
|
||||
private GUIContent materialIndexContent = new GUIContent("材质序号:", "只有模型模式下需要使用,谨慎修改");
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (!agent.isGetByComponet)
|
||||
{
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("customRenderer"),
|
||||
new GUIContent("指定Renderer"));
|
||||
}
|
||||
|
||||
if (agent.isRendererMode)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
int lastIndex = agent.materialIndex;
|
||||
agent.materialIndex = EditorGUILayout.IntField(materialIndexContent, agent.materialIndex);
|
||||
if (lastIndex != agent.materialIndex)
|
||||
{
|
||||
agent.initMatAndShaderByMaterialIndexChange();
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("材质名:" + agent.mat.name + "\t" + "Shader名:" + agent.shader.name);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
DrawPropertyData(ref agent.data0, "Data0", serializedObject.FindProperty("data0"));
|
||||
DrawPropertyData(ref agent.data1, "Data1", serializedObject.FindProperty("data1"));
|
||||
DrawPropertyData(ref agent.data2, "Data2", serializedObject.FindProperty("data2"));
|
||||
DrawPropertyData(ref agent.data3, "Data3", serializedObject.FindProperty("data3"));
|
||||
DrawPropertyData(ref agent.data4, "Data4", serializedObject.FindProperty("data4"));
|
||||
DrawPropertyData(ref agent.data5, "Data5", serializedObject.FindProperty("data5"));
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("添加"))
|
||||
{
|
||||
agent.addProperteData();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("全部删除"))
|
||||
{
|
||||
agent.removeAllProperty();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(agent);
|
||||
if (!agent.isGetByComponet && agent.mat)
|
||||
{
|
||||
if (matEditor) DestroyImmediate(matEditor);
|
||||
matEditor = (MaterialEditor)CreateEditor(agent.mat);
|
||||
}
|
||||
}
|
||||
|
||||
if (!agent.isGetByComponet && agent.mat)
|
||||
{
|
||||
DrawMaterialInspector(matEditor, agent.mat);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawPropertyData(ref MaterialPropertyAgent.PropertyData data, string dataLabel,
|
||||
SerializedProperty property)
|
||||
{
|
||||
EditorGUI.BeginProperty(EditorGUILayout.GetControlRect(false, 0f), GUIContent.none, property);
|
||||
if (data.isActive)
|
||||
{
|
||||
EditorGUILayout.LabelField(dataLabel, EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
float originLabelWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = 80;
|
||||
// data.index = EditorGUILayout.Popup("属性名:", data.index, agent.shaderPropNameArr);
|
||||
// data.index = EditorGUILayout.Popup("属性名:", data.index, agent.shaderPropDescripArr);
|
||||
// string dataDesript = data.descripName;
|
||||
if (GUILayout.Button(data.descripName, EditorStyles.popup))
|
||||
{
|
||||
int dataIndexInAgent = data.dataIndexInAgent;
|
||||
|
||||
StringListSerchProvider provider = ScriptableObject.CreateInstance<StringListSerchProvider>();
|
||||
provider.Initialize(agent.shaderPropDescripsForSerch, (x) =>
|
||||
{
|
||||
if (x != null)
|
||||
{
|
||||
AfterShaderPropSerch(dataIndexInAgent, x);
|
||||
}
|
||||
});
|
||||
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)),
|
||||
provider);
|
||||
// SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)),
|
||||
// new StringListSerchProvider(agent.shaderPropDescripsForSerch, (x) =>
|
||||
// {
|
||||
// if (x != null)
|
||||
// {
|
||||
// AfterShaderPropSerch(dataIndexInAgent,x);
|
||||
// }
|
||||
// }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
EditorGUILayout.LabelField("属性类型:", data.type.ToString());
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUIUtility.labelWidth = originLabelWidth;
|
||||
switch (data.type)
|
||||
{
|
||||
case MaterialPropertyAgent.shaderPropertyType.Color:
|
||||
SerializedProperty colorProp = property.FindPropertyRelative("colorValue");
|
||||
if (data.descripName.ToLower().Contains("hdr"))
|
||||
{
|
||||
colorProp.colorValue = EditorGUILayout.ColorField(new GUIContent(data.propName + " :"),
|
||||
colorProp.colorValue, true, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
colorProp.colorValue =
|
||||
EditorGUILayout.ColorField(data.propName + " :", colorProp.colorValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case MaterialPropertyAgent.shaderPropertyType.Vector:
|
||||
case MaterialPropertyAgent.shaderPropertyType.TexEnv:
|
||||
SerializedProperty vecProp = property.FindPropertyRelative("vecValue");
|
||||
if (data.type == MaterialPropertyAgent.shaderPropertyType.Vector)
|
||||
{
|
||||
vecProp.vector4Value =
|
||||
EditorGUILayout.Vector4Field(data.propName + " :", vecProp.vector4Value);
|
||||
}
|
||||
else if (data.type == MaterialPropertyAgent.shaderPropertyType.TexEnv)
|
||||
{
|
||||
vecProp.vector4Value =
|
||||
EditorGUILayout.Vector4Field(data.propName + ":", vecProp.vector4Value);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
case MaterialPropertyAgent.shaderPropertyType.Float:
|
||||
case MaterialPropertyAgent.shaderPropertyType.Range:
|
||||
SerializedProperty floatProp = property.FindPropertyRelative("floatValue");
|
||||
if (data.type == MaterialPropertyAgent.shaderPropertyType.Float)
|
||||
{
|
||||
floatProp.floatValue =
|
||||
EditorGUILayout.FloatField(data.propName + ":", floatProp.floatValue);
|
||||
}
|
||||
else if (data.type == MaterialPropertyAgent.shaderPropertyType.Range)
|
||||
{
|
||||
floatProp.floatValue = EditorGUILayout.Slider(data.propName + ":", floatProp.floatValue,
|
||||
data.rangMin, data.rangMax);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (GUILayout.Button("删除", new[] { GUILayout.Width(200) }))
|
||||
{
|
||||
data.isActive = false;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
// EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
//因为Action不能有Ref。所以有了这个丑陋的HardCode
|
||||
public void AfterShaderPropSerch(int dataIndexInAgent, string propertyDesrpt)
|
||||
{
|
||||
switch (dataIndexInAgent)
|
||||
{
|
||||
case 0:
|
||||
AfterShaderPropSerch(ref agent.data0, propertyDesrpt);
|
||||
break;
|
||||
case 1:
|
||||
AfterShaderPropSerch(ref agent.data1, propertyDesrpt);
|
||||
break;
|
||||
case 2:
|
||||
AfterShaderPropSerch(ref agent.data2, propertyDesrpt);
|
||||
break;
|
||||
case 3:
|
||||
AfterShaderPropSerch(ref agent.data3, propertyDesrpt);
|
||||
break;
|
||||
case 4:
|
||||
AfterShaderPropSerch(ref agent.data4, propertyDesrpt);
|
||||
break;
|
||||
case 5:
|
||||
AfterShaderPropSerch(ref agent.data5, propertyDesrpt);
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void AfterShaderPropSerch(ref MaterialPropertyAgent.PropertyData data, string propertyDesrpt)
|
||||
{
|
||||
int preservedIndex = data.index;
|
||||
// string propname = data.propName;
|
||||
data.index = Array.FindIndex(agent.shaderPropDescripArr, x => x.Equals(propertyDesrpt));
|
||||
// Debug.Log();
|
||||
if (preservedIndex != data.index) //证明用户进行了更改
|
||||
{
|
||||
if (!agent.isCanUsedIndex(data.index))
|
||||
{
|
||||
//TODO给一个报错提示
|
||||
Debug.LogError("材质属性已经存在:" + agent.shader.GetPropertyDescription(data.index));
|
||||
data.index = agent.getCanUsedIndex();
|
||||
}
|
||||
|
||||
//此处进行内容刷新
|
||||
data.setValueByPropChange();
|
||||
}
|
||||
}
|
||||
|
||||
private MaterialEditor matEditor;
|
||||
|
||||
void DrawMaterialInspector(MaterialEditor editor, Material mat)
|
||||
{
|
||||
if (editor != null && mat != null)
|
||||
{
|
||||
// Draw the material's foldout and the material shader field
|
||||
// Required to call _materialEditor.OnInspectorGUI ();
|
||||
editor.DrawHeader();
|
||||
// We need to prevent the user to edit Unity default materials
|
||||
bool isDefaultMaterial = !AssetDatabase.GetAssetPath(mat).StartsWith("Assets");
|
||||
using (new EditorGUI.DisabledGroupScope(isDefaultMaterial))
|
||||
{
|
||||
|
||||
// Draw the material properties
|
||||
// Works only if the foldout of _materialEditor.DrawHeader () is open
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9544bec10c84ecca289bece4a630d65
|
||||
timeCreated: 1702126025
|
||||
383
Packages/NB_FX/XuanXuanRenderUtility/Editor/MeshUIInspector.cs
Normal file
383
Packages/NB_FX/XuanXuanRenderUtility/Editor/MeshUIInspector.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
using Unity.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using NBShader;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
[CustomEditor(typeof(MeshUI))]
|
||||
[CanEditMultipleObjects]
|
||||
public class MeshUIInspector : Editor
|
||||
{
|
||||
private SerializedProperty _meshProp;
|
||||
private SerializedProperty _subMeshMaterialsProp;
|
||||
private SerializedProperty _colorProp;
|
||||
private SerializedProperty _raycastTargetProp;
|
||||
private SerializedProperty _maskableProp;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_meshProp = serializedObject.FindProperty("mesh");
|
||||
_subMeshMaterialsProp = serializedObject.FindProperty("subMeshMaterials");
|
||||
_colorProp = serializedObject.FindProperty("m_Color");
|
||||
_raycastTargetProp = serializedObject.FindProperty("m_RaycastTarget");
|
||||
_maskableProp = serializedObject.FindProperty("m_Maskable");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawMeshSection();
|
||||
EditorGUILayout.Space();
|
||||
DrawSubMeshMaterials();
|
||||
EditorGUILayout.Space();
|
||||
DrawUGUISection();
|
||||
EditorGUILayout.Space();
|
||||
DrawMeshInfo();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawMeshSection()
|
||||
{
|
||||
EditorGUILayout.LabelField("Mesh", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(_meshProp, new GUIContent("模型"));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
SyncSlotsToMesh();
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
DrawBakeMeshSection();
|
||||
}
|
||||
|
||||
private void DrawSubMeshMaterials()
|
||||
{
|
||||
Mesh mesh = _meshProp.objectReferenceValue as Mesh;
|
||||
int targetSize = mesh != null ? mesh.subMeshCount : _subMeshMaterialsProp.arraySize;
|
||||
|
||||
EditorGUILayout.LabelField("Mesh Materials", EditorStyles.boldLabel);
|
||||
|
||||
if (mesh != null && _subMeshMaterialsProp.arraySize != targetSize)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"当前材质槽位数量为 {_subMeshMaterialsProp.arraySize},Mesh 的 SubMesh 数量为 {targetSize}。更换 Mesh 后会自动同步槽位。", MessageType.Warning);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _subMeshMaterialsProp.arraySize; i++)
|
||||
{
|
||||
SerializedProperty element = _subMeshMaterialsProp.GetArrayElementAtIndex(i);
|
||||
string label = $"Sub Mesh {i}";
|
||||
if (mesh != null && i < mesh.subMeshCount)
|
||||
{
|
||||
label += $" ({mesh.GetTopology(i)})";
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(element, new GUIContent(label));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUGUISection()
|
||||
{
|
||||
EditorGUILayout.LabelField("UGUI", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(_colorProp, new GUIContent("颜色"));
|
||||
EditorGUILayout.PropertyField(_raycastTargetProp, new GUIContent("射线检测"));
|
||||
EditorGUILayout.PropertyField(_maskableProp, new GUIContent("支持 Mask"));
|
||||
}
|
||||
|
||||
private void DrawMeshInfo()
|
||||
{
|
||||
Mesh mesh = _meshProp.objectReferenceValue as Mesh;
|
||||
EditorGUILayout.LabelField("信息", EditorStyles.boldLabel);
|
||||
|
||||
if (mesh == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("未指定 Mesh。请先指定一个包含三角形拓扑的模型。", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
int subMeshCount = mesh.subMeshCount;
|
||||
int triangleSubMeshCount = GetTriangleSubMeshCount(mesh);
|
||||
int vertexCount = mesh.vertexCount;
|
||||
int indexCount = 0;
|
||||
|
||||
for (int i = 0; i < subMeshCount; i++)
|
||||
{
|
||||
if (mesh.GetTopology(i) == MeshTopology.Triangles)
|
||||
{
|
||||
indexCount += (int)mesh.GetIndexCount(i);
|
||||
}
|
||||
}
|
||||
|
||||
string renderMode = GetRenderModeDescription(mesh);
|
||||
string info =
|
||||
$"可读性: {(mesh.isReadable ? "是" : "否")}\n" +
|
||||
$"顶点数: {vertexCount}\n" +
|
||||
$"SubMesh 数量: {subMeshCount}\n" +
|
||||
$"三角形 SubMesh 数量: {triangleSubMeshCount}\n" +
|
||||
$"三角形索引数: {indexCount}\n" +
|
||||
$"运行时模式: {renderMode}";
|
||||
|
||||
EditorGUILayout.HelpBox(info, MessageType.Info);
|
||||
|
||||
if (triangleSubMeshCount == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("当前 Mesh 不包含可渲染的三角形 SubMesh。", MessageType.Error);
|
||||
}
|
||||
else if (triangleSubMeshCount != subMeshCount)
|
||||
{
|
||||
EditorGUILayout.HelpBox("当前 Mesh 中存在非三角形拓扑的 SubMesh,运行时会生成 Mesh,并且只渲染三角形 SubMesh。", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetRenderModeDescription(Mesh mesh)
|
||||
{
|
||||
if (!mesh.isReadable)
|
||||
{
|
||||
return "当前 Mesh 不可读,无法渲染";
|
||||
}
|
||||
|
||||
if (GetTriangleSubMeshCount(mesh) != mesh.subMeshCount)
|
||||
{
|
||||
return "将生成运行时 Mesh(原因:存在非三角形 SubMesh)";
|
||||
}
|
||||
|
||||
Color color = _colorProp.colorValue;
|
||||
if (color != Color.white)
|
||||
{
|
||||
return "将生成运行时 Mesh(原因:需要应用 UGUI 颜色)";
|
||||
}
|
||||
|
||||
return "将直接使用原始 Mesh";
|
||||
}
|
||||
|
||||
private void DrawBakeMeshSection()
|
||||
{
|
||||
Mesh mesh = _meshProp.objectReferenceValue as Mesh;
|
||||
if (mesh == null || mesh.isReadable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("当前 Mesh 没有开启 Read/Write,MeshUI 无法直接通过 CanvasRenderer 渲染。请先烘焙出一个可读的 Mesh。", MessageType.Warning);
|
||||
|
||||
if (GUILayout.Button("烘焙只读Mesh"))
|
||||
{
|
||||
BakeMeshForTargets(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetTriangleSubMeshCount(Mesh mesh)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < mesh.subMeshCount; i++)
|
||||
{
|
||||
if (mesh.GetTopology(i) == MeshTopology.Triangles)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private void BakeMeshForTargets(Mesh sourceMesh)
|
||||
{
|
||||
string defaultDirectory = GetDefaultSaveDirectory(sourceMesh);
|
||||
string defaultName = $"{sourceMesh.name}_MeshUI";
|
||||
string assetPath = EditorUtility.SaveFilePanelInProject(
|
||||
"烘焙 MeshUI Mesh",
|
||||
defaultName,
|
||||
"asset",
|
||||
"请选择烘焙后 Mesh 的保存位置。",
|
||||
defaultDirectory);
|
||||
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mesh bakedMesh = CreateReadableMeshCopy(sourceMesh, System.IO.Path.GetFileNameWithoutExtension(assetPath));
|
||||
if (bakedMesh == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string uniqueAssetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
|
||||
AssetDatabase.CreateAsset(bakedMesh, uniqueAssetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
Mesh bakedAsset = AssetDatabase.LoadAssetAtPath<Mesh>(uniqueAssetPath);
|
||||
if (bakedAsset == null)
|
||||
{
|
||||
Debug.LogError($"无法加载已烘焙的 Mesh 资源: '{uniqueAssetPath}'");
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObjects(targets, "烘焙 MeshUI Mesh");
|
||||
foreach (Object editorTarget in targets)
|
||||
{
|
||||
MeshUI meshUI = editorTarget as MeshUI;
|
||||
if (meshUI == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
meshUI.Mesh = bakedAsset;
|
||||
EditorUtility.SetDirty(meshUI);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(meshUI);
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
SyncSlotsToMesh();
|
||||
}
|
||||
|
||||
private string GetDefaultSaveDirectory(Mesh sourceMesh)
|
||||
{
|
||||
string sourcePath = AssetDatabase.GetAssetPath(sourceMesh);
|
||||
if (string.IsNullOrEmpty(sourcePath))
|
||||
{
|
||||
return "Assets";
|
||||
}
|
||||
|
||||
string directory = System.IO.Path.GetDirectoryName(sourcePath);
|
||||
if (string.IsNullOrEmpty(directory))
|
||||
{
|
||||
return "Assets";
|
||||
}
|
||||
|
||||
return directory.Replace('\\', '/');
|
||||
}
|
||||
|
||||
private static Mesh CreateReadableMeshCopy(Mesh sourceMesh, string newMeshName)
|
||||
{
|
||||
using (Mesh.MeshDataArray meshDataArray = MeshUtility.AcquireReadOnlyMeshData(sourceMesh))
|
||||
{
|
||||
if (meshDataArray.Length == 0)
|
||||
{
|
||||
Debug.LogError($"无法读取 Mesh 数据: {sourceMesh.name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
Mesh.MeshData meshData = meshDataArray[0];
|
||||
Mesh bakedMesh = new Mesh
|
||||
{
|
||||
name = newMeshName,
|
||||
indexFormat = meshData.indexFormat
|
||||
};
|
||||
|
||||
int vertexCount = meshData.vertexCount;
|
||||
if (vertexCount <= 0)
|
||||
{
|
||||
Debug.LogError($"Mesh '{sourceMesh.name}' 不包含任何顶点。");
|
||||
Object.DestroyImmediate(bakedMesh);
|
||||
return null;
|
||||
}
|
||||
|
||||
using (NativeArray<Vector3> vertices = new NativeArray<Vector3>(vertexCount, Allocator.Temp))
|
||||
{
|
||||
meshData.GetVertices(vertices);
|
||||
bakedMesh.SetVertices(vertices);
|
||||
}
|
||||
|
||||
if (meshData.HasVertexAttribute(VertexAttribute.Normal))
|
||||
{
|
||||
using (NativeArray<Vector3> normals = new NativeArray<Vector3>(vertexCount, Allocator.Temp))
|
||||
{
|
||||
meshData.GetNormals(normals);
|
||||
bakedMesh.SetNormals(normals);
|
||||
}
|
||||
}
|
||||
|
||||
if (meshData.HasVertexAttribute(VertexAttribute.Tangent))
|
||||
{
|
||||
using (NativeArray<Vector4> tangents = new NativeArray<Vector4>(vertexCount, Allocator.Temp))
|
||||
{
|
||||
meshData.GetTangents(tangents);
|
||||
bakedMesh.SetTangents(tangents);
|
||||
}
|
||||
}
|
||||
|
||||
if (meshData.HasVertexAttribute(VertexAttribute.TexCoord0))
|
||||
{
|
||||
using (NativeArray<Vector2> uv0 = new NativeArray<Vector2>(vertexCount, Allocator.Temp))
|
||||
{
|
||||
meshData.GetUVs(0, uv0);
|
||||
bakedMesh.SetUVs(0, uv0);
|
||||
}
|
||||
}
|
||||
|
||||
int subMeshCount = sourceMesh.subMeshCount;
|
||||
bakedMesh.subMeshCount = subMeshCount;
|
||||
|
||||
bool hasTriangleSubMesh = false;
|
||||
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
|
||||
{
|
||||
SubMeshDescriptor subMesh = meshData.GetSubMesh(subMeshIndex);
|
||||
if (subMesh.topology == MeshTopology.Triangles && subMesh.indexCount > 0)
|
||||
{
|
||||
hasTriangleSubMesh = true;
|
||||
if (meshData.indexFormat == IndexFormat.UInt16)
|
||||
{
|
||||
using (NativeArray<ushort> indices = new NativeArray<ushort>((int)subMesh.indexCount, Allocator.Temp))
|
||||
{
|
||||
meshData.GetIndices(indices, subMeshIndex, true);
|
||||
bakedMesh.SetIndices(indices, MeshTopology.Triangles, subMeshIndex, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (NativeArray<int> indices = new NativeArray<int>((int)subMesh.indexCount, Allocator.Temp))
|
||||
{
|
||||
meshData.GetIndices(indices, subMeshIndex, true);
|
||||
bakedMesh.SetIndices(indices, MeshTopology.Triangles, subMeshIndex, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bakedMesh.SetIndices(System.Array.Empty<int>(), MeshTopology.Triangles, subMeshIndex, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTriangleSubMesh)
|
||||
{
|
||||
Debug.LogError($"Mesh '{sourceMesh.name}' 不包含可烘焙的三角形 SubMesh。");
|
||||
Object.DestroyImmediate(bakedMesh);
|
||||
return null;
|
||||
}
|
||||
|
||||
bakedMesh.RecalculateBounds();
|
||||
return bakedMesh;
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncSlotsToMesh()
|
||||
{
|
||||
foreach (Object editorTarget in targets)
|
||||
{
|
||||
MeshUI meshUI = editorTarget as MeshUI;
|
||||
if (meshUI == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SerializedObject targetObject = new SerializedObject(meshUI);
|
||||
SerializedProperty meshProp = targetObject.FindProperty("mesh");
|
||||
SerializedProperty materialsProp = targetObject.FindProperty("subMeshMaterials");
|
||||
Mesh mesh = meshProp.objectReferenceValue as Mesh;
|
||||
int targetSize = mesh != null ? mesh.subMeshCount : 0;
|
||||
materialsProp.arraySize = Mathf.Max(0, targetSize);
|
||||
targetObject.ApplyModifiedProperties();
|
||||
|
||||
EditorUtility.SetDirty(meshUI);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(meshUI);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10b17bb5e66542688383e3f64f44717c
|
||||
timeCreated: 1774473600
|
||||
@@ -0,0 +1,31 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public static class ShaderGUIUnityCompat
|
||||
{
|
||||
public static ShaderPropertyType GetPropertyType(MaterialProperty property)
|
||||
{
|
||||
#if UNITY_6000_1_OR_NEWER
|
||||
return property.propertyType;
|
||||
#else
|
||||
#pragma warning disable 0618
|
||||
return (ShaderPropertyType)property.type;
|
||||
#pragma warning restore 0618
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool HasHdrFlag(MaterialProperty property)
|
||||
{
|
||||
#if UNITY_6000_1_OR_NEWER
|
||||
return (property.propertyFlags & ShaderPropertyFlags.HDR) != 0;
|
||||
#else
|
||||
#pragma warning disable 0618
|
||||
return (((ShaderPropertyFlags)property.flags) & ShaderPropertyFlags.HDR) != 0;
|
||||
#pragma warning restore 0618
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab0b838b256742c994e033e50aca5be0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6342c05c0b5437abd5d7332652ef16b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public enum NBFXLanguageMode
|
||||
{
|
||||
FollowEditor,
|
||||
Chinese,
|
||||
English
|
||||
}
|
||||
|
||||
[FilePath("ProjectSettings/NB_FXSettings.asset", FilePathAttribute.Location.ProjectFolder)]
|
||||
public sealed class NBFXProjectSettings : ScriptableSingleton<NBFXProjectSettings>
|
||||
{
|
||||
public const string SettingsPath = "Project/NB_FX";
|
||||
|
||||
private const string LocalizationTableName = "NBShader";
|
||||
private const string LocalizationCsvAssetPath = "Packages/com.xuanxuan.nb.fx/NBShaders2/Editor/Localization/NBShaderInspectorLocalization.csv";
|
||||
private const string FoldoutSessionPrefix = "NBFXProjectSettings.Foldout.";
|
||||
|
||||
private sealed class SettingsSection
|
||||
{
|
||||
public string id;
|
||||
public Func<GUIContent> titleProvider;
|
||||
public Action<string> guiHandler;
|
||||
public List<string> keywords;
|
||||
public int order;
|
||||
public GUIContent titleContent;
|
||||
public string titleLanguage;
|
||||
}
|
||||
|
||||
private static readonly string[] LanguageOptionsChineseFallback =
|
||||
{
|
||||
"跟随编辑器",
|
||||
"中文",
|
||||
"英文"
|
||||
};
|
||||
private static readonly string[] LanguageOptionsEnglishFallback =
|
||||
{
|
||||
"Follow Editor",
|
||||
"Chinese",
|
||||
"English"
|
||||
};
|
||||
private static readonly List<SettingsSection> s_Sections = new List<SettingsSection>();
|
||||
private static readonly Dictionary<string, GUIContent> s_ProjectSettingsContentCache =
|
||||
new Dictionary<string, GUIContent>(StringComparer.Ordinal);
|
||||
private static GUIStyle s_FoldoutHeaderStyle;
|
||||
private static string s_ProjectSettingsEditorLanguage;
|
||||
private static string[] s_ProjectSettingsLanguageOptions;
|
||||
|
||||
[SerializeField]
|
||||
private NBFXLanguageMode languageMode = NBFXLanguageMode.FollowEditor;
|
||||
|
||||
public static NBFXLanguageMode LanguageMode => instance.languageMode;
|
||||
|
||||
public static void SetLanguageMode(NBFXLanguageMode mode)
|
||||
{
|
||||
if (instance.languageMode == mode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
instance.languageMode = mode;
|
||||
instance.Save(true);
|
||||
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
||||
}
|
||||
|
||||
public static void RegisterSettingsSection(
|
||||
string id,
|
||||
Func<GUIContent> titleProvider,
|
||||
Action<string> guiHandler,
|
||||
IEnumerable<string> keywords = null,
|
||||
int order = 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id) || guiHandler == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var section = new SettingsSection
|
||||
{
|
||||
id = id,
|
||||
titleProvider = titleProvider,
|
||||
guiHandler = guiHandler,
|
||||
keywords = keywords == null ? new List<string>() : new List<string>(keywords),
|
||||
order = order
|
||||
};
|
||||
|
||||
for (int i = 0; i < s_Sections.Count; i++)
|
||||
{
|
||||
if (!string.Equals(s_Sections[i].id, id, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
s_Sections[i] = section;
|
||||
SortSections();
|
||||
return;
|
||||
}
|
||||
|
||||
s_Sections.Add(section);
|
||||
SortSections();
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateSettingsProvider()
|
||||
{
|
||||
var provider = new SettingsProvider(SettingsPath, SettingsScope.Project)
|
||||
{
|
||||
label = "NB_FX",
|
||||
guiHandler = DrawSettingsGUI,
|
||||
keywords = BuildKeywords()
|
||||
};
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static void DrawSettingsGUI(string searchContext)
|
||||
{
|
||||
EnsureProjectSettingsContentCacheForEditorLanguage();
|
||||
|
||||
if (DrawSectionFoldout(
|
||||
"General",
|
||||
ProjectSettingsContent("projectSettings.generalSection", "基础设置", "General Settings")))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var selectedMode = (NBFXLanguageMode)EditorGUILayout.Popup(
|
||||
ProjectSettingsContent("projectSettings.defaultLanguage", "默认语言", "Default Language"),
|
||||
(int)LanguageMode,
|
||||
ProjectSettingsLanguageOptions());
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SetLanguageMode(selectedMode);
|
||||
}
|
||||
}
|
||||
|
||||
DrawRegisteredSections(searchContext);
|
||||
}
|
||||
|
||||
private static void DrawRegisteredSections(string searchContext)
|
||||
{
|
||||
for (int i = 0; i < s_Sections.Count; i++)
|
||||
{
|
||||
SettingsSection section = s_Sections[i];
|
||||
EditorGUILayout.Space(10f);
|
||||
if (DrawSectionFoldout(section.id, GetSectionTitleContent(section)))
|
||||
{
|
||||
section.guiHandler(searchContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DrawSectionFoldout(string id, GUIContent content)
|
||||
{
|
||||
string sessionKey = FoldoutSessionPrefix + id;
|
||||
bool expanded = SessionState.GetBool(sessionKey, true);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool nextExpanded = EditorGUILayout.Foldout(expanded, content, true, FoldoutHeaderStyle);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SessionState.SetBool(sessionKey, nextExpanded);
|
||||
}
|
||||
|
||||
return nextExpanded;
|
||||
}
|
||||
|
||||
private static GUIStyle FoldoutHeaderStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_FoldoutHeaderStyle == null)
|
||||
{
|
||||
s_FoldoutHeaderStyle = new GUIStyle(EditorStyles.foldout)
|
||||
{
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
}
|
||||
|
||||
return s_FoldoutHeaderStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private static GUIContent ProjectSettingsContent(string key, string chineseFallback, string englishFallback)
|
||||
{
|
||||
EnsureProjectSettingsContentCacheForEditorLanguage();
|
||||
|
||||
GUIContent content;
|
||||
if (s_ProjectSettingsContentCache.TryGetValue(key, out content))
|
||||
return content;
|
||||
|
||||
var text = ProjectSettingsText(key, chineseFallback, englishFallback, s_ProjectSettingsEditorLanguage);
|
||||
content = new GUIContent(text, text);
|
||||
s_ProjectSettingsContentCache.Add(key, content);
|
||||
return content;
|
||||
}
|
||||
|
||||
private static string ProjectSettingsText(
|
||||
string key,
|
||||
string chineseFallback,
|
||||
string englishFallback,
|
||||
string editorLanguage)
|
||||
{
|
||||
string fallback = IsEnglishLanguage(editorLanguage) ? englishFallback : chineseFallback;
|
||||
return ShaderGUILocalization.GetInspectorText(LocalizationTableName, key, fallback, editorLanguage);
|
||||
}
|
||||
|
||||
private static string[] ProjectSettingsLanguageOptions()
|
||||
{
|
||||
EnsureProjectSettingsContentCacheForEditorLanguage();
|
||||
if (s_ProjectSettingsLanguageOptions != null)
|
||||
return s_ProjectSettingsLanguageOptions;
|
||||
|
||||
string[] fallback = IsEnglishLanguage(s_ProjectSettingsEditorLanguage)
|
||||
? LanguageOptionsEnglishFallback
|
||||
: LanguageOptionsChineseFallback;
|
||||
s_ProjectSettingsLanguageOptions = ShaderGUILocalization.GetInspectorOptions(
|
||||
LocalizationTableName,
|
||||
"projectSettings.languageMode",
|
||||
fallback,
|
||||
s_ProjectSettingsEditorLanguage);
|
||||
return s_ProjectSettingsLanguageOptions;
|
||||
}
|
||||
|
||||
private static GUIContent GetSectionTitleContent(SettingsSection section)
|
||||
{
|
||||
if (section.titleProvider != null)
|
||||
{
|
||||
string language = ShaderGUILocalization.GetCurrentLanguage(LocalizationTableName);
|
||||
if (section.titleContent != null &&
|
||||
string.Equals(section.titleLanguage, language, StringComparison.Ordinal))
|
||||
{
|
||||
return section.titleContent;
|
||||
}
|
||||
|
||||
section.titleLanguage = language;
|
||||
section.titleContent = section.titleProvider();
|
||||
return section.titleContent;
|
||||
}
|
||||
|
||||
GUIContent content;
|
||||
if (s_ProjectSettingsContentCache.TryGetValue(section.id, out content))
|
||||
return content;
|
||||
|
||||
content = new GUIContent(section.id, section.id);
|
||||
s_ProjectSettingsContentCache.Add(section.id, content);
|
||||
return content;
|
||||
}
|
||||
|
||||
private static void EnsureProjectSettingsContentCacheForEditorLanguage()
|
||||
{
|
||||
RegisterLocalization();
|
||||
string editorLanguage = ShaderGUILocalization.GetEditorLanguageName();
|
||||
if (string.Equals(s_ProjectSettingsEditorLanguage, editorLanguage, StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
s_ProjectSettingsEditorLanguage = editorLanguage;
|
||||
s_ProjectSettingsContentCache.Clear();
|
||||
s_ProjectSettingsLanguageOptions = null;
|
||||
}
|
||||
|
||||
private static bool IsEnglishLanguage(string language)
|
||||
{
|
||||
return string.Equals(language, ShaderGUILocalization.EnglishLanguage, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void RegisterLocalization()
|
||||
{
|
||||
ShaderGUILocalization.RegisterCsv(LocalizationTableName, LocalizationCsvAssetPath);
|
||||
}
|
||||
|
||||
private static HashSet<string> BuildKeywords()
|
||||
{
|
||||
var keywords = new HashSet<string>
|
||||
{
|
||||
"NB_FX",
|
||||
"Language",
|
||||
"语言",
|
||||
"默认语言",
|
||||
"Default Language",
|
||||
"General Settings",
|
||||
"基础设置",
|
||||
"NBShader"
|
||||
};
|
||||
|
||||
for (int i = 0; i < s_Sections.Count; i++)
|
||||
{
|
||||
List<string> sectionKeywords = s_Sections[i].keywords;
|
||||
if (sectionKeywords == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int keywordIndex = 0; keywordIndex < sectionKeywords.Count; keywordIndex++)
|
||||
{
|
||||
keywords.Add(sectionKeywords[keywordIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
private static void SortSections()
|
||||
{
|
||||
s_Sections.Sort((a, b) =>
|
||||
{
|
||||
int orderCompare = a.order.CompareTo(b.order);
|
||||
if (orderCompare != 0)
|
||||
{
|
||||
return orderCompare;
|
||||
}
|
||||
|
||||
return string.CompareOrdinal(a.id, b.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5f19e24bcae4dcc8e06c4550b9b1029
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2036
Packages/NB_FX/XuanXuanRenderUtility/Editor/ShaderGUIHelper.cs
Normal file
2036
Packages/NB_FX/XuanXuanRenderUtility/Editor/ShaderGUIHelper.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9279ba81938dc1548b9e9d8297bdcd57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 947243301cab4804d8df47a42b220c4e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class BlockItem : ShaderGUIItem
|
||||
{
|
||||
protected readonly ShaderGUIRootItem SharedRootItem;
|
||||
private readonly ShaderGUIFoldOutHelper _foldOutHelper;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
|
||||
protected virtual GUIStyle TitleStyle => EditorStyles.label;
|
||||
protected virtual bool DrawSeparatorLine => false;
|
||||
|
||||
public string FoldOutPropertyName { get; }
|
||||
|
||||
public BlockItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string foldOutPropertyName,
|
||||
Func<GUIContent> contentProvider) : base(rootItem, parentItem)
|
||||
{
|
||||
SharedRootItem = rootItem;
|
||||
FoldOutPropertyName = foldOutPropertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
GuiContent = _contentProvider();
|
||||
_foldOutHelper = new ShaderGUIFoldOutHelper(rootItem, foldOutPropertyName);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
DrawLeadingSpace();
|
||||
GetRect();
|
||||
_foldOutHelper.DrawFoldOut(LabelRect);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent, TitleStyle);
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
EditorGUI.indentLevel++;
|
||||
if (_foldOutHelper.BeginFadeGroup())
|
||||
{
|
||||
DrawBlock();
|
||||
}
|
||||
_foldOutHelper.EndFadedGroup();
|
||||
EditorGUI.indentLevel--;
|
||||
if (DrawSeparatorLine)
|
||||
{
|
||||
Rect rect = ApplyGlobalRectCompensation(LayoutRect(1));
|
||||
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DrawLeadingSpace()
|
||||
{
|
||||
}
|
||||
|
||||
public override void DrawBlock()
|
||||
{
|
||||
for (int i = 0; i < ChildrenItemList.Count; i++)
|
||||
{
|
||||
ChildrenItemList[i].OnGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BigBlockItem : BlockItem
|
||||
{
|
||||
protected override GUIStyle TitleStyle => EditorStyles.boldLabel;
|
||||
protected override bool DrawSeparatorLine => true;
|
||||
|
||||
public BigBlockItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string foldOutPropertyName,
|
||||
Func<GUIContent> contentProvider) : base(rootItem, parentItem, foldOutPropertyName, contentProvider)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DrawLeadingSpace()
|
||||
{
|
||||
LayoutSpace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d07eceb7cee2d8b46ab0f6f62c4f82e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ColorItem : ShaderGUIItem
|
||||
{
|
||||
private const float ColorFieldLeftInset = EditorGUIIndentWidth;
|
||||
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ColorItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect(false);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
Color color = PropertyInfo.Property.colorValue;
|
||||
bool hdr = ShaderGUIUnityCompat.HasHdrFlag(PropertyInfo.Property);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Rect colorRect = GetLabeledColorFieldRect(ControlRect);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(colorRect, PropertyInfo.Property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
color = EditorGUI.ColorField(colorRect, GUIContent.none, color, true, true, hdr);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
PropertyInfo.Property.colorValue = color;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
internal static Rect GetNoLabelColorFieldRect(Rect rect)
|
||||
{
|
||||
rect.x += ColorFieldLeftInset;
|
||||
rect.width = Mathf.Max(0f, rect.width - ColorFieldLeftInset);
|
||||
return rect;
|
||||
}
|
||||
|
||||
internal static Rect GetLabeledColorFieldRect(Rect rect)
|
||||
{
|
||||
float leftPadding = EditorStyles.colorField.padding.left + ColorFieldLeftInset +1f;
|
||||
rect.x -= leftPadding;
|
||||
rect.width += leftPadding;
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorLineItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly bool _showLabel;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ColorLineItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
bool showLabel,
|
||||
Func<GUIContent> contentProvider = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_showLabel = showLabel;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _showLabel ? _contentProvider() : GUIContent.none;
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Draw(ApplyGlobalRectCompensation(LayoutRect()));
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void Draw(Rect rect)
|
||||
{
|
||||
BaseRect = rect;
|
||||
|
||||
if (_showLabel)
|
||||
{
|
||||
SplitLineRect(rect, out LabelRect, out ControlRect, out ResetRect, false);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LabelRect = new Rect(rect.x, rect.y, 0f, rect.height);
|
||||
SplitControlAndResetRect(rect, out ControlRect, out ResetRect, false);
|
||||
}
|
||||
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
Color color = property.colorValue;
|
||||
bool hdr = ShaderGUIUnityCompat.HasHdrFlag(property);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Rect colorRect = _showLabel
|
||||
? ColorItem.GetLabeledColorFieldRect(ControlRect)
|
||||
: ColorItem.GetNoLabelColorFieldRect(ControlRect);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(colorRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
color = EditorGUI.ColorField(colorRect, GUIContent.none, color, true, true, hdr);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.colorValue = color;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
Vector4 defaultColor = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
isDefaultValue = !PropertyInfo.Property.hasMixedValue &&
|
||||
Approximately(PropertyInfo.Property.colorValue, defaultColor);
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31b44f82d6184d46b683b144f9b5f517
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,533 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class GradientItem : ShaderGUIItem
|
||||
{
|
||||
private static bool s_UndoRedoRegistered;
|
||||
private static int s_UndoRedoVersion;
|
||||
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly string[] _colorPropertyNames;
|
||||
private readonly string[] _alphaPropertyNames;
|
||||
private readonly int _maxCount;
|
||||
private readonly bool _hdr;
|
||||
private readonly ColorSpace _colorSpace;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
private ShaderPropertyInfo[] _colorPropertyInfos;
|
||||
private ShaderPropertyInfo[] _alphaPropertyInfos;
|
||||
private readonly Gradient _gradient = new Gradient();
|
||||
private GradientColorKey[] _colorKeys = Array.Empty<GradientColorKey>();
|
||||
private GradientAlphaKey[] _alphaKeys = Array.Empty<GradientAlphaKey>();
|
||||
private bool _gradientCacheDirty = true;
|
||||
private int _lastUndoRedoVersion = -1;
|
||||
|
||||
public GradientItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string countPropertyName,
|
||||
int maxCount,
|
||||
string[] colorPropertyNames,
|
||||
string[] alphaPropertyNames,
|
||||
Func<GUIContent> contentProvider,
|
||||
bool hdr = false,
|
||||
ColorSpace colorSpace = ColorSpace.Gamma,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = countPropertyName;
|
||||
_maxCount = Mathf.Max(2, maxCount);
|
||||
_colorPropertyNames = colorPropertyNames ?? Array.Empty<string>();
|
||||
_alphaPropertyNames = alphaPropertyNames ?? Array.Empty<string>();
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_hdr = hdr;
|
||||
_colorSpace = colorSpace;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
EnsureUndoRedoRegistered();
|
||||
CacheProperties();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
private static void EnsureUndoRedoRegistered()
|
||||
{
|
||||
if (s_UndoRedoRegistered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
s_UndoRedoRegistered = true;
|
||||
}
|
||||
|
||||
private static void OnUndoRedoPerformed()
|
||||
{
|
||||
s_UndoRedoVersion++;
|
||||
}
|
||||
|
||||
private void CacheProperties()
|
||||
{
|
||||
_colorPropertyInfos = new ShaderPropertyInfo[_colorPropertyNames.Length];
|
||||
for (int i = 0; i < _colorPropertyNames.Length; i++)
|
||||
{
|
||||
if (RootItem.PropertyInfoDic.TryGetValue(_colorPropertyNames[i], out ShaderPropertyInfo info))
|
||||
{
|
||||
_colorPropertyInfos[i] = info;
|
||||
}
|
||||
}
|
||||
|
||||
_alphaPropertyInfos = new ShaderPropertyInfo[_alphaPropertyNames.Length];
|
||||
for (int i = 0; i < _alphaPropertyNames.Length; i++)
|
||||
{
|
||||
if (RootItem.PropertyInfoDic.TryGetValue(_alphaPropertyNames[i], out ShaderPropertyInfo info))
|
||||
{
|
||||
_alphaPropertyInfos[i] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
Gradient gradient = GetCachedGradient();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = GradientPropertyHasMixedValue();
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, PropertyInfo.Property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
gradient = EditorGUI.GradientField(ControlRect, GUIContent.none, gradient, _hdr, _colorSpace);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
WriteGradient(gradient);
|
||||
MarkGradientCacheDirty();
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
private Gradient GetCachedGradient()
|
||||
{
|
||||
if (_lastUndoRedoVersion != s_UndoRedoVersion)
|
||||
{
|
||||
_lastUndoRedoVersion = s_UndoRedoVersion;
|
||||
MarkGradientCacheDirty();
|
||||
}
|
||||
|
||||
if (_gradientCacheDirty)
|
||||
{
|
||||
ReadGradient();
|
||||
_gradientCacheDirty = false;
|
||||
GradientReflectionHelper.RefreshGradientData();
|
||||
}
|
||||
|
||||
return _gradient;
|
||||
}
|
||||
|
||||
private void MarkGradientCacheDirty()
|
||||
{
|
||||
_gradientCacheDirty = true;
|
||||
}
|
||||
|
||||
private void ReadGradient()
|
||||
{
|
||||
bool hasColorProperties = _colorPropertyInfos.Length > 0;
|
||||
bool hasAlphaProperties = _alphaPropertyInfos.Length > 0;
|
||||
bool isBlackAndWhiteGradient = !hasColorProperties && hasAlphaProperties;
|
||||
GetGradientKeyCounts(out int colorKeyCount, out int alphaKeyCount);
|
||||
|
||||
if (_colorKeys.Length != colorKeyCount)
|
||||
{
|
||||
_colorKeys = new GradientColorKey[colorKeyCount];
|
||||
}
|
||||
|
||||
if (_alphaKeys.Length != alphaKeyCount)
|
||||
{
|
||||
_alphaKeys = new GradientAlphaKey[alphaKeyCount];
|
||||
}
|
||||
|
||||
for (int i = 0; i < colorKeyCount; i++)
|
||||
{
|
||||
Color color = Color.white;
|
||||
float colorTime = colorKeyCount == 1 ? 0f : i / (float)(colorKeyCount - 1);
|
||||
if (isBlackAndWhiteGradient)
|
||||
{
|
||||
TryReadBlackAndWhiteKey(i, ref color, ref colorTime);
|
||||
}
|
||||
else if (i < _colorPropertyInfos.Length && _colorPropertyInfos[i] != null)
|
||||
{
|
||||
Color packed = _colorPropertyInfos[i].Property.colorValue;
|
||||
color = packed;
|
||||
colorTime = packed.a;
|
||||
}
|
||||
|
||||
_colorKeys[i] = new GradientColorKey(color, colorTime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < alphaKeyCount; i++)
|
||||
{
|
||||
float alpha = 1f;
|
||||
float alphaTime = alphaKeyCount == 1 ? 0f : i / (float)(alphaKeyCount - 1);
|
||||
if (!isBlackAndWhiteGradient && hasAlphaProperties)
|
||||
{
|
||||
TryReadAlphaKey(i, ref alpha, ref alphaTime);
|
||||
}
|
||||
|
||||
_alphaKeys[i] = new GradientAlphaKey(alpha, alphaTime);
|
||||
}
|
||||
|
||||
_gradient.SetKeys(_colorKeys, _alphaKeys);
|
||||
}
|
||||
|
||||
private void GetGradientKeyCounts(out int colorKeyCount, out int alphaKeyCount)
|
||||
{
|
||||
int countValue = PropertyInfo.Property.intValue;
|
||||
bool hasColorProperties = _colorPropertyInfos.Length > 0;
|
||||
bool hasAlphaProperties = _alphaPropertyInfos.Length > 0;
|
||||
|
||||
if (hasColorProperties && hasAlphaProperties)
|
||||
{
|
||||
colorKeyCount = countValue & 0xFFFF;
|
||||
alphaKeyCount = countValue >> 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
colorKeyCount = countValue;
|
||||
alphaKeyCount = 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void TryReadBlackAndWhiteKey(int index, ref Color color, ref float time)
|
||||
{
|
||||
int vectorIndex = index / 2;
|
||||
int componentIndex = index % 2;
|
||||
if (vectorIndex >= _alphaPropertyInfos.Length || _alphaPropertyInfos[vectorIndex] == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4 packed = _alphaPropertyInfos[vectorIndex].Property.vectorValue;
|
||||
float value = componentIndex == 0 ? packed.x : packed.z;
|
||||
time = componentIndex == 0 ? packed.y : packed.w;
|
||||
color = new Color(value, value, value, 1f);
|
||||
}
|
||||
|
||||
private void TryReadAlphaKey(int index, ref float alpha, ref float time)
|
||||
{
|
||||
int vectorIndex = index / 2;
|
||||
int componentIndex = index % 2;
|
||||
if (vectorIndex >= _alphaPropertyInfos.Length || _alphaPropertyInfos[vectorIndex] == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4 packed = _alphaPropertyInfos[vectorIndex].Property.vectorValue;
|
||||
if (componentIndex == 0)
|
||||
{
|
||||
alpha = packed.x;
|
||||
time = packed.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha = packed.z;
|
||||
time = packed.w;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteGradient(Gradient gradient)
|
||||
{
|
||||
bool hasColorProperties = _colorPropertyInfos.Length > 0;
|
||||
bool hasAlphaProperties = _alphaPropertyInfos.Length > 0;
|
||||
bool isBlackAndWhiteGradient = !hasColorProperties && hasAlphaProperties;
|
||||
int countPropertyValue = PropertyInfo.Property.intValue;
|
||||
|
||||
if (isBlackAndWhiteGradient)
|
||||
{
|
||||
int finalColorKeyCount = gradient.colorKeys.Length;
|
||||
if (finalColorKeyCount <= _maxCount)
|
||||
{
|
||||
WriteBlackAndWhiteGradient(gradient, finalColorKeyCount);
|
||||
PropertyInfo.Property.intValue = finalColorKeyCount;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasColorProperties)
|
||||
{
|
||||
int finalColorKeyCount = gradient.colorKeys.Length;
|
||||
if (finalColorKeyCount <= _maxCount)
|
||||
{
|
||||
WriteColorKeys(gradient, finalColorKeyCount);
|
||||
countPropertyValue &= 0xFFFF << 16;
|
||||
countPropertyValue |= finalColorKeyCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBlackAndWhiteGradient && hasAlphaProperties)
|
||||
{
|
||||
int finalAlphaKeyCount = gradient.alphaKeys.Length;
|
||||
if (finalAlphaKeyCount <= _maxCount)
|
||||
{
|
||||
WriteAlphaKeys(gradient, finalAlphaKeyCount);
|
||||
countPropertyValue &= 0xFFFF;
|
||||
countPropertyValue |= finalAlphaKeyCount << 16;
|
||||
}
|
||||
}
|
||||
|
||||
PropertyInfo.Property.intValue = countPropertyValue;
|
||||
}
|
||||
|
||||
private void WriteColorKeys(Gradient gradient, int colorKeyCount)
|
||||
{
|
||||
for (int i = 0; i < colorKeyCount && i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
if (_colorPropertyInfos[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GradientColorKey key = gradient.colorKeys[i];
|
||||
_colorPropertyInfos[i].Property.colorValue = new Color(key.color.r, key.color.g, key.color.b, key.time);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteBlackAndWhiteGradient(Gradient gradient, int colorKeyCount)
|
||||
{
|
||||
int vectorCount = Mathf.CeilToInt(colorKeyCount / 2f);
|
||||
for (int i = 0; i < vectorCount && i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
if (_alphaPropertyInfos[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector4 value = Vector4.zero;
|
||||
GradientColorKey key0 = gradient.colorKeys[i * 2];
|
||||
value.x = key0.color.r;
|
||||
value.y = key0.time;
|
||||
if (i * 2 + 1 < colorKeyCount)
|
||||
{
|
||||
GradientColorKey key1 = gradient.colorKeys[i * 2 + 1];
|
||||
value.z = key1.color.r;
|
||||
value.w = key1.time;
|
||||
}
|
||||
|
||||
_alphaPropertyInfos[i].Property.vectorValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteAlphaKeys(Gradient gradient, int alphaKeyCount)
|
||||
{
|
||||
int vectorCount = Mathf.CeilToInt(alphaKeyCount / 2f);
|
||||
for (int i = 0; i < vectorCount && i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
if (_alphaPropertyInfos[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector4 value = Vector4.zero;
|
||||
GradientAlphaKey key0 = gradient.alphaKeys[i * 2];
|
||||
value.x = key0.alpha;
|
||||
value.y = key0.time;
|
||||
if (i * 2 + 1 < alphaKeyCount)
|
||||
{
|
||||
GradientAlphaKey key1 = gradient.alphaKeys[i * 2 + 1];
|
||||
value.z = key1.alpha;
|
||||
value.w = key1.time;
|
||||
}
|
||||
|
||||
_alphaPropertyInfos[i].Property.vectorValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GradientPropertyHasMixedValue()
|
||||
{
|
||||
if (PropertyInfo == null || PropertyInfo.Property == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GetGradientKeyCounts(out int colorKeyCount, out int alphaKeyCount);
|
||||
bool hasMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
for (int i = 0; i < colorKeyCount && i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
hasMixedValue |= _colorPropertyInfos[i]?.Property.hasMixedValue == true;
|
||||
}
|
||||
|
||||
int alphaPropertyCount = Mathf.CeilToInt(alphaKeyCount / 2f);
|
||||
for (int i = 0; i < alphaPropertyCount && i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
hasMixedValue |= _alphaPropertyInfos[i]?.Property.hasMixedValue == true;
|
||||
}
|
||||
|
||||
return hasMixedValue;
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = IsCountDefault();
|
||||
for (int i = 0; i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
isDefaultValue &= IsDefault(_colorPropertyInfos[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
isDefaultValue &= IsDefault(_alphaPropertyInfos[i]);
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
ResetCountProperty();
|
||||
for (int i = 0; i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
ResetProperty(_colorPropertyInfos[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
ResetProperty(_alphaPropertyInfos[i]);
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
MarkGradientCacheDirty();
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCountDefault()
|
||||
{
|
||||
if (PropertyInfo == null || PropertyInfo.Property == null || PropertyInfo.Property.hasMixedValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return PropertyInfo.Property.intValue == GetDefaultInt(PropertyInfo);
|
||||
}
|
||||
|
||||
private void ResetCountProperty()
|
||||
{
|
||||
if (PropertyInfo?.Property == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyInfo.Property.intValue = GetDefaultInt(PropertyInfo);
|
||||
}
|
||||
|
||||
private bool IsDefault(ShaderPropertyInfo info)
|
||||
{
|
||||
if (info == null || info.Property == null || info.Property.hasMixedValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (ShaderGUIUnityCompat.GetPropertyType(info.Property))
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
return Mathf.Approximately(info.Property.floatValue, RootItem.Shader.GetPropertyDefaultFloatValue(info.Index));
|
||||
case ShaderPropertyType.Int:
|
||||
return info.Property.intValue == GetDefaultInt(info);
|
||||
case ShaderPropertyType.Color:
|
||||
return Approximately(info.Property.colorValue, RootItem.Shader.GetPropertyDefaultVectorValue(info.Index));
|
||||
case ShaderPropertyType.Vector:
|
||||
return Approximately(info.Property.vectorValue, RootItem.Shader.GetPropertyDefaultVectorValue(info.Index));
|
||||
default:
|
||||
return Mathf.Approximately(info.Property.floatValue, GetDefaultScalar(info));
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetProperty(ShaderPropertyInfo info)
|
||||
{
|
||||
if (info == null || info.Property == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ShaderGUIUnityCompat.GetPropertyType(info.Property))
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
info.Property.floatValue = RootItem.Shader.GetPropertyDefaultFloatValue(info.Index);
|
||||
break;
|
||||
case ShaderPropertyType.Int:
|
||||
info.Property.intValue = GetDefaultInt(info);
|
||||
break;
|
||||
case ShaderPropertyType.Color:
|
||||
info.Property.colorValue = RootItem.Shader.GetPropertyDefaultVectorValue(info.Index);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
info.Property.vectorValue = RootItem.Shader.GetPropertyDefaultVectorValue(info.Index);
|
||||
break;
|
||||
default:
|
||||
info.Property.floatValue = GetDefaultScalar(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetDefaultScalar(ShaderPropertyInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return RootItem.Shader.GetPropertyDefaultFloatValue(info.Index);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return 2f;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDefaultInt(ShaderPropertyInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetShaderPropertyDefaultIntValue(RootItem.Shader, info.Index);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return Mathf.RoundToInt(GetDefaultScalar(info));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17810b41ec32488faa9fbceaaf8b8fd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class HelpBoxItem : ShaderGUIItem
|
||||
{
|
||||
private readonly MessageType _messageType;
|
||||
private readonly Func<string> _messageProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public HelpBoxItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
Func<string> messageProvider,
|
||||
MessageType messageType = MessageType.Info,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_messageProvider = messageProvider ?? (() => string.Empty);
|
||||
_messageType = messageType;
|
||||
_isVisible = isVisible;
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
DrawLayoutHelpBox(_messageProvider(), _messageType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c516e0fb3014df8b96bce4e0925cb45
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class RenderQueueItem : ShaderGUIFloatItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Action _onQueueChanged;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public RenderQueueItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Action onQueueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_onQueueChanged = onQueueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onQueueChanged?.Invoke();
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
_onQueueChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5caca15eb8794602ae6029d798cb5f1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class SectionLabelItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public SectionLabelItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LayoutSpace();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(ApplyGlobalRectCompensation(LayoutRect()), GuiContent, EditorStyles.boldLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75c41d424c5e4203992222069d060bc6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,394 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderGUIFloatItem:ShaderGUIItem
|
||||
{
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ShaderGUIFloatItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem, Func<bool> isVisible = null) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
_isVisible = isVisible;
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
float value = DraggableLabelFloat.Handle(LabelRect, PropertyInfo.Property.floatValue, sensitivity: -1f);//拖动Label控件可以操作Float参数
|
||||
value = EditorGUI.FloatField(ControlRect, value);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ShaderGUISliderItem:ShaderGUIItem
|
||||
{
|
||||
public float Min = 0;
|
||||
public float Max = 1;
|
||||
public string RangePropertyName;
|
||||
ShaderPropertyInfo _rangePropertyInfo;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private const float SliderFrontGap = 5f;
|
||||
private const float SliderBackGap = 2f;
|
||||
private const float RangeFieldWidth = 30f;
|
||||
|
||||
public override void InitTriggerByChild()
|
||||
{
|
||||
if (RangePropertyName != null)
|
||||
{
|
||||
_rangePropertyInfo = RootItem.PropertyInfoDic[RangePropertyName];
|
||||
Min = _rangePropertyInfo.Property.vectorValue.x;
|
||||
Max = _rangePropertyInfo.Property.vectorValue.y;
|
||||
}
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public ShaderGUISliderItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem, Func<bool> isVisible = null) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
_isVisible = isVisible;
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_rangePropertyInfo == null)
|
||||
{
|
||||
base.OnGUI();
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
DrawController();
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
DrawResetButton();
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
if (_rangePropertyInfo != null)
|
||||
{
|
||||
Rect minRect = ControlRect;
|
||||
minRect.width = RangeFieldWidth;
|
||||
Rect maxRect = ControlRect;
|
||||
maxRect.x = ControlRect.xMax - RangeFieldWidth;
|
||||
maxRect.width = RangeFieldWidth;
|
||||
Rect sliderRect = ControlRect;
|
||||
sliderRect.x = minRect.xMax + SliderFrontGap;
|
||||
sliderRect.width = Mathf.Max(0f, maxRect.x - sliderRect.x - SliderBackGap + EditorGUI.indentLevel * UnityEditorGUIIndentWidth);
|
||||
|
||||
RangeVecHasMixedValue(out bool minValueHasMixed,out bool maxValueHasMixed);
|
||||
|
||||
Vector4 rangeVector = _rangePropertyInfo.Property.vectorValue;
|
||||
float min = rangeVector.x;
|
||||
float max = rangeVector.y;
|
||||
|
||||
EditorGUI.showMixedValue = minValueHasMixed;
|
||||
bool minAnimatedScope = BeginAnimatedPropertyBackground(minRect, _rangePropertyInfo.Property);
|
||||
min = EditorGUI.FloatField(minRect, min);
|
||||
EndAnimatedPropertyBackground(minAnimatedScope);
|
||||
EditorGUI.showMixedValue = maxValueHasMixed;
|
||||
bool maxAnimatedScope = BeginAnimatedPropertyBackground(maxRect, _rangePropertyInfo.Property);
|
||||
max = EditorGUI.FloatField(maxRect, max);
|
||||
EndAnimatedPropertyBackground(maxAnimatedScope);
|
||||
rangeVector.x = min;
|
||||
rangeVector.y = max;
|
||||
SetVectorIfDifferent(_rangePropertyInfo.Property, rangeVector);
|
||||
|
||||
float sliderMin = Mathf.Min(min, max);
|
||||
float sliderMax = Mathf.Max(min, max);
|
||||
float value = DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
PropertyInfo.Property.floatValue,
|
||||
DraggableLabelFloat.GetSensitivityByRange(sliderMin, sliderMax),
|
||||
sliderMin,
|
||||
sliderMax);
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
bool sliderAnimatedScope = BeginAnimatedPropertyBackground(sliderRect, PropertyInfo.Property);
|
||||
value = SliderNoIndent(sliderRect, value, sliderMin, sliderMax);
|
||||
EndAnimatedPropertyBackground(sliderAnimatedScope);
|
||||
|
||||
SetFloatIfDifferent(PropertyInfo.Property, Mathf.Clamp(value, sliderMin, sliderMax));
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
float value = DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
PropertyInfo.Property.floatValue,
|
||||
DraggableLabelFloat.GetSensitivityByRange(Min, Max),
|
||||
Min,
|
||||
Max);
|
||||
value = EditorGUI.Slider(ControlRect, value,Min,Max);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static float SliderNoIndent(Rect rect, float value, float min, float max)
|
||||
{
|
||||
int indentLevel = EditorGUI.indentLevel;
|
||||
try
|
||||
{
|
||||
EditorGUI.indentLevel = 0;
|
||||
return EditorGUI.Slider(rect, value, min, max);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
void RangeVecHasMixedValue( out bool minValueHasMixed, out bool maxValueHasMixed)
|
||||
{
|
||||
minValueHasMixed = false;
|
||||
maxValueHasMixed = false;
|
||||
if (RootItem.Mats.Count > 1)
|
||||
{
|
||||
MaterialProperty rangeProperty = _rangePropertyInfo.Property;
|
||||
float minValue = 0;
|
||||
float maxValue = 0;
|
||||
for (int i = 0; i < RootItem.Mats.Count; i++)
|
||||
{
|
||||
Vector4 rangeVec = RootItem.Mats[i].GetVector(RangePropertyName);
|
||||
if (i == 0)
|
||||
{
|
||||
minValue = rangeVec.x;
|
||||
maxValue = rangeVec.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Mathf.Approximately(minValue, rangeVec.x))
|
||||
{
|
||||
minValueHasMixed = true;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(maxValue, rangeVec.y))
|
||||
{
|
||||
maxValueHasMixed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RangePropIsDefaultValue()
|
||||
{
|
||||
MaterialProperty rangeProperty = _rangePropertyInfo.Property;
|
||||
return rangeProperty.vectorValue == RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index) && !rangeProperty.hasMixedValue;
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (RangePropertyName != null)
|
||||
{
|
||||
if (!isCallByChild) //只有自身Reset了,才需要查询自己的状态是否正确
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
|
||||
float defaultValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index);
|
||||
isDefaultValue = Mathf.Approximately(PropertyInfo.Property.floatValue, defaultValue);
|
||||
Vector4 defaultRangeVec = RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
isDefaultValue &= _rangePropertyInfo.Property.vectorValue == defaultRangeVec;
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return; //如果状态没有改变,就不需要做任何操作
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (RangePropertyName != null)
|
||||
{
|
||||
PropertyInfo.Property.floatValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index);
|
||||
_rangePropertyInfo.Property.vectorValue =
|
||||
RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
PropertyIsDefaultValue = true;
|
||||
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
childItem.ExecuteReset(true);
|
||||
}
|
||||
|
||||
HasModified = false;
|
||||
if (!isCallByParent) //直接由用户触发重置
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//用于滑动控制Label的方案。
|
||||
// DraggableLabelFloat.cs
|
||||
public static class DraggableLabelFloat
|
||||
{
|
||||
// 为每个控件实例缓存一次拖拽状态
|
||||
private static readonly System.Collections.Generic.Dictionary<int, DragState> s_Drag =
|
||||
new System.Collections.Generic.Dictionary<int, DragState>();
|
||||
private class DragState
|
||||
{
|
||||
public float startValue;
|
||||
public float dragX; // 累计水平位移(单位:像素)
|
||||
}
|
||||
|
||||
public static float Handle(Rect labelRect, float value, float sensitivity = -1f, float? min = null, float? max = null)
|
||||
{
|
||||
if (!GUI.enabled)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
EditorGUIUtility.AddCursorRect(labelRect, MouseCursor.SlideArrow);
|
||||
|
||||
int id = GUIUtility.GetControlID(FocusType.Passive, labelRect);
|
||||
Event e = Event.current;
|
||||
|
||||
switch (e.GetTypeForControl(id))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (labelRect.Contains(e.mousePosition) && e.button == 0)
|
||||
{
|
||||
GUIUtility.hotControl = id;
|
||||
e.Use();
|
||||
EditorGUIUtility.SetWantsMouseJumping(1);
|
||||
|
||||
s_Drag[id] = new DragState
|
||||
{
|
||||
startValue = value,
|
||||
dragX = 0f
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id && s_Drag.TryGetValue(id, out var st))
|
||||
{
|
||||
// 关键:使用 Event.delta 累加位移,兼容越界/跳回
|
||||
st.dragX += e.delta.x;
|
||||
|
||||
float baseSens = sensitivity > 0f
|
||||
? sensitivity
|
||||
: 0.003f * Mathf.Max(1f, Mathf.Abs(st.startValue));
|
||||
|
||||
float modifier = 1f;
|
||||
if (e.shift) modifier *= 0.1f;
|
||||
if (e.control || e.command) modifier *= 10f;
|
||||
|
||||
float accel = 1f + 0.15f * Mathf.Clamp01(Mathf.Abs(st.dragX) / 50f);
|
||||
|
||||
float newValue = st.startValue + st.dragX * baseSens * modifier * accel;
|
||||
|
||||
if (min.HasValue) newValue = Mathf.Max(min.Value, newValue);
|
||||
if (max.HasValue) newValue = Mathf.Min(max.Value, newValue);
|
||||
|
||||
if (!float.IsNaN(newValue) && !float.IsInfinity(newValue) && !Mathf.Approximately(newValue, value))
|
||||
{
|
||||
GUI.changed = true;
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
s_Drag.Remove(id);
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.KeyDown:
|
||||
// 可选:ESC 取消拖拽并还原
|
||||
if (GUIUtility.hotControl == id && e.keyCode == KeyCode.Escape && s_Drag.TryGetValue(id, out var st2))
|
||||
{
|
||||
value = st2.startValue;
|
||||
GUI.changed = true;
|
||||
|
||||
GUIUtility.hotControl = 0;
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
s_Drag.Remove(id);
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static float GetSensitivityByRange(float min, float max)
|
||||
{
|
||||
float range = Mathf.Abs(max - min);
|
||||
if (float.IsNaN(range) || float.IsInfinity(range) || Mathf.Approximately(range, 0f))
|
||||
{
|
||||
return -1f;
|
||||
}
|
||||
|
||||
return range * 0.003f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b82b6bb5065f449f98cdba68daa4c16f
|
||||
timeCreated: 1758462240
|
||||
@@ -0,0 +1,127 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.AnimatedValues;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
|
||||
public class ShaderGUIFoldOutHelper
|
||||
{
|
||||
private const float FoldOutAnimationSpeed = 10f;
|
||||
|
||||
public ShaderGUIFoldOutHelper(ShaderGUIRootItem rootItem, string foldOutPropertyName)
|
||||
{
|
||||
_rootItem = rootItem;
|
||||
_propertyName = foldOutPropertyName;
|
||||
_propertyInfo = _rootItem.PropertyInfoDic[_propertyName];
|
||||
|
||||
bool isOpen = _propertyInfo.Property.floatValue > 0.5f;
|
||||
animBool.value = isOpen;
|
||||
animBool.target = isOpen;
|
||||
animBool.valueChanged.AddListener(rootItem.MatEditor.Repaint);
|
||||
animBool.speed = FoldOutAnimationSpeed;
|
||||
}
|
||||
|
||||
private ShaderGUIRootItem _rootItem;
|
||||
private string _propertyName;
|
||||
private AnimBool animBool = new AnimBool();
|
||||
|
||||
private ShaderPropertyInfo _propertyInfo;
|
||||
|
||||
public bool BeginFadedGroup(Rect labelRect)
|
||||
{
|
||||
DrawFoldOut(labelRect);
|
||||
return BeginFadeGroup();
|
||||
}
|
||||
|
||||
public bool BeginFadeGroup()
|
||||
{
|
||||
return animBool.faded > 0.0001f;
|
||||
}
|
||||
|
||||
public bool DrawFoldOut(Rect labelRect)
|
||||
{
|
||||
Rect foldOutRect = MakeFoldOutRect(labelRect);
|
||||
bool isOpen = _propertyInfo.Property.floatValue > 0.5f;
|
||||
isOpen = GUI.Toggle(foldOutRect, isOpen, GUIContent.none, EditorStyles.foldout);
|
||||
SetOpen(isOpen);
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
public bool DrawFoldOutLabel(Rect labelRect, GUIContent content, GUIStyle style)
|
||||
{
|
||||
Rect foldOutRect = MakeFoldOutRect(labelRect);
|
||||
bool isOpen = _propertyInfo.Property.floatValue > 0.5f;
|
||||
isOpen = GUI.Toggle(foldOutRect, isOpen, content, style ?? EditorStyles.foldout);
|
||||
SetOpen(isOpen);
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
public void SetOpen(bool isOpen)
|
||||
{
|
||||
if (animBool.target != isOpen)
|
||||
{
|
||||
animBool.target = isOpen;
|
||||
}
|
||||
|
||||
float value = isOpen ? 1f : 0f;
|
||||
if (!Mathf.Approximately(_propertyInfo.Property.floatValue, value))
|
||||
{
|
||||
_propertyInfo.Property.floatValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static Rect MakeFoldOutRect(Rect labelRect)
|
||||
{
|
||||
Rect foldOutRect = labelRect;
|
||||
foldOutRect.x = ShaderGUIItem.GetEditorLabelTextX(labelRect) - ShaderGUIItem.FoldOutArrowWidth;
|
||||
foldOutRect.width = Mathf.Max(0f, labelRect.xMax - foldOutRect.x);
|
||||
return foldOutRect;
|
||||
}
|
||||
|
||||
public void EndFadedGroup()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ShaderGUIBigBlockItem:ShaderGUIItem
|
||||
{
|
||||
ShaderGUIFoldOutHelper _foldOutHelper;
|
||||
GUIStyle _boldStyle = new GUIStyle(EditorStyles.boldLabel);
|
||||
public string FoldOutPropertyName { get; set; }
|
||||
|
||||
public override void InitTriggerByChild()
|
||||
{
|
||||
_foldOutHelper = new ShaderGUIFoldOutHelper(RootItem, FoldOutPropertyName);
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public ShaderGUIBigBlockItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void OnGUI()//完全覆写
|
||||
{
|
||||
LayoutSpace();
|
||||
GetRect();
|
||||
DrawResetButton();
|
||||
bool isOpen = _foldOutHelper.BeginFadedGroup(LabelRect);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent,_boldStyle);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
if (isOpen)
|
||||
{
|
||||
DrawBlock();
|
||||
}
|
||||
_foldOutHelper.EndFadedGroup();
|
||||
EditorGUI.indentLevel--;
|
||||
Rect rect = ApplyGlobalRectCompensation(LayoutRect(1));
|
||||
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6228baa804b041658eaebea96655b431
|
||||
timeCreated: 1758373869
|
||||
@@ -0,0 +1,517 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using System.Collections.Generic;
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public enum MixedBool
|
||||
{
|
||||
False = 0,
|
||||
True = 1,
|
||||
Mixed = -1
|
||||
}
|
||||
|
||||
public class ShaderGUIItem
|
||||
{
|
||||
public const float MinLabelWidth = 120f;
|
||||
public const float LabelWidthRatio = 0.4f;
|
||||
public const float GlobalRectXOffset = -15f;
|
||||
public const float GlobalRectWidthExpansion = 15f;
|
||||
public const float UnityEditorGUIIndentWidth = 15f;
|
||||
public const float EditorGUIIndentWidth = 10f;
|
||||
public const float FoldOutArrowWidth = UnityEditorGUIIndentWidth;
|
||||
public const float ControlResetGap = 3f;
|
||||
public const float ControlIndentCompensation = 10f;
|
||||
public ShaderPropertyInfo PropertyInfo;
|
||||
public ShaderGUIItem ParentItem;
|
||||
public List<ShaderGUIItem> ChildrenItemList = new List<ShaderGUIItem>();
|
||||
public ShaderGUIRootItem RootItem;
|
||||
public string PropertyName;
|
||||
public GUIContent GuiContent;
|
||||
private static int _parentControlDisabledDepth;
|
||||
|
||||
public virtual void InitTriggerByChild()//根Child一定要做这个事情
|
||||
{
|
||||
if ( PropertyInfo == null && PropertyName != null)
|
||||
{
|
||||
PropertyInfo = RootItem.PropertyInfoDic[PropertyName];
|
||||
}
|
||||
CheckIsPropertyModified();
|
||||
}
|
||||
public ShaderGUIItem(ShaderGUIRootItem rtItem,ShaderGUIItem parentItem=null)
|
||||
{
|
||||
RootItem = rtItem;
|
||||
if (parentItem != null)
|
||||
{
|
||||
ParentItem = parentItem;
|
||||
if (!parentItem.ChildrenItemList.Contains(this))
|
||||
{
|
||||
ParentItem.ChildrenItemList.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Rect BaseRect;
|
||||
public Rect LabelRect;
|
||||
public Rect ControlRect;
|
||||
public Rect ResetRect;
|
||||
private static readonly GUIContent TempHelpContent = new GUIContent();
|
||||
public static float ResetButtonSize => EditorGUIUtility.singleLineHeight;
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
private static Material s_ShaderDefaultIntValueMaterial;
|
||||
#endif
|
||||
|
||||
public static int GetShaderPropertyDefaultIntValue(Shader shader, int propertyIndex)
|
||||
{
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
return shader.GetPropertyDefaultIntValue(propertyIndex);
|
||||
#else
|
||||
if (s_ShaderDefaultIntValueMaterial == null ||
|
||||
s_ShaderDefaultIntValueMaterial.shader != shader)
|
||||
{
|
||||
if (s_ShaderDefaultIntValueMaterial != null)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(s_ShaderDefaultIntValueMaterial);
|
||||
}
|
||||
|
||||
// Unity 2021.3 has no int default API; use one hidden material as the shader default-value reader.
|
||||
s_ShaderDefaultIntValueMaterial = new Material(shader)
|
||||
{
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
}
|
||||
|
||||
return s_ShaderDefaultIntValueMaterial.GetInteger(shader.GetPropertyName(propertyIndex));
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void GetRect(bool applyControlIndentCompensation = true)
|
||||
{
|
||||
BaseRect = ApplyGlobalRectCompensation(RootItem.GetControlRect());
|
||||
SplitLineRect(BaseRect, out LabelRect, out ControlRect, out ResetRect, applyControlIndentCompensation);
|
||||
}
|
||||
|
||||
public static Rect ApplyGlobalRectCompensation(Rect rect)
|
||||
{
|
||||
rect.x += GlobalRectXOffset;
|
||||
rect.width = Mathf.Max(0f, rect.width + GlobalRectWidthExpansion);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static Rect ApplyLabelIndentWidth(Rect rect)
|
||||
{
|
||||
float indentDelta = EditorGUI.indentLevel * (EditorGUIIndentWidth - UnityEditorGUIIndentWidth);
|
||||
rect.x += indentDelta;
|
||||
rect.width = Mathf.Max(0f, rect.width - indentDelta);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static Rect ApplyDirectLabelIndentWidth(Rect rect)
|
||||
{
|
||||
float indentOffset = EditorGUI.indentLevel * EditorGUIIndentWidth;
|
||||
rect.x += indentOffset;
|
||||
rect.width = Mathf.Max(0f, rect.width - indentOffset);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static float GetEditorLabelTextX(Rect labelRect)
|
||||
{
|
||||
return labelRect.x + EditorGUI.indentLevel * UnityEditorGUIIndentWidth;
|
||||
}
|
||||
|
||||
public static float GetLabelWidth(Rect baseRect)
|
||||
{
|
||||
return Mathf.Max(MinLabelWidth, baseRect.width * LabelWidthRatio);
|
||||
}
|
||||
|
||||
public static void SplitLineRect(
|
||||
Rect baseRect,
|
||||
out Rect labelRect,
|
||||
out Rect controlRect,
|
||||
out Rect resetRect,
|
||||
bool applyControlIndentCompensation = true)
|
||||
{
|
||||
float labelWidth = GetLabelWidth(baseRect);
|
||||
labelRect = baseRect;
|
||||
labelRect.width = labelWidth;
|
||||
labelRect = ApplyLabelIndentWidth(labelRect);
|
||||
|
||||
Rect controlAndResetRect = baseRect;
|
||||
controlAndResetRect.x += labelWidth;
|
||||
controlAndResetRect.width = Mathf.Max(0f, controlAndResetRect.width - labelWidth);
|
||||
SplitControlAndResetRect(controlAndResetRect, out controlRect, out resetRect, applyControlIndentCompensation);
|
||||
}
|
||||
|
||||
|
||||
protected static GUIContent LocalizedContent(
|
||||
string tableName,
|
||||
string labelKey,
|
||||
string labelFallback,
|
||||
string tooltipKey = null,
|
||||
string tooltipFallback = "")
|
||||
{
|
||||
return ShaderGUILocalization.MakeContent(tableName, labelKey, labelFallback, tooltipKey, tooltipFallback);
|
||||
}
|
||||
|
||||
protected static GUIContent LocalizedInspectorContent(string tableName, string key, string fallback, string tip = "")
|
||||
{
|
||||
return ShaderGUILocalization.MakeInspectorContent(tableName, key, fallback, tip);
|
||||
}
|
||||
|
||||
protected static string LocalizedText(string tableName, string key, string fallback = "")
|
||||
{
|
||||
return ShaderGUILocalization.GetInspectorText(tableName, key, fallback);
|
||||
}
|
||||
|
||||
protected static string[] LocalizedOptions(string tableName, string key, string[] fallback)
|
||||
{
|
||||
return ShaderGUILocalization.GetInspectorOptions(tableName, key, fallback);
|
||||
}
|
||||
|
||||
public static void SplitControlAndResetRect(
|
||||
Rect baseRect,
|
||||
out Rect controlRect,
|
||||
out Rect resetRect,
|
||||
bool applyControlIndentCompensation = true)
|
||||
{
|
||||
resetRect = baseRect;
|
||||
resetRect.x = baseRect.xMax - ResetButtonSize;
|
||||
resetRect.width = ResetButtonSize;
|
||||
|
||||
float controlIndentCompensation = applyControlIndentCompensation ? ControlIndentCompensation : 0f;
|
||||
controlRect = baseRect;
|
||||
controlRect.x -= controlIndentCompensation;
|
||||
controlRect.width = Mathf.Max(0f, baseRect.width - ResetButtonSize - ControlResetGap + controlIndentCompensation);
|
||||
}
|
||||
|
||||
protected Rect LayoutRect(float height = -1f)
|
||||
{
|
||||
return RootItem.GetControlRect(height);
|
||||
}
|
||||
|
||||
protected void LayoutSpace(float height = -1f)
|
||||
{
|
||||
RootItem.Space(height);
|
||||
}
|
||||
|
||||
protected void DrawLayoutHelpBox(string message, MessageType messageType)
|
||||
{
|
||||
TempHelpContent.text = message;
|
||||
float width = Mathf.Max(1f, EditorGUIUtility.currentViewWidth + GlobalRectWidthExpansion + GlobalRectXOffset);
|
||||
float height = Mathf.Max(
|
||||
EditorGUIUtility.singleLineHeight * 2f,
|
||||
EditorStyles.helpBox.CalcHeight(TempHelpContent, width));
|
||||
Rect rect = ApplyGlobalRectCompensation(LayoutRect(height));
|
||||
EditorGUI.HelpBox(rect, message, messageType);
|
||||
TempHelpContent.text = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnGUI()
|
||||
{
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
bool animatedPropertyScope = BeginAnimatedPropertyBackground(BaseRect, PropertyInfo.Property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
DrawController();
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedPropertyScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void DrawResetButton()//如果重写OnGUI,一定要记得调用DrawResetButton
|
||||
{
|
||||
CheckIsPropertyModified();
|
||||
_resetButtonContent.text = HasModified ? "R" : "";
|
||||
_resetButtonStyle = HasModified ? GUI.skin.button : GUI.skin.label;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
if (GUI.Button(ResetRect, _resetButtonContent, _resetButtonStyle))
|
||||
{
|
||||
ExecuteReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void DrawController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected static bool SetFloatIfDifferent(MaterialProperty property, float value)
|
||||
{
|
||||
if (property == null || Mathf.Approximately(property.floatValue, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
property.floatValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static bool SetVectorIfDifferent(MaterialProperty property, Vector4 value)
|
||||
{
|
||||
if (property == null || Approximately(property.vectorValue, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
property.vectorValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static bool SetColorIfDifferent(MaterialProperty property, Color value)
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Color current = property.colorValue;
|
||||
if (Mathf.Approximately(current.r, value.r) &&
|
||||
Mathf.Approximately(current.g, value.g) &&
|
||||
Mathf.Approximately(current.b, value.b) &&
|
||||
Mathf.Approximately(current.a, value.a))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
property.colorValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected readonly struct EditorGUIIndentLevelScope : System.IDisposable
|
||||
{
|
||||
private readonly int _indentLevel;
|
||||
|
||||
public EditorGUIIndentLevelScope(int indentLevel)
|
||||
{
|
||||
_indentLevel = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUI.indentLevel = _indentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsParentControlDisabled => _parentControlDisabledDepth > 0;
|
||||
|
||||
protected EditorGUI.DisabledScope ParentControlDisabledScope(bool disabled = false)
|
||||
{
|
||||
return new EditorGUI.DisabledScope(IsParentControlDisabled || disabled);
|
||||
}
|
||||
|
||||
protected readonly struct InheritedControlDisabledScope : System.IDisposable
|
||||
{
|
||||
private readonly bool _disabled;
|
||||
|
||||
public InheritedControlDisabledScope(bool disabled)
|
||||
{
|
||||
_disabled = disabled;
|
||||
if (_disabled)
|
||||
{
|
||||
_parentControlDisabledDepth++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disabled)
|
||||
{
|
||||
_parentControlDisabledDepth = Mathf.Max(0, _parentControlDisabledDepth - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void DrawBlock()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void OnEndChange()
|
||||
{
|
||||
CheckIsPropertyModified();
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, string> _animationPropertyPathDic = new Dictionary<string, string>();
|
||||
|
||||
protected bool BeginAnimatedPropertyBackground(Rect totalPosition, MaterialProperty property)
|
||||
{
|
||||
if (property == null || RootItem?.MatEditor == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RootItem.MatEditor.BeginAnimatedCheck(totalPosition, property);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void EndAnimatedPropertyBackground(bool scopeActive)
|
||||
{
|
||||
if (scopeActive && RootItem?.MatEditor != null)
|
||||
{
|
||||
RootItem.MatEditor.EndAnimatedCheck();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPropertyAnimated(string propertyName, params string[] componentNames)
|
||||
{
|
||||
if (propertyName == null) return false;
|
||||
if (AnimationMode.InAnimationMode())
|
||||
{
|
||||
foreach (var r in RootItem.RenderersUsingThisMaterial)
|
||||
{
|
||||
if (componentNames != null && componentNames.Length > 0)
|
||||
{
|
||||
foreach (string componentName in componentNames)
|
||||
{
|
||||
if (AnimationMode.IsPropertyAnimated(r, GetAnimationPropertyPath(propertyName, componentName)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AnimationMode.IsPropertyAnimated(r, GetAnimationPropertyPath(propertyName, string.Empty)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetAnimationPropertyPath(string propertyName, string componentName)
|
||||
{
|
||||
string key = string.IsNullOrEmpty(componentName) ? propertyName : propertyName + "." + componentName;
|
||||
if (!_animationPropertyPathDic.TryGetValue(key, out string propertyPath))
|
||||
{
|
||||
propertyPath = string.IsNullOrEmpty(componentName)
|
||||
? "material." + propertyName
|
||||
: "material." + propertyName + "." + componentName;
|
||||
_animationPropertyPathDic.Add(key, propertyPath);
|
||||
}
|
||||
|
||||
return propertyPath;
|
||||
}
|
||||
|
||||
|
||||
#region ResetLogic
|
||||
|
||||
|
||||
private GUIContent _resetButtonContent = new GUIContent("R","重置当前属性及子集属性(如有)");
|
||||
private GUIStyle _resetButtonStyle;
|
||||
public bool HasModified = false;
|
||||
public bool PropertyIsDefaultValue = true;
|
||||
public virtual void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)//只有自身Reset了,才需要查询自己的状态是否正确
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
switch (ShaderGUIUnityCompat.GetPropertyType(PropertyInfo.Property))
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
float defaultValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index);
|
||||
isDefaultValue = Mathf.Approximately(PropertyInfo.Property.floatValue, defaultValue);
|
||||
break;
|
||||
case ShaderPropertyType.Color:
|
||||
Vector4 defaultColor = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
isDefaultValue = Approximately(PropertyInfo.Property.colorValue, defaultColor);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
Vector4 defaultVector = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
isDefaultValue = Approximately(PropertyInfo.Property.vectorValue, defaultVector);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isDefaultValue = true;//如果没有Property,则是看子集的情况
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;//如果状态没有改变,就不需要做任何操作
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public virtual void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
switch (ShaderGUIUnityCompat.GetPropertyType(PropertyInfo.Property))
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
float defaultValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index);
|
||||
PropertyInfo.Property.floatValue = defaultValue;
|
||||
break;
|
||||
case ShaderPropertyType.Color:
|
||||
PropertyInfo.Property.colorValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
PropertyInfo.Property.vectorValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
break;
|
||||
}
|
||||
PropertyIsDefaultValue = true;
|
||||
}
|
||||
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
childItem.ExecuteReset(true);
|
||||
}
|
||||
HasModified = false;
|
||||
if (!isCallByParent)//直接由用户触发重置
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected static bool Approximately(Vector4 a, Vector4 b)
|
||||
{
|
||||
return Mathf.Approximately(a.x, b.x) &&
|
||||
Mathf.Approximately(a.y, b.y) &&
|
||||
Mathf.Approximately(a.z, b.z) &&
|
||||
Mathf.Approximately(a.w, b.w);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f948e090bae4a558a52cb56d0b6c9fc
|
||||
timeCreated: 1758127003
|
||||
@@ -0,0 +1,894 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public static class ShaderGUILocalization
|
||||
{
|
||||
public const string DefaultLanguage = "zh-CN";
|
||||
public const string EnglishLanguage = "en-US";
|
||||
|
||||
private const string TooltipColumnSuffix = "-tip";
|
||||
private const string TooltipColumnAliasSuffix = "-tooltip";
|
||||
|
||||
private static readonly Dictionary<string, LocalizationTable> Tables =
|
||||
new Dictionary<string, LocalizationTable>(StringComparer.OrdinalIgnoreCase);
|
||||
private static NBFXLanguageMode s_CachedLanguageMode;
|
||||
private static string s_CachedPreferredLanguage;
|
||||
private static bool s_CachedPreferredLanguageValid;
|
||||
|
||||
public static void RegisterCsv(string tableName, string csvAssetPath)
|
||||
{
|
||||
tableName = NormalizeTableName(tableName);
|
||||
if (Tables.TryGetValue(tableName, out LocalizationTable table))
|
||||
{
|
||||
if (string.Equals(table.CsvAssetPath, csvAssetPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
table.CsvAssetPath = csvAssetPath;
|
||||
table.Reload();
|
||||
return;
|
||||
}
|
||||
|
||||
Tables[tableName] = new LocalizationTable(csvAssetPath);
|
||||
}
|
||||
|
||||
public static string GetCurrentLanguage(string tableName)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetCurrentLanguage(table);
|
||||
}
|
||||
|
||||
public static string GetEditorLanguageName()
|
||||
{
|
||||
return GetLanguageFromEditor();
|
||||
}
|
||||
|
||||
private static string GetCurrentLanguage(LocalizationTable table)
|
||||
{
|
||||
string preferred = GetPreferredLanguageName();
|
||||
foreach (string language in table.Languages)
|
||||
{
|
||||
if (string.Equals(language, preferred, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultLanguage;
|
||||
}
|
||||
|
||||
public static GUIContent MakeContent(
|
||||
string tableName,
|
||||
string labelKey,
|
||||
string labelFallback,
|
||||
string tooltipKey = null,
|
||||
string tooltipFallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
var cacheKey = new ContentCacheKey(language, labelKey, labelFallback, tooltipKey, tooltipFallback);
|
||||
if (table.ContentCache.TryGetValue(cacheKey, out GUIContent cachedContent))
|
||||
{
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
var content = new GUIContent(
|
||||
GetLocalizedValue(table, labelKey, labelFallback, language),
|
||||
GetTooltip(table, labelKey, tooltipKey, tooltipFallback, language));
|
||||
table.ContentCache[cacheKey] = content;
|
||||
return content;
|
||||
}
|
||||
|
||||
public static GUIContent MakeInspectorContent(string tableName, string key, string fallback, string tip = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
var cacheKey = new InspectorContentCacheKey(language, key, fallback, tip);
|
||||
if (table.InspectorContentCache.TryGetValue(cacheKey, out GUIContent cachedContent))
|
||||
{
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
string labelKey = "inspector." + key + ".label";
|
||||
string tooltipKey = "inspector." + key + ".tip";
|
||||
var content = new GUIContent(
|
||||
GetLocalizedValue(table, labelKey, fallback, language),
|
||||
GetTooltip(table, labelKey, tooltipKey, tip, language));
|
||||
table.InspectorContentCache[cacheKey] = content;
|
||||
return content;
|
||||
}
|
||||
|
||||
public static string GetInspectorText(string tableName, string key, string fallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
return GetInspectorText(table, key, fallback, language);
|
||||
}
|
||||
|
||||
public static string GetInspectorText(string tableName, string key, string fallback, string language)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetInspectorText(table, key, fallback, language);
|
||||
}
|
||||
|
||||
private static string GetInspectorText(LocalizationTable table, string key, string fallback, string language)
|
||||
{
|
||||
var cacheKey = new InspectorTextCacheKey(language, key, fallback);
|
||||
if (table.InspectorTextCache.TryGetValue(cacheKey, out string cachedText))
|
||||
{
|
||||
return cachedText;
|
||||
}
|
||||
|
||||
cachedText = GetLocalizedValue(table, "inspector." + key, fallback, language);
|
||||
table.InspectorTextCache[cacheKey] = cachedText;
|
||||
return cachedText;
|
||||
}
|
||||
|
||||
public static string[] GetInspectorOptions(string tableName, string key, string[] fallback)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
return GetInspectorOptions(table, key, fallback, language);
|
||||
}
|
||||
|
||||
public static string[] GetInspectorOptions(string tableName, string key, string[] fallback, string language)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetInspectorOptions(table, key, fallback, language);
|
||||
}
|
||||
|
||||
private static string[] GetInspectorOptions(LocalizationTable table, string key, string[] fallback, string language)
|
||||
{
|
||||
if (fallback == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var cacheKey = new OptionsCacheKey(language, key, fallback);
|
||||
if (table.InspectorOptionsCache.TryGetValue(cacheKey, out string[] cachedValues))
|
||||
{
|
||||
return cachedValues;
|
||||
}
|
||||
|
||||
string keyPrefix = "inspector." + key + ".option";
|
||||
string[] values = new string[fallback.Length];
|
||||
for (int i = 0; i < fallback.Length; i++)
|
||||
{
|
||||
values[i] = GetLocalizedValue(table, keyPrefix + "." + i, fallback[i], language);
|
||||
}
|
||||
|
||||
table.InspectorOptionsCache[cacheKey] = values;
|
||||
return values;
|
||||
}
|
||||
|
||||
public static string[] GetOptions(string tableName, string keyPrefix, string[] fallback)
|
||||
{
|
||||
if (fallback == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
var cacheKey = new OptionsCacheKey(language, keyPrefix, fallback);
|
||||
if (table.OptionsCache.TryGetValue(cacheKey, out string[] cachedValues))
|
||||
{
|
||||
return cachedValues;
|
||||
}
|
||||
|
||||
string[] values = new string[fallback.Length];
|
||||
for (int i = 0; i < fallback.Length; i++)
|
||||
{
|
||||
values[i] = GetLocalizedValue(table, keyPrefix + "." + i, fallback[i], language);
|
||||
}
|
||||
|
||||
table.OptionsCache[cacheKey] = values;
|
||||
return values;
|
||||
}
|
||||
|
||||
public static string Get(string tableName, string key, string fallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetLocalizedValue(table, key, fallback, GetCurrentLanguage(table));
|
||||
}
|
||||
|
||||
private static string GetLocalizedValue(LocalizationTable table, string key, string fallback, string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (table.Rows.TryGetValue(key, out Dictionary<string, string> row))
|
||||
{
|
||||
if (row.TryGetValue(language, out string localizedValue) && !string.IsNullOrEmpty(localizedValue))
|
||||
{
|
||||
return localizedValue;
|
||||
}
|
||||
|
||||
if (row.TryGetValue(DefaultLanguage, out string defaultValue) && !string.IsNullOrEmpty(defaultValue))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static string GetTooltip(string tableName, string labelKey, string tooltipKey = null, string fallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetTooltip(table, labelKey, tooltipKey, fallback, GetCurrentLanguage(table));
|
||||
}
|
||||
|
||||
private static string GetTooltip(
|
||||
LocalizationTable table,
|
||||
string labelKey,
|
||||
string tooltipKey,
|
||||
string fallback,
|
||||
string language)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(labelKey) &&
|
||||
table.Rows.TryGetValue(labelKey, out Dictionary<string, string> row))
|
||||
{
|
||||
if (TryGetTooltip(row, language, out string localizedTooltip))
|
||||
{
|
||||
return localizedTooltip;
|
||||
}
|
||||
|
||||
if (TryGetTooltip(row, DefaultLanguage, out string defaultTooltip))
|
||||
{
|
||||
return defaultTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tooltipKey))
|
||||
{
|
||||
return GetLocalizedValue(table, tooltipKey, fallback, language);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static void Reload(string tableName = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tableName))
|
||||
{
|
||||
foreach (LocalizationTable table in Tables.Values)
|
||||
{
|
||||
table.Reload();
|
||||
}
|
||||
|
||||
InvalidatePreferredLanguageCache();
|
||||
return;
|
||||
}
|
||||
|
||||
GetTable(tableName).Reload();
|
||||
InvalidatePreferredLanguageCache();
|
||||
}
|
||||
|
||||
private static void InvalidatePreferredLanguageCache()
|
||||
{
|
||||
s_CachedPreferredLanguageValid = false;
|
||||
}
|
||||
|
||||
private static LocalizationTable GetTable(string tableName)
|
||||
{
|
||||
tableName = NormalizeTableName(tableName);
|
||||
if (!Tables.TryGetValue(tableName, out LocalizationTable table))
|
||||
{
|
||||
table = new LocalizationTable(string.Empty);
|
||||
Tables[tableName] = table;
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private static string NormalizeTableName(string tableName)
|
||||
{
|
||||
return string.IsNullOrEmpty(tableName) ? "default" : tableName;
|
||||
}
|
||||
|
||||
private static bool IsTooltipColumn(string header)
|
||||
{
|
||||
return !string.IsNullOrEmpty(header) &&
|
||||
(header.EndsWith(TooltipColumnSuffix, StringComparison.OrdinalIgnoreCase) ||
|
||||
header.EndsWith(TooltipColumnAliasSuffix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool TryGetTooltip(Dictionary<string, string> row, string language, out string tooltip)
|
||||
{
|
||||
tooltip = string.Empty;
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row.TryGetValue(language + TooltipColumnSuffix, out tooltip) && !string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (row.TryGetValue(language + TooltipColumnAliasSuffix, out tooltip) && !string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetPreferredLanguageName()
|
||||
{
|
||||
NBFXLanguageMode languageMode = NBFXProjectSettings.LanguageMode;
|
||||
if (s_CachedPreferredLanguageValid && s_CachedLanguageMode == languageMode)
|
||||
{
|
||||
return s_CachedPreferredLanguage;
|
||||
}
|
||||
|
||||
string language;
|
||||
switch (languageMode)
|
||||
{
|
||||
case NBFXLanguageMode.Chinese:
|
||||
language = DefaultLanguage;
|
||||
break;
|
||||
case NBFXLanguageMode.English:
|
||||
language = EnglishLanguage;
|
||||
break;
|
||||
case NBFXLanguageMode.FollowEditor:
|
||||
default:
|
||||
language = GetLanguageFromEditor();
|
||||
break;
|
||||
}
|
||||
|
||||
s_CachedLanguageMode = languageMode;
|
||||
s_CachedPreferredLanguage = language;
|
||||
s_CachedPreferredLanguageValid = true;
|
||||
return language;
|
||||
}
|
||||
|
||||
private static string GetLanguageFromEditor()
|
||||
{
|
||||
if (TryGetEditorLanguageName(out string language))
|
||||
{
|
||||
return language;
|
||||
}
|
||||
|
||||
return GetSystemLanguageName();
|
||||
}
|
||||
|
||||
private static bool TryGetEditorLanguageName(out string language)
|
||||
{
|
||||
language = string.Empty;
|
||||
|
||||
Type localizationDatabaseType = typeof(Editor).Assembly.GetType("UnityEditor.LocalizationDatabase");
|
||||
if (localizationDatabaseType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
string[] memberNames =
|
||||
{
|
||||
"currentEditorLanguage",
|
||||
"CurrentEditorLanguage"
|
||||
};
|
||||
|
||||
foreach (string memberName in memberNames)
|
||||
{
|
||||
PropertyInfo propertyInfo = localizationDatabaseType.GetProperty(memberName, flags);
|
||||
if (propertyInfo != null && TryGetEditorLanguageValue(() => propertyInfo.GetValue(null, null), out language))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FieldInfo fieldInfo = localizationDatabaseType.GetField(memberName, flags);
|
||||
if (fieldInfo != null && TryGetEditorLanguageValue(() => fieldInfo.GetValue(null), out language))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetEditorLanguageValue(Func<object> valueGetter, out string language)
|
||||
{
|
||||
language = string.Empty;
|
||||
try
|
||||
{
|
||||
return TryMapLanguageValue(valueGetter(), out language);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryMapLanguageValue(object value, out string language)
|
||||
{
|
||||
language = string.Empty;
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is SystemLanguage systemLanguage)
|
||||
{
|
||||
language = MapSystemLanguage(systemLanguage);
|
||||
return true;
|
||||
}
|
||||
|
||||
string languageName = value.ToString();
|
||||
if (string.IsNullOrEmpty(languageName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string normalizedLanguageName = languageName.Replace("_", "-").ToLowerInvariant();
|
||||
if (normalizedLanguageName.StartsWith("en", StringComparison.Ordinal) ||
|
||||
normalizedLanguageName.Contains("english"))
|
||||
{
|
||||
language = EnglishLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (normalizedLanguageName.StartsWith("zh", StringComparison.Ordinal) ||
|
||||
normalizedLanguageName.Contains("chinese"))
|
||||
{
|
||||
language = DefaultLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
language = DefaultLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetSystemLanguageName()
|
||||
{
|
||||
return MapSystemLanguage(Application.systemLanguage);
|
||||
}
|
||||
|
||||
private static string MapSystemLanguage(SystemLanguage systemLanguage)
|
||||
{
|
||||
switch (systemLanguage)
|
||||
{
|
||||
case SystemLanguage.English:
|
||||
return EnglishLanguage;
|
||||
case SystemLanguage.Chinese:
|
||||
case SystemLanguage.ChineseSimplified:
|
||||
case SystemLanguage.ChineseTraditional:
|
||||
return DefaultLanguage;
|
||||
default:
|
||||
return DefaultLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct ContentCacheKey : IEquatable<ContentCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _labelKey;
|
||||
private readonly string _labelFallback;
|
||||
private readonly string _tooltipKey;
|
||||
private readonly string _tooltipFallback;
|
||||
|
||||
public ContentCacheKey(
|
||||
string language,
|
||||
string labelKey,
|
||||
string labelFallback,
|
||||
string tooltipKey,
|
||||
string tooltipFallback)
|
||||
{
|
||||
_language = language;
|
||||
_labelKey = labelKey;
|
||||
_labelFallback = labelFallback;
|
||||
_tooltipKey = tooltipKey;
|
||||
_tooltipFallback = tooltipFallback;
|
||||
}
|
||||
|
||||
public bool Equals(ContentCacheKey other)
|
||||
{
|
||||
return StringEquals(_language, other._language) &&
|
||||
StringEquals(_labelKey, other._labelKey) &&
|
||||
StringEquals(_labelFallback, other._labelFallback) &&
|
||||
StringEquals(_tooltipKey, other._tooltipKey) &&
|
||||
StringEquals(_tooltipFallback, other._tooltipFallback);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ContentCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_labelKey);
|
||||
hash = hash * 31 + StringHash(_labelFallback);
|
||||
hash = hash * 31 + StringHash(_tooltipKey);
|
||||
hash = hash * 31 + StringHash(_tooltipFallback);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct InspectorContentCacheKey : IEquatable<InspectorContentCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _key;
|
||||
private readonly string _fallback;
|
||||
private readonly string _tip;
|
||||
|
||||
public InspectorContentCacheKey(string language, string key, string fallback, string tip)
|
||||
{
|
||||
_language = language;
|
||||
_key = key;
|
||||
_fallback = fallback;
|
||||
_tip = tip;
|
||||
}
|
||||
|
||||
public bool Equals(InspectorContentCacheKey other)
|
||||
{
|
||||
return StringEquals(_language, other._language) &&
|
||||
StringEquals(_key, other._key) &&
|
||||
StringEquals(_fallback, other._fallback) &&
|
||||
StringEquals(_tip, other._tip);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is InspectorContentCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_key);
|
||||
hash = hash * 31 + StringHash(_fallback);
|
||||
hash = hash * 31 + StringHash(_tip);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct OptionsCacheKey : IEquatable<OptionsCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _keyPrefix;
|
||||
private readonly string[] _fallback;
|
||||
private readonly int _fallbackLength;
|
||||
|
||||
public OptionsCacheKey(string language, string keyPrefix, string[] fallback)
|
||||
{
|
||||
_language = language;
|
||||
_keyPrefix = keyPrefix;
|
||||
_fallback = fallback;
|
||||
_fallbackLength = fallback != null ? fallback.Length : 0;
|
||||
}
|
||||
|
||||
public bool Equals(OptionsCacheKey other)
|
||||
{
|
||||
if (!StringEquals(_language, other._language) ||
|
||||
!StringEquals(_keyPrefix, other._keyPrefix) ||
|
||||
_fallbackLength != other._fallbackLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _fallbackLength; i++)
|
||||
{
|
||||
if (!StringEquals(_fallback[i], other._fallback[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is OptionsCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_keyPrefix);
|
||||
hash = hash * 31 + _fallbackLength;
|
||||
for (int i = 0; i < _fallbackLength; i++)
|
||||
{
|
||||
hash = hash * 31 + StringHash(_fallback[i]);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct InspectorTextCacheKey : IEquatable<InspectorTextCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _key;
|
||||
private readonly string _fallback;
|
||||
|
||||
public InspectorTextCacheKey(string language, string key, string fallback)
|
||||
{
|
||||
_language = language;
|
||||
_key = key;
|
||||
_fallback = fallback;
|
||||
}
|
||||
|
||||
public bool Equals(InspectorTextCacheKey other)
|
||||
{
|
||||
return StringEquals(_language, other._language) &&
|
||||
StringEquals(_key, other._key) &&
|
||||
StringEquals(_fallback, other._fallback);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is InspectorTextCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_key);
|
||||
hash = hash * 31 + StringHash(_fallback);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool StringEquals(string a, string b)
|
||||
{
|
||||
return string.Equals(a, b, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static int StringHash(string value)
|
||||
{
|
||||
return value == null ? 0 : StringComparer.Ordinal.GetHashCode(value);
|
||||
}
|
||||
|
||||
private sealed class LocalizationTable
|
||||
{
|
||||
private bool _loaded;
|
||||
private bool _warningLogged;
|
||||
|
||||
public LocalizationTable(string csvAssetPath)
|
||||
{
|
||||
CsvAssetPath = csvAssetPath;
|
||||
Rows = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
Languages = Array.Empty<string>();
|
||||
ContentCache = new Dictionary<ContentCacheKey, GUIContent>();
|
||||
InspectorContentCache = new Dictionary<InspectorContentCacheKey, GUIContent>();
|
||||
InspectorTextCache = new Dictionary<InspectorTextCacheKey, string>();
|
||||
OptionsCache = new Dictionary<OptionsCacheKey, string[]>();
|
||||
InspectorOptionsCache = new Dictionary<OptionsCacheKey, string[]>();
|
||||
}
|
||||
|
||||
public string CsvAssetPath { get; set; }
|
||||
public Dictionary<string, Dictionary<string, string>> Rows { get; private set; }
|
||||
public string[] Languages { get; private set; }
|
||||
public Dictionary<ContentCacheKey, GUIContent> ContentCache { get; }
|
||||
public Dictionary<InspectorContentCacheKey, GUIContent> InspectorContentCache { get; }
|
||||
public Dictionary<InspectorTextCacheKey, string> InspectorTextCache { get; }
|
||||
public Dictionary<OptionsCacheKey, string[]> OptionsCache { get; }
|
||||
public Dictionary<OptionsCacheKey, string[]> InspectorOptionsCache { get; }
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_loaded = false;
|
||||
_warningLogged = false;
|
||||
Rows = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
Languages = Array.Empty<string>();
|
||||
ContentCache.Clear();
|
||||
InspectorContentCache.Clear();
|
||||
InspectorTextCache.Clear();
|
||||
OptionsCache.Clear();
|
||||
InspectorOptionsCache.Clear();
|
||||
}
|
||||
|
||||
public void EnsureLoaded()
|
||||
{
|
||||
if (_loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loaded = true;
|
||||
Rows = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
Languages = Array.Empty<string>();
|
||||
|
||||
if (string.IsNullOrEmpty(CsvAssetPath))
|
||||
{
|
||||
LogWarningOnce("ShaderGUI localization table is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
TextAsset csvAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(CsvAssetPath);
|
||||
if (csvAsset == null)
|
||||
{
|
||||
LogWarningOnce($"ShaderGUI localization csv not found at '{CsvAssetPath}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
ParseCsv(csvAsset.text);
|
||||
}
|
||||
|
||||
private void ParseCsv(string csvText)
|
||||
{
|
||||
List<string> rows = SplitRows(csvText);
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> headers = ParseCsvRow(rows[0]);
|
||||
if (headers.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var languages = new List<string>();
|
||||
for (int i = 1; i < headers.Count; i++)
|
||||
{
|
||||
if (!IsTooltipColumn(headers[i]))
|
||||
{
|
||||
languages.Add(headers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Languages = languages.ToArray();
|
||||
for (int rowIndex = 1; rowIndex < rows.Count; rowIndex++)
|
||||
{
|
||||
List<string> values = ParseCsvRow(rows[rowIndex]);
|
||||
if (values.Count == 0 || string.IsNullOrWhiteSpace(values[0]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var languageMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 1; i < headers.Count; i++)
|
||||
{
|
||||
languageMap[headers[i]] = i < values.Count ? values[i] : string.Empty;
|
||||
}
|
||||
|
||||
Rows[values[0]] = languageMap;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> SplitRows(string csvText)
|
||||
{
|
||||
var rows = new List<string>();
|
||||
if (string.IsNullOrEmpty(csvText))
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
bool inQuotes = false;
|
||||
for (int i = 0; i < csvText.Length; i++)
|
||||
{
|
||||
char current = csvText[i];
|
||||
if (current == '"')
|
||||
{
|
||||
if (inQuotes && i + 1 < csvText.Length && csvText[i + 1] == '"')
|
||||
{
|
||||
builder.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inQuotes && (current == '\n' || current == '\r'))
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
rows.Add(builder.ToString());
|
||||
builder.Length = 0;
|
||||
}
|
||||
|
||||
if (current == '\r' && i + 1 < csvText.Length && csvText[i + 1] == '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
rows.Add(builder.ToString());
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static List<string> ParseCsvRow(string rowText)
|
||||
{
|
||||
var values = new List<string>();
|
||||
var builder = new StringBuilder();
|
||||
bool inQuotes = false;
|
||||
for (int i = 0; i < rowText.Length; i++)
|
||||
{
|
||||
char current = rowText[i];
|
||||
if (current == '"')
|
||||
{
|
||||
if (inQuotes && i + 1 < rowText.Length && rowText[i + 1] == '"')
|
||||
{
|
||||
builder.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inQuotes && current == ',')
|
||||
{
|
||||
values.Add(builder.ToString());
|
||||
builder.Length = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
values.Add(builder.ToString());
|
||||
return values;
|
||||
}
|
||||
|
||||
private void LogWarningOnce(string message)
|
||||
{
|
||||
if (_warningLogged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_warningLogged = true;
|
||||
Debug.LogWarning(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5429c73ee5e4590bbbbccee3d5a3a58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderGUIPopUpItem:ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<string[]> _popupNamesProvider;
|
||||
private readonly Action<MaterialProperty> _onValueChanged;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ShaderGUIPopUpItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
// base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public ShaderGUIPopUpItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<string[]> popupNamesProvider,
|
||||
Action<MaterialProperty> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_popupNamesProvider = popupNamesProvider;
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
PopUpNames = _popupNamesProvider?.Invoke();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public string[] PopUpNames;
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
int value = EditorGUI.Popup(ControlRect, (int)PropertyInfo.Property.floatValue, PopUpNames);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
public class ShaderGUIBitMaskItem : ShaderGUIItem
|
||||
{
|
||||
private static readonly string[] EmptyMaskNames = new string[0];
|
||||
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<string[]> _maskNamesProvider;
|
||||
private readonly Action<MaterialProperty> _onValueChanged;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ShaderGUIBitMaskItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem)
|
||||
: base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
}
|
||||
|
||||
public ShaderGUIBitMaskItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<string[]> maskNamesProvider,
|
||||
Action<MaterialProperty> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_maskNamesProvider = maskNamesProvider;
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
MaskNames = _maskNamesProvider?.Invoke();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public string[] MaskNames;
|
||||
public int ValidMask = -1;
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_contentProvider != null)
|
||||
{
|
||||
GuiContent = _contentProvider();
|
||||
}
|
||||
|
||||
if (_maskNamesProvider != null)
|
||||
{
|
||||
MaskNames = _maskNamesProvider();
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
string[] maskNames = MaskNames ?? EmptyMaskNames;
|
||||
int validMask = GetValidMask(maskNames);
|
||||
int mask = Mathf.RoundToInt(PropertyInfo.Property.floatValue) & validMask;
|
||||
int value = EditorGUI.MaskField(ControlRect, mask, maskNames) & validMask;
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
OnEndChange();
|
||||
}
|
||||
|
||||
private int GetValidMask(string[] maskNames)
|
||||
{
|
||||
if (ValidMask >= 0)
|
||||
{
|
||||
return ValidMask;
|
||||
}
|
||||
|
||||
int optionCount = Mathf.Clamp(maskNames.Length, 0, 30);
|
||||
return optionCount == 0 ? 0 : (1 << optionCount) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4147e85b43d4bcaab43dedf111c41fb
|
||||
timeCreated: 1758444036
|
||||
@@ -0,0 +1,288 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using NBShader;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderPropertyInfo
|
||||
{
|
||||
public MaterialProperty Property;
|
||||
public string Name;
|
||||
public int Index;
|
||||
}
|
||||
public class ShaderGUIRootItem
|
||||
{
|
||||
private static int s_RendererCacheVersion;
|
||||
private static double s_RendererCacheFirstDirtyTime = -1d;
|
||||
private static double s_RendererCacheDirtyTime;
|
||||
private const double RendererCacheHierarchyRefreshDelay = 0.35d;
|
||||
private const double RendererCacheMaxHierarchyRefreshDelay = 2d;
|
||||
private const float DefaultManualLayoutHeight = 1200f;
|
||||
|
||||
public Shader Shader;
|
||||
public MaterialEditor MatEditor;
|
||||
public List<Material> Mats;
|
||||
public Dictionary<string,ShaderPropertyInfo> PropertyInfoDic = new Dictionary<string,ShaderPropertyInfo>();
|
||||
public List<ShaderFlagsBase> ShaderFlags;//各个继承类各自初始化
|
||||
public bool IsInit = true;
|
||||
public Color DefaultBackgroundColor;
|
||||
public bool ClearUnusedTextureReferencesRequested { get; private set; }
|
||||
public List<Renderer> RenderersUsingThisMaterial = new List<Renderer>();
|
||||
public List<ParticleSystemRenderer> ParticleRenderersUsingThisMaterial = new List<ParticleSystemRenderer>();
|
||||
public bool IsUsedByParticleSystem { get; private set; }
|
||||
private int _rendererCacheVersion = -1;
|
||||
private Material _rendererCacheMaterial;
|
||||
private readonly List<Material> _rendererMaterialBuffer = new List<Material>();
|
||||
private bool _manualLayoutActive;
|
||||
private Rect _manualLayoutRect;
|
||||
private float _manualLayoutY;
|
||||
private float _manualLayoutUsedHeight;
|
||||
private float _manualLayoutHeight = DefaultManualLayoutHeight;
|
||||
|
||||
static ShaderGUIRootItem()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += OnHierarchyChanged;
|
||||
}
|
||||
|
||||
private static void OnHierarchyChanged()
|
||||
{
|
||||
double now = EditorApplication.timeSinceStartup;
|
||||
s_RendererCacheVersion++;
|
||||
if (s_RendererCacheFirstDirtyTime < 0d)
|
||||
{
|
||||
s_RendererCacheFirstDirtyTime = now;
|
||||
}
|
||||
|
||||
s_RendererCacheDirtyTime = now;
|
||||
}
|
||||
|
||||
public virtual void OnGUI(MaterialEditor editor,MaterialProperty[] props)
|
||||
{
|
||||
MatEditor = editor;
|
||||
DefaultBackgroundColor = GUI.backgroundColor;
|
||||
if (IsInit || ShouldRebuildMaterialTargets(editor.targets))
|
||||
{
|
||||
IsInit = true;
|
||||
Mats = new List<Material>();
|
||||
foreach (var obj in editor.targets)
|
||||
{
|
||||
Mats.Add(obj as Material);
|
||||
}
|
||||
InitFlags(Mats);
|
||||
Shader = Mats[0].shader;
|
||||
RefreshRendererCacheIfNeeded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshRendererCacheIfNeeded(false);
|
||||
}
|
||||
if (ShouldRebuildPropertyInfo(props))
|
||||
{
|
||||
PropertyInfoDic.Clear();
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
ShaderPropertyInfo propInfo = new ShaderPropertyInfo();
|
||||
propInfo.Property = props[i];
|
||||
propInfo.Name = props[i].name;
|
||||
propInfo.Index = i;
|
||||
PropertyInfoDic.Add(propInfo.Name, propInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
ShaderPropertyInfo propInfo = PropertyInfoDic[props[i].name];
|
||||
propInfo.Property = props[i];
|
||||
propInfo.Index = i;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BeginManualLayout();
|
||||
OnChildOnGUI();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndManualLayout();
|
||||
ClearUnusedTextureReferencesRequested = false;
|
||||
}
|
||||
|
||||
RepaintWhenAnimationModeActive();
|
||||
IsInit = false;
|
||||
}
|
||||
|
||||
public void RequestClearUnusedTextureReferences()
|
||||
{
|
||||
ClearUnusedTextureReferencesRequested = true;
|
||||
}
|
||||
|
||||
public Rect GetControlRect(float height = -1f)
|
||||
{
|
||||
float rectHeight = height >= 0f ? height : EditorGUIUtility.singleLineHeight;
|
||||
if (!_manualLayoutActive)
|
||||
{
|
||||
return EditorGUILayout.GetControlRect(false, rectHeight);
|
||||
}
|
||||
|
||||
Rect rect = new Rect(_manualLayoutRect.x, _manualLayoutY, _manualLayoutRect.width, rectHeight);
|
||||
_manualLayoutY += rectHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
_manualLayoutUsedHeight = Mathf.Max(
|
||||
_manualLayoutUsedHeight,
|
||||
_manualLayoutY - _manualLayoutRect.y - EditorGUIUtility.standardVerticalSpacing);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public void Space(float height = -1f)
|
||||
{
|
||||
float spaceHeight = height >= 0f ? height : EditorGUIUtility.standardVerticalSpacing;
|
||||
if (!_manualLayoutActive)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
return;
|
||||
}
|
||||
|
||||
_manualLayoutY += spaceHeight;
|
||||
_manualLayoutUsedHeight = Mathf.Max(_manualLayoutUsedHeight, _manualLayoutY - _manualLayoutRect.y);
|
||||
}
|
||||
|
||||
private void BeginManualLayout()
|
||||
{
|
||||
float height = Mathf.Max(EditorGUIUtility.singleLineHeight, _manualLayoutHeight);
|
||||
_manualLayoutRect = EditorGUILayout.GetControlRect(false, height);
|
||||
_manualLayoutY = _manualLayoutRect.y;
|
||||
_manualLayoutUsedHeight = 0f;
|
||||
_manualLayoutActive = true;
|
||||
}
|
||||
|
||||
private void EndManualLayout()
|
||||
{
|
||||
if (!_manualLayoutActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_manualLayoutActive = false;
|
||||
float usedHeight = Mathf.Max(EditorGUIUtility.singleLineHeight, _manualLayoutUsedHeight);
|
||||
if (!Mathf.Approximately(_manualLayoutHeight, usedHeight))
|
||||
{
|
||||
_manualLayoutHeight = usedHeight;
|
||||
MatEditor?.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRebuildPropertyInfo(MaterialProperty[] props)
|
||||
{
|
||||
if (PropertyInfoDic.Count != props.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
if (!PropertyInfoDic.ContainsKey(props[i].name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RepaintWhenAnimationModeActive()
|
||||
{
|
||||
if (AnimationMode.InAnimationMode())
|
||||
{
|
||||
MatEditor.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRebuildMaterialTargets(UnityEngine.Object[] targets)
|
||||
{
|
||||
if (Mats == null || targets == null || Mats.Count != targets.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
if (Mats[i] != (targets[i] as Material))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RefreshRendererCacheIfNeeded(bool force)
|
||||
{
|
||||
Material material = Mats != null && Mats.Count > 0 ? Mats[0] : null;
|
||||
if (!force && _rendererCacheMaterial == material && _rendererCacheVersion == s_RendererCacheVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double now = EditorApplication.timeSinceStartup;
|
||||
bool waitingForQuietHierarchy = now - s_RendererCacheDirtyTime < RendererCacheHierarchyRefreshDelay;
|
||||
bool withinMaxDelay = s_RendererCacheFirstDirtyTime >= 0d &&
|
||||
now - s_RendererCacheFirstDirtyTime < RendererCacheMaxHierarchyRefreshDelay;
|
||||
if (!force &&
|
||||
_rendererCacheMaterial == material &&
|
||||
waitingForQuietHierarchy &&
|
||||
withinMaxDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CacheRenderersUsingThisMaterial(material);
|
||||
_rendererCacheMaterial = material;
|
||||
_rendererCacheVersion = s_RendererCacheVersion;
|
||||
s_RendererCacheFirstDirtyTime = -1d;
|
||||
}
|
||||
|
||||
void CacheRenderersUsingThisMaterial(Material material)
|
||||
{
|
||||
Renderer[] renderers = UnityObjectFindCompat.FindAll<Renderer>();
|
||||
RenderersUsingThisMaterial.Clear();
|
||||
ParticleRenderersUsingThisMaterial.Clear();
|
||||
IsUsedByParticleSystem = false;
|
||||
if (material == null || renderers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
if (renderer is ParticleSystemRenderer psr &&
|
||||
(psr.sharedMaterial == material || psr.trailMaterial == material))
|
||||
{
|
||||
IsUsedByParticleSystem = true;
|
||||
ParticleRenderersUsingThisMaterial.Add(psr);
|
||||
}
|
||||
|
||||
_rendererMaterialBuffer.Clear();
|
||||
renderer.GetSharedMaterials(_rendererMaterialBuffer);
|
||||
for (int i = 0; i < _rendererMaterialBuffer.Count; i++)
|
||||
{
|
||||
if (_rendererMaterialBuffer[i] == material)
|
||||
{
|
||||
RenderersUsingThisMaterial.Add(renderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rendererMaterialBuffer.Clear();
|
||||
}
|
||||
|
||||
public virtual void InitFlags(List<Material> mats) { } //各个子类各自实现
|
||||
|
||||
public virtual void OnChildOnGUI()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e57b381204754ae780ab305e5feff475
|
||||
timeCreated: 1758206205
|
||||
@@ -0,0 +1,498 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class TextureItem : ShaderGUIItem
|
||||
{
|
||||
private readonly string _texturePropertyName;
|
||||
private readonly TexturePropertyGroupItem _groupItem;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Action<MaterialProperty> _afterDraw;
|
||||
|
||||
public TextureItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string texturePropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
string colorPropertyName = null,
|
||||
bool drawScaleOffset = true,
|
||||
Action<MaterialProperty> afterDraw = null,
|
||||
Func<bool> isVisible = null,
|
||||
Func<GUIContent> tillingContentProvider = null,
|
||||
Func<GUIContent> offsetContentProvider = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_texturePropertyName = texturePropertyName;
|
||||
_isVisible = isVisible;
|
||||
_afterDraw = afterDraw;
|
||||
_groupItem = new TexturePropertyGroupItem(
|
||||
rootItem,
|
||||
this,
|
||||
texturePropertyName,
|
||||
colorPropertyName,
|
||||
contentProvider,
|
||||
drawScaleOffset,
|
||||
tillingContentProvider: tillingContentProvider,
|
||||
offsetContentProvider: offsetContentProvider);
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
_groupItem.ClearTextureIfRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
_groupItem.OnGUI();
|
||||
if (_afterDraw != null && RootItem.PropertyInfoDic.TryGetValue(_texturePropertyName, out ShaderPropertyInfo texturePropertyInfo))
|
||||
{
|
||||
_afterDraw(texturePropertyInfo.Property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TexturePropertyGroupItem : ShaderGUIItem
|
||||
{
|
||||
private readonly TextureObjectItem _textureItem;
|
||||
private readonly ColorLineItem _colorItem;
|
||||
private readonly TextureScaleOffsetItem _scaleOffsetItem;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public TexturePropertyGroupItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string texturePropertyName,
|
||||
string colorPropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
bool drawScaleOffset = true,
|
||||
Func<bool> isVisible = null,
|
||||
Func<GUIContent> tillingContentProvider = null,
|
||||
Func<GUIContent> offsetContentProvider = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_isVisible = isVisible;
|
||||
_textureItem = new TextureObjectItem(rootItem, this, texturePropertyName, contentProvider);
|
||||
_colorItem = string.IsNullOrEmpty(colorPropertyName)
|
||||
? null
|
||||
: new ColorLineItem(rootItem, this, colorPropertyName, false, contentProvider);
|
||||
_scaleOffsetItem = drawScaleOffset
|
||||
? new TextureScaleOffsetItem(
|
||||
rootItem,
|
||||
this,
|
||||
texturePropertyName,
|
||||
false,
|
||||
tillingContentProvider: tillingContentProvider,
|
||||
offsetContentProvider: offsetContentProvider)
|
||||
: null;
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
_textureItem.ClearTextureIfRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
float singleLineHeight = EditorGUIUtility.singleLineHeight;
|
||||
float rowGap = EditorGUIUtility.standardVerticalSpacing;
|
||||
float textureFieldHeight = 3f * singleLineHeight + 2f * rowGap;
|
||||
float textureContentGap = EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
Rect textureGroupRect = ApplyGlobalRectCompensation(LayoutRect(textureFieldHeight));
|
||||
Rect textureLabelRow = new Rect(textureGroupRect.x, textureGroupRect.y, textureGroupRect.width, singleLineHeight);
|
||||
Rect tillingRow = new Rect(textureGroupRect.x, textureGroupRect.y + singleLineHeight + rowGap, textureGroupRect.width, singleLineHeight);
|
||||
Rect offsetRow = new Rect(textureGroupRect.x, tillingRow.y + singleLineHeight + rowGap, textureGroupRect.width, singleLineHeight);
|
||||
Rect indentedTextureLabelRow = ApplyDirectLabelIndentWidth(textureLabelRow);
|
||||
|
||||
Rect textureRect = new Rect(indentedTextureLabelRow.x + 2f, textureLabelRow.y, textureFieldHeight, textureFieldHeight);
|
||||
textureRect.x -= 2f;
|
||||
textureRect.y += 2f;
|
||||
|
||||
float contentX = textureRect.x + textureRect.width + textureContentGap;
|
||||
Rect textureLabelRect = MoveRectLeftKeepingRight(textureLabelRow, contentX);
|
||||
|
||||
SplitLineRect(tillingRow, out _, out Rect tillingVec2Rect, out Rect tillingResetRect, false);
|
||||
Rect tillingLabelRect = MakeLabelRect(tillingRow, contentX, tillingVec2Rect.x);
|
||||
tillingVec2Rect = ApplyControlIndentCompensation(tillingVec2Rect);
|
||||
|
||||
SplitLineRect(offsetRow, out _, out Rect offsetVec2Rect, out Rect offsetResetRect, false);
|
||||
Rect offsetLabelRect = MakeLabelRect(offsetRow, contentX, offsetVec2Rect.x);
|
||||
offsetVec2Rect = ApplyControlIndentCompensation(offsetVec2Rect);
|
||||
|
||||
_textureItem.Draw(textureRect, textureLabelRect);
|
||||
_scaleOffsetItem?.Draw(tillingLabelRect, tillingVec2Rect, tillingResetRect, offsetLabelRect, offsetVec2Rect, offsetResetRect);
|
||||
_colorItem?.OnGUI();
|
||||
}
|
||||
|
||||
public void ClearTextureIfRequested()
|
||||
{
|
||||
_textureItem.ClearTextureIfRequested();
|
||||
}
|
||||
|
||||
private static Rect MoveRectLeftKeepingRight(Rect rect, float x)
|
||||
{
|
||||
float xMax = rect.xMax;
|
||||
rect.x = Mathf.Min(x, xMax);
|
||||
rect.width = xMax - rect.x;
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static Rect MakeLabelRect(Rect rowRect, float labelX, float controlX)
|
||||
{
|
||||
Rect labelRect = rowRect;
|
||||
labelRect.x = Mathf.Min(labelX, controlX);
|
||||
labelRect.width = Mathf.Max(0f, controlX - labelRect.x);
|
||||
return labelRect;
|
||||
}
|
||||
|
||||
private static Rect ApplyControlIndentCompensation(Rect rect)
|
||||
{
|
||||
rect.x -= ControlIndentCompensation;
|
||||
rect.width += ControlIndentCompensation;
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
public class TextureObjectItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public TextureObjectItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string texturePropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = texturePropertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
ClearTextureIfRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
Draw(ControlRect, LabelRect, ResetRect);
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void Draw(Rect textureRect, Rect labelAndResetRect)
|
||||
{
|
||||
SplitControlAndResetRect(labelAndResetRect, out Rect labelRect, out Rect resetRect, false);
|
||||
Draw(textureRect, labelRect, resetRect);
|
||||
}
|
||||
|
||||
public void Draw(Rect textureRect, Rect labelRect, Rect resetRect)
|
||||
{
|
||||
ControlRect = textureRect;
|
||||
LabelRect = labelRect;
|
||||
ResetRect = resetRect;
|
||||
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
if (RootItem.ClearUnusedTextureReferencesRequested && (IsParentControlDisabled || !GUI.enabled))
|
||||
{
|
||||
ClearTextureIfRequested();
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
GUI.Label(LabelRect, GuiContent, EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
Texture texture;
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
texture = (Texture)EditorGUI.ObjectField(ControlRect, property.textureValue, typeof(Texture2D), false);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.textureValue = texture;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public void ClearTextureIfRequested()
|
||||
{
|
||||
if (!RootItem.ClearUnusedTextureReferencesRequested || PropertyInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
if (property == null || property.textureValue == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
property.textureValue = null;
|
||||
OnEndChange();
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = PropertyInfo == null ||
|
||||
(!PropertyInfo.Property.hasMixedValue && PropertyInfo.Property.textureValue == null);
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
PropertyInfo.Property.textureValue = null;
|
||||
PropertyIsDefaultValue = true;
|
||||
}
|
||||
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TextureScaleOffsetItem : ShaderGUIItem
|
||||
{
|
||||
private static readonly GUIContent TillingContent = new GUIContent("Tilling");
|
||||
private static readonly GUIContent OffsetContent = new GUIContent("Offset");
|
||||
private static readonly Vector4 DefaultScaleOffset = new Vector4(1f, 1f, 0f, 0f);
|
||||
|
||||
private readonly bool _isVectorProperty;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Func<GUIContent> _tillingContentProvider;
|
||||
private readonly Func<GUIContent> _offsetContentProvider;
|
||||
private readonly GUIContent _tillingContent;
|
||||
private readonly GUIContent _offsetContent;
|
||||
|
||||
public TextureScaleOffsetItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
bool isVectorProperty,
|
||||
Func<bool> isVisible = null,
|
||||
Func<GUIContent> tillingContentProvider = null,
|
||||
Func<GUIContent> offsetContentProvider = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_isVectorProperty = isVectorProperty;
|
||||
_isVisible = isVisible;
|
||||
_tillingContentProvider = tillingContentProvider ?? (() => TillingContent);
|
||||
_offsetContentProvider = offsetContentProvider ?? (() => OffsetContent);
|
||||
_tillingContent = _tillingContentProvider();
|
||||
_offsetContent = _offsetContentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
Rect tillingLabelRect = LabelRect;
|
||||
Rect tillingVec2Rect = ControlRect;
|
||||
Rect tillingResetRect = ResetRect;
|
||||
|
||||
GetRect();
|
||||
Rect offsetLabelRect = LabelRect;
|
||||
Rect offsetVec2Rect = ControlRect;
|
||||
Rect offsetResetRect = ResetRect;
|
||||
|
||||
Draw(tillingLabelRect, tillingVec2Rect, tillingResetRect, offsetLabelRect, offsetVec2Rect, offsetResetRect, true);
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void Draw(
|
||||
Rect tillingLabelRect,
|
||||
Rect tillingVec2Rect,
|
||||
Rect tillingResetRect,
|
||||
Rect offsetLabelRect,
|
||||
Rect offsetVec2Rect,
|
||||
Rect offsetResetRect,
|
||||
bool useEditorLabelField = false)
|
||||
{
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
Vector4 scaleOffset = GetScaleOffset(property);
|
||||
|
||||
DrawLabel(tillingLabelRect, _tillingContent, useEditorLabelField);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
Vector2 tilling = new Vector2(scaleOffset.x, scaleOffset.y);
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool tillingAnimatedScope = BeginAnimatedPropertyBackground(tillingVec2Rect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
tilling = EditorGUI.Vector2Field(tillingVec2Rect, GUIContent.none, tilling);
|
||||
}
|
||||
EndAnimatedPropertyBackground(tillingAnimatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
scaleOffset.x = tilling.x;
|
||||
scaleOffset.y = tilling.y;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
|
||||
bool tillingModified = property.hasMixedValue || tilling != Vector2.one;
|
||||
if (GUI.Button(tillingResetRect, tillingModified ? "R" : string.Empty, tillingModified ? GUI.skin.button : GUI.skin.label))
|
||||
{
|
||||
scaleOffset.x = 1f;
|
||||
scaleOffset.y = 1f;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
}
|
||||
|
||||
DrawLabel(offsetLabelRect, _offsetContent, useEditorLabelField);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
Vector2 offset = new Vector2(scaleOffset.z, scaleOffset.w);
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool offsetAnimatedScope = BeginAnimatedPropertyBackground(offsetVec2Rect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
offset = EditorGUI.Vector2Field(offsetVec2Rect, GUIContent.none, offset);
|
||||
}
|
||||
EndAnimatedPropertyBackground(offsetAnimatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
scaleOffset.z = offset.x;
|
||||
scaleOffset.w = offset.y;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
|
||||
bool offsetModified = property.hasMixedValue || offset != Vector2.zero;
|
||||
if (GUI.Button(offsetResetRect, offsetModified ? "R" : string.Empty, offsetModified ? GUI.skin.button : GUI.skin.label))
|
||||
{
|
||||
scaleOffset.z = 0f;
|
||||
scaleOffset.w = 0f;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLabel(Rect rect, GUIContent content, bool useEditorLabelField)
|
||||
{
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
if (useEditorLabelField)
|
||||
{
|
||||
EditorGUI.LabelField(rect, content);
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.Label(rect, content);
|
||||
}
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
isDefaultValue = !property.hasMixedValue && Approximately(GetScaleOffset(property), DefaultScaleOffset);
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
SetScaleOffset(PropertyInfo.Property, DefaultScaleOffset);
|
||||
PropertyIsDefaultValue = true;
|
||||
}
|
||||
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector4 GetScaleOffset(MaterialProperty property)
|
||||
{
|
||||
return _isVectorProperty ? property.vectorValue : property.textureScaleAndOffset;
|
||||
}
|
||||
|
||||
private void Apply(Vector4 scaleOffset)
|
||||
{
|
||||
SetScaleOffset(PropertyInfo.Property, scaleOffset);
|
||||
OnEndChange();
|
||||
}
|
||||
|
||||
private void SetScaleOffset(MaterialProperty property, Vector4 scaleOffset)
|
||||
{
|
||||
if (_isVectorProperty)
|
||||
{
|
||||
property.vectorValue = scaleOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.textureScaleAndOffset = scaleOffset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f026fa752b843a0b469f9306b2ff461
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ToggleItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Action<bool> _onValueChanged;
|
||||
|
||||
public ToggleItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Action<bool> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
bool value = PropertyInfo.Property.floatValue > 0.5f;
|
||||
value = EditorGUI.Toggle(ControlRect, value);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value ? 1f : 0f);
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property.floatValue > 0.5f);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property.floatValue > 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afba064c4c70401bbb7133de835d2d35
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,610 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class Vector2LineItem : ShaderGUIItem
|
||||
{
|
||||
private readonly bool _firstLine;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public Vector2LineItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
bool firstLine,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_firstLine = firstLine;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector4 vector = property.vectorValue;
|
||||
Vector2 value = _firstLine ? new Vector2(vector.x, vector.y) : new Vector2(vector.z, vector.w);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.Vector2Field(ControlRect, GUIContent.none, value);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (_firstLine)
|
||||
{
|
||||
vector.x = value.x;
|
||||
vector.y = value.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector.z = value.x;
|
||||
vector.w = value.y;
|
||||
}
|
||||
|
||||
property.vectorValue = vector;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (PropertyInfo == null)
|
||||
{
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCallByChild)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
Vector4 currentValue = PropertyInfo.Property.vectorValue;
|
||||
bool isDefaultValue = _firstLine
|
||||
? Mathf.Approximately(currentValue.x, defaultValue.x) && Mathf.Approximately(currentValue.y, defaultValue.y)
|
||||
: Mathf.Approximately(currentValue.z, defaultValue.z) && Mathf.Approximately(currentValue.w, defaultValue.w);
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
Vector4 currentValue = PropertyInfo.Property.vectorValue;
|
||||
if (_firstLine)
|
||||
{
|
||||
currentValue.x = defaultValue.x;
|
||||
currentValue.y = defaultValue.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentValue.z = defaultValue.z;
|
||||
currentValue.w = defaultValue.w;
|
||||
}
|
||||
|
||||
PropertyInfo.Property.vectorValue = currentValue;
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class VectorComponentItem : ShaderGUIItem
|
||||
{
|
||||
private readonly int _componentIndex;
|
||||
private readonly bool _isSlider;
|
||||
private readonly float _min;
|
||||
private readonly float _max;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public VectorComponentItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
int componentIndex,
|
||||
Func<GUIContent> contentProvider,
|
||||
bool isSlider,
|
||||
float min = 0f,
|
||||
float max = 1f,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_componentIndex = componentIndex;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isSlider = isSlider;
|
||||
_min = min;
|
||||
_max = max;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector4 vector = property.vectorValue;
|
||||
float value = GetValue(vector);
|
||||
value = _isSlider
|
||||
? DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
value,
|
||||
DraggableLabelFloat.GetSensitivityByRange(_min, _max),
|
||||
_min,
|
||||
_max)
|
||||
: DraggableLabelFloat.Handle(LabelRect, value);
|
||||
if (_isSlider)
|
||||
{
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.Slider(ControlRect, value, _min, _max);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.FloatField(ControlRect, value);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SetValue(ref vector, value);
|
||||
property.vectorValue = vector;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
private float GetValue(Vector4 vector)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0: return vector.x;
|
||||
case 1: return vector.y;
|
||||
case 2: return vector.z;
|
||||
case 3: return vector.w;
|
||||
default: return vector.x;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValue(ref Vector4 vector, float value)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0:
|
||||
vector.x = value;
|
||||
break;
|
||||
case 1:
|
||||
vector.y = value;
|
||||
break;
|
||||
case 2:
|
||||
vector.z = value;
|
||||
break;
|
||||
case 3:
|
||||
vector.w = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (PropertyInfo == null)
|
||||
{
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCallByChild)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
bool isDefaultValue = Mathf.Approximately(GetValue(PropertyInfo.Property.vectorValue), GetValue(defaultValue));
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
SetValue(ref vector, GetValue(defaultValue));
|
||||
PropertyInfo.Property.vectorValue = vector;
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class VectorComponentRangeSliderItem : ShaderGUIItem
|
||||
{
|
||||
private readonly int _componentIndex;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly string _rangePropertyName;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private ShaderPropertyInfo _rangePropertyInfo;
|
||||
|
||||
private const float SliderFrontGap = 5f;
|
||||
private const float SliderBackGap = 2f;
|
||||
|
||||
public VectorComponentRangeSliderItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
int componentIndex,
|
||||
string rangePropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_componentIndex = componentIndex;
|
||||
_rangePropertyName = rangePropertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void InitTriggerByChild()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_rangePropertyName))
|
||||
{
|
||||
_rangePropertyInfo = RootItem.PropertyInfoDic[_rangePropertyName];
|
||||
}
|
||||
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
DrawController();
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
Vector4 range = _rangePropertyInfo.Property.vectorValue;
|
||||
float min = range.x;
|
||||
float max = range.y;
|
||||
float sliderMin = Mathf.Min(min, max);
|
||||
float sliderMax = Mathf.Max(min, max);
|
||||
float value = GetValue(vector);
|
||||
|
||||
float rangeFieldWidth = EditorGUIUtility.fieldWidth;
|
||||
Rect minRect = ControlRect;
|
||||
minRect.width = rangeFieldWidth;
|
||||
Rect maxRect = ControlRect;
|
||||
maxRect.x = ControlRect.xMax - rangeFieldWidth;
|
||||
maxRect.width = rangeFieldWidth;
|
||||
Rect sliderRect = ControlRect;
|
||||
sliderRect.x = minRect.xMax + SliderFrontGap;
|
||||
sliderRect.width = Mathf.Max(0f, maxRect.x - sliderRect.x - SliderBackGap + EditorGUI.indentLevel * UnityEditorGUIIndentWidth);
|
||||
|
||||
RangeVecHasMixedValue(out bool minValueHasMixed, out bool maxValueHasMixed);
|
||||
|
||||
EditorGUI.showMixedValue = minValueHasMixed;
|
||||
bool minAnimatedScope = BeginAnimatedPropertyBackground(minRect, _rangePropertyInfo.Property);
|
||||
min = EditorGUI.FloatField(minRect, min);
|
||||
EndAnimatedPropertyBackground(minAnimatedScope);
|
||||
|
||||
EditorGUI.showMixedValue = maxValueHasMixed;
|
||||
bool maxAnimatedScope = BeginAnimatedPropertyBackground(maxRect, _rangePropertyInfo.Property);
|
||||
max = EditorGUI.FloatField(maxRect, max);
|
||||
EndAnimatedPropertyBackground(maxAnimatedScope);
|
||||
|
||||
range.x = min;
|
||||
range.y = max;
|
||||
SetVectorIfDifferent(_rangePropertyInfo.Property, range);
|
||||
|
||||
sliderMin = Mathf.Min(min, max);
|
||||
sliderMax = Mathf.Max(min, max);
|
||||
value = DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
value,
|
||||
DraggableLabelFloat.GetSensitivityByRange(sliderMin, sliderMax),
|
||||
sliderMin,
|
||||
sliderMax);
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
bool sliderAnimatedScope = BeginAnimatedPropertyBackground(sliderRect, PropertyInfo.Property);
|
||||
value = SliderNoIndent(sliderRect, value, sliderMin, sliderMax);
|
||||
EndAnimatedPropertyBackground(sliderAnimatedScope);
|
||||
|
||||
SetValue(ref vector, Mathf.Clamp(value, sliderMin, sliderMax));
|
||||
SetVectorIfDifferent(PropertyInfo.Property, vector);
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
|
||||
private static float SliderNoIndent(Rect rect, float value, float min, float max)
|
||||
{
|
||||
int indentLevel = EditorGUI.indentLevel;
|
||||
try
|
||||
{
|
||||
EditorGUI.indentLevel = 0;
|
||||
return EditorGUI.Slider(rect, value, min, max);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private void RangeVecHasMixedValue(out bool minValueHasMixed, out bool maxValueHasMixed)
|
||||
{
|
||||
minValueHasMixed = false;
|
||||
maxValueHasMixed = false;
|
||||
if (RootItem.Mats.Count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float firstMin = 0f;
|
||||
float firstMax = 0f;
|
||||
for (int i = 0; i < RootItem.Mats.Count; i++)
|
||||
{
|
||||
Vector4 range = RootItem.Mats[i].GetVector(_rangePropertyName);
|
||||
if (i == 0)
|
||||
{
|
||||
firstMin = range.x;
|
||||
firstMax = range.y;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(firstMin, range.x))
|
||||
{
|
||||
minValueHasMixed = true;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(firstMax, range.y))
|
||||
{
|
||||
maxValueHasMixed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float GetValue(Vector4 vector)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0: return vector.x;
|
||||
case 1: return vector.y;
|
||||
case 2: return vector.z;
|
||||
case 3: return vector.w;
|
||||
default: return vector.x;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValue(ref Vector4 vector, float value)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0:
|
||||
vector.x = value;
|
||||
break;
|
||||
case 1:
|
||||
vector.y = value;
|
||||
break;
|
||||
case 2:
|
||||
vector.z = value;
|
||||
break;
|
||||
case 3:
|
||||
vector.w = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
Vector4 defaultRange = RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
bool isDefaultValue = Mathf.Approximately(GetValue(PropertyInfo.Property.vectorValue), GetValue(defaultValue)) &&
|
||||
_rangePropertyInfo.Property.vectorValue == defaultRange;
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
SetValue(ref vector, GetValue(defaultValue));
|
||||
PropertyInfo.Property.vectorValue = vector;
|
||||
_rangePropertyInfo.Property.vectorValue = RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Vector3Item : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Action<Vector3> _onValueChanged;
|
||||
|
||||
public Vector3Item(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Action<Vector3> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector4 vector = property.vectorValue;
|
||||
Vector3 value = new Vector3(vector.x, vector.y, vector.z);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.Vector3Field(ControlRect, GUIContent.none, value);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
vector.x = value.x;
|
||||
vector.y = value.y;
|
||||
vector.z = value.z;
|
||||
property.vectorValue = vector;
|
||||
OnEndChange();
|
||||
_onValueChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
_onValueChanged?.Invoke(new Vector3(vector.x, vector.y, vector.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20a960b448954fc29c9197c2357e89ec
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,426 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using ShaderPropertyPack = NBShaderEditor.ShaderGUIHelper.ShaderPropertyPack;
|
||||
using UnityEditor;
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderGUIResetTool
|
||||
{
|
||||
private ShaderGUIHelper _helper;
|
||||
private Shader _shader;
|
||||
public bool IsInitResetData = false;
|
||||
|
||||
private Stack<(string,string)> _scopeContextStack = new Stack<(string,string)>();
|
||||
|
||||
public void CheckAllModifyOnValueChange()
|
||||
{
|
||||
foreach (var item in ResetItemDict.Values)
|
||||
{
|
||||
item.HasModified = item.CheckHasModifyOnValueChange();
|
||||
}
|
||||
}
|
||||
public void Init(ShaderGUIHelper helper)
|
||||
{
|
||||
_helper = helper;
|
||||
_shader = helper.shader;
|
||||
IsInitResetData = true;
|
||||
ResetItemDict.Clear();
|
||||
_scopeContextStack.Clear();
|
||||
}
|
||||
|
||||
public void EndInit()
|
||||
{
|
||||
IsInitResetData = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_needUpdate)
|
||||
{
|
||||
_needUpdate = false;
|
||||
CheckAllModifyOnValueChange();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _needUpdate = false;
|
||||
public void NeedUpdate()
|
||||
{
|
||||
_needUpdate = true;
|
||||
}
|
||||
|
||||
public ShaderGUIResetTool(ShaderGUIHelper helper)
|
||||
{
|
||||
Init(helper);
|
||||
}
|
||||
|
||||
public Dictionary<(string, string), ResetItem> ResetItemDict = new Dictionary<(string, string), ResetItem>();
|
||||
|
||||
public class ResetItem
|
||||
{
|
||||
public ResetItem Parent;
|
||||
public List<ResetItem> ChildResetItems = new List<ResetItem>();
|
||||
public Action ResetCallBack;
|
||||
public Action OnValueChangedCallBack;
|
||||
public Func<bool> CheckHasModifyOnValueChange;
|
||||
public Func<bool> CheckHasMixedValueOnValueChange;
|
||||
public (string, string) NameTuple;
|
||||
public bool HasModified = false;
|
||||
public bool HasMixedValue =false;
|
||||
public bool ChildHasModified = false;
|
||||
public bool ChildHasMixedValue = false;
|
||||
|
||||
|
||||
public void Init((string, string) nameTuple,Action resetCallBack,Action onValueChangedCallBack,Func<bool> checkHasModifyOnValueChange,Func<bool> checkHasMixedValueOnValueChange)
|
||||
{
|
||||
NameTuple = nameTuple;
|
||||
ResetCallBack = resetCallBack;
|
||||
OnValueChangedCallBack = onValueChangedCallBack;
|
||||
CheckHasModifyOnValueChange = checkHasModifyOnValueChange;
|
||||
CheckHasMixedValueOnValueChange = checkHasMixedValueOnValueChange;
|
||||
}
|
||||
|
||||
public void Execute(bool isParentCall = false)
|
||||
{
|
||||
ResetCallBack?.Invoke();
|
||||
OnValueChangedCallBack?.Invoke();
|
||||
HasModified = CheckHasModifyOnValueChange();
|
||||
HasMixedValue = CheckHasMixedValueOnValueChange();
|
||||
CheckOnValueChange(isParentCall);
|
||||
foreach (var item in ChildResetItems)
|
||||
{
|
||||
item.Execute(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void CheckOnValueChange(bool isParentCall = false)
|
||||
{
|
||||
HasModified = CheckHasModifyOnValueChange();
|
||||
HasMixedValue = CheckHasMixedValueOnValueChange();
|
||||
foreach (var childItem in ChildResetItems)
|
||||
{
|
||||
HasMixedValue |= childItem.HasMixedValue;
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
if (!isParentCall && Parent != null)
|
||||
{
|
||||
Parent.CheckOnValueChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckOnValueChange((string,string) nameTuple)
|
||||
{
|
||||
if (ResetItemDict.ContainsKey(nameTuple))
|
||||
{
|
||||
ResetItemDict[nameTuple].CheckOnValueChange();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("不包含ResetItemDict:"+nameTuple);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawResetModifyButton(Rect rect,(string, string) nameTuple, Action resetCallBack,
|
||||
Action onValueChangedCallBack,Func<bool> checkHasModifyOnValueChange,Func<bool> checkHasMixedValueOnValueChange,bool isSharedGlobalParent = false)
|
||||
{
|
||||
ConstructResetItem(nameTuple,resetCallBack,onValueChangedCallBack,checkHasModifyOnValueChange,checkHasMixedValueOnValueChange,isSharedGlobalParent);
|
||||
DrawResetModifyButtonFinal(rect,nameTuple);
|
||||
}
|
||||
public void DrawResetModifyButton(Rect rect,(string,string)nameTuple,ShaderPropertyPack pack, Action resetAction,Action onValueChangedCallBack,VectorValeType vectorValeType = VectorValeType.Undefine,bool isSharedGlobalParent = false)
|
||||
{
|
||||
|
||||
// (string, string) nameTuple = (label, pack.property.name);
|
||||
ConstructResetItem(nameTuple,
|
||||
resetAction: ()=>{
|
||||
SetPropertyToDefaultValue(pack,vectorValeType);
|
||||
resetAction?.Invoke();
|
||||
},onValueChangedCallBack:onValueChangedCallBack,
|
||||
checkHasModifyOnValueChange: () => IsPropertyModified(pack,vectorValeType),
|
||||
checkHasMixedValueOnValueChange: () => pack.property.hasMixedValue,
|
||||
isSharedGlobalParent: isSharedGlobalParent
|
||||
);
|
||||
if (ResetItemDict.ContainsKey(nameTuple))
|
||||
{
|
||||
DrawResetModifyButtonFinal(rect,nameTuple);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawResetModifyButton(Rect rect, string label)
|
||||
{
|
||||
//大部分功能都是触发子类
|
||||
ConstructResetItem((label, ""), resetAction: () => { },onValueChangedCallBack: () => { }, () => false, () => false);
|
||||
DrawResetModifyButtonFinal(rect,(label,""));
|
||||
}
|
||||
|
||||
//isSharedGlobalParent==>有些组件是公用的,比如极坐标一类。这些是不会设置父Item的。
|
||||
public void ConstructResetItem((string,string) nameTuple, Action resetAction,
|
||||
Action onValueChangedCallBack,Func<bool> checkHasModifyOnValueChange,Func<bool> checkHasMixedValueOnValueChange,bool isSharedGlobalParent = false)
|
||||
{
|
||||
if(!IsInitResetData) return;
|
||||
if (!ResetItemDict.ContainsKey(nameTuple))
|
||||
{
|
||||
ResetItem item = new ResetItem();
|
||||
item.Init(nameTuple,resetAction,onValueChangedCallBack,checkHasModifyOnValueChange,checkHasMixedValueOnValueChange);
|
||||
ResetItemDict.Add(nameTuple,item);
|
||||
|
||||
if (_scopeContextStack.Count > 0 && !isSharedGlobalParent)
|
||||
{
|
||||
var contextNameTuple = _scopeContextStack.Peek();
|
||||
ResetItem parentItem = ResetItemDict[contextNameTuple];
|
||||
parentItem.ChildResetItems.Add(item);
|
||||
item.Parent = parentItem;
|
||||
}
|
||||
|
||||
if (_helper.matEditor.targets.Length == 1)
|
||||
{
|
||||
onValueChangedCallBack?.Invoke();//属性值初始化
|
||||
}
|
||||
item.CheckOnValueChange();//Init
|
||||
}
|
||||
else
|
||||
{
|
||||
// Debug.LogError("ResetItem已经存在:"+nameTuple.ToString());
|
||||
}
|
||||
//就算是已经ContainsKey了,也Push和Pop一下。没有作用,但让写法更简单。
|
||||
_scopeContextStack.Push(nameTuple);
|
||||
}
|
||||
|
||||
|
||||
public void EndResetModifyButtonScope()
|
||||
{
|
||||
if(!IsInitResetData) return;
|
||||
if(_scopeContextStack.Count == 0) return;
|
||||
_scopeContextStack.Pop();
|
||||
}
|
||||
|
||||
public float ResetButtonSize => EditorGUIUtility.singleLineHeight;
|
||||
|
||||
private GUIContent resetIconContent = new GUIContent();
|
||||
//仅仅只是Drawer
|
||||
private void DrawResetModifyButtonFinal(Rect position, (string, string) nameTuple)
|
||||
{
|
||||
ResetItem item;
|
||||
// GUILayout.FlexibleSpace();
|
||||
if (ResetItemDict.ContainsKey(nameTuple))
|
||||
{
|
||||
item = ResetItemDict[nameTuple];
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
float btnSize = ResetButtonSize;
|
||||
string iconText;
|
||||
bool isDisabled = true;
|
||||
GUIStyle iconStyle;
|
||||
if (item.HasModified || item.HasMixedValue)
|
||||
{
|
||||
isDisabled = false;
|
||||
iconText = "R";
|
||||
iconStyle = GUI.skin.button;
|
||||
}
|
||||
else
|
||||
{
|
||||
isDisabled = true;
|
||||
iconText = "";
|
||||
iconStyle = GUI.skin.label;
|
||||
}
|
||||
|
||||
resetIconContent.text = iconText;
|
||||
EditorGUI.BeginDisabledGroup(isDisabled);
|
||||
// if (GUILayout.Button(iconTexture, GUILayout.Width(btnSize), GUILayout.Height(btnSize)))
|
||||
if (position.width <= 0)
|
||||
{
|
||||
position = GUILayoutUtility.GetRect(resetIconContent, GUI.skin.button, GUILayout.Width(btnSize),
|
||||
GUILayout.Height(btnSize));
|
||||
}
|
||||
if(GUI.Button(position,resetIconContent,iconStyle))
|
||||
{
|
||||
item.Execute();
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
public void SetPropertyToDefaultValue(ShaderPropertyPack pack,VectorValeType vectorValeType = VectorValeType.Undefine)
|
||||
{
|
||||
MaterialProperty property = pack.property;
|
||||
ShaderPropertyType propertyType = ShaderGUIUnityCompat.GetPropertyType(property);
|
||||
if (propertyType == ShaderPropertyType.Texture && vectorValeType != VectorValeType.Undefine)
|
||||
{
|
||||
propertyType = ShaderPropertyType.Vector;//Tilling or Offset
|
||||
}
|
||||
switch (propertyType)
|
||||
{
|
||||
case ShaderPropertyType.Color:
|
||||
Vector4 colorValue = _shader.GetPropertyDefaultVectorValue(pack.index);
|
||||
property.colorValue = new Color(colorValue.x, colorValue.y, colorValue.z, colorValue.x);
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Vector:
|
||||
Vector4 defaultVecValue;
|
||||
Vector4 vecValue;
|
||||
if (vectorValeType == VectorValeType.Tilling || vectorValeType == VectorValeType.Offset)
|
||||
{
|
||||
defaultVecValue = new Vector4(1f, 1f, 0f, 0f);
|
||||
vecValue = property.textureScaleAndOffset;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultVecValue = _shader.GetPropertyDefaultVectorValue(pack.index);
|
||||
vecValue = property.vectorValue;
|
||||
}
|
||||
switch (vectorValeType)
|
||||
{
|
||||
case VectorValeType.Undefine: Debug.LogError("VectorValeType is undefined"); break;
|
||||
case VectorValeType.X: vecValue.x = defaultVecValue.x;property.vectorValue = vecValue;break;
|
||||
case VectorValeType.Y: vecValue.y = defaultVecValue.y;property.vectorValue = vecValue;break;
|
||||
case VectorValeType.Z: vecValue.z = defaultVecValue.z;property.vectorValue = vecValue;break;
|
||||
case VectorValeType.W: vecValue.w = defaultVecValue.w;property.vectorValue = vecValue;break;
|
||||
case VectorValeType.XY:vecValue.x = defaultVecValue.x; vecValue.y = defaultVecValue.y;
|
||||
property.vectorValue = vecValue;break;
|
||||
case VectorValeType.Tilling:vecValue.x = defaultVecValue.x; vecValue.y = defaultVecValue.y;
|
||||
if (ShaderGUIUnityCompat.GetPropertyType(property) == ShaderPropertyType.Texture)
|
||||
{
|
||||
property.textureScaleAndOffset = vecValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.vectorValue = vecValue;
|
||||
}
|
||||
break;
|
||||
case VectorValeType.ZW:vecValue.z = defaultVecValue.z; vecValue.w = defaultVecValue.w;
|
||||
property.vectorValue = vecValue;break;
|
||||
case VectorValeType.Offset:vecValue.z = defaultVecValue.z; vecValue.w = defaultVecValue.w;
|
||||
if (ShaderGUIUnityCompat.GetPropertyType(property) == ShaderPropertyType.Texture)
|
||||
{
|
||||
property.textureScaleAndOffset = vecValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.vectorValue = vecValue;
|
||||
}
|
||||
break;
|
||||
case VectorValeType.XYZ:vecValue.x = defaultVecValue.x; vecValue.y = defaultVecValue.y;
|
||||
vecValue.z = defaultVecValue.z; property.vectorValue = vecValue;break;
|
||||
case VectorValeType.XYZW: property.vectorValue = defaultVecValue;break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Float or ShaderPropertyType.Range:
|
||||
float value = _shader.GetPropertyDefaultFloatValue(pack.index);
|
||||
property.floatValue = value;
|
||||
break;
|
||||
|
||||
case ShaderPropertyType.Texture:
|
||||
if (property.textureValue == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.textureValue = null;
|
||||
break;
|
||||
}
|
||||
// return property.textureValue.name == shader.GetPropertyTextureDefaultName(pack.index) ? false : true;
|
||||
|
||||
default:
|
||||
// 如果不属于上述类型,输出提示信息
|
||||
Debug.Log($"{property.displayName} has no default value or unsupported type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPropertyModified(ShaderPropertyPack pack,VectorValeType vectorValeType = VectorValeType.Undefine)
|
||||
{
|
||||
MaterialProperty property = pack.property;
|
||||
ShaderPropertyType propertyType = ShaderGUIUnityCompat.GetPropertyType(property);
|
||||
if (propertyType == ShaderPropertyType.Texture && vectorValeType != VectorValeType.Undefine)
|
||||
{
|
||||
propertyType = ShaderPropertyType.Vector;//Tilling or Offset
|
||||
}
|
||||
switch (propertyType)
|
||||
{
|
||||
case ShaderPropertyType.Color:
|
||||
Vector4 colorValue = _shader.GetPropertyDefaultVectorValue(pack.index);
|
||||
Color color = new Color(colorValue.x, colorValue.y, colorValue.z, colorValue.w);
|
||||
return property.colorValue == color ? false : true;
|
||||
|
||||
case ShaderPropertyType.Vector:
|
||||
|
||||
Vector4 defaultVecValue;
|
||||
Vector4 vecValue;
|
||||
if (vectorValeType == VectorValeType.Tilling || vectorValeType == VectorValeType.Offset)
|
||||
{
|
||||
defaultVecValue = new Vector4(1f, 1f, 0f, 0f);
|
||||
if (ShaderGUIUnityCompat.GetPropertyType(property) == ShaderPropertyType.Texture)
|
||||
{
|
||||
vecValue = property.textureScaleAndOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecValue = property.vectorValue;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultVecValue = _shader.GetPropertyDefaultVectorValue(pack.index);
|
||||
vecValue = property.vectorValue;
|
||||
}
|
||||
|
||||
|
||||
Vector2 defaultVecXYValue = new Vector2(defaultVecValue.x, defaultVecValue.y);
|
||||
Vector2 defaultVecZWValue = new Vector2(defaultVecValue.z, defaultVecValue.w);
|
||||
Vector2 vecXYValue = new Vector2(vecValue.x, vecValue.y);
|
||||
Vector2 vecZWValue = new Vector2(vecValue.z, vecValue.w);
|
||||
Vector2 defaultVecXYZValue = new Vector3(defaultVecValue.x, defaultVecValue.y,defaultVecValue.z);
|
||||
Vector2 vecXYZValue = new Vector3(vecValue.x, vecValue.y,vecValue.z);
|
||||
|
||||
|
||||
bool isVecModified = false;
|
||||
switch (vectorValeType)
|
||||
{
|
||||
case VectorValeType.Undefine: Debug.LogError("VectorValeType is undefined"); break;
|
||||
case VectorValeType.X: isVecModified = Mathf.Approximately(vecValue.x,defaultVecValue.x) ? false : true;break;
|
||||
case VectorValeType.Y: isVecModified = Mathf.Approximately(vecValue.y,defaultVecValue.y) ? false : true;break;
|
||||
case VectorValeType.Z: isVecModified = Mathf.Approximately(vecValue.z,defaultVecValue.z) ? false : true;break;
|
||||
case VectorValeType.W: isVecModified = Mathf.Approximately(vecValue.w,defaultVecValue.w) ? false : true;break;
|
||||
case VectorValeType.XY:case VectorValeType.Tilling:
|
||||
isVecModified = vecXYValue == defaultVecXYValue ? false : true;break;
|
||||
case VectorValeType.ZW:case VectorValeType.Offset:
|
||||
isVecModified = vecZWValue == defaultVecZWValue ? false : true;break;
|
||||
case VectorValeType.XYZ:isVecModified = vecXYZValue == defaultVecXYZValue ? false : true ; break;
|
||||
case VectorValeType.XYZW:isVecModified= vecValue == defaultVecValue? false : true ; break;
|
||||
}
|
||||
return isVecModified;
|
||||
|
||||
case ShaderPropertyType.Float or ShaderPropertyType.Range:
|
||||
return Mathf.Approximately(property.floatValue, _shader.GetPropertyDefaultFloatValue(pack.index)) ? false : true;
|
||||
|
||||
case ShaderPropertyType.Texture:
|
||||
if (property.textureValue == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return property.textureValue.name == "textureExternal" ? false : true;
|
||||
}
|
||||
// return property.textureValue.name == shader.GetPropertyTextureDefaultName(pack.index) ? false : true;
|
||||
|
||||
default:
|
||||
// 如果不属于上述类型,输出提示信息
|
||||
return false;
|
||||
// Debug.Log($"{property.displayName} has no default value or unsupported type");
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73135babda09451c9e5798b36897637b
|
||||
timeCreated: 1754727843
|
||||
250
Packages/NB_FX/XuanXuanRenderUtility/Editor/ShaderGUIToolBar.cs
Normal file
250
Packages/NB_FX/XuanXuanRenderUtility/Editor/ShaderGUIToolBar.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Rendering;
|
||||
using System.Collections.Generic;
|
||||
using NBShader;
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
|
||||
public class ShaderGUIToolBar
|
||||
{
|
||||
public ShaderGUIHelper Helper;
|
||||
|
||||
private int viewModeIndex;
|
||||
private readonly string[] viewModes = { "List", "Grid" };
|
||||
// private string searchText = "";
|
||||
private MaterialEditor _editor => Helper.matEditor;
|
||||
public ShaderGUIToolBar(ShaderGUIHelper helper)
|
||||
{
|
||||
Helper = helper;
|
||||
}
|
||||
|
||||
private static Material copiedMaterial;
|
||||
private static Shader copiedShader;
|
||||
|
||||
// 帮助链接URL
|
||||
private const string HELP_URL = "https://owejt9diz2c.feishu.cn/wiki/BHz8wHHSjiYJagk7WrmcAcconlb?from=from_copylink";
|
||||
|
||||
// private Vector2 imagePos;
|
||||
// private Texture2D icon;
|
||||
// private Texture2D image;
|
||||
|
||||
public void DrawToolbar()
|
||||
{
|
||||
|
||||
float BtnWidth = 30f;
|
||||
// 开始工具栏区域 (背景)
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
|
||||
// 1. 选择当前材质
|
||||
if (GUILayout.Button(EditorGUIUtility.IconContent("Material On Icon","跳到当前材质|跳到当前材质"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
EditorGUIUtility.PingObject(Helper.mats[0]);
|
||||
}
|
||||
|
||||
if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash","清除没有使用的贴图|清除没有使用的贴图"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
foreach (var mat in Helper.mats)
|
||||
{
|
||||
CleanUnusedTextureProperties(Helper.mats[0]);//先清理不属于当前Shader的贴图
|
||||
}
|
||||
Helper.isClearUnUsedTexture = true;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("C","复制材质属性"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
copiedMaterial = Helper.mats[0];
|
||||
copiedShader = copiedMaterial.shader;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("V","粘贴材质属性"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
if (copiedShader)
|
||||
{
|
||||
Helper.mats[0].shader = copiedShader;
|
||||
}
|
||||
|
||||
if (copiedMaterial)
|
||||
{
|
||||
Helper.mats[0].CopyPropertiesFromMaterial(copiedMaterial);
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button(new GUIContent("R","特殊重置功能"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
ShowResetPopupMenu();
|
||||
}
|
||||
if (GUILayout.Button(EditorGUIUtility.IconContent("d_UnityEditor.HierarchyWindow","折叠所有控件|折叠所有控件"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
for (int i = 0;i<Helper.shaderFlags.Length;i++)
|
||||
{
|
||||
ShaderFlagsBase shaderFlags = Helper.shaderFlags[i];
|
||||
for (int j = 3; j <= 5; j++)
|
||||
{
|
||||
Helper.mats[i].SetInteger(shaderFlags.GetShaderFlagsId(j),0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 添加下拉菜单
|
||||
// viewModeIndex = EditorGUILayout.Popup(viewModeIndex, viewModes, EditorStyles.toolbarPopup, GUILayout.Width(BtnWidth));
|
||||
|
||||
// 3. 添加搜索框
|
||||
GUILayout.FlexibleSpace(); // 将搜索框推到中间
|
||||
|
||||
// // 搜索框样式
|
||||
// GUIStyle searchField = new GUIStyle("SearchTextField");
|
||||
// GUIStyle cancelButton = new GUIStyle("SearchCancelButton");
|
||||
//
|
||||
// EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300));
|
||||
// {
|
||||
// EditorGUI.BeginChangeCheck();
|
||||
// searchText = EditorGUILayout.TextField(searchText, searchField);
|
||||
// if (EditorGUI.EndChangeCheck())
|
||||
// {
|
||||
// Helper.isSearchText = searchText.Length > 0;
|
||||
// Helper.searchText = searchText;
|
||||
// }
|
||||
//
|
||||
// // 清除搜索按钮
|
||||
// if (GUILayout.Button("", cancelButton))
|
||||
// {
|
||||
// searchText = "";
|
||||
// GUI.FocusControl(null); // 移除焦点
|
||||
// }
|
||||
// }
|
||||
// EditorGUILayout.EndHorizontal();
|
||||
|
||||
// // 4. 右侧按钮组
|
||||
// if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Refresh"), EditorStyles.toolbarButton))
|
||||
// {
|
||||
// Debug.Log("Refresh clicked");
|
||||
// }
|
||||
//
|
||||
// // 选项菜单
|
||||
// if (GUILayout.Button(EditorGUIUtility.IconContent("_Popup"), EditorStyles.toolbarButton))
|
||||
// {
|
||||
// // 创建下拉菜单
|
||||
// GenericMenu menu = new GenericMenu();
|
||||
// menu.AddItem(new GUIContent("Option 1"), false, () => Debug.Log("Option 1"));
|
||||
// menu.AddItem(new GUIContent("Option 2"), false, () => Debug.Log("Option 2"));
|
||||
// menu.ShowAsContext();
|
||||
// }
|
||||
// 贴图加载
|
||||
// if (icon == null)
|
||||
// icon = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath("eaa39f504c2ce7646aece103ba9c4766"));
|
||||
// if (image == null)
|
||||
// image = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath("cc6c30349a33a1d4c8242a9ef1a68830"));
|
||||
// if (GUILayout.Button(new GUIContent(icon,"爸爸!"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
// {
|
||||
// // 弹出 PopupWindow
|
||||
// // 弹出浮动窗口
|
||||
// FloatingImageWindow.ShowWindow(image);
|
||||
// }
|
||||
|
||||
|
||||
if (GUILayout.Button(EditorGUIUtility.IconContent("d__Help@2x","说明文档|说明文档"), EditorStyles.toolbarButton,GUILayout.Width(BtnWidth)))
|
||||
{
|
||||
// 打开浏览器跳转到帮助链接
|
||||
Application.OpenURL(HELP_URL);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal(); // 结束工具栏
|
||||
}
|
||||
|
||||
private void ShowResetPopupMenu()
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(new GUIContent("重置特殊UV通道"), false, () =>
|
||||
{
|
||||
Helper.ResetTool.ResetItemDict[("特殊UV通道选择","_SpecialUVChannelMode")].Execute();
|
||||
});
|
||||
menu.AddItem(new GUIContent("重置旋转扭曲"), false, () =>
|
||||
{
|
||||
Helper.ResetTool.ResetItemDict[("","_UTwirlEnabled")].Execute();
|
||||
});
|
||||
menu.AddItem(new GUIContent("重置极坐标"), false, () =>
|
||||
{
|
||||
Helper.ResetTool.ResetItemDict[("","_PolarCoordinatesEnabled")].Execute();
|
||||
});
|
||||
|
||||
// 弹出位置可以用 Event.current.mousePosition
|
||||
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
|
||||
}
|
||||
|
||||
private void CleanUnusedTextureProperties(Material mat)
|
||||
{
|
||||
if (mat == null || mat.shader == null) return;
|
||||
|
||||
Shader shader = mat.shader;
|
||||
|
||||
// 收集 Shader 里声明过的贴图属性
|
||||
var shaderTexProps = new HashSet<string>();
|
||||
int count = shader.GetPropertyCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (shader.GetPropertyType(i) == ShaderPropertyType.Texture)
|
||||
{
|
||||
shaderTexProps.Add(shader.GetPropertyName(i));
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历材质所有贴图属性,找到 shader 不再声明的
|
||||
var allProps = mat.GetTexturePropertyNames();
|
||||
foreach (var propName in allProps)
|
||||
{
|
||||
if (!shaderTexProps.Contains(propName))
|
||||
{
|
||||
if (mat.GetTexture(propName) != null)
|
||||
{
|
||||
mat.SetTexture(propName, null);
|
||||
Debug.Log($"清理 {mat.name} 的无效贴图属性: {propName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EditorWindow 显示图片
|
||||
public class FloatingImageWindow : EditorWindow
|
||||
{
|
||||
private Texture2D popupImage;
|
||||
|
||||
public static void ShowWindow(Texture2D image)
|
||||
{
|
||||
// 创建窗口
|
||||
FloatingImageWindow window = CreateInstance<FloatingImageWindow>();
|
||||
window.titleContent = new GUIContent("谢谢爸爸");
|
||||
window.popupImage = image;
|
||||
|
||||
// 设置初始尺寸
|
||||
if (image != null)
|
||||
window.position = new Rect(Screen.width / 2f - image.width / 2f,
|
||||
Screen.height / 2f - image.height / 2f,
|
||||
image.width,
|
||||
image.height);
|
||||
else
|
||||
window.position = new Rect(Screen.width / 2f - 100, Screen.height / 2f - 100, 200, 200);
|
||||
|
||||
window.ShowUtility(); // 浮动窗口
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (popupImage != null)
|
||||
{
|
||||
// 绘制图片
|
||||
Rect rect = new Rect(0, 0, position.width, position.height);
|
||||
GUI.DrawTexture(rect, popupImage, ScaleMode.ScaleToFit);
|
||||
}
|
||||
|
||||
// 可选:增加关闭按钮
|
||||
if (GUI.Button(new Rect(position.width - 25, 5, 20, 20), "X"))
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: deccdbc6963a48b2ace3358cbba67689
|
||||
timeCreated: 1754406958
|
||||
165
Packages/NB_FX/XuanXuanRenderUtility/Editor/StencilTestHelper.cs
Normal file
165
Packages/NB_FX/XuanXuanRenderUtility/Editor/StencilTestHelper.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class StencilValues
|
||||
{
|
||||
public int DefaultQueue = 2000;
|
||||
[BinaryInt(8, true, 2)] public int Ref = 0;
|
||||
public CompareFunction Comp = CompareFunction.Always;
|
||||
public StencilOp Pass = StencilOp.Keep;
|
||||
public StencilOp Fail = StencilOp.Keep;
|
||||
public StencilOp ZFail = StencilOp.Keep;
|
||||
[BinaryInt(8, true, 1)] public int ReadMask = 255;
|
||||
[BinaryInt(8, true, 1)] public int WriteMask = 255;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class StencilTestHelper
|
||||
{
|
||||
// private static StencilValuesConfig stencilValuesConfig;
|
||||
public class StencilPropertyNames
|
||||
{
|
||||
public string stencil = "_Stencil";
|
||||
public string stencilComp = "_StencilComp";
|
||||
public string stencilOp = "_StencilOp";
|
||||
public string stencilWriteMask = "_StencilWriteMask";
|
||||
public string stencilReadMask = "_StencilReadMask";
|
||||
public string stencilZFail = "_StencilZFail";
|
||||
public string stencilFail = "_StencilFail";
|
||||
public string stencilKexIndex = "_StencilKeyIndex";
|
||||
|
||||
public StencilPropertyNames()
|
||||
{
|
||||
}
|
||||
|
||||
public StencilPropertyNames(string stencilName, string stencilCompName, string stencilOpName,
|
||||
string stencilWriteMaskName, string stencilReadMaskName, string stencilZFailName,
|
||||
string stencilFailName, string stencilKexIndexName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stencilName))
|
||||
{
|
||||
stencil = stencilName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilCompName))
|
||||
{
|
||||
stencilComp = stencilCompName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilOpName))
|
||||
{
|
||||
stencilOp = stencilOpName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilWriteMaskName))
|
||||
{
|
||||
stencilWriteMask = stencilWriteMaskName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilReadMaskName))
|
||||
{
|
||||
stencilReadMask = stencilReadMaskName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilZFailName))
|
||||
{
|
||||
stencilZFail = stencilZFailName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilFailName))
|
||||
{
|
||||
stencilFail = stencilFailName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilKexIndexName))
|
||||
{
|
||||
stencilKexIndex = stencilKexIndexName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static StencilPropertyNames defaultStencilPropertyNames = new StencilPropertyNames();
|
||||
|
||||
// public static void SetMaterialStencil(Material mat,StencilValues stencilValues,out int defaultQueue)
|
||||
public static void SetMaterialStencil(Material mat, string stencilConfigKey,
|
||||
StencilValuesConfig stencilValuesConfig, out int defaultQueue,
|
||||
StencilPropertyNames stencilPropertyNames = null)
|
||||
{
|
||||
if (stencilValuesConfig == null)
|
||||
{
|
||||
Debug.LogError(mat.name + ": 缺少模板预设,设置Stencil失败");
|
||||
// stencilValuesConfig =
|
||||
// AssetDatabase.LoadAssetAtPath<StencilValuesConfig>(
|
||||
// "Assets/AddressableAssets/Shader/StencilConfig.asset");
|
||||
}
|
||||
|
||||
|
||||
if (stencilPropertyNames == null)
|
||||
{
|
||||
stencilPropertyNames = defaultStencilPropertyNames;
|
||||
}
|
||||
|
||||
StencilValues stencilValues;
|
||||
if (stencilValuesConfig.ContainsKey(stencilConfigKey))
|
||||
{
|
||||
stencilValues = stencilValuesConfig[stencilConfigKey];
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencil))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencil, stencilValues.Ref);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilComp))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilComp, (float)stencilValues.Comp);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilOp))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilOp, (float)stencilValues.Pass);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilWriteMask))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilWriteMask, stencilValues.WriteMask);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilReadMask))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilReadMask, stencilValues.ReadMask);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilZFail))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilZFail, (float)stencilValues.ZFail);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilFail))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilFail, (float)stencilValues.Fail);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stencilPropertyNames.stencilKexIndex))
|
||||
{
|
||||
mat.SetFloat(stencilPropertyNames.stencilKexIndex,
|
||||
stencilValuesConfig.GetKeyIndex(stencilConfigKey));
|
||||
}
|
||||
|
||||
defaultQueue = stencilValues.DefaultQueue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("无法设置材质模板参数,因为没有配置模板值", mat);
|
||||
defaultQueue = mat.renderQueue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7d6268a1ca7d824d8da2adf4cb8f74a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using NBShader;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class StencilValuesConfig : ScriptableObject
|
||||
{
|
||||
// public Dictionary<string, StencilValues> Config = new Dictionary<string, StencilValues>();
|
||||
// public StencilValuesConfigDictionary Config = new StencilValuesConfigDictionary();
|
||||
[Serializable]
|
||||
public class KeyStencilValues
|
||||
{
|
||||
public string key;
|
||||
public StencilValues Values;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public List<KeyStencilValues> Config = new List<KeyStencilValues>();
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
if (GetStencilValues(key) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public StencilValues GetStencilValues(string key)
|
||||
{
|
||||
foreach (var item in Config)
|
||||
{
|
||||
if (item.key == key)
|
||||
{
|
||||
return item.Values;
|
||||
}
|
||||
}
|
||||
Debug.LogError("StencilValuesConfig: 不存在Key"+key);
|
||||
return null;
|
||||
}
|
||||
|
||||
public int GetKeyIndex(string key)
|
||||
{
|
||||
for (int i = 0; i < Config.Count; i++)
|
||||
{
|
||||
if (Config[i].key == key)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
Debug.LogError("StencilValuesConfig: 不存在Key"+key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
public string GetKeyByIndex(int index)
|
||||
{
|
||||
return Config[index].key;
|
||||
}
|
||||
|
||||
|
||||
public StencilValues this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetStencilValues(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9177edf81a5b2354490ec4d66c4c069d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Experimental.GraphView;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
public class StringListSerchProvider : ScriptableObject , ISearchWindowProvider
|
||||
// public class StringListSerchProvider
|
||||
{
|
||||
|
||||
public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
List<SearchTreeEntry> serchList = new List<SearchTreeEntry>();
|
||||
serchList.Add(new SearchTreeGroupEntry(new GUIContent("List"),0));
|
||||
List<string> listItemAfterSort = listItems.ToList();
|
||||
|
||||
sortListItems(ref listItemAfterSort);
|
||||
|
||||
List<string> groups = new List<string>();
|
||||
foreach (var item in listItemAfterSort)
|
||||
{
|
||||
string[] entryTitle = item.Split("/");
|
||||
string groupName = "";
|
||||
for (int i = 0; i < entryTitle.Length - 1; i++)
|
||||
{
|
||||
groupName += entryTitle[i];
|
||||
if (!groups.Contains(groupName))
|
||||
{
|
||||
serchList.Add(new SearchTreeGroupEntry(new GUIContent(entryTitle[i]),i+1));
|
||||
}
|
||||
groupName += "/";
|
||||
}
|
||||
|
||||
SearchTreeEntry entry = new SearchTreeEntry(new GUIContent(entryTitle.Last()));
|
||||
entry.level = entryTitle.Length;
|
||||
entry.userData = item;//这里是serch操作点击最后的返回值,是什么都可以的。
|
||||
// Debug.Log(entry.userData);
|
||||
serchList.Add(entry);
|
||||
}
|
||||
|
||||
|
||||
return serchList;
|
||||
|
||||
}
|
||||
|
||||
public bool OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
|
||||
{
|
||||
string data = (string)searchTreeEntry.userData;
|
||||
onSetIndexCallback?.Invoke(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
private string[] listItems;
|
||||
private Action<string> onSetIndexCallback;
|
||||
public StringListSerchProvider(string[] items, Action<string> callBack)
|
||||
{
|
||||
listItems = items;
|
||||
onSetIndexCallback = callBack;
|
||||
}
|
||||
|
||||
public void Initialize(string[] items, Action<string> callBack)
|
||||
{
|
||||
listItems = items;
|
||||
onSetIndexCallback = callBack;
|
||||
}
|
||||
|
||||
// private List<string> sortListItems;
|
||||
|
||||
void sortListItems(ref List<string> list)
|
||||
{
|
||||
// sortListItems = listItems.ToList();
|
||||
list.Sort((a, b) =>
|
||||
{
|
||||
string[] splits1 = a.Split('/');
|
||||
string[] splits2 = b.Split('/');
|
||||
|
||||
for (int i = 0; i < splits1.Length; i++)
|
||||
{
|
||||
if (i >= splits2.Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int value = splits1[i].CompareTo(splits2[i]);
|
||||
if (value != 0)
|
||||
{
|
||||
if (splits1.Length != splits2.Length && (i == splits1.Length - 1 || i == splits2.Length - 1))
|
||||
{
|
||||
return splits1.Length < splits2.Length ? 1 : -1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aec459410f5254c4bb4d42e7ec8b4b5e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
|
||||
namespace NBShader.Editor
|
||||
{
|
||||
public sealed class VATTextureImporter : AssetPostprocessor
|
||||
{
|
||||
private const string VatKeyword = "vat";
|
||||
private const string VatKeyword2 = "vertex_animation_textures";
|
||||
|
||||
private const string ExrExtension = ".exr";
|
||||
private const string PngExtension = ".png";
|
||||
private const string DefaultPlatformName = "DefaultTexturePlatform";
|
||||
private const string StandalonePlatformName = "Standalone";
|
||||
private const string AndroidPlatformName = "Android";
|
||||
private const int AndroidDataTextureMaxSize = 8192;
|
||||
|
||||
private void OnPreprocessTexture()
|
||||
{
|
||||
if (!(assetImporter is TextureImporter textureImporter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ShouldImport(assetPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyImportSettings(textureImporter);
|
||||
}
|
||||
|
||||
internal static bool ShouldImport(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasSupportedExtension(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string fileName = Path.GetFileName(path);
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldImport = false;
|
||||
shouldImport |= fileName.IndexOf(VatKeyword, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
shouldImport |= fileName.IndexOf(VatKeyword2, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
return shouldImport;
|
||||
}
|
||||
|
||||
private static bool HasSupportedExtension(string path)
|
||||
{
|
||||
string extension = Path.GetExtension(path);
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return extension.Equals(ExrExtension, StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(PngExtension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void ApplyImportSettings(TextureImporter textureImporter)
|
||||
{
|
||||
ApplyCommonDataSettings(textureImporter);
|
||||
|
||||
string extension = Path.GetExtension(textureImporter.assetPath);
|
||||
if (extension.Equals(ExrExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyPlatformSettings(textureImporter, TextureImporterFormat.RGBAHalf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (extension.Equals(PngExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ApplyPlatformSettings(textureImporter, TextureImporterFormat.RGBA32);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyCommonDataSettings(TextureImporter textureImporter)
|
||||
{
|
||||
textureImporter.textureType = TextureImporterType.Default;
|
||||
textureImporter.sRGBTexture = false;
|
||||
// textureImporter.isReadable = true;
|
||||
textureImporter.mipmapEnabled = false;
|
||||
textureImporter.alphaIsTransparency = false;
|
||||
textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
|
||||
}
|
||||
|
||||
private static void ApplyPlatformSettings(TextureImporter textureImporter, TextureImporterFormat format)
|
||||
{
|
||||
ApplyPlatformSettings(textureImporter, DefaultPlatformName, format);
|
||||
ApplyPlatformSettings(textureImporter, StandalonePlatformName, format);
|
||||
ApplyPlatformSettings(textureImporter, AndroidPlatformName, format);
|
||||
}
|
||||
|
||||
private static void ApplyPlatformSettings(TextureImporter textureImporter, string platformName, TextureImporterFormat format)
|
||||
{
|
||||
TextureImporterPlatformSettings settings = textureImporter.GetPlatformTextureSettings(platformName);
|
||||
settings.name = platformName;
|
||||
settings.overridden = true;
|
||||
settings.format = format;
|
||||
settings.textureCompression = TextureImporterCompression.Uncompressed;
|
||||
settings.compressionQuality = 100;
|
||||
settings.crunchedCompression = false;
|
||||
settings.allowsAlphaSplitting = false;
|
||||
if (platformName.Equals(AndroidPlatformName, StringComparison.Ordinal) &&
|
||||
settings.maxTextureSize < AndroidDataTextureMaxSize)
|
||||
{
|
||||
settings.maxTextureSize = AndroidDataTextureMaxSize;
|
||||
}
|
||||
|
||||
textureImporter.SetPlatformTextureSettings(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0eb1e10af3780b0499d3c7e1393a0749
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "com.xuanxuan.render.utility.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:8f9e4d586616f13449cfeb86c5f704c2",
|
||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8495541fcd41b0c40b5b6e6e7a7639d1
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,188 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class RefreshMaterialInspectorWindow : EditorWindow
|
||||
{
|
||||
[MenuItem("ArtTools/刷新选中材质球面板")]
|
||||
static void refershSelectMaterialInspector()
|
||||
{
|
||||
EditorWindow.GetWindow<RefreshMaterialInspectorWindow>("材质球刷新");
|
||||
}
|
||||
public static bool isRefreshing = false;
|
||||
List<Material> materials = new List<Material>();
|
||||
int i = 0;
|
||||
private void OnGUI()
|
||||
{
|
||||
if (GUILayout.Button("开始刷新材质面板"))
|
||||
{
|
||||
materials.Clear();
|
||||
foreach (UnityEngine.Object o in Selection.GetFiltered(typeof(Material), SelectionMode.DeepAssets))
|
||||
{
|
||||
string path = AssetDatabase.GetAssetPath(o);
|
||||
string fileExtension = Path.GetExtension(path);
|
||||
if (fileExtension == ".mat")
|
||||
{
|
||||
materials.Add((Material)o);
|
||||
|
||||
}
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Debug.Log("刷新材质打开");
|
||||
|
||||
isRefreshing = true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(materials.Count <= 0) return;
|
||||
if (i < materials.Count)
|
||||
{
|
||||
// isRefreshing = true;
|
||||
// SetNormalToUV1(materials[i]);
|
||||
Selection.activeObject = materials[i];
|
||||
Debug.Log(materials[i].name,materials[i]);
|
||||
EditorUtility.DisplayProgressBar("材质球刷新", $"{i + 1}/{materials.Count}", (i + 1) / (float)materials.Count);
|
||||
i++;
|
||||
// isRefreshing = false;
|
||||
}
|
||||
if (i >= materials.Count)
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
materials.Clear();
|
||||
Debug.Log("材质刷新结束,isRefreshing布尔值 = "+isRefreshing);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Debug.Log("刷新材质关闭");
|
||||
isRefreshing = false;
|
||||
|
||||
}
|
||||
|
||||
// void SetNormalToUV1(Material mat)
|
||||
// {
|
||||
// if (mat.HasProperty("_NormalFrom"))
|
||||
// {
|
||||
// mat.SetInteger("_NormalFrom",1);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // [MenuItem("Assets/测试刷新材质", priority = 1)]
|
||||
// public static void TestRefeshMaterial()
|
||||
// {
|
||||
// W9ParticleShaderFlags flags = new W9ParticleShaderFlags();
|
||||
// int id = Shader.PropertyToID("_Mask2_Toggle");
|
||||
// foreach (UnityEngine.Object o in Selection.GetFiltered(typeof(Material), SelectionMode.DeepAssets))
|
||||
// {
|
||||
// Material mat = o as Material;
|
||||
// if (mat.shader.name == "XuanXuan/Effects/Particle_NiuBi")
|
||||
// {
|
||||
// bool isToggle = mat.GetFloat(id) > 0.5f;
|
||||
// if (isToggle)
|
||||
// {
|
||||
// mat.DisableKeyword("_MASKMAP2");
|
||||
// flags.SetMaterial(mat);
|
||||
// flags.SetFlagBits(W9ParticleShaderFlags.FLAG_BIT_PARTICLE_1_MASK_MAP2,index:1);
|
||||
// }
|
||||
// // flags.SetMaterial(mat);
|
||||
// }
|
||||
// }
|
||||
// AssetDatabase.SaveAssets();
|
||||
// }
|
||||
//
|
||||
// [MenuItem("Assets/Effect Tool/刷新选中文件夹内材质Wrap模式", priority = 1)]
|
||||
// public static void SetSelectParticleMaterialWrapMode()
|
||||
// {
|
||||
// foreach (UnityEngine.Object o in Selection.GetFiltered(typeof(Material), SelectionMode.DeepAssets))
|
||||
// {
|
||||
// Material mat = (Material)o;
|
||||
// // Debug.Log(mat.shader.name);
|
||||
// SetParticleMaterialWrapMode((Material)o);
|
||||
// }
|
||||
// AssetDatabase.SaveAssets();
|
||||
// }
|
||||
//
|
||||
// public static void SetParticleMaterialWrapMode(Material mat)
|
||||
// {
|
||||
//
|
||||
// if (mat.shader.name == "XuanXuan/Effects/Particle_NiuBi")
|
||||
// {
|
||||
// W9ParticleShaderFlags flags = new W9ParticleShaderFlags(mat);
|
||||
// SetWrapModeFlag(flags,mat,"_BaseMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_BASEMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_MaskMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_MASKMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_MaskMap2",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_MASKMAP2);
|
||||
// SetWrapModeFlag(flags,mat,"_NoiseMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_NOISEMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_EmissionMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_EMISSIONMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_DissolveMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_DISSOLVE_MAP);
|
||||
// SetWrapModeFlag(flags,mat,"_DissolveMaskMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_DISSOLVE_MASKMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_DissolveRampMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_DISSOLVE_RAMPMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_ColorBlendMap",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_COLORBLENDMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_VertexOffset_Map",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_VERTEXOFFSETMAP);
|
||||
// SetWrapModeFlag(flags,mat,"_ParallaxMapping_Map",W9ParticleShaderFlags.FLAG_BIT_WRAPMODE_PARALLAXMAPPINGMAP);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void SetParticleMaterialMaskStrenth(Material mat)
|
||||
// {
|
||||
// if (mat.shader.name == "XuanXuan/Effects/Particle_NiuBi")
|
||||
// {
|
||||
// Vector4 vec = mat.GetVector("_MaskMap3OffsetAnition");
|
||||
// if (vec.z < 1)
|
||||
// {
|
||||
// Vector4 newVec = new Vector4(vec.x, vec.y, 1, vec.w);
|
||||
// mat.SetVector("_MaskMap3OffsetAnition",newVec);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static void SetWrapModeFlag(W9ParticleShaderFlags flags, Material mat, string matTexPropertyName, int flagBit)
|
||||
// {
|
||||
// if (mat.GetTexture(matTexPropertyName) != null)
|
||||
// {
|
||||
// Texture tex = mat.GetTexture(matTexPropertyName);
|
||||
// Debug.Log(tex.name);
|
||||
// if (tex.wrapMode == TextureWrapMode.Clamp)
|
||||
// {
|
||||
// Debug.Log("This is Clamp");
|
||||
// flags.SetFlagBits(flagBit,index:2);
|
||||
// }
|
||||
// else if (tex.wrapMode == TextureWrapMode.Repeat)
|
||||
// {
|
||||
// Debug.Log("This is Repeat");
|
||||
// flags.ClearFlagBits(flagBit,index:2);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// static IEnumerator refreshMaterialInspector(Material[] materials)
|
||||
// {
|
||||
// for (int i = 0; i < materials.Length; i++)
|
||||
// {
|
||||
// EditorUtility.DisplayProgressBar("材质球刷新", $"{i + 1}/{materials.Length}", (i + 1) / (float)materials.Length);
|
||||
// Debug.Log(i);
|
||||
// // Selection.activeObject = materials[i];
|
||||
// yield return null;
|
||||
// }
|
||||
// EditorUtility.ClearProgressBar();
|
||||
// // yield break;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 317bb692129be184ebd7249bba265210
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/NB_FX/XuanXuanRenderUtility/Runtime.meta
Normal file
8
Packages/NB_FX/XuanXuanRenderUtility/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72e9a7523c1a5994c8fb317e7a9514e0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
// using Sirenix.OdinInspector;
|
||||
// using Unity.Mathematics;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
[ExecuteInEditMode]
|
||||
public class AnimationSheetHelper : MonoBehaviour,IMaterialModifier
|
||||
{
|
||||
|
||||
public bool isParticleBaseShader = true;
|
||||
|
||||
public string propertyName = "_BaseMap_ST";
|
||||
|
||||
private int _propertyID;
|
||||
private int _particleBaseAniBlendStPropertyID = Shader.PropertyToID("_BaseMap_AnimationSheetBlend_ST");
|
||||
private int _particleBaseAniBlendIntensityPropertyID = Shader.PropertyToID("_AnimationSheetHelperBlendIntensity");
|
||||
private static readonly int ParticleShaderFlagsId = Shader.PropertyToID("_W9ParticleShaderFlags");
|
||||
private static readonly int ParticleShaderFlags1Id = Shader.PropertyToID("_W9ParticleShaderFlags1");
|
||||
private static readonly int NBShaderFlagsId = Shader.PropertyToID("_NBShaderFlags");
|
||||
private static readonly int NBShaderFlags1Id = Shader.PropertyToID("_NBShaderFlags1");
|
||||
private const int FlagBitUIEffectOn = 1 << 14;
|
||||
private const int FlagBitAnimationSheetHelper = 1 << 15;
|
||||
private const int FlagBitUIEffectBaseMapMode = 1 << 22;
|
||||
|
||||
|
||||
|
||||
// [LabelText("序列帧图横向帧数量")]
|
||||
// [OnValueChanged("Init")]
|
||||
public int xSize = 4;
|
||||
|
||||
// [LabelText("序列帧图纵向帧数量")]
|
||||
// [OnValueChanged("Init")]
|
||||
public int ySize = 4;
|
||||
|
||||
// [LabelText("手动控制播放")]
|
||||
public bool manualPlay = false;
|
||||
|
||||
// [ShowIf("manualPlay")]
|
||||
// [LabelText("手动播放位置")] [Range(0, 1)]
|
||||
public float manualPlayePos = 0;
|
||||
|
||||
// [LabelText("播放速度fps")]
|
||||
// [HideIf("manualPlay")]
|
||||
public float speed = 16;
|
||||
|
||||
// [ReadOnly]
|
||||
// [LabelText("TillingOffset")]
|
||||
public Vector4 scaleOffset;
|
||||
// [ReadOnly]
|
||||
public Material mat;
|
||||
|
||||
// [ReadOnly]
|
||||
public int frameIndex;
|
||||
// [ReadOnly]
|
||||
public int frameCount;
|
||||
|
||||
private float _time;
|
||||
|
||||
private float _xScale;
|
||||
private float _yScale;
|
||||
|
||||
private static List<Material> usedMaterialList = new List<Material>();
|
||||
|
||||
|
||||
// // Start is called before the first frame update
|
||||
// void Start()
|
||||
// {
|
||||
// Debug.Log( "ASUpadate_Start");
|
||||
// Init();
|
||||
// }
|
||||
private void OnEnable()
|
||||
{
|
||||
// Debug.Log( "ASUpadate_OnEnable");
|
||||
Init();
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.update += EditorUpdate;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Init();
|
||||
|
||||
//清掉,避免参数保留。
|
||||
if (isParticleBaseShader)
|
||||
{
|
||||
if (mat)
|
||||
{
|
||||
GetShaderFlagIds(mat, out _, out int flags1Id);
|
||||
ClearFlagBits(mat, flags1Id, FlagBitAnimationSheetHelper);
|
||||
}
|
||||
}
|
||||
|
||||
if (usedMaterialList.Contains(mat))
|
||||
{
|
||||
usedMaterialList.Remove(mat);
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.update -= EditorUpdate;
|
||||
#endif
|
||||
}
|
||||
|
||||
// [Button("初始化")]
|
||||
public void Init()
|
||||
{
|
||||
_time = 0;
|
||||
if (xSize <= 0)
|
||||
{
|
||||
xSize = 1;
|
||||
}
|
||||
|
||||
if (ySize <= 0)
|
||||
{
|
||||
ySize = 1;
|
||||
}
|
||||
frameCount = xSize * ySize;
|
||||
|
||||
_xScale = 1 / (float)xSize;
|
||||
_yScale = 1 / (float)ySize;
|
||||
// if (frameCount <= 0)
|
||||
// {
|
||||
// frameCount = 1;
|
||||
// }
|
||||
// scaleOffset = new Vector4(1f/(float)xSize,1f/ (float)ySize, 0, 0);
|
||||
|
||||
if (gameObject.TryGetComponent(out Graphic g))
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
mat = g.materialForRendering;
|
||||
}
|
||||
else
|
||||
{
|
||||
mat = g.material;
|
||||
}
|
||||
}
|
||||
else if (gameObject.TryGetComponent(out Renderer r))
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
mat = r.material;
|
||||
}
|
||||
else
|
||||
{
|
||||
mat = r.sharedMaterial;
|
||||
}
|
||||
// Debug.Log("AS_GetRenderer:Mat--"+mat.name);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
mat = null;
|
||||
}
|
||||
|
||||
InitParticleBaseShaderToggle();
|
||||
// InitPostProcessToggle();
|
||||
|
||||
|
||||
_propertyID = Shader.PropertyToID(propertyName);
|
||||
if (mat != null)
|
||||
{
|
||||
mat.SetVector(_propertyID,CalSt(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void InitParticleBaseShaderToggle()
|
||||
{
|
||||
if (isParticleBaseShader)
|
||||
{
|
||||
GetShaderFlagIds(mat, out int flagsId, out int flags1Id);
|
||||
// Debug.Log(CheckFlagBits(mat, ParticleShaderFlags1Id, FlagBitUIEffectBaseMapMode));
|
||||
if(CheckFlagBits(mat, flagsId, FlagBitUIEffectOn) && !CheckFlagBits(mat, flags1Id, FlagBitUIEffectBaseMapMode))
|
||||
{
|
||||
propertyName = "_UI_MainTex_ST";
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyName = "_BaseMap_ST";
|
||||
}
|
||||
if (mat)
|
||||
{
|
||||
SetFlagBits(mat, flags1Id, FlagBitAnimationSheetHelper);
|
||||
}
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// if (mat)
|
||||
// {
|
||||
// if (mat.shader.name == "Mh2/Effects/Particle_NiuBi")
|
||||
// {
|
||||
// _flags.SetMaterial(mat);
|
||||
// _flags.ClearFlagBits(W9ParticleShaderFlags.FLAG_BIT_PARTICLE_1_ANIMATION_SHEET_HELPER,index:1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// public void InitPostProcessToggle()
|
||||
// {
|
||||
// if (isPostProcessShader)
|
||||
// {
|
||||
// TryGetComponent<PostProcessingController>(out postProcessingController);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// postProcessingController = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// Update is called once per frame
|
||||
private int _lastIndex;
|
||||
private int _nextIndex = 1;
|
||||
private float _blendLerp;
|
||||
private void Update()
|
||||
{
|
||||
// if (!isPostProcessShader)//后处理控制不通过才知。
|
||||
// {
|
||||
// if(!mat || _propertyID==0) return;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
float frameIndexFloat;
|
||||
if (manualPlay)
|
||||
{
|
||||
float playPos = Mathf.Repeat(manualPlayePos, 1f);
|
||||
frameIndexFloat = playPos * frameCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
_time += (float)editorDeltaTime * speed;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
_time += Time.deltaTime * speed;
|
||||
}
|
||||
frameIndexFloat = _time % frameCount;
|
||||
}
|
||||
frameIndex = (int) frameIndexFloat;
|
||||
if (frameIndex == frameCount)
|
||||
{
|
||||
frameIndex = frameCount - 1;
|
||||
}
|
||||
|
||||
|
||||
if (_lastIndex != frameIndex)
|
||||
{
|
||||
// if (isPostProcessShader)
|
||||
// {
|
||||
// postProcessingController.distortSpeedTexSt = CalSt(frameIndex);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
mat.SetVector(_propertyID,CalSt(frameIndex));
|
||||
// }
|
||||
_lastIndex = frameIndex;
|
||||
_nextIndex = frameIndex + 1;
|
||||
if (isParticleBaseShader)
|
||||
{
|
||||
mat.SetVector(_particleBaseAniBlendStPropertyID,CalSt(_nextIndex));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (isParticleBaseShader)
|
||||
{
|
||||
_blendLerp = Mathf.Repeat(frameIndexFloat,1f);
|
||||
mat.SetFloat(_particleBaseAniBlendIntensityPropertyID,_blendLerp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Material GetModifiedMaterial(Material baseMaterial)
|
||||
{
|
||||
if (usedMaterialList.Contains(baseMaterial))
|
||||
{
|
||||
//有可能打断UI合批,烦请客户端判断
|
||||
Material newMat = Instantiate(baseMaterial);
|
||||
mat = newMat;
|
||||
usedMaterialList.Add(newMat);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat = baseMaterial;
|
||||
usedMaterialList.Add(baseMaterial);
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private Vector4 CalSt(int index)
|
||||
{
|
||||
float xOffset = (index % xSize)*_xScale;
|
||||
float yOffset = (ySize - index / xSize -1)*_yScale;
|
||||
return new Vector4(_xScale, _yScale, xOffset, yOffset);
|
||||
}
|
||||
|
||||
private static void SetFlagBits(Material material, int propertyId, int bits)
|
||||
{
|
||||
if (!material)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
material.SetInteger(propertyId, material.GetInteger(propertyId) | bits);
|
||||
}
|
||||
|
||||
private static void ClearFlagBits(Material material, int propertyId, int bits)
|
||||
{
|
||||
if (!material)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
material.SetInteger(propertyId, material.GetInteger(propertyId) & ~bits);
|
||||
}
|
||||
|
||||
private static bool CheckFlagBits(Material material, int propertyId, int bits)
|
||||
{
|
||||
return material && (material.GetInteger(propertyId) & bits) != 0;
|
||||
}
|
||||
|
||||
private static void GetShaderFlagIds(Material material, out int flagsId, out int flags1Id)
|
||||
{
|
||||
if (material && (material.HasProperty(NBShaderFlagsId) || material.HasProperty(NBShaderFlags1Id)))
|
||||
{
|
||||
flagsId = NBShaderFlagsId;
|
||||
flags1Id = NBShaderFlags1Id;
|
||||
return;
|
||||
}
|
||||
|
||||
flagsId = ParticleShaderFlagsId;
|
||||
flags1Id = ParticleShaderFlags1Id;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private double editorDeltaTime = 0;
|
||||
private double lastEditorTime = 0;
|
||||
void EditorUpdate()
|
||||
{
|
||||
|
||||
if (lastEditorTime == 0)
|
||||
{
|
||||
lastEditorTime = EditorApplication.timeSinceStartup;
|
||||
}
|
||||
editorDeltaTime = EditorApplication.timeSinceStartup - lastEditorTime;
|
||||
lastEditorTime = EditorApplication.timeSinceStartup;
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf793623f3ea13b4db35460280cb6ded
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace NBShader
|
||||
{
|
||||
public class InspectorButtonAttribute : PropertyAttribute
|
||||
{
|
||||
public string Label;
|
||||
public string MethodName;
|
||||
|
||||
public InspectorButtonAttribute(string label, string methodName)
|
||||
{
|
||||
this.Label = label;
|
||||
this.MethodName = methodName;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(InspectorButtonAttribute))]
|
||||
public class InspectorButtonPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
|
||||
InspectorButtonAttribute buttonAttribute = (InspectorButtonAttribute)attribute;
|
||||
|
||||
|
||||
// 绘制按钮
|
||||
if (GUI.Button(position, buttonAttribute.Label))
|
||||
{
|
||||
// 获取包含该方法的对象
|
||||
var targetObject = property.serializedObject.targetObject;
|
||||
|
||||
// 获取方法信息
|
||||
var methodInfo = targetObject.GetType().GetMethod(buttonAttribute.MethodName,
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (methodInfo != null)
|
||||
{
|
||||
// 调用方法
|
||||
methodInfo.Invoke(targetObject, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Method '{buttonAttribute.MethodName}' not found in {targetObject.name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight; // 返回按钮的高度
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45f557806bb54c4dbeaec7a2ded5be83
|
||||
timeCreated: 1737792133
|
||||
@@ -0,0 +1,598 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NBShader
|
||||
{
|
||||
//TODO增加一键去重功能。测试排查BUG
|
||||
[ExecuteInEditMode]
|
||||
public class MaterialPropertyAgent : MonoBehaviour, IMaterialModifier
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct PropertyData
|
||||
{
|
||||
[HideInInspector] public int dataIndexInAgent;
|
||||
[HideInInspector] public MaterialPropertyAgent agent;
|
||||
[HideInInspector] public int id;
|
||||
|
||||
|
||||
public int index;
|
||||
[HideInInspector] public string propName;
|
||||
|
||||
public shaderPropertyType type;
|
||||
|
||||
|
||||
public string descripName;
|
||||
|
||||
|
||||
public Color colorValue;
|
||||
|
||||
public Vector4 vecValue;
|
||||
|
||||
public float floatValue;
|
||||
|
||||
[SerializeField] public IEnumerable propNameList;
|
||||
[HideInInspector] public bool isActive;
|
||||
[HideInInspector] public Shader shader;
|
||||
[HideInInspector] public Material mat;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public float rangMin { get; set; }
|
||||
public float rangMax { get; set; }
|
||||
|
||||
public void setValueByPropChange()
|
||||
{
|
||||
agent.refreshShderPropNameList();
|
||||
if (!agent.isCanUsedIndex(index))
|
||||
{
|
||||
index = agent.getCanUsedIndex();
|
||||
}
|
||||
|
||||
string propertyName = shader.GetPropertyName(index);
|
||||
id = Shader.PropertyToID(propertyName);
|
||||
descripName = shader.GetPropertyDescription(index);
|
||||
type = (shaderPropertyType)shader.GetPropertyType(index);
|
||||
if (type == shaderPropertyType.TexEnv)
|
||||
{
|
||||
propName = propertyName + "_ST";
|
||||
}
|
||||
else
|
||||
{
|
||||
propName = propertyName;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case shaderPropertyType.Color:
|
||||
colorValue = mat.GetColor(id);
|
||||
break;
|
||||
case shaderPropertyType.Float:
|
||||
floatValue = mat.GetFloat(id);
|
||||
break;
|
||||
case shaderPropertyType.Range:
|
||||
Vector2 rangeLimits = shader.GetPropertyRangeLimits(index);
|
||||
rangMin = rangeLimits.x;
|
||||
rangMax = rangeLimits.y;
|
||||
floatValue = mat.GetFloat(id);
|
||||
break;
|
||||
case shaderPropertyType.Vector:
|
||||
vecValue = mat.GetVector(id);
|
||||
break;
|
||||
case shaderPropertyType.TexEnv:
|
||||
string stName = propertyName + "_ST";
|
||||
vecValue = mat.GetVector(stName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void inActivateThis()
|
||||
{
|
||||
isActive = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//因为ShaderUtil只是用于Editor,所以复制一个枚举对属性类型进行识别。
|
||||
|
||||
public enum shaderPropertyType
|
||||
{
|
||||
//
|
||||
// 摘要:
|
||||
// Color Property.
|
||||
Color = 0,
|
||||
|
||||
//
|
||||
// 摘要:
|
||||
// Vector Property.
|
||||
Vector = 1,
|
||||
|
||||
//
|
||||
// 摘要:
|
||||
// Float Property.
|
||||
Float = 2,
|
||||
|
||||
//
|
||||
// 摘要:
|
||||
// Range Property.
|
||||
Range = 3,
|
||||
|
||||
//
|
||||
// 摘要:
|
||||
// Texture Property.
|
||||
TexEnv = 4
|
||||
}
|
||||
|
||||
|
||||
public PropertyData data0 = new PropertyData();
|
||||
|
||||
public PropertyData data1 = new PropertyData();
|
||||
|
||||
public PropertyData data2 = new PropertyData();
|
||||
|
||||
public PropertyData data3 = new PropertyData();
|
||||
|
||||
public PropertyData data4 = new PropertyData();
|
||||
|
||||
public PropertyData data5 = new PropertyData();
|
||||
|
||||
public Shader shader;
|
||||
public Material mat;
|
||||
|
||||
|
||||
public int materialIndex = 0;
|
||||
public Renderer customRenderer;
|
||||
|
||||
void initMatAndShader(bool initMat = false)
|
||||
{
|
||||
if (!initMat)
|
||||
{
|
||||
if (mat != null) return;
|
||||
}
|
||||
|
||||
if (customRenderer || GetComponent<Renderer>())
|
||||
{
|
||||
Material[] materials;
|
||||
Renderer r;
|
||||
if (customRenderer)
|
||||
{
|
||||
r = customRenderer;
|
||||
}
|
||||
else
|
||||
{
|
||||
r = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
materials = r.materials;
|
||||
}
|
||||
else
|
||||
{
|
||||
materials = r.sharedMaterials;
|
||||
}
|
||||
|
||||
mat = materials[materialIndex];
|
||||
shader = mat.shader;
|
||||
}
|
||||
else if (this.GetComponent<Graphic>())
|
||||
{
|
||||
Graphic graphic = this.GetComponent<Graphic>();
|
||||
|
||||
//测试,要不要用IMaterialModifier来处理
|
||||
// if (Application.isPlaying)
|
||||
// {
|
||||
// graphic.material = Material.Instantiate(graphic.material);
|
||||
// }
|
||||
|
||||
mat = graphic.material;
|
||||
|
||||
|
||||
shader = mat.shader;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("MaterialPropertyAgent未找到材质", this.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
initMatAndShader(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
initMatAndShader(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (mat == null)
|
||||
{
|
||||
// initMatAndShader();
|
||||
return;
|
||||
}
|
||||
|
||||
updateData(data0);
|
||||
updateData(data1);
|
||||
updateData(data2);
|
||||
updateData(data3);
|
||||
updateData(data4);
|
||||
updateData(data5);
|
||||
}
|
||||
|
||||
void updateData(PropertyData data)
|
||||
{
|
||||
if (!data.isActive) return;
|
||||
if (mat == null) return;
|
||||
switch (data.type)
|
||||
{
|
||||
case shaderPropertyType.Color:
|
||||
mat.SetColor(data.propName, data.colorValue);
|
||||
break;
|
||||
case shaderPropertyType.Vector:
|
||||
case shaderPropertyType.TexEnv:
|
||||
mat.SetVector(data.propName, data.vecValue);
|
||||
break;
|
||||
case shaderPropertyType.Float:
|
||||
case shaderPropertyType.Range:
|
||||
mat.SetFloat(data.propName, data.floatValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//实际上是修改了graphic.materialForRendering
|
||||
public Material GetModifiedMaterial(Material baseMaterial)
|
||||
{
|
||||
if (mat)
|
||||
{
|
||||
return mat;
|
||||
}
|
||||
else
|
||||
{
|
||||
return baseMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void OnRenderObject()
|
||||
{
|
||||
if (!UnityEditor.EditorApplication.isPlaying)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void addProperteData()
|
||||
{
|
||||
refreshShderPropNameList();
|
||||
|
||||
if (!data0.isActive)
|
||||
{
|
||||
initData(ref data0, 0);
|
||||
}
|
||||
else if (!data1.isActive)
|
||||
{
|
||||
initData(ref data1, 1);
|
||||
}
|
||||
else if (!data2.isActive)
|
||||
{
|
||||
initData(ref data2, 2);
|
||||
}
|
||||
else if (!data3.isActive)
|
||||
{
|
||||
initData(ref data3, 3);
|
||||
}
|
||||
else if (!data4.isActive)
|
||||
{
|
||||
initData(ref data4, 4);
|
||||
}
|
||||
else if (!data5.isActive)
|
||||
{
|
||||
initData(ref data5, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("已用掉可用的6个属性");
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllProperty()
|
||||
{
|
||||
data0.isActive = false;
|
||||
data1.isActive = false;
|
||||
data2.isActive = false;
|
||||
data3.isActive = false;
|
||||
data4.isActive = false;
|
||||
data5.isActive = false;
|
||||
}
|
||||
|
||||
public void initData(ref PropertyData data, int dataIndexInAgent)
|
||||
{
|
||||
data.dataIndexInAgent = dataIndexInAgent;
|
||||
data.agent = this;
|
||||
data.shader = shader;
|
||||
data.mat = mat;
|
||||
data.isActive = true;
|
||||
|
||||
data.index = getCanUsedIndex();
|
||||
data.setValueByPropChange();
|
||||
}
|
||||
|
||||
#region TODO自动排除已用Property
|
||||
|
||||
List<string> usedPropertyName = new List<string>();
|
||||
|
||||
void collectUsedPropName()
|
||||
{
|
||||
usedPropertyName.Clear();
|
||||
if (data0.isActive)
|
||||
{
|
||||
usedPropertyName.Add(data0.propName);
|
||||
}
|
||||
|
||||
if (data1.isActive)
|
||||
{
|
||||
usedPropertyName.Add(data1.propName);
|
||||
}
|
||||
|
||||
if (data2.isActive)
|
||||
{
|
||||
usedPropertyName.Add(data2.propName);
|
||||
}
|
||||
|
||||
if (data3.isActive)
|
||||
{
|
||||
usedPropertyName.Add(data3.propName);
|
||||
}
|
||||
|
||||
if (data4.isActive)
|
||||
{
|
||||
usedPropertyName.Add(data4.propName);
|
||||
}
|
||||
|
||||
if (data5.isActive)
|
||||
{
|
||||
usedPropertyName.Add(data5.propName);
|
||||
}
|
||||
}
|
||||
|
||||
public int getCanUsedIndex()
|
||||
{
|
||||
int index = -1;
|
||||
collectUsedPropName();
|
||||
for (int i = 0; i < shaderPropNameArr.Length; i++)
|
||||
{
|
||||
string propertyName;
|
||||
if (shader.GetPropertyType(i) == ShaderPropertyType.Texture)
|
||||
{
|
||||
propertyName = shaderPropNameArr[i] + "_ST";
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyName = shaderPropNameArr[i];
|
||||
}
|
||||
|
||||
if (usedPropertyName.Contains(propertyName)) continue;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public bool isCanUsedIndex(int i)
|
||||
{
|
||||
string propertyName;
|
||||
if (shader.GetPropertyType(i) == ShaderPropertyType.Texture)
|
||||
{
|
||||
propertyName = shaderPropNameArr[i] + "_ST";
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyName = shaderPropNameArr[i];
|
||||
}
|
||||
|
||||
collectUsedPropName();
|
||||
if (usedPropertyName.Contains(propertyName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public string[] shaderPropNameArr;
|
||||
public string[] shaderPropDescripArr;
|
||||
public string[] shaderPropDescripsForSerch;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
// if (XLuaManager.Instance != null)
|
||||
// {
|
||||
// if (XLuaManager.Instance.HasGameStart)//判断是游戏运行状态才进行实例化
|
||||
// {
|
||||
// return;//游戏进行中不允许编辑
|
||||
// }
|
||||
// }
|
||||
// Debug.Log("MaterialPropertyAgent : " + "OnValidate");
|
||||
refreshShderPropNameList();
|
||||
if (TryGetComponent<Renderer>(out Renderer r) || customRenderer)
|
||||
{
|
||||
isRendererMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isRendererMode = false;
|
||||
}
|
||||
|
||||
if (TryGetComponent<Renderer>(out Renderer r2) || TryGetComponent<Graphic>(out Graphic g))
|
||||
{
|
||||
isGetByComponet = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isGetByComponet = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool isRendererMode = false;
|
||||
public bool isGetByComponet = false;
|
||||
|
||||
public void initMatAndShaderByMaterialIndexChange()
|
||||
{
|
||||
initMatAndShader(true);
|
||||
refreshShderPropNameList();
|
||||
if (data0.isActive && data0.shader != mat.shader)
|
||||
{
|
||||
data0.shader = mat.shader;
|
||||
data0.mat = mat;
|
||||
}
|
||||
|
||||
if (data1.isActive && data1.shader != mat.shader)
|
||||
{
|
||||
data1.shader = mat.shader;
|
||||
data1.mat = mat;
|
||||
}
|
||||
|
||||
if (data2.isActive && data2.shader != mat.shader)
|
||||
{
|
||||
data2.shader = mat.shader;
|
||||
data2.mat = mat;
|
||||
}
|
||||
|
||||
if (data3.isActive && data3.shader != mat.shader)
|
||||
{
|
||||
data3.shader = mat.shader;
|
||||
data3.mat = mat;
|
||||
}
|
||||
|
||||
if (data4.isActive && data4.shader != mat.shader)
|
||||
{
|
||||
data4.shader = mat.shader;
|
||||
data4.mat = mat;
|
||||
}
|
||||
|
||||
if (data5.isActive && data5.shader != mat.shader)
|
||||
{
|
||||
data5.shader = mat.shader;
|
||||
data5.mat = mat;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
List<string> shaderPropNameList = new List<string>();
|
||||
private List<string> shaderPropDescripList = new List<string>();
|
||||
private List<string> shaderPropDescripListForSerch = new List<string>();
|
||||
public void refreshShderPropNameList()
|
||||
{
|
||||
initMatAndShader();
|
||||
if (shader == null) return;
|
||||
|
||||
shaderPropNameList.Clear();
|
||||
shaderPropDescripList.Clear();
|
||||
shaderPropDescripListForSerch.Clear();
|
||||
for (int i = 0; i < shader.GetPropertyCount(); i++)
|
||||
{
|
||||
shaderPropNameList.Add(shader.GetPropertyName(i));
|
||||
string descript = shader.GetPropertyDescription(i);
|
||||
shaderPropDescripList.Add(descript);
|
||||
string lowerDesc = descript.ToLower();
|
||||
if (!(lowerDesc.Contains("ignore") || lowerDesc.Contains("mode") || lowerDesc.Contains("toggle") ||
|
||||
lowerDesc.Contains("enable") || lowerDesc.Contains("flag")))
|
||||
{
|
||||
shaderPropDescripListForSerch.Add(descript);
|
||||
}
|
||||
}
|
||||
|
||||
shaderPropNameArr = shaderPropNameList.ToArray();
|
||||
shaderPropDescripArr = shaderPropDescripList.ToArray();
|
||||
shaderPropDescripsForSerch = shaderPropDescripListForSerch.ToArray();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
[CustomPropertyDrawer(typeof(MaterialPropertyAgent.PropertyData))]
|
||||
public class PropertyAgentPropertyDataDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
var isActive = property.FindPropertyRelative("isActive");
|
||||
if (isActive.boolValue)
|
||||
{
|
||||
EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
var index = property.FindPropertyRelative("index");
|
||||
MaterialPropertyAgent agent = property.FindPropertyRelative("agent").objectReferenceValue as MaterialPropertyAgent;
|
||||
int preservedIndex = index.intValue;
|
||||
float originLabelWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = 80;
|
||||
index.intValue = EditorGUILayout.Popup("属性名:", index.intValue, agent.shaderPropNameArr);
|
||||
if (preservedIndex != index.intValue)//证明用户进行了更改
|
||||
{
|
||||
if (!agent.isCanUsedIndex(index.intValue))
|
||||
{
|
||||
//TODO给一个报错提示
|
||||
Debug.Log(agent.shader.GetPropertyDescription(index.intValue));
|
||||
index.intValue = agent.getCanUsedIndex();
|
||||
}
|
||||
//此处进行内容刷新
|
||||
data.setValueByPropChange();
|
||||
}
|
||||
|
||||
|
||||
EditorGUILayout.LabelField("属性类型:", data.type.ToString());
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUIUtility.labelWidth = originLabelWidth;
|
||||
switch (data.type)
|
||||
{
|
||||
case MaterialPropertyAgent.shaderPropertyType.Color:
|
||||
data.colorValue = EditorGUILayout.ColorField(data.descripName + " :", data.colorValue);
|
||||
break;
|
||||
case MaterialPropertyAgent.shaderPropertyType.Vector:
|
||||
data.vecValue = EditorGUILayout.Vector4Field(data.descripName + " :", data.vecValue);
|
||||
break;
|
||||
case MaterialPropertyAgent.shaderPropertyType.Float:
|
||||
data.floatValue = EditorGUILayout.FloatField(data.descripName + ":", data.floatValue);
|
||||
break;
|
||||
case MaterialPropertyAgent.shaderPropertyType.Range:
|
||||
data.floatValue = EditorGUILayout.Slider(data.descripName + ":", data.floatValue, data.rangMin, data.rangMax);
|
||||
break;
|
||||
case MaterialPropertyAgent.shaderPropertyType.TexEnv:
|
||||
data.vecValue = EditorGUILayout.Vector4Field(data.propName + "_ST:", data.vecValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("删除", new[] { GUILayout.Width(200) }))
|
||||
{
|
||||
data.isActive = false;
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 781e80efab11a1d438f7c4d37603bdff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
337
Packages/NB_FX/XuanXuanRenderUtility/Runtime/MeshUI.cs
Normal file
337
Packages/NB_FX/XuanXuanRenderUtility/Runtime/MeshUI.cs
Normal file
@@ -0,0 +1,337 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NBShader
|
||||
{
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
[AddComponentMenu("UI/Mesh UI")]
|
||||
public class MeshUI : MaskableGraphic
|
||||
{
|
||||
[SerializeField] private Mesh mesh;
|
||||
[SerializeField] private List<Material> subMeshMaterials = new List<Material>();
|
||||
|
||||
private readonly List<Vector3> _positions = new List<Vector3>();
|
||||
private readonly List<Vector3> _normals = new List<Vector3>();
|
||||
private readonly List<Vector4> _tangents = new List<Vector4>();
|
||||
private readonly List<Vector2> _uv0S = new List<Vector2>();
|
||||
private readonly List<Color32> _colors = new List<Color32>();
|
||||
private readonly List<int[]> _subMeshIndices = new List<int[]>();
|
||||
private readonly List<int> _renderSubMeshMap = new List<int>();
|
||||
private Mesh _workingMesh;
|
||||
private int _renderSubMeshCount;
|
||||
private bool _hasLoggedUnreadableMesh;
|
||||
|
||||
public Mesh Mesh
|
||||
{
|
||||
get => mesh;
|
||||
set
|
||||
{
|
||||
if (mesh == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mesh = value;
|
||||
_hasLoggedUnreadableMesh = false;
|
||||
SetMaterialDirty();
|
||||
SetVerticesDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Material> SubMeshMaterials => subMeshMaterials;
|
||||
|
||||
public override Texture mainTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
Material currentMaterial = material;
|
||||
if (currentMaterial != null && currentMaterial.mainTexture != null)
|
||||
{
|
||||
return currentMaterial.mainTexture;
|
||||
}
|
||||
|
||||
return s_WhiteTexture;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
SetAllDirty();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
canvasRenderer.Clear();
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
ReleaseWorkingMesh();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
protected override void OnPopulateMesh(VertexHelper vh)
|
||||
{
|
||||
vh.Clear();
|
||||
}
|
||||
|
||||
protected override void UpdateGeometry()
|
||||
{
|
||||
if (canvasRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PrepareRenderMesh(out Mesh renderMesh))
|
||||
{
|
||||
canvasRenderer.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
canvasRenderer.SetMesh(renderMesh);
|
||||
}
|
||||
|
||||
protected override void UpdateMaterial()
|
||||
{
|
||||
if (canvasRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int materialCount = Mathf.Max(1, _renderSubMeshCount);
|
||||
canvasRenderer.materialCount = materialCount;
|
||||
|
||||
for (int i = 0; i < materialCount; i++)
|
||||
{
|
||||
Material sourceMaterial = GetRenderMaterial(i);
|
||||
Material modifiedMaterial = sourceMaterial != null ? GetModifiedMaterial(sourceMaterial) : defaultGraphicMaterial;
|
||||
canvasRenderer.SetMaterial(modifiedMaterial, i);
|
||||
}
|
||||
|
||||
canvasRenderer.SetTexture(mainTexture);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
SetMaterialDirty();
|
||||
SetVerticesDirty();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void OnDidApplyAnimationProperties()
|
||||
{
|
||||
base.OnDidApplyAnimationProperties();
|
||||
SetVerticesDirty();
|
||||
}
|
||||
|
||||
private bool PrepareRenderMesh(out Mesh renderMesh)
|
||||
{
|
||||
renderMesh = null;
|
||||
_renderSubMeshCount = 0;
|
||||
|
||||
if (mesh == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mesh.isReadable)
|
||||
{
|
||||
if (!_hasLoggedUnreadableMesh)
|
||||
{
|
||||
Debug.LogWarning($"MeshUI requires a read/write enabled mesh. Bake a readable mesh in the inspector before using '{mesh.name}'.", this);
|
||||
_hasLoggedUnreadableMesh = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mesh.vertexCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectRenderableSubMeshes(mesh, _subMeshIndices, _renderSubMeshMap);
|
||||
if (_subMeshIndices.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_renderSubMeshCount = _subMeshIndices.Count;
|
||||
|
||||
if (ShouldUseSourceMesh())
|
||||
{
|
||||
renderMesh = mesh;
|
||||
return true;
|
||||
}
|
||||
|
||||
EnsureWorkingMesh();
|
||||
BuildWorkingMesh();
|
||||
renderMesh = _workingMesh;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldUseSourceMesh()
|
||||
{
|
||||
if (color != Color.white)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _renderSubMeshCount == mesh.subMeshCount;
|
||||
}
|
||||
|
||||
private void BuildWorkingMesh()
|
||||
{
|
||||
Vector3[] srcVertices = mesh.vertices;
|
||||
int vertexCount = srcVertices.Length;
|
||||
PrepareVertexLists(vertexCount);
|
||||
|
||||
Vector2[] srcUv0 = mesh.uv;
|
||||
Vector3[] srcNormals = mesh.normals;
|
||||
Vector4[] srcTangents = mesh.tangents;
|
||||
Color32 vertexColor = color;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
_positions.Add(srcVertices[i]);
|
||||
_uv0S.Add(srcUv0 != null && i < srcUv0.Length ? srcUv0[i] : Vector2.zero);
|
||||
_normals.Add(srcNormals != null && i < srcNormals.Length ? srcNormals[i] : Vector3.back);
|
||||
_tangents.Add(srcTangents != null && i < srcTangents.Length ? srcTangents[i] : new Vector4(1f, 0f, 0f, 1f));
|
||||
_colors.Add(vertexColor);
|
||||
}
|
||||
|
||||
_workingMesh.Clear();
|
||||
_workingMesh.indexFormat = mesh.indexFormat;
|
||||
_workingMesh.SetVertices(_positions);
|
||||
_workingMesh.SetNormals(_normals);
|
||||
_workingMesh.SetTangents(_tangents);
|
||||
_workingMesh.SetUVs(0, _uv0S);
|
||||
_workingMesh.SetColors(_colors);
|
||||
_workingMesh.subMeshCount = _renderSubMeshCount;
|
||||
|
||||
for (int i = 0; i < _renderSubMeshCount; i++)
|
||||
{
|
||||
_workingMesh.SetTriangles(_subMeshIndices[i], i, false);
|
||||
}
|
||||
|
||||
_workingMesh.RecalculateBounds();
|
||||
}
|
||||
|
||||
private static void CollectRenderableSubMeshes(Mesh sourceMesh, List<int[]> subMeshIndices, List<int> renderSubMeshMap)
|
||||
{
|
||||
subMeshIndices.Clear();
|
||||
renderSubMeshMap.Clear();
|
||||
|
||||
int subMeshCount = Mathf.Max(1, sourceMesh.subMeshCount);
|
||||
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
|
||||
{
|
||||
if (sourceMesh.GetTopology(subMeshIndex) != MeshTopology.Triangles)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] indices = sourceMesh.GetTriangles(subMeshIndex);
|
||||
if (indices == null || indices.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
subMeshIndices.Add(indices);
|
||||
renderSubMeshMap.Add(subMeshIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareVertexLists(int capacity)
|
||||
{
|
||||
_positions.Clear();
|
||||
_normals.Clear();
|
||||
_tangents.Clear();
|
||||
_uv0S.Clear();
|
||||
_colors.Clear();
|
||||
|
||||
if (_positions.Capacity < capacity)
|
||||
{
|
||||
_positions.Capacity = capacity;
|
||||
}
|
||||
|
||||
if (_normals.Capacity < capacity)
|
||||
{
|
||||
_normals.Capacity = capacity;
|
||||
}
|
||||
|
||||
if (_tangents.Capacity < capacity)
|
||||
{
|
||||
_tangents.Capacity = capacity;
|
||||
}
|
||||
|
||||
if (_uv0S.Capacity < capacity)
|
||||
{
|
||||
_uv0S.Capacity = capacity;
|
||||
}
|
||||
|
||||
if (_colors.Capacity < capacity)
|
||||
{
|
||||
_colors.Capacity = capacity;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureWorkingMesh()
|
||||
{
|
||||
if (_workingMesh != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_workingMesh = new Mesh
|
||||
{
|
||||
name = "MeshUI Generated Mesh"
|
||||
};
|
||||
_workingMesh.MarkDynamic();
|
||||
}
|
||||
|
||||
private void ReleaseWorkingMesh()
|
||||
{
|
||||
if (_workingMesh == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Destroy(_workingMesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyImmediate(_workingMesh);
|
||||
}
|
||||
|
||||
_workingMesh = null;
|
||||
}
|
||||
|
||||
private Material GetRenderMaterial(int subMeshIndex)
|
||||
{
|
||||
int sourceSubMeshIndex = subMeshIndex;
|
||||
if (subMeshIndex >= 0 && subMeshIndex < _renderSubMeshMap.Count)
|
||||
{
|
||||
sourceSubMeshIndex = _renderSubMeshMap[subMeshIndex];
|
||||
}
|
||||
|
||||
if (subMeshMaterials != null && sourceSubMeshIndex >= 0 && sourceSubMeshIndex < subMeshMaterials.Count)
|
||||
{
|
||||
Material subMeshMaterial = subMeshMaterials[sourceSubMeshIndex];
|
||||
if (subMeshMaterial != null)
|
||||
{
|
||||
return subMeshMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96ee1f1d0f8f4cb3a4993f12831f42b7
|
||||
timeCreated: 1774387200
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShader
|
||||
{
|
||||
public static class UnityObjectFindCompat
|
||||
{
|
||||
public static T FindAny<T>() where T : Object
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
return Object.FindAnyObjectByType<T>();
|
||||
#else
|
||||
#pragma warning disable 0618
|
||||
return Object.FindObjectOfType<T>();
|
||||
#pragma warning restore 0618
|
||||
#endif
|
||||
}
|
||||
|
||||
public static T[] FindAll<T>() where T : Object
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
return Object.FindObjectsByType<T>(FindObjectsSortMode.None);
|
||||
#else
|
||||
#pragma warning disable 0618
|
||||
return Object.FindObjectsOfType<T>();
|
||||
#pragma warning restore 0618
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6399d1dc93fb4e0c8a757240070a1313
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShader
|
||||
{
|
||||
[ExecuteAlways]
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(ParticleSystem))]
|
||||
public sealed class NBParticleLocalTransformHelper : MonoBehaviour
|
||||
{
|
||||
private const string NBShaderName = "Effects/NBShader";
|
||||
private const string LegacyShaderName = "Effects/NBShader(Legacy)";
|
||||
private const string CustomLocalTransformKeyword = "_CUSTOM_LOCAL_TRANSFORM";
|
||||
|
||||
private static readonly int CustomLocalTransformLocalToWorldId =
|
||||
Shader.PropertyToID("_CustomLocalTransformLocalToWorld");
|
||||
|
||||
private static readonly int CustomLocalTransformWorldToLocalId =
|
||||
Shader.PropertyToID("_CustomLocalTransformWorldToLocal");
|
||||
|
||||
private ParticleSystemRenderer _particleRenderer;
|
||||
private Material _runtimeMaterial;
|
||||
private Material _lastAppliedMaterial;
|
||||
private string _lastWarning;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
CacheRenderer();
|
||||
ApplyCustomTransform();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
ApplyCustomTransform();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
CacheRenderer();
|
||||
ApplyCustomTransform();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
SetCustomTransformKeyword(false);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
SetCustomTransformKeyword(false);
|
||||
}
|
||||
|
||||
private void CacheRenderer()
|
||||
{
|
||||
if (_particleRenderer == null)
|
||||
{
|
||||
TryGetComponent(out _particleRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyCustomTransform()
|
||||
{
|
||||
if (!TryGetWritableMaterial(out Material material))
|
||||
{
|
||||
DisableLastAppliedKeyword();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastAppliedMaterial != null && _lastAppliedMaterial != material)
|
||||
{
|
||||
_lastAppliedMaterial.DisableKeyword(CustomLocalTransformKeyword);
|
||||
}
|
||||
|
||||
Matrix4x4 localToWorld = transform.localToWorldMatrix;
|
||||
Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
|
||||
|
||||
material.SetMatrix(CustomLocalTransformLocalToWorldId, localToWorld);
|
||||
material.SetMatrix(CustomLocalTransformWorldToLocalId, worldToLocal);
|
||||
material.EnableKeyword(CustomLocalTransformKeyword);
|
||||
|
||||
_lastAppliedMaterial = material;
|
||||
_lastWarning = null;
|
||||
}
|
||||
|
||||
private void SetCustomTransformKeyword(bool enabled)
|
||||
{
|
||||
Material material = GetExistingMaterial();
|
||||
if (material == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
material.EnableKeyword(CustomLocalTransformKeyword);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(CustomLocalTransformKeyword);
|
||||
if (material == _lastAppliedMaterial)
|
||||
{
|
||||
_lastAppliedMaterial = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Material GetExistingMaterial()
|
||||
{
|
||||
if (_lastAppliedMaterial != null)
|
||||
{
|
||||
return _lastAppliedMaterial;
|
||||
}
|
||||
|
||||
CacheRenderer();
|
||||
if (_particleRenderer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
return _runtimeMaterial;
|
||||
}
|
||||
|
||||
return _particleRenderer.sharedMaterial;
|
||||
}
|
||||
|
||||
private bool TryGetWritableMaterial(out Material material)
|
||||
{
|
||||
CacheRenderer();
|
||||
if (_particleRenderer == null)
|
||||
{
|
||||
material = null;
|
||||
LogWarningOnce("NBParticleLocalTransformHelper requires a ParticleSystemRenderer on the same GameObject.");
|
||||
return false;
|
||||
}
|
||||
|
||||
material = Application.isPlaying ? GetRuntimeMaterial() : _particleRenderer.sharedMaterial;
|
||||
if (material == null)
|
||||
{
|
||||
LogWarningOnce("NBParticleLocalTransformHelper could not find a material on the ParticleSystemRenderer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (material.shader == null || !IsSupportedShader(material.shader))
|
||||
{
|
||||
material = null;
|
||||
LogWarningOnce("NBParticleLocalTransformHelper only supports NBShader materials using shader '" +
|
||||
NBShaderName + "' or '" + LegacyShaderName + "'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSupportedShader(Shader shader)
|
||||
{
|
||||
return shader.name == NBShaderName || shader.name == LegacyShaderName;
|
||||
}
|
||||
|
||||
private Material GetRuntimeMaterial()
|
||||
{
|
||||
if (_runtimeMaterial == null && _particleRenderer != null)
|
||||
{
|
||||
_runtimeMaterial = _particleRenderer.material;
|
||||
}
|
||||
|
||||
return _runtimeMaterial;
|
||||
}
|
||||
|
||||
private void DisableLastAppliedKeyword()
|
||||
{
|
||||
if (_lastAppliedMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastAppliedMaterial.DisableKeyword(CustomLocalTransformKeyword);
|
||||
_lastAppliedMaterial = null;
|
||||
}
|
||||
|
||||
private void LogWarningOnce(string message)
|
||||
{
|
||||
if (_lastWarning == message)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastWarning = message;
|
||||
Debug.LogWarning(message, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70a2d4dce4af4875b6c1cb93bf60442d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,99 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
// using Sirenix.OdinInspector;
|
||||
namespace NBShader
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class NBShaderSpriteHelper : MonoBehaviour
|
||||
{
|
||||
private const string NBShaderName = "Effects/NBShader";
|
||||
private const string LegacyShaderName = "Effects/NBShader(Legacy)";
|
||||
|
||||
public Sprite sprite;
|
||||
|
||||
// [ReadOnly]
|
||||
public SpriteRenderer spRenderer;
|
||||
|
||||
// [ReadOnly]
|
||||
public Image image;
|
||||
|
||||
[InspectorButton("初始化", "Init")] public bool ButtomInspector;
|
||||
|
||||
private Material mat;
|
||||
|
||||
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
if (TryGetComponent<SpriteRenderer>(out SpriteRenderer spr))
|
||||
{
|
||||
spRenderer = spr;
|
||||
sprite = spr.sprite;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TryGetComponent<Image>(out Image im))
|
||||
{
|
||||
image = im;
|
||||
sprite = im.sprite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (sprite)
|
||||
{
|
||||
Texture texture = sprite.texture;
|
||||
Rect rect = sprite.textureRect;
|
||||
// sharedMaterial.SetVector("_BaseMapReverseST",CalScaleOffset(spRenderer.sprite.textureRect,spRenderer.sprite.texture));
|
||||
|
||||
if (spRenderer)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
mat = spRenderer.material;
|
||||
}
|
||||
else
|
||||
{
|
||||
mat = spRenderer.sharedMaterial;
|
||||
}
|
||||
}
|
||||
else if (image)
|
||||
{
|
||||
mat = image.material;
|
||||
}
|
||||
|
||||
if (mat && IsSupportedShader(mat.shader))
|
||||
{
|
||||
mat.SetVector("_MainTex_Reverse_ST", CalScaleOffset(rect, texture));
|
||||
Debug.Log(mat.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Vector4 CalScaleOffset(Rect textureRect, Texture texture)
|
||||
{
|
||||
//这是一个反向的scale offset。
|
||||
//算法:如果原scale offset。转换后会是 scaleAfter= 1 / scale , offsetAfter = - offset/scale;
|
||||
Vector2 scaleAfter = new Vector2(texture.width / textureRect.width, texture.height / textureRect.height);
|
||||
Vector2 offsetAfter = new Vector2(-(textureRect.x / texture.width) * scaleAfter.x,
|
||||
-(textureRect.y / texture.height) * scaleAfter.y);
|
||||
Vector4 scaleOffset = new Vector4(scaleAfter.x, scaleAfter.y, offsetAfter.x, offsetAfter.y);
|
||||
return scaleOffset;
|
||||
}
|
||||
|
||||
private static bool IsSupportedShader(Shader shader)
|
||||
{
|
||||
return shader && (shader.name == NBShaderName || shader.name == LegacyShaderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58ec7ff2f9b58f5459946ac86028eacf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShader
|
||||
{
|
||||
public abstract class ShaderFlagsBase
|
||||
{
|
||||
|
||||
private Material _material;
|
||||
|
||||
public Material material
|
||||
{
|
||||
get { return _material; }
|
||||
}
|
||||
|
||||
protected ShaderFlagsBase(Material material)
|
||||
{
|
||||
_material = material;
|
||||
}
|
||||
|
||||
public void SetMaterial(Material material)
|
||||
{
|
||||
_material = material;
|
||||
}
|
||||
|
||||
public Material GetMaterial()
|
||||
{
|
||||
return _material;
|
||||
}
|
||||
|
||||
public abstract int GetShaderFlagsId(int index = 0);
|
||||
protected abstract string GetShaderFlagsName(int index = 0);
|
||||
|
||||
private void SetIntValue(Material material, int flagBits, int index = 0)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
material.SetInteger(GetShaderFlagsId(index), flagBits);
|
||||
#else
|
||||
material.SetInteger(GetShaderFlagsId(index), flagBits);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetFlagBits(int flagBits, MaterialPropertyBlock propertyBlock = null, int index = 0)
|
||||
{
|
||||
if (propertyBlock is null)
|
||||
{
|
||||
if (_material is null) return;
|
||||
int flags = _material.GetInteger(GetShaderFlagsId(index));
|
||||
SetIntValue(_material, flags | flagBits, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
int flags = propertyBlock.GetInt(GetShaderFlagsId(index));
|
||||
propertyBlock.SetInt(GetShaderFlagsId(index), flags | flagBits);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFlagBits(int flagBits, MaterialPropertyBlock propertyBlock = null, int index = 0)
|
||||
{
|
||||
if (propertyBlock is null)
|
||||
{
|
||||
if (_material is null) return;
|
||||
int flags = _material.GetInteger(GetShaderFlagsId(index));
|
||||
SetIntValue(_material, flags & ~flagBits, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
int flags = propertyBlock.GetInteger(GetShaderFlagsId(index));
|
||||
propertyBlock.SetInteger(GetShaderFlagsId(index), flags & ~flagBits);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckFlagBits(int flagBits, MaterialPropertyBlock propertyBlock = null, int index = 0)
|
||||
{
|
||||
int flags = 0;
|
||||
if (propertyBlock is null)
|
||||
{
|
||||
if (_material is null) throw new NullReferenceException("material");
|
||||
flags = _material.GetInteger(GetShaderFlagsId(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
flags = propertyBlock.GetInteger(GetShaderFlagsId(index));
|
||||
}
|
||||
|
||||
return (flags & flagBits) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bcf591e67bd470e9375d72c63838643
|
||||
timeCreated: 1655437380
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "com.xuanxuan.render.utility",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:15fc0a57446b3144c949da3e2b9737a9"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f9e4d586616f13449cfeb86c5f704c2
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/NB_FX/XuanXuanRenderUtility/Shader.meta
Normal file
8
Packages/NB_FX/XuanXuanRenderUtility/Shader.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97647516635f92f43a6cd4807c3899d8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL.meta
Normal file
8
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa89a0fdb680e154f89c18f1a7fea2c6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
521
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/HoudiniVAT.hlsl
Normal file
521
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/HoudiniVAT.hlsl
Normal file
@@ -0,0 +1,521 @@
|
||||
#ifndef HOUDINI_VAT_INCLUDED
|
||||
#define HOUDINI_VAT_INCLUDED
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Houdini VAT 3.0 — 统一实现(SoftBody / RigidBody / DynamicRemeshing / ParticleSprite)
|
||||
// 集成于 ParticleBase.shader 的粒子着色器管线
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── 纹理声明 ──────────────────────────────────────────────────────────
|
||||
TEXTURE2D(_posTexture); SAMPLER(sampler_posTexture);
|
||||
TEXTURE2D(_posTexture2); SAMPLER(sampler_posTexture2);
|
||||
TEXTURE2D(_rotTexture); SAMPLER(sampler_rotTexture);
|
||||
TEXTURE2D(_colTexture); SAMPLER(sampler_colTexture);
|
||||
TEXTURE2D(_lookupTable); SAMPLER(sampler_lookupTable);
|
||||
|
||||
#if defined(_VAT) && defined(_VAT_HOUDINI) && \
|
||||
!defined(_HOUDINI_VAT_SOFTBODY) && \
|
||||
!defined(_HOUDINI_VAT_RIGIDBODY) && \
|
||||
!defined(_HOUDINI_VAT_DYNAMIC_REMESH) && \
|
||||
!defined(_HOUDINI_VAT_PARTICLE_SPRITE)
|
||||
#define _HOUDINI_VAT_SOFTBODY
|
||||
#endif
|
||||
|
||||
// ── extern 材质属性(Unity 自动从材质中查找同名值) ───────────────────
|
||||
|
||||
// Playback
|
||||
extern float _B_autoPlayback;
|
||||
extern float _gameTimeAtFirstFrame;
|
||||
extern float _playbackSpeed;
|
||||
extern float _houdiniFPS;
|
||||
extern float _displayFrame;
|
||||
extern float _B_interpolate;
|
||||
extern float _animateFirstFrame;
|
||||
extern float _frameCount;
|
||||
|
||||
// Bounds
|
||||
extern float _boundMinX, _boundMinY, _boundMinZ;
|
||||
extern float _boundMaxX, _boundMaxY, _boundMaxZ;
|
||||
|
||||
// Scale
|
||||
extern float _globalPscaleMul;
|
||||
extern float _B_pscaleAreInPosA;
|
||||
|
||||
// Particle Sprite
|
||||
extern float _widthBaseScale;
|
||||
extern float _heightBaseScale;
|
||||
extern float _B_hideOverlappingOrigin;
|
||||
extern float _originRadius;
|
||||
extern float _B_CAN_SPIN;
|
||||
extern float _B_spinFromHeading;
|
||||
extern float _spinPhase;
|
||||
extern float _scaleByVelAmount;
|
||||
extern float _particleTexUScale;
|
||||
extern float _particleTexVScale;
|
||||
|
||||
// Flags
|
||||
extern float _B_LOAD_POS_TWO_TEX;
|
||||
extern float _B_UNLOAD_ROT_TEX;
|
||||
extern float _B_LOAD_COL_TEX;
|
||||
extern float _B_LOAD_LOOKUP_TABLE;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 工具函数
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
// 用单位四元数旋转向量
|
||||
// 公式: v' = v + 2 * cross(q.xyz, q.w*v + cross(q.xyz, v))
|
||||
float3 HVAT_RotateByQuat(float3 v, float4 q)
|
||||
{
|
||||
float3 t = cross(q.xyz, v);
|
||||
return v + cross(q.xyz, t + v * q.w) * 2.0;
|
||||
}
|
||||
|
||||
// 从 3 个最小组件重建单位四元数(RigidBody 专用)
|
||||
// maxComp (0-3) 标识被省略的分量
|
||||
float4 HVAT_DecodeQuaternion(float3 xyz, float maxComp)
|
||||
{
|
||||
float w = sqrt(saturate(1.0 - dot(xyz, xyz)));
|
||||
float4 q = float4(xyz.x, xyz.y, xyz.z, w);
|
||||
int mc = (int)maxComp;
|
||||
if (mc == 1) q = float4( w, xyz.y, xyz.z, xyz.x);
|
||||
else if (mc == 2) q = float4(xyz.x, -w, xyz.z, -xyz.y);
|
||||
else if (mc == 3) q = float4(xyz.x, xyz.y, -w, -xyz.z);
|
||||
return q;
|
||||
}
|
||||
|
||||
// 从 posTexture.a 解码 5-bit spheremap 压缩法线
|
||||
float3 HVAT_DecodeCompressedNormal(float posA)
|
||||
{
|
||||
float scaledA = posA * 1024.0;
|
||||
float xIdx = floor(scaledA / 32.0);
|
||||
float yRaw = scaledA - xIdx * 32.0;
|
||||
float xNorm = xIdx / 31.5;
|
||||
float yNorm = yRaw / 31.5;
|
||||
float2 xy = float2(xNorm, yNorm) * 4.0 - 2.0;
|
||||
float d = dot(xy, xy);
|
||||
float sqrtF = sqrt(saturate(1.0 - d * 0.25));
|
||||
return float3(-sqrtF * xy.x, 1.0 - d * 0.5, sqrtF * xy.y);
|
||||
}
|
||||
|
||||
// Lookup table 解码:从 RGBA 得到高精度采样 UV
|
||||
float2 HVAT_DecodeLookupUV(float4 lookupSample, float boundMinX)
|
||||
{
|
||||
float lookupHDR = (frac(-boundMinX * 10.0) >= 0.5) ? 1.0 : 0.0;
|
||||
float divisor = lookupHDR ? 2048.0 : 255.0;
|
||||
float lookupX = lookupSample.r + lookupSample.g / divisor;
|
||||
float lookupY = 1.0 - (lookupSample.b + lookupSample.a / divisor);
|
||||
return float2(lookupX, lookupY);
|
||||
}
|
||||
|
||||
// 计算 VAT 采样 UV
|
||||
float2 HVAT_VatUV(float selectedFrame, float uv_r, float uv_g,
|
||||
float oneMinusBoundMaxR, float multiplyBoundMinB, float totalFrames)
|
||||
{
|
||||
float wrapped = fmod(selectedFrame - 1.0, totalFrames);
|
||||
float vBase = (1.0 - uv_g) * oneMinusBoundMaxR
|
||||
+ (wrapped / totalFrames) * oneMinusBoundMaxR;
|
||||
return float2(multiplyBoundMinB, 1.0 - vBase);
|
||||
}
|
||||
|
||||
// 2D hash 随机 [0, 1]
|
||||
float HVAT_HashRandom2D(float2 seed)
|
||||
{
|
||||
return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 帧选择
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
float2 HVAT_GetVatUV1(AttributesParticle input)
|
||||
{
|
||||
return CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM)
|
||||
? input.texcoords.zw
|
||||
: input.Custom1.xy;
|
||||
}
|
||||
|
||||
void HVAT_ComputeFrameSelection(AttributesParticle input, out float selectedFrame, out float frameAlpha)
|
||||
{
|
||||
float totalFrames = _frameCount;
|
||||
float animTime = (_Time.y - _gameTimeAtFirstFrame)
|
||||
* (_houdiniFPS / (totalFrames - 0.01))
|
||||
* _playbackSpeed;
|
||||
float frameFloat = frac(animTime) * totalFrames;
|
||||
|
||||
selectedFrame = _B_autoPlayback
|
||||
? floor(frameFloat) + 1.0
|
||||
: floor(_displayFrame);
|
||||
frameAlpha = frac(_B_autoPlayback ? frameFloat : _displayFrame);
|
||||
|
||||
if (CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM))
|
||||
{
|
||||
float frameCustomData = GetCustomData(NB_CUSTOM_DATA_FLAG_2, FLAGBIT_POS_2_CUSTOMDATA_VAT_FRAME, -1.0, input.Custom1, input.Custom2);
|
||||
if (frameCustomData >= 0.0)
|
||||
{
|
||||
float customFrame = saturate(frameCustomData) * max(totalFrames - 1.0, 0.0) + 1.0;
|
||||
selectedFrame = floor(customFrame);
|
||||
frameAlpha = frac(customFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 主函数:ApplyHoudiniVAT
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
void ApplyHoudiniVAT(AttributesParticle input, inout float4 positionOS, inout float3 normalOS)
|
||||
{
|
||||
// ── 共享 Bounds 常量(不依赖 UV 的部分) ──
|
||||
float comparisonBoundMaxb = (frac(_boundMaxZ * 10.0) >= 0.5) ? 1.0 : 0.0;
|
||||
float oneMinusBoundMaxR = 1.0 - frac(_boundMaxX * (-10.0));
|
||||
float boundMinMul10z = _boundMinZ * 10.0;
|
||||
float oneMinusBoundMinB = 1.0 - (ceil(boundMinMul10z) - boundMinMul10z);
|
||||
float pscaleDenom = max(1.0 - frac(_boundMaxY * 10.0), 1e-5);
|
||||
|
||||
// ── 帧选择 ──
|
||||
float selectedFrame, frameAlpha;
|
||||
HVAT_ComputeFrameSelection(input, selectedFrame, frameAlpha);
|
||||
float totalFrames = _frameCount;
|
||||
|
||||
float3 boundsMax = float3(_boundMaxX, _boundMaxY, _boundMaxZ);
|
||||
float3 boundsMin = float3(_boundMinX, _boundMinY, _boundMinZ);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Sub Mode 0: SoftBody — 加法位移 + 四元数法线 / 压缩法线
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
#if defined(_HOUDINI_VAT_SOFTBODY)
|
||||
{
|
||||
float2 vatUV1 = HVAT_GetVatUV1(input);
|
||||
float uv1r = vatUV1.r;
|
||||
float uv1g = vatUV1.g;
|
||||
float multiplyBoundMinB = uv1r * oneMinusBoundMinB;
|
||||
|
||||
float2 texUV = HVAT_VatUV(selectedFrame, uv1r, uv1g,
|
||||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||||
|
||||
// 采样位置
|
||||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, texUV, 0);
|
||||
float3 posRGB = posSample.rgb;
|
||||
float posA = posSample.a;
|
||||
|
||||
// 双纹理高精度位置
|
||||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||||
{
|
||||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, texUV, 0);
|
||||
posRGB += pos2.rgb * 0.01;
|
||||
}
|
||||
|
||||
// 解码位移
|
||||
float3 posDecoded = posRGB * (boundsMax - boundsMin) + boundsMin;
|
||||
float3 displacement = comparisonBoundMaxb ? posRGB : posDecoded;
|
||||
|
||||
// SoftBody: 原始位置 + 位移
|
||||
positionOS.xyz += displacement;
|
||||
|
||||
// 法线
|
||||
if (_B_UNLOAD_ROT_TEX > 0.5)
|
||||
{
|
||||
normalOS = normalize(HVAT_DecodeCompressedNormal(posA));
|
||||
}
|
||||
else
|
||||
{
|
||||
float4 rotSample = SAMPLE_TEXTURE2D_LOD(_rotTexture, sampler_rotTexture, texUV, 0);
|
||||
float4 rotFinal = comparisonBoundMaxb ? rotSample : (rotSample - 0.5) * 2.0;
|
||||
normalOS = normalize(HVAT_RotateByQuat(float3(0.0, 1.0, 0.0), rotFinal));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Sub Mode 1: RigidBody — Pivot 旋转 + Pscale + 帧间插值
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
#elif defined(_HOUDINI_VAT_RIGIDBODY)
|
||||
{
|
||||
if (CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float2 vatUV1 = HVAT_GetVatUV1(input);
|
||||
float uv1r = vatUV1.r;
|
||||
float uv1g = vatUV1.g;
|
||||
float multiplyBoundMinB = uv1r * oneMinusBoundMinB;
|
||||
|
||||
// 当前帧和下一帧 UV
|
||||
float2 texUV = HVAT_VatUV(selectedFrame, uv1r, uv1g,
|
||||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||||
float2 texUV_next = HVAT_VatUV(selectedFrame + 1.0, uv1r, uv1g,
|
||||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||||
|
||||
// 采样旋转
|
||||
float4 rotSample = SAMPLE_TEXTURE2D_LOD(_rotTexture, sampler_rotTexture, texUV, 0);
|
||||
float4 rotRemapped = (rotSample - 0.5) * 2.0;
|
||||
float4 rotFinal = comparisonBoundMaxb ? rotSample : rotRemapped;
|
||||
|
||||
// 采样位置
|
||||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, texUV, 0);
|
||||
float3 posRGB = posSample.rgb;
|
||||
float posA = posSample.a;
|
||||
|
||||
// 四元数 maxComp 索引(从 posA 整数部分)
|
||||
float quatMaxIdxScaled = posA * 4.0;
|
||||
float quatMaxIdx = floor(quatMaxIdxScaled);
|
||||
|
||||
// 解码四元数
|
||||
float4 q = HVAT_DecodeQuaternion(rotFinal.rgb, quatMaxIdx);
|
||||
|
||||
// 解码位置
|
||||
float3 posRawForDecode = posRGB;
|
||||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||||
{
|
||||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, texUV, 0);
|
||||
posRawForDecode = posRGB + pos2.rgb * 0.01;
|
||||
}
|
||||
|
||||
float3 posDecoded = posRawForDecode * (boundsMax - boundsMin) + boundsMin;
|
||||
float3 piecePos = comparisonBoundMaxb ? posRawForDecode : posDecoded;
|
||||
|
||||
// Pscale
|
||||
float pscaleFromPosA = (1.0 - frac(quatMaxIdxScaled)) / pscaleDenom;
|
||||
float pscale = (_B_pscaleAreInPosA > 0.5) ? pscaleFromPosA : 1.0;
|
||||
float totalScale = _globalPscaleMul * pscale;
|
||||
|
||||
// Rest pivot 从 UV2/UV3
|
||||
// Custom2 = TEXCOORD2 (uv2), vatTexcoord5 = TEXCOORD4 (uv3)
|
||||
float3 restPivot = float3(-input.Custom2.r, input.vatTexcoord5.r, 1.0 - input.vatTexcoord5.g);
|
||||
|
||||
// 旋转局部偏移
|
||||
float3 localOffset = positionOS.xyz - restPivot;
|
||||
float3 rotatedLocal = HVAT_RotateByQuat(localOffset, q);
|
||||
float3 scaledLocal = rotatedLocal * totalScale;
|
||||
|
||||
// 帧间插值
|
||||
float3 finalPiecePos = piecePos;
|
||||
if (_B_interpolate > 0.5)
|
||||
{
|
||||
float4 posSampleNext = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, texUV_next, 0);
|
||||
float3 posRGBNext = posSampleNext.rgb;
|
||||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||||
{
|
||||
float4 pos2Next = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, texUV_next, 0);
|
||||
posRGBNext += pos2Next.rgb * 0.01;
|
||||
}
|
||||
float3 posDecodedNext = posRGBNext * (boundsMax - boundsMin) + boundsMin;
|
||||
float3 piecePosNext = comparisonBoundMaxb ? posRGBNext : posDecodedNext;
|
||||
finalPiecePos = lerp(piecePos, piecePosNext, frameAlpha);
|
||||
}
|
||||
|
||||
// 组装最终位置
|
||||
float3 animatedPos = scaledLocal + finalPiecePos;
|
||||
|
||||
// 首帧复位
|
||||
float wrappedForCheck = fmod(selectedFrame - 1.0, totalFrames);
|
||||
bool isRestFrame = (wrappedForCheck < 0.5) && !(_animateFirstFrame > 0.5);
|
||||
float3 finalPosOS = isRestFrame ? positionOS.xyz : animatedPos;
|
||||
|
||||
// 崩塌:无 piece 关联
|
||||
finalPosOS = (uv1g <= 0.1) ? float3(0, 0, 0) : finalPosOS;
|
||||
|
||||
// 法线
|
||||
float3 rotatedNormalOS = isRestFrame
|
||||
? normalOS
|
||||
: normalize(HVAT_RotateByQuat(normalOS, q));
|
||||
normalOS = rotatedNormalOS;
|
||||
|
||||
positionOS.xyz = finalPosOS;
|
||||
return;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Sub Mode 2: DynamicRemeshing — Lookup Table → 绝对位置
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
#elif defined(_HOUDINI_VAT_DYNAMIC_REMESH)
|
||||
{
|
||||
// 使用 uv0 (texcoords.r/g) 作为 piece UV
|
||||
float uv0r = input.texcoords.r;
|
||||
float uv0g = input.texcoords.g;
|
||||
float multiplyBoundMinB = uv0r * oneMinusBoundMinB;
|
||||
|
||||
float2 vatUV = HVAT_VatUV(selectedFrame, uv0r, uv0g,
|
||||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||||
|
||||
// Lookup Table
|
||||
float4 lookupSample = SAMPLE_TEXTURE2D_LOD(_lookupTable, sampler_lookupTable, vatUV, 0);
|
||||
float2 lookupUV = HVAT_DecodeLookupUV(lookupSample, _boundMinX);
|
||||
|
||||
// 采样位置(绝对坐标)
|
||||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, lookupUV, 0);
|
||||
float3 posRGB = posSample.rgb;
|
||||
float posA = posSample.a;
|
||||
|
||||
// 双纹理高精度位置
|
||||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||||
{
|
||||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, lookupUV, 0);
|
||||
posRGB += pos2.rgb * 0.01;
|
||||
}
|
||||
|
||||
// 解码绝对位置
|
||||
float3 posDecoded = posRGB * (boundsMax - boundsMin) + boundsMin;
|
||||
float3 finalPosOS = comparisonBoundMaxb ? posRGB : posDecoded;
|
||||
|
||||
// 无有效 piece 塌陷
|
||||
finalPosOS = (uv0g <= 0.1) ? float3(0, 0, 0) : finalPosOS;
|
||||
|
||||
positionOS.xyz = finalPosOS;
|
||||
|
||||
// 法线
|
||||
if (_B_UNLOAD_ROT_TEX > 0.5)
|
||||
{
|
||||
normalOS = normalize(HVAT_DecodeCompressedNormal(posA));
|
||||
}
|
||||
else
|
||||
{
|
||||
float4 rotSample = SAMPLE_TEXTURE2D_LOD(_rotTexture, sampler_rotTexture, lookupUV, 0);
|
||||
float4 rotFinal = comparisonBoundMaxb ? rotSample : (rotSample - 0.5) * 2.0;
|
||||
normalOS = normalize(HVAT_RotateByQuat(float3(0.0, 1.0, 0.0), rotFinal));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
#elif defined(_HOUDINI_VAT_PARTICLE_SPRITE)
|
||||
// Sub Mode 3: ParticleSprite — Billboard + Spin + Origin Mask
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
{
|
||||
float2 vatUV1 = HVAT_GetVatUV1(input);
|
||||
float uv1r = vatUV1.r;
|
||||
float uv1g = vatUV1.g;
|
||||
float multiplyBoundMinB = uv1r * oneMinusBoundMinB;
|
||||
|
||||
// 当前帧 + 下一帧 UV
|
||||
float2 vatUV = HVAT_VatUV(selectedFrame, uv1r, uv1g,
|
||||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||||
float2 vatUV_next = HVAT_VatUV(selectedFrame + 1.0, uv1r, uv1g,
|
||||
oneMinusBoundMaxR, multiplyBoundMinB, totalFrames);
|
||||
|
||||
// 采样位置
|
||||
float4 posSample = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, vatUV, 0);
|
||||
float4 posSample_next = SAMPLE_TEXTURE2D_LOD(_posTexture, sampler_posTexture, vatUV_next, 0);
|
||||
|
||||
float3 posRGB = posSample.rgb;
|
||||
float3 posRGB_next = posSample_next.rgb;
|
||||
float posA = posSample.a;
|
||||
float posA_next = posSample_next.a;
|
||||
|
||||
// 双纹理高精度位置
|
||||
if (_B_LOAD_POS_TWO_TEX > 0.5)
|
||||
{
|
||||
float4 pos2 = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, vatUV, 0);
|
||||
float4 pos2_next = SAMPLE_TEXTURE2D_LOD(_posTexture2, sampler_posTexture2, vatUV_next, 0);
|
||||
posRGB += pos2.rgb * 0.01;
|
||||
posRGB_next += pos2_next.rgb * 0.01;
|
||||
}
|
||||
|
||||
// 解码粒子中心
|
||||
float3 posDecoded = posRGB * (boundsMax - boundsMin) + boundsMin;
|
||||
float3 posDecoded_next = posRGB_next * (boundsMax - boundsMin) + boundsMin;
|
||||
|
||||
float3 particlePos = comparisonBoundMaxb ? posRGB : posDecoded;
|
||||
float3 particlePos_next = comparisonBoundMaxb ? posRGB_next : posDecoded_next;
|
||||
|
||||
// 帧间插值
|
||||
float3 particlePosF = (_B_interpolate > 0.5)
|
||||
? lerp(particlePos, particlePos_next, frameAlpha)
|
||||
: particlePos;
|
||||
|
||||
// Pscale
|
||||
float posA_f = (_B_interpolate > 0.5) ? lerp(posA, posA_next, frameAlpha) : posA;
|
||||
float pscaleRaw = posA_f / pscaleDenom;
|
||||
|
||||
// 每粒子随机缩放
|
||||
float perParticleRandom = HVAT_HashRandom2D(float2(uv1r, uv1g));
|
||||
float additionalPscaleMul = 1.0 + perParticleRandom;
|
||||
|
||||
// 原点遮挡 mask
|
||||
float distThis = distance(particlePos, float3(0, 0, 0));
|
||||
float distNext = distance(particlePos_next, float3(0, 0, 0));
|
||||
float maskThis = saturate(sign(distThis - _originRadius));
|
||||
float maskNext = saturate(sign(distNext - _originRadius));
|
||||
float maskF = (_B_interpolate > 0.5) ? lerp(maskThis, maskNext, frameAlpha) : maskThis;
|
||||
|
||||
float pscaleFinal;
|
||||
if (_B_pscaleAreInPosA > 0.5)
|
||||
{
|
||||
pscaleFinal = (_B_hideOverlappingOrigin > 0.5)
|
||||
? pscaleRaw * _globalPscaleMul * additionalPscaleMul * maskF
|
||||
: pscaleRaw * _globalPscaleMul * additionalPscaleMul;
|
||||
}
|
||||
else
|
||||
{
|
||||
pscaleFinal = (_B_hideOverlappingOrigin > 0.5)
|
||||
? _globalPscaleMul * additionalPscaleMul * maskF
|
||||
: _globalPscaleMul * additionalPscaleMul;
|
||||
}
|
||||
|
||||
// Billboard 方向轴
|
||||
float3 viewRight = float3(1, 0, 0);
|
||||
float3 viewUp = float3(0, 1, 0);
|
||||
float velStretch = 1.0;
|
||||
float3 headingViewDir = float3(0, 0, 0);
|
||||
|
||||
if (_B_CAN_SPIN > 0.5)
|
||||
{
|
||||
// 从颜色通道读取 heading
|
||||
if (_B_LOAD_COL_TEX > 0.5)
|
||||
{
|
||||
float4 colThis = SAMPLE_TEXTURE2D_LOD(_colTexture, sampler_colTexture, vatUV, 0);
|
||||
headingViewDir = float3(-colThis.r, colThis.g, colThis.b);
|
||||
}
|
||||
|
||||
if (_B_spinFromHeading > 0.5)
|
||||
{
|
||||
float2 hXY = headingViewDir.xy;
|
||||
float hLen = length(hXY);
|
||||
float2 hDir = (hLen > 1e-5) ? hXY / hLen : float2(1, 0);
|
||||
viewRight = float3(hDir.x, hDir.y, 0);
|
||||
viewUp = cross(viewRight, float3(0, 0, -1));
|
||||
velStretch = _scaleByVelAmount * hLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
float angle = frac(_spinPhase) * 6.283185;
|
||||
float c = cos(angle);
|
||||
float s = sin(angle);
|
||||
viewRight = float3(c, s, 0);
|
||||
viewUp = cross(viewRight, float3(0, 0, -1));
|
||||
}
|
||||
}
|
||||
|
||||
// 视图空间 → 世界空间 → 物体空间
|
||||
float3 worldRight = mul((float3x3)UNITY_MATRIX_I_V, viewRight);
|
||||
float3 worldUp = mul((float3x3)UNITY_MATRIX_I_V, viewUp);
|
||||
float3 worldFwd = mul((float3x3)UNITY_MATRIX_I_V, float3(0, 0, 1));
|
||||
|
||||
float3 rightOS = normalize(TransformWorldToObjectDir(worldRight));
|
||||
float3 upOS = normalize(TransformWorldToObjectDir(worldUp));
|
||||
float3 normalOS_new = normalize(TransformWorldToObjectDir(worldFwd));
|
||||
|
||||
// Billboard 顶点偏移
|
||||
float cornerX = input.texcoords.r - 0.5;
|
||||
float cornerY = input.texcoords.g - 0.5;
|
||||
|
||||
float3 offsetX = rightOS * cornerX * _widthBaseScale * pscaleFinal;
|
||||
float3 offsetY = upOS * cornerY * _heightBaseScale * pscaleFinal * velStretch;
|
||||
|
||||
float3 finalPosOS = particlePosF + offsetX + offsetY;
|
||||
|
||||
// 崩塌
|
||||
finalPosOS = (uv1g <= 0.1) ? float3(0, 0, 0) : finalPosOS;
|
||||
|
||||
positionOS.xyz = finalPosOS;
|
||||
normalOS = normalOS_new;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // HOUDINI_VAT_INCLUDED
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 802859f4f9ef96442bf245ade4894f0d
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,356 @@
|
||||
#ifndef SIX_WAY_SMOKE_LIT_HLSL
|
||||
#define SIX_WAY_SMOKE_LIT_HLSL
|
||||
//这部分尽量借鉴 UnityEditor.VFX.HDRP.SixWaySmokeLit
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
||||
|
||||
// Generated from UnityEditor.VFX.HDRP.SixWaySmokeLit+BSDFData
|
||||
// PackingRules = Exact
|
||||
struct BSDFData
|
||||
{
|
||||
uint materialFeatures;
|
||||
float absorptionRange;
|
||||
real4 diffuseColor;
|
||||
real3 fresnel0;
|
||||
real ambientOcclusion;
|
||||
float3 normalWS;
|
||||
float4 tangentWS;
|
||||
real3 geomNormalWS;
|
||||
real3 rigRTBk;
|
||||
real3 rigLBtF;
|
||||
real3 bakeDiffuseLighting0;//rigRTBk.x
|
||||
real3 bakeDiffuseLighting1;//rigRTBk.y
|
||||
real3 bakeDiffuseLighting2;// bsdfData.tangentWS.w > 0.0f ? rigRTBk.z : rigLBtF.z
|
||||
real3 backBakeDiffuseLighting0;//rigLBtF.x
|
||||
real3 backBakeDiffuseLighting1;//rigLBtF.y
|
||||
real3 backBakeDiffuseLighting2;// bsdfData.tangentWS.w > 0.0f ? rigLBtF.z : rigRTBk.z
|
||||
|
||||
//-----NBShaders-----
|
||||
real3 emission;
|
||||
real emissionInput;//NegativeTex.a
|
||||
real alpha;//PositiveTex.a
|
||||
|
||||
};
|
||||
|
||||
#define ABSORPTION_EPSILON max(REAL_MIN, 1e-5)
|
||||
|
||||
real3 ComputeDensityScales(real3 absorptionColor)
|
||||
{
|
||||
absorptionColor.rgb = max(ABSORPTION_EPSILON, absorptionColor.rgb);
|
||||
|
||||
// Empirical value used to parametrize absorption from color
|
||||
const real absorptionStrength = 0.2f;
|
||||
return 1.0f + log2(absorptionColor.rgb) / log2(absorptionStrength);
|
||||
}
|
||||
|
||||
real3 GetTransmissionWithAbsorption(real transmission, real4 absorptionColor, real absorptionRange)
|
||||
{
|
||||
#if defined(VFX_SIX_WAY_ABSORPTION)
|
||||
real3 densityScales = ComputeDensityScales(absorptionColor.rgb);
|
||||
|
||||
#ifdef VFX_BLENDMODE_PREMULTIPLY
|
||||
absorptionRange *= (absorptionColor.a > 0) ? absorptionColor.a : 1.0f;
|
||||
#endif
|
||||
|
||||
// real3 outTransmission = GetTransmissionWithAbsorption(transmission, densityScales, absorptionRange);
|
||||
real3 outTransmission = pow(saturate(transmission / absorptionRange), densityScales);
|
||||
outTransmission *= absorptionRange;
|
||||
|
||||
return outTransmission;
|
||||
#else
|
||||
return transmission.xxx * absorptionColor.rgb; // simple multiply
|
||||
#endif
|
||||
}
|
||||
|
||||
void ModifyBakedDiffuseLighting(BSDFData bsdfData, inout float3 bakeDiffuseLighting)
|
||||
{
|
||||
bakeDiffuseLighting = 0;
|
||||
|
||||
// Scale to be energy conserving: Total energy = 4*pi; divided by 6 directions
|
||||
float scale = 4.0f * PI / 6.0f;
|
||||
|
||||
float3 frontBakeDiffuseLighting = bsdfData.tangentWS.w > 0.0f ? bsdfData.bakeDiffuseLighting2 : bsdfData.backBakeDiffuseLighting2;
|
||||
float3 backBakeDiffuseLighting = bsdfData.tangentWS.w > 0.0f ? bsdfData.backBakeDiffuseLighting2 : bsdfData.bakeDiffuseLighting2;
|
||||
|
||||
float3x3 bakeDiffuseLightingMat;
|
||||
bakeDiffuseLightingMat[0] = bsdfData.bakeDiffuseLighting0;
|
||||
bakeDiffuseLightingMat[1] = bsdfData.bakeDiffuseLighting1;
|
||||
bakeDiffuseLightingMat[2] = frontBakeDiffuseLighting;
|
||||
bakeDiffuseLighting += GetTransmissionWithAbsorption(bsdfData.rigRTBk.x, bsdfData.diffuseColor, bsdfData.absorptionRange) * bakeDiffuseLightingMat[0];
|
||||
bakeDiffuseLighting += GetTransmissionWithAbsorption(bsdfData.rigRTBk.y, bsdfData.diffuseColor, bsdfData.absorptionRange) * bakeDiffuseLightingMat[1];
|
||||
bakeDiffuseLighting += GetTransmissionWithAbsorption(bsdfData.rigRTBk.z, bsdfData.diffuseColor, bsdfData.absorptionRange) * bakeDiffuseLightingMat[2];
|
||||
|
||||
bakeDiffuseLightingMat[0] = bsdfData.backBakeDiffuseLighting0;
|
||||
bakeDiffuseLightingMat[1] = bsdfData.backBakeDiffuseLighting1;
|
||||
bakeDiffuseLightingMat[2] = backBakeDiffuseLighting;
|
||||
bakeDiffuseLighting += GetTransmissionWithAbsorption(bsdfData.rigLBtF.x, bsdfData.diffuseColor, bsdfData.absorptionRange) * bakeDiffuseLightingMat[0];
|
||||
bakeDiffuseLighting += GetTransmissionWithAbsorption(bsdfData.rigLBtF.y, bsdfData.diffuseColor, bsdfData.absorptionRange) * bakeDiffuseLightingMat[1];
|
||||
bakeDiffuseLighting += GetTransmissionWithAbsorption(bsdfData.rigLBtF.z, bsdfData.diffuseColor, bsdfData.absorptionRange) * bakeDiffuseLightingMat[2];
|
||||
|
||||
bakeDiffuseLighting *= scale;
|
||||
|
||||
}
|
||||
|
||||
//世界空间到切线空间方向转换
|
||||
float3 TransformToLocalFrame(float3 L, BSDFData bsdfData)
|
||||
{
|
||||
float3 zVec = -bsdfData.normalWS;
|
||||
float3 xVec = bsdfData.tangentWS.xyz;
|
||||
float3 yVec = -cross(zVec, xVec) * bsdfData.tangentWS.w;//原代码没有负值,实际测试需要负值
|
||||
float3x3 tbn = float3x3(xVec, yVec, zVec);
|
||||
return mul(tbn, L);
|
||||
}
|
||||
|
||||
CBSDF EvaluateBSDF(float3 L, BSDFData bsdfData)
|
||||
{
|
||||
CBSDF cbsdf;
|
||||
ZERO_INITIALIZE(CBSDF, cbsdf);
|
||||
|
||||
float3 dir = TransformToLocalFrame(L, bsdfData);
|
||||
float3 weights = dir >= 0 ? bsdfData.rigRTBk.xyz : bsdfData.rigLBtF.xyz;
|
||||
float3 sqrDir = dir*dir;
|
||||
|
||||
cbsdf.diffR = GetTransmissionWithAbsorption(dot(sqrDir, weights), bsdfData.diffuseColor, bsdfData.absorptionRange);
|
||||
|
||||
return cbsdf;
|
||||
}
|
||||
|
||||
|
||||
//这一步最好在面板上做完
|
||||
half GetAbsorptionRange(float absorptionStrenth)
|
||||
{
|
||||
return INV_PI + saturate(absorptionStrenth) * (1 - INV_PI);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//---------NBShaderUtility-----------
|
||||
|
||||
//UseInVerTex
|
||||
void GetSixWayBakeDiffuseLight(real3 normalWS,real3 tangentWS,real3 biTangentWS,
|
||||
inout half3 bakeDiffuseLighting0,inout half3 bakeDiffuseLighting1,inout half3 bakeDiffuseLighting2,
|
||||
inout half3 backBakeDiffuseLighting0,inout half3 backBakeDiffuseLighting1,inout half3 backBakeDiffuseLighting2)
|
||||
{
|
||||
bakeDiffuseLighting0 = SampleSHVertex(tangentWS);
|
||||
bakeDiffuseLighting1 = SampleSHVertex(biTangentWS);
|
||||
bakeDiffuseLighting2 = SampleSHVertex(-normalWS);
|
||||
backBakeDiffuseLighting0 = SampleSHVertex(-tangentWS);
|
||||
backBakeDiffuseLighting1 = SampleSHVertex(-biTangentWS);
|
||||
backBakeDiffuseLighting2 = SampleSHVertex(normalWS);
|
||||
}
|
||||
|
||||
LightingData CreateSixWayLightingData(InputData inputData, half3 emission)
|
||||
{
|
||||
LightingData lightingData;
|
||||
|
||||
lightingData.giColor = inputData.bakedGI;
|
||||
lightingData.emissionColor = emission;
|
||||
lightingData.vertexLightingColor = 0;
|
||||
lightingData.mainLightColor = 0;
|
||||
lightingData.additionalLightsColor = 0;
|
||||
|
||||
return lightingData;
|
||||
}
|
||||
|
||||
void GetSixWayEmission(inout BSDFData bsdfData,Texture2D rampMap,half4 emissionColor,bool isRampMap)
|
||||
{
|
||||
float input = pow(bsdfData.emissionInput,_SixWayInfo.y);
|
||||
half3 emission = emissionColor * emissionColor.a;
|
||||
if (isRampMap)
|
||||
{
|
||||
half4 rampSample = rampMap.Sample(sampler_linear_clamp,half2(input,0.5));
|
||||
emission = emission * rampSample * rampSample.a;
|
||||
}
|
||||
else
|
||||
{
|
||||
emission *= input;
|
||||
}
|
||||
bsdfData.emission = emission;
|
||||
}
|
||||
|
||||
half3 LightingSixWay(Light light,InputData inputData, BSDFData bsdfData)
|
||||
{
|
||||
half3 cbsdf_R = EvaluateBSDF(light.direction,bsdfData).diffR;
|
||||
half3 radiance = light.color * light.distanceAttenuation * light.shadowAttenuation;
|
||||
return PI * cbsdf_R * radiance;
|
||||
}
|
||||
|
||||
|
||||
//光照流程--->原型为UniversalFragmentBlinnPhong
|
||||
half4 UniversalFragmentSixWay(InputData inputData,BSDFData bsdfData)
|
||||
{
|
||||
// #if defined(DEBUG_DISPLAY)
|
||||
// half4 debugColor;
|
||||
//
|
||||
// if (CanDebugOverrideOutputColor(inputData, surfaceData, debugColor))
|
||||
// {
|
||||
// return debugColor;
|
||||
// }
|
||||
// #endif
|
||||
|
||||
#ifdef _LIGHT_LAYERS
|
||||
uint meshRenderingLayers = GetMeshRenderingLayer();
|
||||
#endif
|
||||
half4 shadowMask = CalculateShadowMask(inputData);
|
||||
// AmbientOcclusionFactor aoFactor = CreateAmbientOcclusionFactor(inputData, surfaceData);
|
||||
AmbientOcclusionFactor aoFactor;
|
||||
aoFactor.directAmbientOcclusion = 1;
|
||||
aoFactor.indirectAmbientOcclusion = 1;
|
||||
Light mainLight = GetMainLight(inputData, shadowMask, aoFactor);
|
||||
|
||||
// MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, aoFactor);
|
||||
|
||||
// inputData.bakedGI *= surfaceData.albedo;
|
||||
|
||||
// LightingData lightingData = CreateLightingData(inputData, surfaceData);
|
||||
LightingData lightingData = CreateSixWayLightingData(inputData,bsdfData.emission);
|
||||
|
||||
#ifdef _LIGHT_LAYERS
|
||||
if (IsMatchingLightLayer(mainLight.layerMask, meshRenderingLayers))
|
||||
#endif
|
||||
{
|
||||
lightingData.mainLightColor += LightingSixWay(mainLight, inputData, bsdfData);
|
||||
}
|
||||
|
||||
#if defined(_ADDITIONAL_LIGHTS)
|
||||
uint pixelLightCount = GetAdditionalLightsCount();
|
||||
|
||||
#if USE_FORWARD_PLUS
|
||||
for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
|
||||
{
|
||||
FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
|
||||
|
||||
Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
|
||||
#ifdef _LIGHT_LAYERS
|
||||
if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
|
||||
#endif
|
||||
{
|
||||
lightingData.additionalLightsColor += LightingSixWay(light, inputData, bsdfData);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
LIGHT_LOOP_BEGIN(pixelLightCount)
|
||||
Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
|
||||
#ifdef _LIGHT_LAYERS
|
||||
if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
|
||||
#endif
|
||||
{
|
||||
lightingData.additionalLightsColor += LightingSixWay(light, inputData, bsdfData);
|
||||
}
|
||||
LIGHT_LOOP_END
|
||||
#endif
|
||||
|
||||
#if defined(_ADDITIONAL_LIGHTS_VERTEX)
|
||||
lightingData.vertexLightingColor += inputData.vertexLighting * surfaceData.albedo;
|
||||
#endif
|
||||
|
||||
return CalculateFinalColor(lightingData, bsdfData.alpha);
|
||||
}
|
||||
half3 LightingHalfLambert(half3 lightColor, half3 lightDir, half3 normal)
|
||||
{
|
||||
half NdotL = saturate(dot(normal, lightDir));
|
||||
NdotL = NdotL*0.5 + 0.5;
|
||||
return lightColor * NdotL;
|
||||
}
|
||||
half3 CalculateHalfLambertBlinnPhong(Light light, InputData inputData, SurfaceData surfaceData)
|
||||
{
|
||||
half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
|
||||
half3 lightDiffuseColor = LightingHalfLambert(attenuatedLightColor, light.direction, inputData.normalWS);
|
||||
|
||||
half3 lightSpecularColor = half3(0,0,0);
|
||||
#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
|
||||
half smoothness = exp2(10 * surfaceData.smoothness + 1);
|
||||
|
||||
lightSpecularColor += LightingSpecular(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, half4(surfaceData.specular, 1), smoothness);
|
||||
#endif
|
||||
|
||||
#if _ALPHAPREMULTIPLY_ON
|
||||
return lightDiffuseColor * surfaceData.albedo * surfaceData.alpha + lightSpecularColor;
|
||||
#else
|
||||
return lightDiffuseColor * surfaceData.albedo + lightSpecularColor;
|
||||
#endif
|
||||
}
|
||||
half4 UniversalFragmentHalfLambert(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha, half3 normalTS)
|
||||
{
|
||||
|
||||
SurfaceData surfaceData;
|
||||
|
||||
surfaceData.albedo = diffuse;
|
||||
surfaceData.alpha = alpha;
|
||||
surfaceData.emission = emission;
|
||||
surfaceData.metallic = 0;
|
||||
surfaceData.occlusion = 1;
|
||||
surfaceData.smoothness = smoothness;
|
||||
surfaceData.specular = specularGloss.rgb;
|
||||
surfaceData.clearCoatMask = 0;
|
||||
surfaceData.clearCoatSmoothness = 1;
|
||||
surfaceData.normalTS = normalTS;
|
||||
|
||||
|
||||
// #if defined(DEBUG_DISPLAY)
|
||||
// half4 debugColor;
|
||||
//
|
||||
// if (CanDebugOverrideOutputColor(inputData, surfaceData, debugColor))
|
||||
// {
|
||||
// return debugColor;
|
||||
// }
|
||||
// #endif
|
||||
|
||||
#ifdef _LIGHT_LAYERS
|
||||
uint meshRenderingLayers = GetMeshRenderingLayer();
|
||||
#endif
|
||||
|
||||
half4 shadowMask = CalculateShadowMask(inputData);
|
||||
AmbientOcclusionFactor aoFactor = CreateAmbientOcclusionFactor(inputData, surfaceData);
|
||||
Light mainLight = GetMainLight(inputData, shadowMask, aoFactor);
|
||||
|
||||
MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, aoFactor);
|
||||
|
||||
inputData.bakedGI *= surfaceData.albedo;
|
||||
|
||||
LightingData lightingData = CreateLightingData(inputData, surfaceData);
|
||||
#ifdef _LIGHT_LAYERS
|
||||
if (IsMatchingLightLayer(mainLight.layerMask, meshRenderingLayers))
|
||||
#endif
|
||||
{
|
||||
lightingData.mainLightColor += CalculateHalfLambertBlinnPhong(mainLight, inputData, surfaceData);
|
||||
}
|
||||
|
||||
#if defined(_ADDITIONAL_LIGHTS)
|
||||
uint pixelLightCount = GetAdditionalLightsCount();
|
||||
|
||||
#if USE_FORWARD_PLUS
|
||||
for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
|
||||
{
|
||||
FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
|
||||
|
||||
Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
|
||||
#ifdef _LIGHT_LAYERS
|
||||
if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
|
||||
#endif
|
||||
{
|
||||
lightingData.additionalLightsColor += CalculateBlinnPhong(light, inputData, surfaceData);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
LIGHT_LOOP_BEGIN(pixelLightCount)
|
||||
Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
|
||||
#ifdef _LIGHT_LAYERS
|
||||
if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
|
||||
#endif
|
||||
{
|
||||
lightingData.additionalLightsColor += CalculateHalfLambertBlinnPhong(light, inputData, surfaceData);
|
||||
}
|
||||
LIGHT_LOOP_END
|
||||
#endif
|
||||
|
||||
#if defined(_ADDITIONAL_LIGHTS_VERTEX)
|
||||
lightingData.vertexLightingColor += inputData.vertexLighting * surfaceData.albedo;
|
||||
#endif
|
||||
|
||||
return CalculateFinalColor(lightingData, surfaceData.alpha);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70e94bdedafe40bfb7f8e77cdc4bef0f
|
||||
timeCreated: 1751704445
|
||||
585
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/TyflowVAT.hlsl
Normal file
585
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/TyflowVAT.hlsl
Normal file
@@ -0,0 +1,585 @@
|
||||
#ifndef TYFLOW_VAT_INCLUDED
|
||||
#define TYFLOW_VAT_INCLUDED
|
||||
|
||||
#define TYFLOW_VAT_SKIN_MAX_BONES 7
|
||||
|
||||
#if defined(_VAT) && defined(_VAT_TYFLOW) && \
|
||||
!defined(_TYFLOW_VAT_ABSOLUTE) && \
|
||||
!defined(_TYFLOW_VAT_RELATIVE) && \
|
||||
!defined(_TYFLOW_VAT_SKIN_R) && \
|
||||
!defined(_TYFLOW_VAT_SKIN_PR) && \
|
||||
!defined(_TYFLOW_VAT_SKIN_PRSAVE) && \
|
||||
!defined(_TYFLOW_VAT_SKIN_PRSXYZ)
|
||||
#define _TYFLOW_VAT_ABSOLUTE
|
||||
#endif
|
||||
|
||||
#if defined(_TYFLOW_VAT_SKIN_R) || \
|
||||
defined(_TYFLOW_VAT_SKIN_PR) || \
|
||||
defined(_TYFLOW_VAT_SKIN_PRSAVE) || \
|
||||
defined(_TYFLOW_VAT_SKIN_PRSXYZ)
|
||||
#define TYFLOW_VAT_SKIN_MODE
|
||||
#endif
|
||||
|
||||
struct TyflowVatMatrix3
|
||||
{
|
||||
float3 row0;
|
||||
float3 row1;
|
||||
float3 row2;
|
||||
float3 row3;
|
||||
};
|
||||
|
||||
struct TyflowVatTMParts
|
||||
{
|
||||
float3 pos;
|
||||
float4 rot;
|
||||
float3 scale;
|
||||
};
|
||||
|
||||
inline int TyflowVatUnpackIntRGBA(int4 bytes)
|
||||
{
|
||||
return ((bytes.x << 24) + (bytes.y << 16) + (bytes.z << 8) + (bytes.w << 0));
|
||||
}
|
||||
|
||||
inline float TyflowVatUnpackFloatRGBA(int4 bytes)
|
||||
{
|
||||
int sign = (bytes.r & 128) > 0 ? -1 : 1;
|
||||
|
||||
int expR = (bytes.r & 127) << 1;
|
||||
int expG = bytes.g >> 7;
|
||||
int exponent = expR + expG;
|
||||
|
||||
int signifG = (bytes.g & 127) << 16;
|
||||
int signifB = bytes.b << 8;
|
||||
|
||||
float significand = (signifG + signifB + bytes.a) / pow(2, 23);
|
||||
significand += 1;
|
||||
|
||||
return sign * significand * pow(2, exponent - 127);
|
||||
}
|
||||
|
||||
inline half TyflowVatUnpackHalfRGBA(int2 bytes)
|
||||
{
|
||||
uint value = (bytes.x << 8) | bytes.y;
|
||||
|
||||
uint sign = (value & 0x8000) > 0;
|
||||
int exponent = (value & 0x7C00) >> 10;
|
||||
uint mantissa = (value & 0x03FF);
|
||||
|
||||
if ((value & 0x7FFF) == 0)
|
||||
{
|
||||
return sign ? -0 : 0;
|
||||
}
|
||||
|
||||
if (exponent == 0x001F)
|
||||
{
|
||||
if (mantissa == 0)
|
||||
{
|
||||
return sign ? -1e+28 : 1e+28;
|
||||
}
|
||||
|
||||
return 1e+28;
|
||||
}
|
||||
|
||||
if (exponent > 0)
|
||||
{
|
||||
float result = pow(2.0, exponent - 15) * (1 + mantissa * (1 / 1024.0f));
|
||||
return sign ? -result : result;
|
||||
}
|
||||
|
||||
float subnormal = pow(2.0, -24) * mantissa;
|
||||
return sign ? -subnormal : subnormal;
|
||||
}
|
||||
|
||||
float4 TyflowVatSampleTexel(uint x, uint y)
|
||||
{
|
||||
float2 uv = float2(
|
||||
((float)x + 0.5f) * _VATTex_TexelSize.x,
|
||||
1.0f - (((float)y + 0.5f) * _VATTex_TexelSize.y));
|
||||
|
||||
return SAMPLE_TEXTURE2D_LOD(_VATTex, sampler_point_clamp, uv, 0);
|
||||
}
|
||||
|
||||
float4 TyflowVatSampleEncodedTexel(uint x, uint y)
|
||||
{
|
||||
float4 sample = TyflowVatSampleTexel(x, y);
|
||||
|
||||
if (_LinearToGamma > 0.5f)
|
||||
{
|
||||
sample.r = LinearToGammaSpaceExact(sample.r);
|
||||
sample.g = LinearToGammaSpaceExact(sample.g);
|
||||
sample.b = LinearToGammaSpaceExact(sample.b);
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
int TyflowVatTex2DInt(int arrInx)
|
||||
{
|
||||
uint width = (uint)_VATTex_TexelSize.z;
|
||||
uint x = (uint)arrInx % width;
|
||||
uint y = (uint)arrInx / width;
|
||||
|
||||
float4 bytes = TyflowVatSampleEncodedTexel(x, y);
|
||||
int4 byteInts = int4(round(bytes.x * 255), round(bytes.y * 255), round(bytes.z * 255), round(bytes.w * 255));
|
||||
return TyflowVatUnpackIntRGBA(byteInts.wzyx);
|
||||
}
|
||||
|
||||
float TyflowVatTex2DFloat(int arrInx)
|
||||
{
|
||||
uint width = (uint)_VATTex_TexelSize.z;
|
||||
uint x = (uint)arrInx % width;
|
||||
uint y = (uint)arrInx / width;
|
||||
|
||||
float4 bytes = TyflowVatSampleEncodedTexel(x, y);
|
||||
int4 byteInts = int4(round(bytes.x * 255), round(bytes.y * 255), round(bytes.z * 255), round(bytes.w * 255));
|
||||
return TyflowVatUnpackFloatRGBA(byteInts.wzyx);
|
||||
}
|
||||
|
||||
half TyflowVatTex2DHalf(float arrInxF)
|
||||
{
|
||||
uint arrInx = (uint)floor(arrInxF + 0.1f);
|
||||
uint width = (uint)_VATTex_TexelSize.z;
|
||||
uint x = arrInx % width;
|
||||
uint y = arrInx / width;
|
||||
|
||||
float4 bytes = TyflowVatSampleEncodedTexel(x, y);
|
||||
int4 byteInts = int4(round(bytes.x * 255), round(bytes.y * 255), round(bytes.z * 255), round(bytes.w * 255));
|
||||
|
||||
if (abs(arrInxF - round(arrInxF)) > 0.25f)
|
||||
{
|
||||
return TyflowVatUnpackHalfRGBA(byteInts.wz);
|
||||
}
|
||||
|
||||
return TyflowVatUnpackHalfRGBA(byteInts.yx);
|
||||
}
|
||||
|
||||
half2 TyflowVatTex2DHalfs2(float arrInxF)
|
||||
{
|
||||
uint arrInx = (uint)floor(arrInxF + 0.1f);
|
||||
uint width = (uint)_VATTex_TexelSize.z;
|
||||
uint x = arrInx % width;
|
||||
uint y = arrInx / width;
|
||||
|
||||
float4 bytes = TyflowVatSampleEncodedTexel(x, y);
|
||||
int4 byteInts = int4(round(bytes.x * 255), round(bytes.y * 255), round(bytes.z * 255), round(bytes.w * 255));
|
||||
|
||||
return half2(TyflowVatUnpackHalfRGBA(byteInts.yx), TyflowVatUnpackHalfRGBA(byteInts.wz));
|
||||
}
|
||||
|
||||
float3 TyflowVatMultiplyPosition(TyflowVatMatrix3 matrixValue, float3 pos)
|
||||
{
|
||||
return float3(
|
||||
pos.x * matrixValue.row0[0] + pos.y * matrixValue.row1[0] + pos.z * matrixValue.row2[0] + matrixValue.row3[0],
|
||||
pos.x * matrixValue.row0[1] + pos.y * matrixValue.row1[1] + pos.z * matrixValue.row2[1] + matrixValue.row3[1],
|
||||
pos.x * matrixValue.row0[2] + pos.y * matrixValue.row1[2] + pos.z * matrixValue.row2[2] + matrixValue.row3[2]);
|
||||
}
|
||||
|
||||
TyflowVatMatrix3 TyflowVatQuaternionToTM(float4 quat)
|
||||
{
|
||||
TyflowVatMatrix3 matrixValue;
|
||||
|
||||
float x = quat.x;
|
||||
float y = quat.y;
|
||||
float z = quat.z;
|
||||
float w = quat.w;
|
||||
|
||||
matrixValue.row0[0] = 1 - 2 * (y * y + z * z);
|
||||
matrixValue.row0[1] = 2 * (x * y + z * w);
|
||||
matrixValue.row0[2] = 2 * (x * z - y * w);
|
||||
|
||||
matrixValue.row1[0] = 2 * (x * y - z * w);
|
||||
matrixValue.row1[1] = 1 - 2 * (x * x + z * z);
|
||||
matrixValue.row1[2] = 2 * (y * z + x * w);
|
||||
|
||||
matrixValue.row2[0] = 2 * (x * z + y * w);
|
||||
matrixValue.row2[1] = 2 * (y * z - x * w);
|
||||
matrixValue.row2[2] = 1 - 2 * (x * x + y * y);
|
||||
|
||||
matrixValue.row3 = float3(0, 0, 0);
|
||||
|
||||
return matrixValue;
|
||||
}
|
||||
|
||||
TyflowVatMatrix3 TyflowVatScaleTM(TyflowVatMatrix3 matrixValue, float3 scale)
|
||||
{
|
||||
matrixValue.row0 *= scale.x;
|
||||
matrixValue.row1 *= scale.y;
|
||||
matrixValue.row2 *= scale.z;
|
||||
return matrixValue;
|
||||
}
|
||||
|
||||
TyflowVatMatrix3 TyflowVatTranslateTM(TyflowVatMatrix3 matrixValue, float3 translation)
|
||||
{
|
||||
matrixValue.row3 = translation;
|
||||
return matrixValue;
|
||||
}
|
||||
|
||||
float4 TyflowVatQlerp(float4 a, float4 b, float blend)
|
||||
{
|
||||
float s1 = 1.0f - blend;
|
||||
float s2 = dot(a, b) < 0.0f ? -blend : blend;
|
||||
|
||||
return normalize(float4(
|
||||
s1 * a.x + s2 * b.x,
|
||||
s1 * a.y + s2 * b.y,
|
||||
s1 * a.z + s2 * b.z,
|
||||
s1 * a.w + s2 * b.w));
|
||||
}
|
||||
|
||||
int TyflowVatGetMetaDataSize()
|
||||
{
|
||||
#if defined(_TYFLOW_VAT_SKIN_PRSXYZ)
|
||||
return 12;
|
||||
#else
|
||||
if (_DeformingSkin > 0.5f)
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
return 3;
|
||||
#endif
|
||||
}
|
||||
|
||||
float3 TyflowVatGetTMPos(float startIndex)
|
||||
{
|
||||
float3 result;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
result[i] = TyflowVatTex2DFloat(startIndex + i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float4 TyflowVatGetTMRot(float startIndex)
|
||||
{
|
||||
half2 rotHalfs1 = TyflowVatTex2DHalfs2(startIndex);
|
||||
half2 rotHalfs2 = TyflowVatTex2DHalfs2(startIndex + 1);
|
||||
|
||||
return float4(-rotHalfs1.x, -rotHalfs1.y, -rotHalfs2.x, rotHalfs2.y);
|
||||
}
|
||||
|
||||
float3 TyflowVatGetTMScaleXYZ(float startIndex)
|
||||
{
|
||||
half2 scaleHalfs1 = TyflowVatTex2DHalfs2(startIndex);
|
||||
half2 scaleHalfs2 = TyflowVatTex2DHalfs2(startIndex + 1);
|
||||
|
||||
return float3(scaleHalfs1.x, scaleHalfs1.y, scaleHalfs2.x);
|
||||
}
|
||||
|
||||
float3 TyflowVatGetTMScaleAve(float startIndex)
|
||||
{
|
||||
half scaleHalf = TyflowVatTex2DHalf(startIndex);
|
||||
return float3(scaleHalf, scaleHalf, scaleHalf);
|
||||
}
|
||||
|
||||
TyflowVatMatrix3 TyflowVatTMFromParts(TyflowVatTMParts parts)
|
||||
{
|
||||
TyflowVatMatrix3 matrixValue = TyflowVatQuaternionToTM(parts.rot);
|
||||
matrixValue = TyflowVatScaleTM(matrixValue, parts.scale);
|
||||
matrixValue = TyflowVatTranslateTM(matrixValue, parts.pos);
|
||||
return matrixValue;
|
||||
}
|
||||
|
||||
TyflowVatMatrix3 TyflowVatGetVertexInvTM(int tmInx)
|
||||
{
|
||||
TyflowVatMatrix3 matrixValue;
|
||||
int pixelsPerTM = TyflowVatGetMetaDataSize();
|
||||
|
||||
float tmRowInx = 2 + (pixelsPerTM * tmInx);
|
||||
matrixValue.row0 = TyflowVatGetTMPos(tmRowInx);
|
||||
tmRowInx += 3;
|
||||
matrixValue.row1 = TyflowVatGetTMPos(tmRowInx);
|
||||
tmRowInx += 3;
|
||||
matrixValue.row2 = TyflowVatGetTMPos(tmRowInx);
|
||||
tmRowInx += 3;
|
||||
matrixValue.row3 = TyflowVatGetTMPos(tmRowInx);
|
||||
|
||||
return matrixValue;
|
||||
}
|
||||
|
||||
TyflowVatTMParts TyflowVatGetVertexTMPartsAtFrame(int tmInx, int frame, int numTMs)
|
||||
{
|
||||
float4 rot = float4(0, 0, 0, 1);
|
||||
float3 pos = float3(0, 0, 0);
|
||||
float3 scale = float3(1, 1, 1);
|
||||
|
||||
float pixelsPerTM = 0;
|
||||
#if defined(_TYFLOW_VAT_SKIN_R)
|
||||
pixelsPerTM = 2;
|
||||
#elif defined(_TYFLOW_VAT_SKIN_PR)
|
||||
pixelsPerTM = 5;
|
||||
#elif defined(_TYFLOW_VAT_SKIN_PRSXYZ)
|
||||
pixelsPerTM = 7;
|
||||
#elif defined(_TYFLOW_VAT_SKIN_PRSAVE)
|
||||
pixelsPerTM = 6;
|
||||
#endif
|
||||
|
||||
float frameTMInx = (2 + numTMs * TyflowVatGetMetaDataSize()) + (frame * numTMs * pixelsPerTM) + (pixelsPerTM * tmInx);
|
||||
|
||||
#if defined(_TYFLOW_VAT_SKIN_R)
|
||||
pos = TyflowVatGetTMPos(2 + tmInx * TyflowVatGetMetaDataSize());
|
||||
rot = TyflowVatGetTMRot(frameTMInx);
|
||||
#elif defined(_TYFLOW_VAT_SKIN_PR)
|
||||
pos = TyflowVatGetTMPos(frameTMInx);
|
||||
frameTMInx += 3;
|
||||
rot = TyflowVatGetTMRot(frameTMInx);
|
||||
#elif defined(_TYFLOW_VAT_SKIN_PRSXYZ)
|
||||
pos = TyflowVatGetTMPos(frameTMInx);
|
||||
frameTMInx += 3;
|
||||
rot = TyflowVatGetTMRot(frameTMInx);
|
||||
frameTMInx += 2;
|
||||
scale = TyflowVatGetTMScaleXYZ(frameTMInx);
|
||||
#elif defined(_TYFLOW_VAT_SKIN_PRSAVE)
|
||||
pos = TyflowVatGetTMPos(frameTMInx);
|
||||
frameTMInx += 3;
|
||||
rot = TyflowVatGetTMRot(frameTMInx);
|
||||
frameTMInx += 2;
|
||||
scale = TyflowVatGetTMScaleAve(frameTMInx);
|
||||
#endif
|
||||
|
||||
TyflowVatTMParts parts;
|
||||
parts.pos = pos;
|
||||
parts.rot = rot;
|
||||
parts.scale = scale;
|
||||
return parts;
|
||||
}
|
||||
|
||||
float4 TyflowVatGetVertexValueAtFrame(uint vertexIndex, int vertexCount, int frame, int frameOffset)
|
||||
{
|
||||
float4 result = float4(0, 0, 0, 0);
|
||||
|
||||
vertexIndex += (vertexCount * frame) + (vertexCount * frameOffset);
|
||||
|
||||
if (_RGBAEncoded > 0.5f)
|
||||
{
|
||||
if (_RGBAHalf > 0.5f)
|
||||
{
|
||||
vertexIndex *= 3;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
float arrInxF = (vertexIndex + i) * 0.5f;
|
||||
result[i] = TyflowVatTex2DHalf(arrInxF);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
vertexIndex *= 3;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
result[i] = TyflowVatTex2DFloat(vertexIndex + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint width = (uint)_VATTex_TexelSize.z;
|
||||
uint x = vertexIndex % width;
|
||||
uint y = vertexIndex / width;
|
||||
result = TyflowVatSampleTexel(x, y);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float3 TyflowVatGetLocalVertexPosFromTM(float3 pos, TyflowVatMatrix3 invTM)
|
||||
{
|
||||
float3 localPos = ((pos / _ImportScale) * float3(-1, 1, 1));
|
||||
return TyflowVatMultiplyPosition(invTM, localPos);
|
||||
}
|
||||
|
||||
float3 TyflowVatGetLocalVertexPosFromPos(float3 pos, float boneInx)
|
||||
{
|
||||
int tmInx = 2 + (int)round(boneInx) * TyflowVatGetMetaDataSize();
|
||||
float3 tmPos = float3(0, 0, 0);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
tmPos[i] = TyflowVatTex2DFloat(tmInx + i);
|
||||
}
|
||||
|
||||
return ((pos / _ImportScale) * float3(-1, 1, 1)) - tmPos;
|
||||
}
|
||||
|
||||
float2 TyflowVatGetSkinTexcoord(AttributesParticle input, int index)
|
||||
{
|
||||
if (index == 0) return input.Custom1.xy;
|
||||
if (index == 1) return input.Custom2.xy;
|
||||
#if !defined(_FLIPBOOKBLENDING_ON)
|
||||
if (index == 2) return input.vatTexcoord4;
|
||||
#endif
|
||||
if (index == 3) return input.vatTexcoord5;
|
||||
if (index == 4) return input.vatTexcoord6;
|
||||
if (index == 5) return input.vatTexcoord7;
|
||||
if (index == 6) return input.vatTexcoord8;
|
||||
return float2(0, 0);
|
||||
}
|
||||
|
||||
void ApplyTyflowVAT(AttributesParticle input, inout float4 positionOS, inout float3 normalOS)
|
||||
{
|
||||
#if defined(SHADOWS_DEPTH)
|
||||
if (_AffectsShadows < 0.5f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
float frameBase = _Frame;
|
||||
float frameCustomData = GetCustomData(NB_CUSTOM_DATA_FLAG_2, FLAGBIT_POS_2_CUSTOMDATA_VAT_FRAME, -1.0f, input.Custom1, input.Custom2);
|
||||
if (frameCustomData >= 0.0f)
|
||||
{
|
||||
frameBase = saturate(frameCustomData) * max(_Frames - 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
float frame = abs(frameBase + ((_Autoplay > 0.5f) ? (time * 30.0f * _AutoplaySpeed) : 0.0f));
|
||||
frame = (_Loop > 0.5f) ? fmod(frame, _Frames) : min(frame, _Frames - 1.0f);
|
||||
|
||||
if ((_Loop > 0.5f) && (_InterpolateLoop < 0.5f) && (frame >= _Frames - 1.0f))
|
||||
{
|
||||
frame = _Frames - 1.0f;
|
||||
}
|
||||
|
||||
uint frame0 = (uint)floor(frame);
|
||||
uint frame1 = (uint)ceil(frame) % (uint)_Frames;
|
||||
float frameInterp = frame - frame0;
|
||||
|
||||
#if defined(TYFLOW_VAT_SKIN_MODE)
|
||||
if (CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
int numTMs = TyflowVatTex2DInt(1);
|
||||
float3 combinedPos = float3(0, 0, 0);
|
||||
float3 combinedNormal = float3(0, 0, 0);
|
||||
int loopCount = (_DeformingSkin > 0.5f) ? (int)_SkinBoneCount : 1;
|
||||
|
||||
[unroll]
|
||||
for (int i = 0; i < TYFLOW_VAT_SKIN_MAX_BONES; i++)
|
||||
{
|
||||
if (i >= loopCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
float weight = 1.0f;
|
||||
float tmInx = round(input.Custom1.y);
|
||||
|
||||
if (_DeformingSkin > 0.5f)
|
||||
{
|
||||
float2 texcoord = TyflowVatGetSkinTexcoord(input, i);
|
||||
tmInx = round(texcoord.x);
|
||||
weight = texcoord.y;
|
||||
}
|
||||
|
||||
TyflowVatTMParts tmParts0 = TyflowVatGetVertexTMPartsAtFrame((int)tmInx, frame0, numTMs);
|
||||
|
||||
if (_FrameInterpolation > 0.5f)
|
||||
{
|
||||
TyflowVatTMParts tmParts1 = TyflowVatGetVertexTMPartsAtFrame((int)tmInx, frame1, numTMs);
|
||||
tmParts0.pos = lerp(tmParts0.pos, tmParts1.pos, frameInterp);
|
||||
tmParts0.rot = TyflowVatQlerp(tmParts0.rot, tmParts1.rot, frameInterp);
|
||||
tmParts0.scale = lerp(tmParts0.scale, tmParts1.scale, frameInterp);
|
||||
}
|
||||
|
||||
TyflowVatMatrix3 tm = TyflowVatTMFromParts(tmParts0);
|
||||
TyflowVatMatrix3 invStartTM;
|
||||
|
||||
#if defined(_TYFLOW_VAT_SKIN_PRSXYZ)
|
||||
bool useInvStartTM = true;
|
||||
#else
|
||||
bool useInvStartTM = _DeformingSkin > 0.5f;
|
||||
#endif
|
||||
|
||||
if (useInvStartTM)
|
||||
{
|
||||
invStartTM = TyflowVatGetVertexInvTM((int)tmInx);
|
||||
}
|
||||
else
|
||||
{
|
||||
invStartTM.row0 = float3(0, 0, 0);
|
||||
invStartTM.row1 = float3(0, 0, 0);
|
||||
invStartTM.row2 = float3(0, 0, 0);
|
||||
invStartTM.row3 = float3(0, 0, 0);
|
||||
}
|
||||
|
||||
float3 localPos;
|
||||
if (useInvStartTM)
|
||||
{
|
||||
localPos = TyflowVatGetLocalVertexPosFromTM(positionOS.xyz, invStartTM);
|
||||
}
|
||||
else
|
||||
{
|
||||
localPos = TyflowVatGetLocalVertexPosFromPos(positionOS.xyz, tmInx);
|
||||
}
|
||||
|
||||
float3 pos = TyflowVatMultiplyPosition(tm, localPos) * float3(-1, 1, 1);
|
||||
combinedPos += (pos * _ImportScale) * weight;
|
||||
|
||||
#if !defined(SHADOWS_DEPTH)
|
||||
{
|
||||
float3 animatedNormal = normalOS;
|
||||
tm.row3 = float3(0, 0, 0);
|
||||
|
||||
if (useInvStartTM)
|
||||
{
|
||||
invStartTM.row3 = float3(0, 0, 0);
|
||||
float3 localNormal = TyflowVatGetLocalVertexPosFromTM(animatedNormal, invStartTM);
|
||||
animatedNormal = normalize(TyflowVatMultiplyPosition(tm, localNormal)) * float3(-1, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
tm = TyflowVatTranslateTM(tm, float3(0, 0, 0));
|
||||
animatedNormal = normalize(TyflowVatMultiplyPosition(tm, animatedNormal * float3(-1, 1, 1))) * float3(-1, 1, 1);
|
||||
}
|
||||
|
||||
combinedNormal += animatedNormal * weight;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
positionOS.xyz = combinedPos;
|
||||
normalOS = combinedNormal;
|
||||
}
|
||||
#elif defined(_TYFLOW_VAT_ABSOLUTE) || defined(_TYFLOW_VAT_RELATIVE)
|
||||
{
|
||||
float2 tyflowVatIndexData = CheckLocalFlags1(FLAG_BIT_PARTICLE_1_IS_PARTICLE_SYSTEM)
|
||||
? input.texcoords.zw
|
||||
: input.Custom1.xy;
|
||||
uint vertexIndex = (uint)round(tyflowVatIndexData.x);
|
||||
uint vertexCount = (uint)round(tyflowVatIndexData.y);
|
||||
|
||||
float4 vertexOffset0 = TyflowVatGetVertexValueAtFrame(vertexIndex, vertexCount, frame0, 0);
|
||||
float4 vertexOffset = vertexOffset0;
|
||||
|
||||
if (_FrameInterpolation > 0.5f)
|
||||
{
|
||||
float4 vertexOffset1 = TyflowVatGetVertexValueAtFrame(vertexIndex, vertexCount, frame1, 0);
|
||||
vertexOffset = lerp(vertexOffset0, vertexOffset1, frameInterp);
|
||||
}
|
||||
|
||||
#if defined(_TYFLOW_VAT_RELATIVE)
|
||||
positionOS.xyz += (vertexOffset * float4(-1, 1, 1, 1) * _ImportScale).xyz;
|
||||
#else
|
||||
positionOS.xyz = (vertexOffset * float4(-1, 1, 1, 1) * _ImportScale).xyz;
|
||||
#endif
|
||||
|
||||
#if !defined(SHADOWS_DEPTH)
|
||||
if (_VATIncludesNormals > 0.5f)
|
||||
{
|
||||
float4 normal0 = TyflowVatGetVertexValueAtFrame(vertexIndex, vertexCount, frame0, (int)_Frames);
|
||||
float4 animatedNormal = normal0;
|
||||
if (_FrameInterpolation > 0.5f)
|
||||
{
|
||||
float4 normal1 = TyflowVatGetVertexValueAtFrame(vertexIndex, vertexCount, frame1, (int)_Frames);
|
||||
animatedNormal = lerp(normal0, normal1, frameInterp) * float4(-1, 1, 1, 1);
|
||||
}
|
||||
normalOS = normalize(animatedNormal.xyz);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5bde8a4dd9946d4ab49c8b1f3bc6d12
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/VAT.hlsl
Normal file
23
Packages/NB_FX/XuanXuanRenderUtility/Shader/HLSL/VAT.hlsl
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef PARTICLE_VAT_INCLUDED
|
||||
#define PARTICLE_VAT_INCLUDED
|
||||
|
||||
#include "HoudiniVAT.hlsl"
|
||||
#include "TyflowVAT.hlsl"
|
||||
|
||||
void ApplyVAT(AttributesParticle input, inout float4 positionOS, inout float3 normalOS)
|
||||
{
|
||||
#if defined(_VAT)
|
||||
if (_VAT_Toggle < 0.5f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(_VAT_HOUDINI)
|
||||
ApplyHoudiniVAT(input, positionOS, normalOS);
|
||||
#elif defined(_VAT_TYFLOW)
|
||||
ApplyTyflowVAT(input, positionOS, normalOS);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0126cd1b294e894f9ac10e46b7776d6
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,391 @@
|
||||
#ifndef XUANXUAN_UTILITY
|
||||
#define XUANXUAN_UTILITY
|
||||
|
||||
|
||||
|
||||
//引入自UnityCG.cginc
|
||||
#define UNITY_PI 3.14159265359f
|
||||
#define UNITY_TWO_PI 6.28318530718f
|
||||
#define UNITY_FOUR_PI 12.56637061436f
|
||||
#define UNITY_INV_PI 0.31830988618f
|
||||
#define UNITY_INV_TWO_PI 0.15915494309f
|
||||
#define UNITY_INV_FOUR_PI 0.07957747155f
|
||||
#define UNITY_HALF_PI 1.57079632679f
|
||||
#define UNITY_INV_HALF_PI 0.636619772367f
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
|
||||
|
||||
inline half NB_Remap(half x, half inMin, half inMax, half outMin, half outMax)
|
||||
{
|
||||
// x= clamp(x,inMin,inMax);
|
||||
return saturate((x - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin;
|
||||
}
|
||||
|
||||
inline half NB_Remap01(half x, half inMin, half inMax)
|
||||
{
|
||||
// x= clamp(x,inMin,inMax);
|
||||
return saturate((x - inMin) / (inMax - inMin));
|
||||
}
|
||||
|
||||
inline half NB_RemapNoClamp(half x, half inMin, half inMax, half outMin, half outMax)
|
||||
{
|
||||
// x= clamp(x,inMin,inMax);
|
||||
return ((x - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin;
|
||||
}
|
||||
|
||||
//原生SmoothStep开销很大:https://zhuanlan.zhihu.com/p/34629262
|
||||
//在很多时候可以用简单线性映射代替。
|
||||
half SimpleSmoothstep(half min,half max,half interp)
|
||||
{
|
||||
return saturate((interp - min)/(max-min));
|
||||
}
|
||||
|
||||
half4 TryLinearize(half4 color)
|
||||
{
|
||||
#if defined(UNITY_COLORSPACE_GAMMA)
|
||||
return pow(color, 2.2f);
|
||||
#else
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
half4 TryLinearizeWithoutAlpha(half4 color)
|
||||
{
|
||||
#if defined(UNITY_COLORSPACE_GAMMA)
|
||||
return half4(pow(color.rgb, 2.2f), color.a);
|
||||
#else
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
half3 TryLinearize(half3 color)
|
||||
{
|
||||
#if defined(UNITY_COLORSPACE_GAMMA)
|
||||
return pow(color, 2.2f);
|
||||
#else
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
half2 TryLinearize(half2 color)
|
||||
{
|
||||
#if defined(UNITY_COLORSPACE_GAMMA)
|
||||
return pow(color, 2.2f);
|
||||
#else
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
half TryLinearize(half color)
|
||||
{
|
||||
#if defined(UNITY_COLORSPACE_GAMMA)
|
||||
return pow(color, 2.2f);
|
||||
#else
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
half4 tex2D_TryLinearizeWithoutAlpha(sampler2D tex, float2 uv)
|
||||
{
|
||||
#if defined(UNITY_COLORSPACE_GAMMA)
|
||||
return TryLinearizeWithoutAlpha(tex2D(tex, uv));
|
||||
#else
|
||||
return tex2D(tex, uv);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline float LinearToGammaSpaceExact (float value)
|
||||
{
|
||||
if (value <= 0.0F)
|
||||
return 0.0F;
|
||||
else if (value <= 0.0031308F)
|
||||
return 12.92F * value;
|
||||
else if (value < 1.0F)
|
||||
return 1.055F * pow(value, 0.4166667F) - 0.055F;
|
||||
else
|
||||
return pow(value, 0.45454545F);
|
||||
}
|
||||
|
||||
// real FastSRGBToLinear(real c)
|
||||
// {
|
||||
// return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878);
|
||||
// }
|
||||
|
||||
// float2 Rotate_Radians_float(float2 UV, float2 Center, float Rotation)
|
||||
// {
|
||||
// if(Rotation == 0)
|
||||
// {
|
||||
// return UV;
|
||||
// }
|
||||
//
|
||||
// // Rotation = Rotation / 180 * 3.14; //从角度转为弧度。
|
||||
// Rotation *= 0.01745329222; //从角度转为弧度。
|
||||
// UV -= Center;
|
||||
// float s = sin(Rotation);
|
||||
// float c = cos(Rotation);
|
||||
// float2x2 rMatrix = float2x2(c, -s, s, c);
|
||||
// rMatrix *= 0.5;
|
||||
// rMatrix += 0.5;
|
||||
// rMatrix = rMatrix * 2 - 1;
|
||||
// UV.xy = mul(UV.xy, rMatrix);
|
||||
// UV += Center;
|
||||
// return UV;
|
||||
// }
|
||||
|
||||
float2 Rotate_Radians_float(float2 UV, float2 Center, float Rotation)
|
||||
{
|
||||
if(Rotation == 0)
|
||||
{
|
||||
return UV;
|
||||
}
|
||||
|
||||
// Rotation = Rotation / 180 * 3.14; //从角度转为弧度。
|
||||
Rotation *= 0.01745329222; //从角度转为弧度。
|
||||
float s, c; // 如果 angle 是 uniform,每帧或每物件算一次更省
|
||||
sincos(Rotation, s, c); // HLSL 内置,一次算出 sin/cos
|
||||
|
||||
float2 d = UV - Center;
|
||||
// 旋转 (x', y') = ( c*x - s*y, s*x + c*y )
|
||||
float2 r = float2(dot(d, float2( c, -s)),
|
||||
dot(d, float2( s, c)));
|
||||
|
||||
return r + Center;
|
||||
}
|
||||
|
||||
inline half luminance(half3 color)
|
||||
{
|
||||
return dot(color, float3(0.2126f, 0.7152f, 0.0722f));
|
||||
}
|
||||
|
||||
inline half DepthFactor(float Z, float near, float far)
|
||||
{
|
||||
Z = saturate((Z-near)/(far - near));
|
||||
return Z;
|
||||
}
|
||||
|
||||
//抽自Unity.cginc
|
||||
inline half3 LinearToGammaSpace (half3 linRGB)
|
||||
{
|
||||
linRGB = max(linRGB, half3(0.h, 0.h, 0.h));
|
||||
// An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
|
||||
return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.h);
|
||||
|
||||
// Exact version, useful for debugging.
|
||||
//return half3(LinearToGammaSpaceExact(linRGB.r), LinearToGammaSpaceExact(linRGB.g), LinearToGammaSpaceExact(linRGB.b));
|
||||
}
|
||||
|
||||
//ASE
|
||||
float2 voronoihash1( float2 p )
|
||||
{
|
||||
|
||||
p = float2( dot( p, float2( 127.1, 311.7 ) ), dot( p, float2( 269.5, 183.3 ) ) );
|
||||
return frac( sin( p ) *43758.5453);
|
||||
}
|
||||
|
||||
//ASE
|
||||
float voronoi1( float2 v, float time, inout float2 id, inout float2 mr, float smoothness )
|
||||
{
|
||||
float2 n = floor( v );
|
||||
float2 f = frac( v );
|
||||
float F1 = 8.0;
|
||||
float F2 = 8.0; float2 mg = 0;
|
||||
for ( int j = -1; j <= 1; j++ )
|
||||
{
|
||||
for ( int i = -1; i <= 1; i++ )
|
||||
{
|
||||
float2 g = float2( i, j );
|
||||
float2 o = voronoihash1( n + g );
|
||||
o = ( sin( time + o * 6.2831 ) * 0.5 + 0.5 ); float2 r = f - g - o;
|
||||
float d = 0.5 * dot( r, r );
|
||||
if( d<F1 ) {
|
||||
F2 = F1;
|
||||
F1 = d; mg = g; mr = r; id = o;
|
||||
} else if( d<F2 ) {
|
||||
F2 = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return F2 - F1;
|
||||
}
|
||||
|
||||
void voroniForgraphfunc_half(half2 uv,half angle,half scale,out float outVoroni1 )
|
||||
{
|
||||
uv = uv*scale;
|
||||
float2 id1 = 0;
|
||||
float2 uv1 = 0;
|
||||
outVoroni1 = voronoi1( uv, angle, id1, uv1, 0 );
|
||||
|
||||
}
|
||||
|
||||
inline float2 unity_voronoi_noise_randomVector (float2 UV, float offset)
|
||||
{
|
||||
float2x2 m = float2x2(15.27, 47.63, 99.41, 89.98);
|
||||
UV = frac(sin(mul(UV, m)) * 46839.32);
|
||||
return float2(sin(UV.y*+offset)*0.5+0.5, cos(UV.x*offset)*0.5+0.5);
|
||||
}
|
||||
|
||||
void Unity_Voronoi_float(float2 UV, float AngleOffset, float CellDensity, out float Out, out float Cells)
|
||||
{
|
||||
float2 g = floor(UV * CellDensity);
|
||||
float2 f = frac(UV * CellDensity);
|
||||
float t = 8.0;
|
||||
float3 res = float3(8.0, 0.0, 0.0);
|
||||
|
||||
for(int y=-1; y<=1; y++)
|
||||
{
|
||||
for(int x=-1; x<=1; x++)
|
||||
{
|
||||
float2 lattice = float2(x,y);
|
||||
float2 offset = unity_voronoi_noise_randomVector(lattice + g, AngleOffset);
|
||||
float d = distance(lattice + offset, f);
|
||||
if(d < res.x)
|
||||
{
|
||||
res = float3(d, offset.x, offset.y);
|
||||
Out = res.x;
|
||||
Cells = res.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Unity_Blend_Overlay_float4(float4 Base, float4 Blend, float Opacity, out float4 Out)
|
||||
{
|
||||
float4 result1 = 1.0 - 2.0 * (1.0 - Base) * (1.0 - Blend);
|
||||
float4 result2 = 2.0 * Base * Blend;
|
||||
float4 zeroOrOne = step(Base, 0.5);
|
||||
Out = result2 * zeroOrOne + (1 - zeroOrOne) * result1;
|
||||
Out = lerp(Base, Out, Opacity);
|
||||
}
|
||||
|
||||
void Unity_Blend_HardLight_half(half Base, half Blend, half Opacity, out half Out)
|
||||
{
|
||||
half result1 = 1.0 - 2.0 * (1.0 - Base) * (1.0 - Blend);
|
||||
half result2 = 2.0 * Base * Blend;
|
||||
half zeroOrOne = step(Blend, 0.5);
|
||||
Out = result2 * zeroOrOne + (1 - zeroOrOne) * result1;
|
||||
Out = lerp(Base, Out, Opacity);
|
||||
}
|
||||
|
||||
void Blend_HardLight_half(half Base, half Blend, out half Out)
|
||||
{
|
||||
half result1 = 1.0 - 2.0 * (1.0 - Base) * (1.0 - Blend);
|
||||
half result2 = 2.0 * Base * Blend;
|
||||
half zeroOrOne = step(Blend, 0.5);
|
||||
Out = result2 * zeroOrOne + (1 - zeroOrOne) * result1;
|
||||
}
|
||||
|
||||
float2 randomGradient(float2 p) {
|
||||
p = p + 0.02;
|
||||
float x = dot(p, float2(123.4, 234.5));
|
||||
float y = dot(p, float2(234.5, 345.6));
|
||||
float2 gradient = float2(x, y);
|
||||
gradient = sin(gradient);
|
||||
gradient = gradient * 43758.5453;
|
||||
|
||||
// part 4.5 - update noise function with time
|
||||
// gradient = sin(gradient + u_time);
|
||||
return gradient;
|
||||
|
||||
// gradient = sin(gradient);
|
||||
// return gradient;
|
||||
}
|
||||
|
||||
#include "./jp.keijiro.noiseshader/Shader/SimplexNoise3D.hlsl"
|
||||
half SimplexNoise(float2 uv,float time)
|
||||
{
|
||||
return SimplexNoise(float3(uv,time))*0.5+0.5;
|
||||
}
|
||||
|
||||
#include "./jp.keijiro.noiseshader/Shader/ClassicNoise3D.hlsl"
|
||||
half PerlinNoise(float2 uv,float time)
|
||||
{
|
||||
return ClassicNoise(float3(uv,time))*0.5+0.5;
|
||||
}
|
||||
|
||||
float2 PolarCoordinates(float2 UV, float2 _PCCenter)
|
||||
{
|
||||
// float2 uvsource = float2(0, 0);
|
||||
|
||||
float2 delta = UV - _PCCenter.xy;//校准UV到中心
|
||||
float radius = length(delta) * 2;
|
||||
float angle = atan2(delta.x, delta.y) * UNITY_INV_TWO_PI ;
|
||||
return float2(angle, radius);//翻转可以调整横向和纵向。
|
||||
}
|
||||
|
||||
float2 PolarCoordinatesStrengthAndST(float2 UVBeforPollarCoord,float2 UVAfterPolarCoord,float polarStrenth ,float4 tex_ST)
|
||||
{
|
||||
UVAfterPolarCoord = UVAfterPolarCoord*tex_ST.xy + tex_ST.zw;//利用相应功能贴图的坐标对ST进行修改。
|
||||
UVAfterPolarCoord = lerp(UVBeforPollarCoord, UVAfterPolarCoord, polarStrenth);
|
||||
return UVAfterPolarCoord;
|
||||
}
|
||||
|
||||
|
||||
//极坐标//transformation为极坐标强度
|
||||
float2 PolarCoordinates(float2 UV, float3 _PCCenter, float4 tex_ST)
|
||||
{
|
||||
// float2 uvsource = float2(0, 0);
|
||||
//
|
||||
// float2 delta = UV - _PCCenter.xy;//校准UV到中心
|
||||
// float radius = length(delta) * 2;
|
||||
// float angle = atan2(delta.x, delta.y) * UNITY_INV_TWO_PI ;
|
||||
// uvsource = float2(angle, radius);//翻转可以调整横向和纵向。
|
||||
// uvsource = uvsource*tex_ST.xy + tex_ST.zw;//利用相应功能贴图的坐标对ST进行修改。
|
||||
// uvsource = lerp(UV, uvsource, _PCCenter.z);
|
||||
// return uvsource;
|
||||
|
||||
//拆分成两步,第一步求极坐标太耗(atan),一般源UV都是一样的,只是ST不同。
|
||||
float2 uvsource = PolarCoordinates(UV,_PCCenter.xy);
|
||||
uvsource = PolarCoordinatesStrengthAndST(UV,uvsource,_PCCenter.z,tex_ST);
|
||||
return uvsource;
|
||||
|
||||
}
|
||||
|
||||
float2 CylinderCoordinate(float3 positionOS)
|
||||
{
|
||||
float angle = atan2(positionOS.x,positionOS.z)* UNITY_INV_TWO_PI ;
|
||||
return float2(angle,positionOS.y);
|
||||
}
|
||||
|
||||
|
||||
float2 UVOffsetAnimaiton(float2 UV,half2 OffsetSpeed,float time)
|
||||
{
|
||||
float2 newUV = float2(OffsetSpeed.x*time+UV.x,OffsetSpeed.y*time+UV.y);
|
||||
return newUV;
|
||||
}
|
||||
|
||||
half3 rgb2hsv(half3 c)
|
||||
{
|
||||
half4 K = half4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
half4 p = lerp(half4(c.bg, K.wz), half4(c.gb, K.xy), step(c.b, c.g));
|
||||
half4 q = lerp(half4(p.xyw, c.r), half4(c.r, p.yzx), step(p.x, c.r));
|
||||
|
||||
float d = q.x - min(q.w, q.y);
|
||||
float e = 1.0e-10;
|
||||
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
||||
}
|
||||
|
||||
half3 hsv2rgb(half3 c)
|
||||
{
|
||||
half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
half3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
|
||||
}
|
||||
|
||||
half3 ColorSaturate(half3 color, half colorSaturation)
|
||||
{
|
||||
half3 lum = luminance(color);
|
||||
return max(0, lerp(lum, color, colorSaturation));
|
||||
}
|
||||
|
||||
half2 Rotate(half2 v, half cos0, half sin0)
|
||||
{
|
||||
return half2(v.x * cos0 - v.y * sin0,
|
||||
v.x * sin0 + v.y * cos0);
|
||||
}
|
||||
|
||||
half SmoothStep01(half interval)//让01线性过渡变成SmoothStep过渡,但是控制计算量。
|
||||
{
|
||||
interval = saturate(interval);
|
||||
return interval * interval * ( 3 - 2 * interval );
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f34986983f558c40a7e30a353dc3c34
|
||||
timeCreated: 1684725413
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e95a8f1b5d23f4141b5121f98f90f10a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
Noise Shader Library for Unity
|
||||
==============================
|
||||
|
||||
**NoiseShader** is a Unity package that provides 2D/3D gradient noise
|
||||
functions written in the shader language. These functions are ported from the
|
||||
[webgl-noise] library that is originally written by Stefan Gustavson and Ahima
|
||||
Arts.
|
||||
|
||||
[webgl-noise]: https://github.com/ashima/webgl-noise
|
||||
|
||||
At the moment, it contains the following functions:
|
||||
|
||||
- Classic Perlin noise (2D/3D)
|
||||
- Periodic Perlin noise (2D/3D)
|
||||
- Simplex noise (2D/3D)
|
||||
- Analytical derivatives of simplex noise (2D/3D)
|
||||
|
||||
How To Install
|
||||
--------------
|
||||
|
||||
This package uses the [scoped registry] feature to resolve package dependencies.
|
||||
Please add the following sections to the manifest file (Packages/manifest.json).
|
||||
|
||||
[scoped registry]: https://docs.unity3d.com/Manual/upm-scoped.html
|
||||
|
||||
To the `scopedRegistries` section:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "Keijiro",
|
||||
"url": "https://registry.npmjs.com",
|
||||
"scopes": [ "jp.keijiro" ]
|
||||
}
|
||||
```
|
||||
|
||||
To the `dependencies` section:
|
||||
|
||||
```
|
||||
"jp.keijiro.noiseshader": "2.0.0"
|
||||
```
|
||||
|
||||
After changes, the manifest file should look like below:
|
||||
|
||||
```
|
||||
{
|
||||
"scopedRegistries": [
|
||||
{
|
||||
"name": "Keijiro",
|
||||
"url": "https://registry.npmjs.com",
|
||||
"scopes": [ "jp.keijiro" ]
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"jp.keijiro.noiseshader": "2.0.0",
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b92baefe22663afa5ad0ab303b3dcc20
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77b21af505211947eb730e678820eb8b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user