Skip to content

Instantly share code, notes, and snippets.

@Pyredrid
Last active May 17, 2024 09:19
Show Gist options
  • Save Pyredrid/79e54b26c20d0fc8aa439504f9704957 to your computer and use it in GitHub Desktop.
Save Pyredrid/79e54b26c20d0fc8aa439504f9704957 to your computer and use it in GitHub Desktop.
Gradient Texture Material Property Drawer
using UnityEngine;
using UnityEditor;
using System;
using System.Runtime.InteropServices;
/// <summary>
/// Use with "[GradientTexture]" before a texture shader property.
/// Gives a gradient editor instead of a texture selector when viewing
/// in the editor. Meaning you don't have to open any paint programs
/// to make gradient textures.
///
/// This is probably the hackiest code I've ever written... Good luck!
/// -Aaron "Pyredrid" B-D
/// </summary>
public class GradientTextureDrawer : MaterialPropertyDrawer {
//This is fairly arbitrary, just remember that textures generated are currently
//nearest/point filtered due to the whole serializing gradients thing (read below)
private const int WIDTH = 256;
private Gradient textureGradient;
public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) {
Texture value = (prop.textureValue);
EditorGUI.BeginChangeCheck();
EditorGUI.showMixedValue = prop.hasMixedValue;
if (textureGradient == null) {
textureGradient = TextureToGradient((Texture2D)prop.textureValue);
}
EditorGUILayout.LabelField(label);
textureGradient = EditorGUILayout.GradientField(textureGradient);
EditorGUI.showMixedValue = false;
if (EditorGUI.EndChangeCheck()) {
value = GradientToTexture(textureGradient);
prop.textureValue = value;
}
}
private Texture2D GradientToTexture(Gradient gradient) {
Texture2D texture = new Texture2D(WIDTH, 2);
texture.filterMode = FilterMode.Point;
texture.wrapMode = TextureWrapMode.Clamp;
//After setting up the texture, store the gradients colors in regular intervals...
//Also serialize the gradient iteself into the texture
//This no doubt has issue, security problems, bugs, overflows...
//codeHackery++;
byte[] gradientBytes = gradient.RawSerializeEx();
Color32[] gradientColorArray = new Color32[WIDTH];
Color32[] colorArray = new Color32[WIDTH];
for (int i = 0; i < WIDTH; i += 4) {
if (i < gradientBytes.Length) {
Color32 color = new Color32(gradientBytes[i + 0], gradientBytes[i + 1], gradientBytes[i + 2], gradientBytes[i + 3]);
gradientColorArray[i / 4] = color;
} else {
break;
}
}
for (int i = 0; i < WIDTH; i++) {
colorArray[i] = gradient.Evaluate((float)i / WIDTH);
}
texture.SetPixels32(0, 0, WIDTH, 1, colorArray);
texture.SetPixels32(0, 1, WIDTH, 1, gradientColorArray);
texture.Apply();
return texture;
}
private Gradient TextureToGradient(Texture2D texture) {
if (texture == null) {
return new Gradient();
}
try {
//Unity, why the fuck can't GetPixels32() be used to get a block of pixels...
//Seriously, the documentation even says "to get a block of pixels"
//Why doesn't this do the same stuff as GetPixels()
//What the fuck unity
//codeHackery++;
Color[] gradientColorArrayNon32 = new Color[WIDTH];
byte[] gradientBytes = new byte[WIDTH * 4];
gradientColorArrayNon32 = texture.GetPixels(0, 1, WIDTH, 1);
for (int i = 0; i < WIDTH; i += 4) {
gradientBytes[i + 0] = (byte)(gradientColorArrayNon32[i].r / 256);
gradientBytes[i + 1] = (byte)(gradientColorArrayNon32[i].g / 256);
gradientBytes[i + 2] = (byte)(gradientColorArrayNon32[i].b / 256);
gradientBytes[i + 3] = (byte)(gradientColorArrayNon32[i].a / 256);
}
//After all that, we now have a serialized byte array of the gradient
//stored in the texture itself.
//codeHackery++;
Gradient gradient = RawDeserializeEx<Gradient>(gradientBytes);
return gradient;
} catch (Exception e) {
Debug.LogException(e);
return new Gradient();
}
}
//Both of these are taken from somewhere on stackoverflow
//They're pretty terribly hacky and also only being used because Unity is bad
//By which I mean, all of Unity's structs and classes aren't
//Marked with System.Serializable, they do their own weird thing...
//Whatever that is... <.<'
//codeHackery++;
public static byte[] RawSerializeEx(object obj) {
int rawsize = Marshal.SizeOf(obj);
byte[] data = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(obj, buffer, false);
handle.Free();
return data;
}
public static T RawDeserializeEx<T>(byte[] data) where T : class {
int rawsize = Marshal.SizeOf(typeof(T));
if (rawsize > data.Length) {
return null;
}
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject();
T retobj = Marshal.PtrToStructure<T>(buffer);
handle.Free();
return retobj;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment