yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
072fa89
master
1using UnityEngine ; 2using UnityEditor ; 3 4public class StackTextures3D : EditorWindow 5{ 6Texture2D [] _slices = new Texture2D [ 0 ]; 7SerializedObject _so ; 8SerializedProperty _slicesProp ; 9[ SerializeField ] bool reverseStackOrder = false ; 10 11[ MenuItem ( "Tools/yum_food/Stack Textures to 3D" )] 12static void Open () => GetWindow < StackTextures3D > ( "Stack Textures to 3D" ); 13 14void OnEnable () 15{ 16_so = new SerializedObject ( this ); 17} 18 19// SerializedObject needs a backing field with SerializeField for array GUI. 20// Unity EditorWindow supports this natively. 21[ SerializeField ] Texture2D [] slices = new Texture2D [ 0 ]; 22 23void OnGUI () 24{ 25_so . Update (); 26 27EditorGUILayout . LabelField ( "Stack Textures to 3D" , EditorStyles . boldLabel ); 28EditorGUILayout . HelpBox ( 29"Provide an ordered array of 2D textures. Each texture becomes one depth slice. " + 30"All textures must share the same dimensions and format." , 31MessageType . Info ); 32 33var prop = _so . FindProperty ( "slices" ); 34EditorGUILayout . PropertyField ( prop , new GUIContent ( "Slices" ), true ); 35EditorGUILayout . PropertyField ( 36_so . FindProperty ( "reverseStackOrder" ), 37new GUIContent ( "Reverse Stack Order" )); 38_so . ApplyModifiedProperties (); 39 40if ( slices == null || slices . Length == 0 ) 41return ; 42 43EditorGUILayout . Space (); 44 45// Show preview info. 46int nullCount = 0 ; 47foreach ( var s in slices ) 48if ( s == null ) nullCount ++ ; 49 50if ( nullCount > 0 ) 51EditorGUILayout . HelpBox ( $" { nullCount } null slot(s) — fill all slots before generating." , MessageType . Warning ); 52 53if ( nullCount == 0 && slices . Length > 0 ) 54{ 55var first = slices [ 0 ]; 56EditorGUILayout . LabelField ( $"Resolution: { first . width } x { first . height } x { slices . Length } " ); 57} 58 59EditorGUI . BeginDisabledGroup ( nullCount > 0 ); 60if ( GUILayout . Button ( "Generate" )) 61DoGenerate (); 62EditorGUI . EndDisabledGroup (); 63} 64 65void DoGenerate () 66{ 67if ( slices . Length == 0 ) 68return ; 69 70int width = slices [ 0 ]. width ; 71int height = slices [ 0 ]. height ; 72int depth = slices . Length ; 73TextureFormat fmt = slices [ 0 ]. format ; 74 75for ( int i = 1 ; i < depth ; i ++ ) 76{ 77if ( slices [ i ]. width != width || slices [ i ]. height != height ) 78{ 79EditorUtility . DisplayDialog ( "Error" , 80$"Slice { i } ( { slices [ i ]. name } ) is { slices [ i ]. width } x { slices [ i ]. height } , " + 81$"expected { width } x { height } ." , "OK" ); 82return ; 83} 84if ( slices [ i ]. format != fmt ) 85{ 86EditorUtility . DisplayDialog ( "Error" , 87$"Slice { i } ( { slices [ i ]. name } ) format is { slices [ i ]. format } , " + 88$"expected { fmt } ." , "OK" ); 89return ; 90} 91} 92 93bool mipMaps = false ; 94var tex = new Texture3D ( width , height , depth , fmt , mipMaps ); 95 96for ( int z = 0 ; z < depth ; z ++ ) 97{ 98int sourceIndex = reverseStackOrder ? ( depth - 1 - z ) : z ; 99var pixels = slices [ sourceIndex ]. GetPixels (); 100for ( int y = 0 ; y < height ; y ++ ) 101for ( int x = 0 ; x < width ; x ++ ) 102{ 103tex . SetPixel ( x , y , z , pixels [ y * width + x ]); 104} 105} 106 107tex . wrapMode = TextureWrapMode . Repeat ; 108tex . filterMode = FilterMode . Bilinear ; 109tex . Apply (); 110 111string dir = System . IO . Path . GetDirectoryName ( AssetDatabase . GetAssetPath ( slices [ 0 ])); 112string path = $" { dir } /Stacked3D_ { width } x { height } x { depth } .asset" ; 113path = AssetDatabase . GenerateUniqueAssetPath ( path ); 114 115AssetDatabase . CreateAsset ( tex , path ); 116AssetDatabase . SaveAssets (); 117EditorGUIUtility . PingObject ( tex ); 118Debug . Log ( $"[StackTextures3D] Saved { width } x { height } x { depth } ( { fmt } ) to { path } " ); 119} 120}