Skip to content

Instantly share code, notes, and snippets.

@koturn
Created September 16, 2023 15:18
Show Gist options
  • Save koturn/6e036ef61119ce1759966252436f452c to your computer and use it in GitHub Desktop.
Save koturn/6e036ef61119ce1759966252436f452c to your computer and use it in GitHub Desktop.
Unityのシェーダーで[Toggle]が付与されているプロパティのキーワードをC#側から切り替えるやつ(シェーダー側の定義が主)
using System;
using System.Linq.Expressions;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Koturn
{
/// <summary>
/// Provides utility method about MaterialToggleUIDrawer.
/// </summary>
public static class ToggleKeywordUtil
{
/// <summary>
/// Cache of reflection result of following lambda.
/// </summary>
/// <remarks><seealso cref="CreateToggleKeywordDelegate"/></remarks>
private static Action<Shader, MaterialProperty, bool> _toggleKeyword;
/// <summary>
/// Cache of reflection result of following lambda.
/// </summary>
/// <remarks><seealso cref="CreateToggleKeywordExDelegate"/></remarks>
private static Action<Material, string, bool> _toggleKeywordEx;
/// <summary>
/// Enable or disable keyword of <see cref="MaterialProperty"/> which has MaterialToggleUIDrawer.
/// </summary>
/// <param name="shader">Target <see cref="Shader"/>.</param>
/// <param name="prop">Target <see cref="MaterialProperty"/>.</param>
public static void ToggleKeyword(Shader shader, MaterialProperty prop)
{
ToggleKeyword(shader, prop, ToBool(prop.floatValue));
}
/// <summary>
/// Enable or disable keyword of <see cref="MaterialProperty"/> which has MaterialToggleUIDrawer.
/// </summary>
/// <param name="shader">Target <see cref="Shader"/>.</param>
/// <param name="prop">Target <see cref="MaterialProperty"/>.</param>
/// <param name="isOn">True to enable (define) keyword, false to disable (undefine) keyword.</param>
public static void ToggleKeyword(Shader shader, MaterialProperty prop, bool isOn)
{
try
{
(_toggleKeyword ?? (_toggleKeyword = CreateSetKeywordDelegate()))(shader, prop, isOn);
}
catch (Exception ex)
{
Debug.LogError(ex.ToString());
}
}
/// <summary>
/// Enable or disable keyword of <see cref="Material"/> which has MaterialToggleDrawer.
/// </summary>
/// <param name="material">Target <see cref="Material"/>.</param>
/// <param name="propName">Name of toggle property.</param>
/// <param name="isOn">True to enable (define) keyword, false to disable (undefine) keyword.</param>
public static void ToggleKeyword(Material material, string propName)
{
if (material.HasProperty(propName))
{
ToggleKeyword(material, propName, ToBool(material.GetFloat(propName)));
}
else
{
throw new ArgumentException("Could not find Property: '" + propName + "'");
}
}
/// <summary>
/// Enable or disable keyword of <see cref="Material"/> which has MaterialToggleDrawer.
/// </summary>
/// <param name="material">Target <see cref="Material"/>.</param>
/// <param name="propName">Name of toggle property.</param>
/// <param name="isOn">True to enable (define) keyword, false to disable (undefine) keyword.</param>
public static void ToggleKeyword(Material material, string propName, bool isOn)
{
try
{
(_toggleKeywordEx ?? (_toggleKeywordEx = CreateToggleKeywordExDelegate()))(material, propName, isOn);
}
catch (Exception ex)
{
Debug.LogError(ex.ToString());
}
}
/// <summary>
/// <para>Create delegate of reflection results about UnityEditor.MaterialToggleUIDrawer.</para>
/// <code>
/// (Shader shader, MaterialProperty prop, bool isOn) =>
/// {
/// MaterialPropertyHandler mph = UnityEditor.MaterialPropertyHandler.GetHandler(shader, prop.name);
/// if (mph is null)
/// {
/// throw new ArgumentException("Specified MaterialProperty does not have UnityEditor.MaterialPropertyHandler");
/// }
/// MaterialToggleUIDrawer mpud = mph.propertyDrawer as MaterialToggleUIDrawer;
/// if (mpud is null)
/// {
/// throw new ArgumentException("Specified MaterialProperty does not have UnityEditor.MaterialToggleUIDrawer");
/// }
/// mpud.SetKeyword(prop, isOn);
/// }
/// </code>
/// </summary>
private static Action<Shader, MaterialProperty, bool> CreateToggleKeywordDelegate()
{
// Get assembly from public class.
var asm = Assembly.GetAssembly(typeof(UnityEditor.MaterialPropertyDrawer));
// Get type of UnityEditor.MaterialPropertyHandler which is the internal class.
var typeMph = asm.GetType("UnityEditor.MaterialPropertyHandler")
?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialPropertyHandler");
var typeMtud = asm.GetType("UnityEditor.MaterialToggleUIDrawer")
?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialToggleUIDrawer");
var ciArgumentException = typeof(ArgumentException).GetConstructor(new[] {typeof(string)});
var pShader = Expression.Parameter(typeof(Shader), "shader");
var pMaterialPropertyHandler = Expression.Parameter(typeMph, "mph");
var pMaterialToggleUIDrawer = Expression.Parameter(typeMtud, "mtud");
var pMaterialProperty = Expression.Parameter(typeof(MaterialProperty), "mp");
var pIsOn = Expression.Parameter(typeof(bool), "isOn");
var cNull = Expression.Constant(null);
return Expression.Lambda<Action<Shader, MaterialProperty, bool>>(
Expression.Block(
new[]
{
pMaterialPropertyHandler,
pMaterialToggleUIDrawer
},
Expression.Assign(
pMaterialPropertyHandler,
Expression.Call(
typeMph.GetMethod(
"GetHandler",
BindingFlags.NonPublic
| BindingFlags.Static)
?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialPropertyHandler.GetHandler"),
pShader,
Expression.Property(
pMaterialProperty,
typeof(MaterialProperty).GetProperty(
"name",
BindingFlags.GetProperty
| BindingFlags.Public
| BindingFlags.Instance)))),
Expression.IfThen(
Expression.Equal(
pMaterialPropertyHandler,
cNull),
Expression.Throw(
Expression.New(
ciArgumentException,
Expression.Constant("Specified MaterialProperty does not have UnityEditor.MaterialPropertyHandler")))),
Expression.Assign(
pMaterialToggleUIDrawer,
Expression.TypeAs(
Expression.Property(
pMaterialPropertyHandler,
typeMph.GetProperty(
"propertyDrawer",
BindingFlags.GetProperty
| BindingFlags.Public
| BindingFlags.Instance)
?? throw new InvalidOperationException("PropertyInfo not found: UnityEditor.MaterialPropertyHandler.propertyDrawer")),
typeMtud)),
Expression.IfThen(
Expression.Equal(
pMaterialToggleUIDrawer,
cNull),
Expression.Throw(
Expression.New(
ciArgumentException,
Expression.Constant("Specified MaterialProperty does not have UnityEditor.MaterialToggleUIDrawer")))),
Expression.Call(
pMaterialToggleUIDrawer,
typeMtud.GetMethod(
"SetKeyword",
BindingFlags.NonPublic
| BindingFlags.Instance)
?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialToggleUIDrawer.SetKeyword"),
pMaterialProperty,
pIsOn)),
"ToggleKeyword",
new []
{
pShader,
pMaterialProperty,
pIsOn
}).Compile();
}
/// <summary>
/// <para>Create delegate of reflection results about UnityEditor.MaterialToggleDrawer.</para>
/// <code>
/// (Material material, string propName, bool isOn) =>
/// {
/// MaterialPropertyHandler mph = UnityEditor.MaterialPropertyHandler.GetHandler(material.shader, propName);
/// if (mph is null)
/// {
/// throw new ArgumentException("Specified MaterialProperty does not have UnityEditor.MaterialPropertyHandler");
/// }
///
/// MaterialToggleDrawer mpd = mph.propertyDrawer as MaterialToggleDrawer;
/// if (mpd is null)
/// {
/// throw new ArgumentException("Specified MaterialProperty does not have UnityEditor.MaterialToggleDrawer");
/// }
///
/// var keyword = mpud.keyword;
/// if (string.IsNullOrEmpty(keyword))
/// {
/// keyword = propName.ToUpperInvariant() + (mpd is MaterialToggleOffDrawer ? "_OFF" : "_ON")
/// }
///
/// if (mpd is MaterialToggleOffDrawer)
/// {
/// isOn = !isOn;
/// }
///
/// if (isOn)
/// {
/// material.EnableKeyword(keyword);
/// }
/// else
/// {
/// material.DisableKeyword(keyword);
/// }
/// };
/// </code>
/// </summary>
private static Action<Material, string, bool> CreateToggleKeywordExDelegate()
{
// Get assembly from public class.
var asm = Assembly.GetAssembly(typeof(UnityEditor.MaterialPropertyDrawer));
// Get type of UnityEditor.MaterialPropertyHandler which is the internal class.
var typeMph = asm.GetType("UnityEditor.MaterialPropertyHandler")
?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialPropertyHandler");
var typeMtd = asm.GetType("UnityEditor.MaterialToggleDrawer")
?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialToggleDrawer");
var typeMtod = asm.GetType("UnityEditor.MaterialToggleOffDrawer")
?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialToggleOffDrawer");
var ciArgumentException = typeof(ArgumentException).GetConstructor(new[] {typeof(string)});
// Arguments.
var pMaterial = Expression.Parameter(typeof(Material), "material");
var pPropName = Expression.Parameter(typeof(string), "propName");
var pIsOn = Expression.Parameter(typeof(bool), "isOn");
// Local variables.
var pShader = Expression.Parameter(typeof(Shader), "shader");
var pMaterialPropertyHandler = Expression.Parameter(typeMph, "mph");
var pMaterialToggleDrawer = Expression.Parameter(typeMtd, "mtd");
var pKeyword = Expression.Parameter(typeof(string), "keyword");
// Constants.
var cNull = Expression.Constant(null);
return Expression.Lambda<Action<Material, string, bool>>(
Expression.Block(
new[]
{
pMaterialPropertyHandler,
pMaterialToggleDrawer,
pKeyword
},
Expression.Assign(
pMaterialPropertyHandler,
Expression.Call(
typeMph.GetMethod(
"GetHandler",
BindingFlags.NonPublic
| BindingFlags.Static)
?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialPropertyHandler.GetHandler"),
Expression.Property(
pMaterial,
typeof(Material).GetProperty(
"shader",
BindingFlags.GetProperty
| BindingFlags.Public
| BindingFlags.Instance)
?? throw new InvalidOperationException("PropertyInfo not found: UnityEngine.Material.shader")),
pPropName)),
Expression.IfThen(
Expression.Equal(
pMaterialPropertyHandler,
cNull),
Expression.Throw(
Expression.New(
ciArgumentException,
Expression.Constant("Specified MaterialProperty does not have UnityEditor.MaterialPropertyHandler")))),
Expression.Assign(
pMaterialToggleDrawer,
Expression.TypeAs(
Expression.Property(
pMaterialPropertyHandler,
typeMph.GetProperty(
"propertyDrawer",
BindingFlags.GetProperty
| BindingFlags.Public
| BindingFlags.Instance)
?? throw new InvalidOperationException("PropertyInfo not found: UnityEditor.MaterialPropertyHandler.propertyDrawer")),
typeMtd)),
Expression.IfThen(
Expression.Equal(
pMaterialToggleDrawer,
cNull),
Expression.Throw(
Expression.New(
ciArgumentException,
Expression.Constant("Specified MaterialProperty does not have UnityEditor.MaterialToggleDrawer")))),
Expression.Assign(
pKeyword,
Expression.Field(
pMaterialToggleDrawer,
typeMtd.GetField(
"keyword",
BindingFlags.GetField
| BindingFlags.NonPublic
| BindingFlags.Instance)
?? throw new InvalidOperationException("FieldInfo not found: UnityEditor.MaterialToggleDrawer.keyword"))),
Expression.IfThen(
Expression.Call(
typeof(string).GetMethod(
"IsNullOrEmpty",
BindingFlags.Public
| BindingFlags.Static)
?? throw new InvalidOperationException("MethodInfo not found: String.IsNullOrEmpty"),
pKeyword),
Expression.Assign(
pKeyword,
Expression.Call(
typeof(string).GetMethod(
"Concat",
new Type[]
{
typeof(string),
typeof(string)
})
?? throw new InvalidOperationException("MethodInfo not found: String.IsNullOrEmpty"),
Expression.Call(
pPropName,
typeof(string).GetMethod(
"ToUpperInvariant",
BindingFlags.Public
| BindingFlags.Instance)
?? throw new InvalidOperationException("MethodInfo not found: String.ToUpperInvariant")),
Expression.Condition(
Expression.TypeIs(
pMaterialToggleDrawer,
typeMtod),
Expression.Constant("_OFF"),
Expression.Constant("_ON"))))),
Expression.IfThen(
Expression.TypeIs(
pMaterialToggleDrawer,
typeMtod),
Expression.Assign(
pIsOn,
Expression.Not(pIsOn))),
Expression.IfThenElse(
pIsOn,
Expression.Call(
pMaterial,
typeof(Material).GetMethod(
"EnableKeyword",
BindingFlags.Public
| BindingFlags.Instance)
?? throw new InvalidOperationException("MethodInfo not found: UnityEngine.Material.EnableKeyword"),
pKeyword),
Expression.Call(
pMaterial,
typeof(Material).GetMethod(
"DisableKeyword",
BindingFlags.Public
| BindingFlags.Instance)
?? throw new InvalidOperationException("MethodInfo not found: UnityEngine.Material.DisableKeyword"),
pKeyword))),
"ToggleKeyword",
new []
{
pMaterial,
pPropName,
pIsOn
}).Compile();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment