Last active
January 23, 2025 04:16
-
-
Save Pyredrid/79e54b26c20d0fc8aa439504f9704957 to your computer and use it in GitHub Desktop.
Gradient Texture Material Property Drawer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
I could not tell you how this 7 year old snippet of code works, if it even remotely does on modern Unity. Haven't touched that godsforesaken engine in over 2 years.
Fair enough! Thanks for the quick reply.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Do you have an example of using this?
gradient.RawSerializeEx() doesn't map to public static byte[] RawSerializeEx(object obj) {