using UnityEngine; using UnityEditor; public class StackTextures3D : EditorWindow { Texture2D[] _slices = new Texture2D[0]; SerializedObject _so; SerializedProperty _slicesProp; [MenuItem("Tools/yum_food/Stack Textures to 3D")] static void Open() => GetWindow("Stack Textures to 3D"); void OnEnable() { _so = new SerializedObject(this); } // SerializedObject needs a backing field with SerializeField for array GUI. // Unity EditorWindow supports this natively. [SerializeField] Texture2D[] slices = new Texture2D[0]; void OnGUI() { _so.Update(); EditorGUILayout.LabelField("Stack Textures to 3D", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "Provide an ordered array of 2D textures. Each texture becomes one depth slice. " + "All textures must share the same dimensions and format.", MessageType.Info); var prop = _so.FindProperty("slices"); EditorGUILayout.PropertyField(prop, new GUIContent("Slices"), true); _so.ApplyModifiedProperties(); if (slices == null || slices.Length == 0) return; EditorGUILayout.Space(); // Show preview info. int nullCount = 0; foreach (var s in slices) if (s == null) nullCount++; if (nullCount > 0) EditorGUILayout.HelpBox($"{nullCount} null slot(s) — fill all slots before generating.", MessageType.Warning); if (nullCount == 0 && slices.Length > 0) { var first = slices[0]; EditorGUILayout.LabelField($"Resolution: {first.width} x {first.height} x {slices.Length}"); } EditorGUI.BeginDisabledGroup(nullCount > 0); if (GUILayout.Button("Generate")) DoGenerate(); EditorGUI.EndDisabledGroup(); } void DoGenerate() { if (slices.Length == 0) return; int width = slices[0].width; int height = slices[0].height; int depth = slices.Length; TextureFormat fmt = slices[0].format; for (int i = 1; i < depth; i++) { if (slices[i].width != width || slices[i].height != height) { EditorUtility.DisplayDialog("Error", $"Slice {i} ({slices[i].name}) is {slices[i].width}x{slices[i].height}, " + $"expected {width}x{height}.", "OK"); return; } if (slices[i].format != fmt) { EditorUtility.DisplayDialog("Error", $"Slice {i} ({slices[i].name}) format is {slices[i].format}, " + $"expected {fmt}.", "OK"); return; } } bool mipMaps = true; var tex = new Texture3D(width, height, depth, fmt, mipMaps); for (int z = 0; z < depth; z++) { var pixels = slices[z].GetPixels(); for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { tex.SetPixel(x, y, z, pixels[y * width + x]); } } tex.wrapMode = TextureWrapMode.Repeat; tex.filterMode = FilterMode.Trilinear; tex.Apply(); string dir = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(slices[0])); string path = $"{dir}/Stacked3D_{width}x{height}x{depth}.asset"; path = AssetDatabase.GenerateUniqueAssetPath(path); AssetDatabase.CreateAsset(tex, path); AssetDatabase.SaveAssets(); EditorGUIUtility.PingObject(tex); Debug.Log($"[StackTextures3D] Saved {width}x{height}x{depth} ({fmt}) to {path}"); } }