[Unity]将所有 TGA、TIFF、PSD 和 BMP(可自定义)纹理转换为 PNG,以减小项目大小,而不会在 Unity 中造成任何质量损失

本文主要是介绍[Unity]将所有 TGA、TIFF、PSD 和 BMP(可自定义)纹理转换为 PNG,以减小项目大小,而不会在 Unity 中造成任何质量损失,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

如何使用
只需在“项目”窗口中创建一个名为“编辑器”的文件夹,然后在其中添加此脚本即可。然后,打开窗口-Convert Textures to PNG,配置参数并点击“Convert to PNG! ”。

就我而言,它已将某些 3D 资源的总文件大小从 1.08 GB 减少到 510 MB。

只要禁用“Keep Original Files”或将项目的资源序列化模式设置为“强制文本”,就会保留对转换后的纹理的引用。
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;public class ConvertTexturesToPNG : EditorWindow
{private const string DUMMY_TEXTURE_PATH = "Assets/convert_dummyy_texturee.png";private const bool REMOVE_MATTE_FROM_PSD_BY_DEFAULT = true;private readonly GUIContent[] maxTextureSizeStrings = { new GUIContent( "32" ), new GUIContent( "64" ), new GUIContent( "128" ), new GUIContent( "256" ), new GUIContent( "512" ), new GUIContent( "1024" ), new GUIContent( "2048" ), new GUIContent( "4096" ), new GUIContent( "8192" ), new GUIContent( "16384" ) };private readonly int[] maxTextureSizeValues = { 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 };private readonly GUIContent rootPathContent = new GUIContent( "Root Path:", "Textures inside this folder (recursive) will be converted" );private readonly GUIContent textureExtensionsContent = new GUIContent( "Textures to Convert:", "Only Textures with these extensions will be converted (';' separated)" );private readonly GUIContent excludedDirectoriesContent = new GUIContent( "Excluded Directories:", "Textures inside these directories won't be converted (';' separated)" );private readonly GUIContent keepOriginalFilesContent = new GUIContent( "Keep Original Files:", "If selected, original Texture files won't be deleted after the conversion" );private readonly GUIContent maxTextureSizeContent = new GUIContent( "Max Texture Size:", "Textures larger than this size will be downscaled to this size" );private readonly GUIContent optiPNGPathContent = new GUIContent( "OptiPNG Path (Optional):", "If 'optipng.exe' is selected, it will be used to reduce the image sizes even further (roughly 20%) but the process will take more time" );private readonly GUIContent optiPNGOptimizationContent = new GUIContent( "OptiPNG Optimization:", "Determines how many trials OptiPNG will do to optimize the image sizes. As this value increases, computation time will increase exponentially" );private readonly GUIContent optiPNGURL = new GUIContent( "...", "http://optipng.sourceforge.net/" );private readonly GUILayoutOption GL_WIDTH_25 = GUILayout.Width( 25f );private string rootPath = "";private string textureExtensions = ".tga;.psd;.tiff;.tif;.bmp";private string excludedDirectories = "";private bool keepOriginalFiles = false;private int maxTextureSize = 8192;private string optiPNGPath = "";private int optiPNGOptimization = 3;private Vector2 scrollPos;[MenuItem( "Window/Convert Textures to PNG" )]private static void Init(){ConvertTexturesToPNG window = GetWindow<ConvertTexturesToPNG>();window.titleContent = new GUIContent( "Convert to PNG" );window.minSize = new Vector2( 285f, 160f );window.Show();}private void OnEnable(){// By default, Root Path points to this project's Assets folderif( string.IsNullOrEmpty( rootPath ) )rootPath = Application.dataPath;}private void OnGUI(){scrollPos = GUILayout.BeginScrollView( scrollPos );rootPath = PathField( rootPathContent, rootPath, true, "Choose target directory" );textureExtensions = EditorGUILayout.TextField( textureExtensionsContent, textureExtensions );excludedDirectories = EditorGUILayout.TextField( excludedDirectoriesContent, excludedDirectories );keepOriginalFiles = EditorGUILayout.Toggle( keepOriginalFilesContent, keepOriginalFiles );maxTextureSize = EditorGUILayout.IntPopup( maxTextureSizeContent, maxTextureSize, maxTextureSizeStrings, maxTextureSizeValues );optiPNGPath = PathField( optiPNGPathContent, optiPNGPath, false, "Choose optipng.exe path", optiPNGURL );if( !string.IsNullOrEmpty( optiPNGPath ) ){EditorGUI.indentLevel++;optiPNGOptimization = EditorGUILayout.IntSlider( optiPNGOptimizationContent, optiPNGOptimization, 2, 7 );EditorGUI.indentLevel--;}EditorGUILayout.Space();// Convert Textures to PNGif( GUILayout.Button( "Convert to PNG!" ) ){double startTime = EditorApplication.timeSinceStartup;List<string> convertedPaths = new List<string>( 128 );long originalTotalSize = 0L, convertedTotalSize = 0L, convertedTotalSizeOptiPNG = 0L;try{rootPath = rootPath.Trim();excludedDirectories = excludedDirectories.Trim();textureExtensions = textureExtensions.ToLowerInvariant().Replace( ".png", "" ).Trim();optiPNGPath = optiPNGPath.Trim();if( rootPath.Length == 0 )rootPath = Application.dataPath;if( optiPNGPath.Length > 0 && !File.Exists( optiPNGPath ) )Debug.LogWarning( "OptiPNG doesn't exist at path: " + optiPNGPath );string[] paths = FindTexturesToConvert();string pathsLengthStr = paths.Length.ToString();float progressMultiplier = paths.Length > 0 ? ( 1f / paths.Length ) : 1f;CreateDummyTexture(); // Dummy Texture is used while reading Textures' pixelsfor( int i = 0; i < paths.Length; i++ ){if( EditorUtility.DisplayCancelableProgressBar( "Please wait...", string.Concat( "Converting: ", ( i + 1 ).ToString(), "/", pathsLengthStr ), ( i + 1 ) * progressMultiplier ) )throw new Exception( "Conversion aborted" );string pngFile = Path.ChangeExtension( paths[i], ".png" );string pngMetaFile = pngFile + ".meta";string originalMetaFile = paths[i] + ".meta";bool isPSDImage = Path.GetExtension( paths[i] ).ToLowerInvariant() == ".psd";// Make sure to respect PSD assets' "Remove Matte (PSD)" optionif( isPSDImage ){bool removeMatte = REMOVE_MATTE_FROM_PSD_BY_DEFAULT;if( File.Exists( originalMetaFile ) ){const string removeMatteOption = "pSDRemoveMatte: ";string metaContents = File.ReadAllText( originalMetaFile );int removeMatteIndex = metaContents.IndexOf( removeMatteOption );if( removeMatteIndex >= 0 )removeMatte = metaContents[removeMatteIndex + removeMatteOption.Length] != '0';}SerializedProperty removeMatteProp = new SerializedObject( AssetImporter.GetAtPath( DUMMY_TEXTURE_PATH ) ).FindProperty( "m_PSDRemoveMatte" );if( removeMatteProp != null && removeMatteProp.boolValue != removeMatte ){removeMatteProp.boolValue = removeMatte;removeMatteProp.serializedObject.ApplyModifiedPropertiesWithoutUndo();}}// Temporarily copy the image file to Assets folder to create a read-write enabled Texture from itFile.Copy( paths[i], DUMMY_TEXTURE_PATH, true );AssetDatabase.ImportAsset( DUMMY_TEXTURE_PATH, ImportAssetOptions.ForceUpdate );// Convert the Texture to PNG and save itbyte[] pngBytes = AssetDatabase.LoadAssetAtPath<Texture2D>( DUMMY_TEXTURE_PATH ).EncodeToPNG();File.WriteAllBytes( pngFile, pngBytes );originalTotalSize += new FileInfo( paths[i] ).Length;convertedTotalSize += new FileInfo( pngFile ).Length;// Run OptiPNG to optimize the PNGif( optiPNGPath.Length > 0 && File.Exists( optiPNGPath ) ){try{Process.Start( new ProcessStartInfo( optiPNGPath ){Arguments = string.Concat( "-o ", optiPNGOptimization.ToString(), " \"", pngFile, "\"" ),CreateNoWindow = true,UseShellExecute = false} ).WaitForExit();}catch( Exception e ){Debug.LogException( e );}convertedTotalSizeOptiPNG += new FileInfo( pngFile ).Length;}// If .meta file exists, copy it to PNG imageif( File.Exists( originalMetaFile ) ){File.Copy( originalMetaFile, pngMetaFile, true );// Try changing original meta file's GUID to avoid collisions with PNG (Credit: https://gist.github.com/ZimM-LostPolygon/7e2f8a3e5a1be183ac19)if( keepOriginalFiles ){string metaContents = File.ReadAllText( originalMetaFile );int guidIndex = metaContents.IndexOf( "guid: " );if( guidIndex >= 0 ){string guid = metaContents.Substring( guidIndex + 6, 32 );string newGuid = Guid.NewGuid().ToString( "N" );metaContents = metaContents.Replace( guid, newGuid );File.WriteAllText( originalMetaFile, metaContents );}}// Don't show "Remote Matte (PSD)" option for converted Texturesif( isPSDImage ){string metaContents = File.ReadAllText( pngMetaFile );bool modifiedMeta = false;if( metaContents.Contains( "pSDShowRemoveMatteOption: 1" ) ){metaContents = metaContents.Replace( "pSDShowRemoveMatteOption: 1", "pSDShowRemoveMatteOption: 0" );modifiedMeta = true;}if( metaContents.Contains( "pSDRemoveMatte: 1" ) ){metaContents = metaContents.Replace( "pSDRemoveMatte: 1", "pSDRemoveMatte: 0" );modifiedMeta = true;}if( modifiedMeta )File.WriteAllText( pngMetaFile, metaContents );}}if( !keepOriginalFiles ){File.Delete( paths[i] );if( File.Exists( originalMetaFile ) )File.Delete( originalMetaFile );}convertedPaths.Add( paths[i] );}}catch( Exception e ){Debug.LogException( e );}finally{EditorUtility.ClearProgressBar();if( File.Exists( DUMMY_TEXTURE_PATH ) )AssetDatabase.DeleteAsset( DUMMY_TEXTURE_PATH );// Force Unity to import PNG images (otherwise we'd have to minimize Unity and then maximize it)AssetDatabase.Refresh();// Print information to ConsoleStringBuilder sb = new StringBuilder( 100 + convertedPaths.Count * 75 );sb.Append( "Converted " ).Append( convertedPaths.Count ).Append( " Texture(s) to PNG in " ).Append( ( EditorApplication.timeSinceStartup - startTime ).ToString( "F2" ) ).Append( " seconds (" ).Append( EditorUtility.FormatBytes( originalTotalSize ) ).Append( " -> " ).Append( EditorUtility.FormatBytes( convertedTotalSize ) );if( convertedTotalSizeOptiPNG > 0L )sb.Append( " -> " ).Append( EditorUtility.FormatBytes( convertedTotalSizeOptiPNG ) ).Append( " with OptiPNG" );sb.AppendLine( "):" );for( int i = 0; i < convertedPaths.Count; i++ )sb.Append( "- " ).AppendLine( convertedPaths[i] );Debug.Log( sb.ToString() );}}GUILayout.EndScrollView();}private string PathField( GUIContent label, string path, bool isDirectory, string title, GUIContent downloadURL = null ){GUILayout.BeginHorizontal();path = EditorGUILayout.TextField( label, path );if( GUILayout.Button( "o", GL_WIDTH_25 ) ){string selectedPath = isDirectory ? EditorUtility.OpenFolderPanel( title, "", "" ) : EditorUtility.OpenFilePanel( title, "", "exe" );if( !string.IsNullOrEmpty( selectedPath ) )path = selectedPath;GUIUtility.keyboardControl = 0; // Remove focus from active text field}if( downloadURL != null && GUILayout.Button( downloadURL, GL_WIDTH_25 ) )Application.OpenURL( downloadURL.tooltip );GUILayout.EndHorizontal();return path;}private string[] FindTexturesToConvert(){HashSet<string> texturePaths = new HashSet<string>();HashSet<string> targetExtensions = new HashSet<string>( textureExtensions.Split( ';' ) );// Get directories to excludestring[] excludedPaths = excludedDirectories.Split( ';' );for( int i = 0; i < excludedPaths.Length; i++ ){excludedPaths[i] = excludedPaths[i].Trim();if( excludedPaths[i].Length == 0 )excludedPaths[i] = "NULL/";else{excludedPaths[i] = Path.GetFullPath( excludedPaths[i] );// Make sure excluded directory paths end with directory separator charif( Directory.Exists( excludedPaths[i] ) && !excludedPaths[i].EndsWith( Path.DirectorySeparatorChar.ToString() ) )excludedPaths[i] += Path.DirectorySeparatorChar;}}// Iterate through all files in Root Pathstring[] allFiles = Directory.GetFiles( rootPath, "*.*", SearchOption.AllDirectories );for( int i = 0; i < allFiles.Length; i++ ){// Only process filtered image filesif( targetExtensions.Contains( Path.GetExtension( allFiles[i] ).ToLowerInvariant() ) ){bool isExcluded = false;if( excludedPaths.Length > 0 ){// Make sure the image file isn't part of an excluded directorystring fileFullPath = Path.GetFullPath( allFiles[i] );for( int j = 0; j < excludedPaths.Length; j++ ){if( fileFullPath.StartsWith( excludedPaths[j] ) ){isExcluded = true;break;}}}if( !isExcluded )texturePaths.Add( allFiles[i] );}}string[] result = new string[texturePaths.Count];texturePaths.CopyTo( result );return result;}// Creates dummy Texture asset that will be used to read Textures' pixelsprivate void CreateDummyTexture(){if( !File.Exists( DUMMY_TEXTURE_PATH ) ){File.WriteAllBytes( DUMMY_TEXTURE_PATH, new Texture2D( 2, 2 ).EncodeToPNG() );AssetDatabase.ImportAsset( DUMMY_TEXTURE_PATH, ImportAssetOptions.ForceUpdate );}TextureImporter textureImporter = AssetImporter.GetAtPath( DUMMY_TEXTURE_PATH ) as TextureImporter;textureImporter.maxTextureSize = maxTextureSize;textureImporter.isReadable = true;textureImporter.filterMode = FilterMode.Point;textureImporter.mipmapEnabled = false;textureImporter.alphaSource = TextureImporterAlphaSource.FromInput;textureImporter.npotScale = TextureImporterNPOTScale.None;textureImporter.textureCompression = TextureImporterCompression.Uncompressed;textureImporter.SaveAndReimport();}
}

这篇关于[Unity]将所有 TGA、TIFF、PSD 和 BMP(可自定义)纹理转换为 PNG,以减小项目大小,而不会在 Unity 中造成任何质量损失的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/282715

相关文章

Python打印对象所有属性和值的方法小结

《Python打印对象所有属性和值的方法小结》在Python开发过程中,调试代码时经常需要查看对象的当前状态,也就是对象的所有属性和对应的值,然而,Python并没有像PHP的print_r那样直接提... 目录python中打印对象所有属性和值的方法实现步骤1. 使用vars()和pprint()2. 使

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Python pip下载包及所有依赖到指定文件夹的步骤说明

《Pythonpip下载包及所有依赖到指定文件夹的步骤说明》为了方便开发和部署,我们常常需要将Python项目所依赖的第三方包导出到本地文件夹中,:本文主要介绍Pythonpip下载包及所有依... 目录步骤说明命令格式示例参数说明离线安装方法注意事项总结要使用pip下载包及其所有依赖到指定文件夹,请按照以

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

java Long 与long之间的转换流程

《javaLong与long之间的转换流程》Long类提供了一些方法,用于在long和其他数据类型(如String)之间进行转换,本文将详细介绍如何在Java中实现Long和long之间的转换,感... 目录概述流程步骤1:将long转换为Long对象步骤2:将Longhttp://www.cppcns.c

如何自定义一个log适配器starter

《如何自定义一个log适配器starter》:本文主要介绍如何自定义一个log适配器starter的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求Starter 项目目录结构pom.XML 配置LogInitializer实现MDCInterceptor

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

在Java中将XLS转换为XLSX的实现方案

《在Java中将XLS转换为XLSX的实现方案》在本文中,我们将探讨传统ExcelXLS格式与现代XLSX格式的结构差异,并为Java开发者提供转换方案,通过了解底层原理、性能优势及实用工具,您将掌握... 目录为什么升级XLS到XLSX值得投入?实际转换过程解析推荐技术方案对比Apache POI实现编程

springboot项目中使用JOSN解析库的方法

《springboot项目中使用JOSN解析库的方法》JSON,全程是JavaScriptObjectNotation,是一种轻量级的数据交换格式,本文给大家介绍springboot项目中使用JOSN... 目录一、jsON解析简介二、Spring Boot项目中使用JSON解析1、pom.XML文件引入依