Skip to content

Instantly share code, notes, and snippets.

@ghysc
Forked from totallyRonja/MaterialGradientDrawer.cs
Last active June 9, 2022 13:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ghysc/b4f9b3266ee82edf2b02e00cef0bc6b7 to your computer and use it in GitHub Desktop.
Save ghysc/b4f9b3266ee82edf2b02e00cef0bc6b7 to your computer and use it in GitHub Desktop.
A Material Property Drawer for the [Curve] attribute which lets you edit curves and adds them to the shader as textures. Forked from Ronja's Material Gradient Drawer. More information here: https://twitter.com/CyrilJGhys/status/1529496742642728964 and here: https://twitter.com/totallyRonja/status/1368704187580682240
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
public class MaterialCurveDrawer : MaterialPropertyDrawer {
private int resolution;
public MaterialCurveDrawer() {
resolution = 256;
}
public MaterialCurveDrawer(float res) {
resolution = (int)res;
}
private static bool IsPropertyTypeSuitable(MaterialProperty prop) {
return prop.type == MaterialProperty.PropType.Texture;
}
public string TextureName(MaterialProperty prop) => $"{prop.name}Tex";
public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) {
if (!IsPropertyTypeSuitable(prop)) {
EditorGUI.HelpBox(position, $"[Curve] used on non-texture property \"{prop.name}\"", MessageType.Error);
return;
}
if (!AssetDatabase.Contains(prop.targets.FirstOrDefault())) {
EditorGUI.HelpBox(position, $"Material \"{prop.targets.FirstOrDefault()?.name}\" is not an Asset!",
MessageType.Error);
return;
}
var textureName = TextureName(prop);
AnimationCurve currentCurve = null;
if (prop.targets.Length == 1) {
var target = (Material) prop.targets[0];
var path = AssetDatabase.GetAssetPath(target);
var textureAsset = GetTextureAsset(path, textureName);
if (textureAsset != null)
currentCurve = Decode(prop, textureAsset.name);
if (currentCurve == null)
currentCurve = new AnimationCurve(){};
EditorGUI.showMixedValue = false;
} else {
EditorGUI.showMixedValue = true;
}
using (var changeScope = new EditorGUI.ChangeCheckScope()) {
currentCurve = EditorGUILayout.CurveField(label, currentCurve, Color.green, new Rect(0f, 0f, 1f, 1f));
if (changeScope.changed) {
string encodedCurve = Encode(currentCurve);
string fullAssetName = textureName + encodedCurve;
foreach (Object target in prop.targets) {
if (!AssetDatabase.Contains(target)) //failsafe for non-asset materials - should never trigger
continue;
var path = AssetDatabase.GetAssetPath(target);
var textureAsset = GetTexture(path, textureName);
Undo.RecordObject(textureAsset, "Change Material Curve");
textureAsset.name = fullAssetName;
BakeCurve(currentCurve, textureAsset);
EditorUtility.SetDirty(textureAsset);
//we need ImportAsset for changes to show up, but also it costs 100ms per tick and I dont wanna pay that ^^
//AssetDatabase.ImportAsset(path);
var material = (Material) target;
material.SetTexture(prop.name, textureAsset);
}
}
}
EditorGUI.showMixedValue = false;
}
private Texture2D GetTexture(string path, string name) {
var textureAsset = GetTextureAsset(path, name);
if (textureAsset == null) {
textureAsset = CreateTexture(path, name);
}
if (textureAsset.width != resolution) {
textureAsset.Resize(resolution, 1);
}
return textureAsset;
}
private Texture2D CreateTexture(string path, string name = "unnamed texture") {
//I'm actually unsure about the "no mipchain" thing, mipmapping could also be neat?
var textureAsset = new Texture2D(resolution, 1, TextureFormat.ARGB32, false);
textureAsset.wrapMode = TextureWrapMode.Clamp;
textureAsset.name = name;
AssetDatabase.AddObjectToAsset(textureAsset, path);
AssetDatabase.ImportAsset(path);
return textureAsset;
}
private string Encode(AnimationCurve curve) {
if (curve == null)
return null;
return JsonUtility.ToJson(new CurveRepresentation(curve));
}
private AnimationCurve Decode(MaterialProperty prop, string name) {
string json = name.Substring(TextureName(prop).Length);
try {
return JsonUtility.FromJson<CurveRepresentation>(json).ToCurve();
} catch (Exception) {
return null;
}
}
private Texture2D GetTextureAsset(string path, string name) {
return AssetDatabase.LoadAllAssetsAtPath(path).FirstOrDefault(asset => asset.name.StartsWith(name)) as Texture2D;
}
private void BakeCurve(AnimationCurve curve, Texture2D texture) {
if (curve == null)
return;
for (int x = 0; x < texture.width; x++) {
float value = curve.Evaluate((float)x / (texture.width - 1));
Color color = new Color(value, value, value);
for (int y = 0; y < texture.height; y++) {
texture.SetPixel(x, y, color);
}
}
texture.Apply();
}
[MenuItem("Assets/Remove All Subassets")]
static void RemoveAllSubassets() {
foreach (Object asset in Selection.GetFiltered<Object>(SelectionMode.Assets)) {
var path = AssetDatabase.GetAssetPath(asset);
foreach (Object subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(path)) {
Object.DestroyImmediate(subAsset, true);
}
AssetDatabase.ImportAsset(path);
}
}
class CurveRepresentation {
public Key[] keys;
public CurveRepresentation() { }
public CurveRepresentation(AnimationCurve source) {
FromCurve(source);
}
public void FromCurve(AnimationCurve source) {
keys = source.keys.Select(key => new Key(key)).ToArray();
}
public void ToCurve(AnimationCurve curve) {
curve.keys = keys.Select(key => key.ToCurveKey()).ToArray();
}
public AnimationCurve ToCurve() {
var curve = new AnimationCurve();
ToCurve(curve);
return curve;
}
[Serializable]
public struct Key
{
public float time;
public float value;
public float inTangent;
public float outTangent;
public float inWeight;
public float outWeight;
public WeightedMode weightedMode;
public Key(Keyframe source)
{
time = default;
value = default;
inTangent = default;
outTangent = default;
inWeight = default;
outWeight = default;
weightedMode = default;
FromCurveKey(source);
}
public void FromCurveKey(Keyframe source)
{
time = source.time;
value = source.value;
inTangent = source.inTangent;
outTangent = source.outTangent;
inWeight = source.inWeight;
outWeight = source.outWeight;
weightedMode = source.weightedMode;
}
public Keyframe ToCurveKey()
{
Keyframe key = default;
key.time = time;
key.value = value;
key.inTangent = inTangent;
key.outTangent = outTangent;
key.inWeight = inWeight;
key.outWeight = outWeight;
key.weightedMode = weightedMode;
return key;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment