using System.IO; using System.Reflection; using UnityEngine; using UnityEditor; public class TextureConverter : ScriptableWizard { public Material TargetMaterial; [Header("Packing")] public TexturePackingTemplate template; [Space(20)] public Shader TargetShader; public PackingTargetLayout[] PackingArray; public ReassignTargetLayout[] ReassigningArray; TexturePackingTemplate PrevTemplate; [MenuItem("CONTEXT/Material/Convert Texture")] public static void CreateWizard(MenuCommand menuCommand) { var wiz = ScriptableWizard.DisplayWizard("Convert Textures", "Convert & Close", "Convert Textures"); var contexMat = menuCommand.context as Material; wiz.TargetMaterial = contexMat; //Set material from menu context wiz.template = GrabDefault(contexMat.shader); //Get default template wiz.LoadTemplate(); //Intial Load } public static TexturePackingTemplate GrabDefault(Shader shader) { string filename = "Template Settings"; string path = "Assets/Settings/" + filename+".asset"; AssetDatabase.SaveAssets(); ConverterSettings templateSettings = AssetDatabase.LoadAssetAtPath(path); if (templateSettings == null) //Generate intial default settings { Debug.Log("No settings file found. Generating..."); //FileUtil.CopyFileOrDirectory("Packages/com.unity.render-pipelines.universal/Editor/Converter/TextureConvert/DefaultTemplateSettings.asset", path); //Copys even the name :/ ConverterSettings defaultset = AssetDatabase.LoadAssetAtPath("Packages/com.unity.render-pipelines.universal/Editor/Converter/TextureConvert/DefaultTemplateSettings.asset"); ConverterSettings parseset = ScriptableObject.CreateInstance(); parseset.ShaderDefaultTemplate = defaultset.ShaderDefaultTemplate;//Copy data rather than ref AssetDatabase.CreateAsset(parseset, path); AssetDatabase.ImportAsset(path); templateSettings = AssetDatabase.LoadAssetAtPath(path); if (templateSettings != null) Debug.Log("Created template settings file inside the settings folder"); } if (templateSettings.ShaderDefaultTemplate.Length < 1) return null; int tempint = WhereOnArray(templateSettings, shader); if (tempint < 0) return null; return templateSettings.ShaderDefaultTemplate[tempint].DefaultTemplate ; } static int WhereOnArray(ConverterSettings templateSettings, Shader shader) { for (int i = 0; i < templateSettings.ShaderDefaultTemplate.Length; i++) //Extracting shaders to compare { if (templateSettings.ShaderDefaultTemplate[i].shader == shader) { return i; } } return -1; } void OnWizardUpdate() { if (PrevTemplate != template) //Checking currently loaded template and reloading if different { LoadTemplate(); } } void LoadTemplate() { if (template == null) return; TargetShader = template.TargetShader; if (template.Packing.Length != 0) PackingArray = new PackingTargetLayout[template.Packing.Length]; if (template.Reassigning.Length != 0) ReassigningArray = new ReassignTargetLayout[template.Reassigning.Length]; for (int i = 0; i < template.Packing.Length; i++) { PackingArray[i] = ParsePackingTemplate(template.Packing[i]); } for (int i = 0; i < template.Reassigning.Length; i++) { ReassigningArray[i] = ParseReassiningTemplate(template.Reassigning[i]); } PrevTemplate = template; } private void OnWizardOtherButton() //Convert { TextureConvert(); } public void OnWizardCreate() //Convert and close { TextureConvert(); } public PackingTargetLayout ParsePackingTemplate(PackingLayout layout) //Read template and load any connected textures { PackingTargetLayout targetLayout = new PackingTargetLayout(); var props = TargetMaterial.GetTexturePropertyNames(); targetLayout.PropertyName = layout.PropertyName; if (ArrayUtility.Contains(props, layout.RedInputProperty)) { targetLayout.RedInputTexture = TargetMaterial.GetTexture(layout.RedInputProperty) as Texture2D; } targetLayout.RedOptions = layout.RedOptions; if (ArrayUtility.Contains(props, layout.GreenInputProperty)) targetLayout.GreenInputTexture = TargetMaterial.GetTexture(layout.GreenInputProperty) as Texture2D; targetLayout.GreenOptions = layout.GreenOptions; if (ArrayUtility.Contains(props, layout.BlueInputProperty)) targetLayout.BlueInputTexture = TargetMaterial.GetTexture(layout.BlueInputProperty) as Texture2D; targetLayout.BlueOptions = layout.BlueOptions; if (ArrayUtility.Contains(props, layout.AlphaInputProperty)) targetLayout.AlphaInputTexture = TargetMaterial.GetTexture(layout.AlphaInputProperty) as Texture2D; targetLayout.AlphaOptions = layout.AlphaOptions; targetLayout.packingOptions = layout.packingOptions; return targetLayout; } public ReassignTargetLayout ParseReassiningTemplate(ReassignLayout layout) //Read template and load any connected textures { ReassignTargetLayout targetLayout = new ReassignTargetLayout(); var props = TargetMaterial.GetTexturePropertyNames(); targetLayout.PropertyName = layout.PropertyName; if (ArrayUtility.Contains(props, layout.InputPropertyName)) targetLayout.InputTexture = TargetMaterial.GetTexture(layout.InputPropertyName) as Texture2D; return targetLayout; } string SavedPath; public void TextureConvert() { if (PackingArray.Length < 1 && ReassigningArray.Length < 1) return; //SHADER CONVERSION// //Set textures and blit using a custom shader for (int i = 0; i < PackingArray.Length; i++) //Convert Textures { SavedPath = null; UncompressBeforeTask(PackingArray[i]); Vector2Int maxSize; //Be a bit smarter about this maxSize = PackingArray[i].RedInputTexture.GetImageSize(); maxSize = Vector2Int.Max(maxSize, PackingArray[i].GreenInputTexture.GetImageSize() ); maxSize = Vector2Int.Max(maxSize, PackingArray[i].BlueInputTexture.GetImageSize() ); maxSize = Vector2Int.Max(maxSize, PackingArray[i].AlphaInputTexture.GetImageSize() ); //Read Render texture and convert to texture2D var packedtexture = PackToTexture(PackingArray[i], maxSize).ConvertToTexture2D(); ////// //Save as new Texture if (PackingArray[i].packingOptions.OverrideTexture == OverrideSlot.New) { SaveNewTexture(packedtexture, PackingArray[i]); } else { string OverridePath; switch (PackingArray[i].packingOptions.OverrideTexture) { case OverrideSlot.Red: OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].RedInputTexture); break; case OverrideSlot.Green: OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].GreenInputTexture); break; case OverrideSlot.Blue: OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].BlueInputTexture); break; case OverrideSlot.Alpha: OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].AlphaInputTexture); break; default: OverridePath = null; break; } if (OverridePath == null || OverridePath.Length == 0) { Debug.Log("Texture override not valid. Making new texture instead."); SaveNewTexture(packedtexture, PackingArray[i]); break; } object extractedext; if (!System.Enum.TryParse(typeof(TextureFileExtension), Path.GetExtension(OverridePath).Substring(1), true, out extractedext)) { Debug.Log("Texture type " + Path.GetExtension(OverridePath).Substring(1) + " is not supported. Making new texture instead."); SaveNewTexture(packedtexture, PackingArray[i]); break; } else { TextureFileExtension ext = (TextureFileExtension)extractedext; byte[] pixels = packedtexture.EncodeTexture(ext); File.WriteAllBytes(OverridePath, pixels); SavedPath = OverridePath; AssetDatabase.Refresh(); DeleteOldTextures(PackingArray[i]); ResetTextureCompressions(PackingArray[i]); AssignProperty(PackingArray[i]); } } } for (int i = 0; i < ReassigningArray.Length; i++) //Reassign Textures { TargetMaterial.SetTexture(ReassigningArray[i].PropertyName, ReassigningArray[i].InputTexture); } if (TargetShader!=null) TargetMaterial.shader = TargetShader; } TextureImporterCompression cRed; TextureImporterCompression cGreen; TextureImporterCompression cBlue; TextureImporterCompression cAlpha; void UncompressBeforeTask(PackingTargetLayout targetLayout) { if (targetLayout.packingOptions.UncompressBeforeTask) return; cRed=UncompressFile(targetLayout.RedInputTexture); cGreen=UncompressFile(targetLayout.GreenInputTexture); cBlue=UncompressFile(targetLayout.BlueInputTexture); cAlpha=UncompressFile(targetLayout.AlphaInputTexture); } TextureImporterCompression UncompressFile(Texture2D texture) { TextureImporterCompression compression; var path = AssetDatabase.GetAssetPath(texture); if (path == null || path.Length == 0) return TextureImporterCompression.Uncompressed; TextureImporter ti = (TextureImporter) TextureImporter.GetAtPath( path); compression = ti.textureCompression; //Storing compression ti.textureCompression = TextureImporterCompression.Uncompressed; ti.maxTextureSize = 8192; ti.SaveAndReimport(); AssetDatabase.Refresh(); return compression; } void AssignProperty(PackingTargetLayout packingTargetLayout) { AssetDatabase.Refresh(); //Load new asset and set it to the correct slot TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(SavedPath); if (!packingTargetLayout.packingOptions.EnableAlphaChannel) ti.alphaSource = TextureImporterAlphaSource.None; ti.textureCompression = packingTargetLayout.packingOptions.textureCompression; ti.sRGBTexture = packingTargetLayout.packingOptions.sRGB; ti.SaveAndReimport(); TargetMaterial.SetTexture(packingTargetLayout.PropertyName, AssetDatabase.LoadAssetAtPath(SavedPath)); AssetDatabase.Refresh(); } void ResetTextureCompressions(PackingTargetLayout targetLayout) { if (targetLayout.packingOptions.UncompressBeforeTask) return; ResetCompression(targetLayout.RedInputTexture, cRed); ResetCompression(targetLayout.GreenInputTexture, cGreen); ResetCompression(targetLayout.BlueInputTexture, cBlue); ResetCompression(targetLayout.AlphaInputTexture, cAlpha); } void ResetCompression(Texture2D texture, TextureImporterCompression compression) { var path = AssetDatabase.GetAssetPath(texture); if (path == null || path.Length == 0) return; TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(path); ti.textureCompression = compression; ti.SaveAndReimport(); AssetDatabase.Refresh(); } void DeleteOldTextures(PackingTargetLayout targetLayout) { if (targetLayout.packingOptions.DeleteSource == false) return; deleteTexture(targetLayout.RedInputTexture); deleteTexture(targetLayout.GreenInputTexture); deleteTexture(targetLayout.BlueInputTexture); deleteTexture(targetLayout.AlphaInputTexture); AssetDatabase.Refresh(); } void deleteTexture(Texture2D texture) { var path = AssetDatabase.GetAssetPath(texture); if (path == null || path.Length == 0 || SavedPath == path) return; File.Delete(path); File.Delete(path+ ".meta"); Debug.Log("Deleted " + path); AssetDatabase.Refresh(); } public void SaveNewTexture(Texture2D packedtexture, PackingTargetLayout packingTargetLayout) { // string fullPath = Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + "_MAS.png"; string caughtFilePath = GetPathFromInputs(packingTargetLayout); //Path.GetDirectoryName(caughtFilePath); string UserVerifiedPath = EditorUtility.SaveFilePanel("Save Texture", Path.GetDirectoryName(caughtFilePath), Path.GetFileNameWithoutExtension(caughtFilePath) + packingTargetLayout.packingOptions.Suffix, packingTargetLayout.packingOptions.textureFileExtension.ToString()); if (UserVerifiedPath == null || UserVerifiedPath.Length == 0) return; byte[] pixels = packedtexture.EncodeTexture(packingTargetLayout.packingOptions.textureFileExtension); // File.WriteAllBytes(Application.dataPath + "/../" + fullPath, pixels); File.WriteAllBytes(UserVerifiedPath, pixels); string relativepath; if (UserVerifiedPath.StartsWith(Application.dataPath)) relativepath = "Assets" + UserVerifiedPath.Substring(Application.dataPath.Length); else relativepath = UserVerifiedPath; SavedPath = relativepath; Debug.Log("Saved texture at " + UserVerifiedPath); AssetDatabase.Refresh(); DeleteOldTextures(packingTargetLayout); AssignProperty(packingTargetLayout); ResetTextureCompressions(packingTargetLayout); } public string GetPathFromInputs(PackingTargetLayout targetLayout) { if (targetLayout.RedInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.RedInputTexture); else if (targetLayout.GreenInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.GreenInputTexture); else if (targetLayout.BlueInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.BlueInputTexture); else if (targetLayout.AlphaInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.AlphaInputTexture); else return "/Asset/"; } public RenderTexture PackToTexture(PackingTargetLayout targetLayout, Vector2Int Resolution ) { //RenderTextureDescriptor descriptor = new RenderTextureDescriptor(); //descriptor.width = (int)Resolution.x; //descriptor.height = (int)Resolution.y; //descriptor.colorFormat = RenderTextureFormat.ARGBHalf; //descriptor.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat; //descriptor.sRGB = true; //descriptor.dimension = UnityEngine.Rendering.TextureDimension.Tex2D; //descriptor.volumeDepth = 1; //descriptor.msaaSamples = 1; //RenderTexture render = new RenderTexture(descriptor); RenderTexture render = new RenderTexture((int)Resolution.x, (int)Resolution.y, 0, RenderTextureFormat.Default); render.dimension = UnityEngine.Rendering.TextureDimension.Tex2D; render.enableRandomWrite = true; render.wrapMode = TextureWrapMode.Clamp; render.Create(); // Debug.Log(render.sRGB); ComputeShader Packer = AssetDatabase.LoadAssetAtPath("Packages/com.unity.render-pipelines.universal/Editor/Converter/TextureConvert/TexturePacker.compute"); //Todo: Fix build error. Find better way to load? int kernelIndex = Packer.FindKernel("CSMain"); Packer.SetTexture(kernelIndex, "InputTextureRed", GrabChannelTexture(targetLayout, RGBA.Red) ) ; Packer.SetTexture(kernelIndex, "InputTextureGreen", GrabChannelTexture(targetLayout, RGBA.Green) ); Packer.SetTexture(kernelIndex, "InputTextureBlue", GrabChannelTexture(targetLayout, RGBA.Blue) ); Packer.SetTexture(kernelIndex, "InputTextureAlpha", GrabChannelTexture(targetLayout, RGBA.Alpha) ) ; Packer.SetBool("invertRed", targetLayout.RedOptions.invert); Packer.SetBool("invertGreen", targetLayout.GreenOptions.invert); Packer.SetBool("invertBlue", targetLayout.BlueOptions.invert); Packer.SetBool("invertAlpha", targetLayout.AlphaOptions.invert); Packer.SetInt("inputChannelRed", ChannelToInt(targetLayout.RedOptions.InputChannel)); Packer.SetInt("inputChannelGreen", ChannelToInt(targetLayout.GreenOptions.InputChannel)); Packer.SetInt("inputChannelBlue", ChannelToInt(targetLayout.BlueOptions.InputChannel)); Packer.SetInt("inputChannelAlpha", ChannelToInt(targetLayout.AlphaOptions.InputChannel)); //Packer.SetBool("RedSRGB", ChannelToInt(targetLayout.RedOptions.InputChannel)); //Packer.SetBool("GreenSRGB", ChannelToInt(targetLayout.GreenOptions.InputChannel)); //Packer.SetBool("BlueSRGB", ChannelToInt(targetLayout.BlueOptions.InputChannel)); //Packer.SetBool("AlphaSRGB", ChannelToInt(targetLayout.BlueOptions.InputChannel)); Packer.SetTexture(kernelIndex, "Result", render); Packer.Dispatch(kernelIndex, (int)Resolution.x, (int)Resolution.y, 1); return render; } Texture2D GrabChannelTexture(PackingTargetLayout targetLayout, RGBA rgba ) { switch (rgba) { case RGBA.Red: if (targetLayout.RedInputTexture != null) return targetLayout.RedInputTexture; else return GrabDefault(targetLayout.RedOptions.defaultColor); case RGBA.Green: if (targetLayout.GreenInputTexture != null) return targetLayout.GreenInputTexture; else return GrabDefault(targetLayout.GreenOptions.defaultColor); case RGBA.Blue: if (targetLayout.BlueInputTexture != null) return targetLayout.BlueInputTexture; else return GrabDefault(targetLayout.BlueOptions.defaultColor); case RGBA.Alpha: if (targetLayout.AlphaInputTexture != null) return targetLayout.AlphaInputTexture; else return GrabDefault(targetLayout.AlphaOptions.defaultColor); } return Texture2D.whiteTexture; } Texture2D GrabDefault(DefaultColor defaultColor) { switch (defaultColor) { case DefaultColor.Black: return Texture2D.blackTexture; case DefaultColor.White: return Texture2D.whiteTexture; case DefaultColor.Gray: return Texture2D.grayTexture; case DefaultColor.LinearGray: return Texture2D.linearGrayTexture; default: return Texture2D.blackTexture; } } int ChannelToInt(RGBA rgba) { switch (rgba) { case RGBA.Red: return 0; case RGBA.Green: return 1; case RGBA.Blue: return 2; case RGBA.Alpha: return 3; default: return -1; } } }