游戏中纹理压缩格式之自动压缩纹理

记载目录

1.杂言杂语
2.自动处理代码
3.接入NGUI实现全自动化
4.项目补充
5.项目工程下载地址

杂言杂语

从事游戏开发很苦,很累。似乎永远有开发不完的功能,修不完的bug,加不完的班。为了让自己更轻松点工作和少些做一些重复性的劳作,我想尽可能的利用自动化处理,将所有能够自动化的东西全部都弄成自动化,解放自己。

自动处理代码

using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
/**
 * 全自动的原理是引用一个图集后自动设置纹理格式到指定的纹理格式
 * 代码引用有一个配置文件
 * 还有一个Newtonsoft.Json.dll
 * 这份代码可以实现自动化,但是效率不高(存在大量的转换RGBA32),可以再度修改
 * 文章最后可以下载整个项目工程
 */
namespace TextureFormat
{   
    public class AtlerTextureFormat
    {
        // 配置路径
        readonly static string ETC_SHADER_PATH = "Assets/Resources/Shader/EtcAlpha_1.shader";
        readonly static string ATLER_FORMAT_CONFIG_PATH = Application.dataPath + "/Editor/AtlerTextureFormat/AtlerFormatConfig.txt";


        #region 对图片纹理进行格式化

        /// <summary>
        /// 格式化Texture
        /// NGUI调用入口、生成图片入口
        /// </summary>
        /// <param name="tex_path">贴图路径</param>
        /// <param name="mat_path">材质球路径</param>
        public static void FormatTexture(string tex_path, string mat_path)
        {   
            if (string.IsNullOrEmpty(tex_path) || !System.IO.File.Exists(tex_path))
            {
                Debug.Log("Can't find tex_path path: " + tex_path);
                return;
            }
            if (string.IsNullOrEmpty(mat_path) || !System.IO.File.Exists(mat_path))
            {
                Debug.Log("Can't find mat_path path: " + mat_path);
                return;
            }

            // 临时存储通道路径
            string alpha_tex_path = "";

            // 读取配置表,获取压缩格式
            // 根据压缩格式调用对应的压缩代码压缩图片
            // 压缩图片
            var tex_name = System.IO.Path.GetFileNameWithoutExtension(tex_path);
            var tex_format = GetTextureFormatFromConfig(tex_name);

            // 对于rpga16 进行特殊处理。抖动
            // 对于etc进行剥离通道
            if (tex_format == TextureImporterFormat.RGBA16)
            {
                SetTextureDither(tex_path);

            }
            else if (tex_format == TextureImporterFormat.ETC_RGB4)
            {
                SetTextureFormat(tex_path, TextureImporterFormat.RGBA32);
                alpha_tex_path = FormatTextureToRGB4(tex_path);
                SetTextureFormat(alpha_tex_path, TextureImporterFormat.ETC_RGB4);
            }

            // 设置材质球的shader
            if ((tex_format == TextureImporterFormat.ETC_RGB4))
            {
                Texture tex_rgb = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture)) as Texture;
                Texture tex_alpha = AssetDatabase.LoadAssetAtPath(alpha_tex_path, typeof(Texture)) as Texture;

                Material mat = AssetDatabase.LoadAssetAtPath(mat_path, typeof(Material)) as Material;
                Shader shader = AssetDatabase.LoadAssetAtPath(ETC_SHADER_PATH, typeof(Shader)) as Shader;
                mat.shader = shader;
                mat.SetTexture("_MainTex", tex_rgb);
                mat.SetTexture("_MainTex_A", tex_alpha);
            }
            else
            {
                Texture tex_rgb = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture)) as Texture;
                Material mat = AssetDatabase.LoadAssetAtPath(mat_path, typeof(Material)) as Material;
                Shader shader = Shader.Find(NGUISettings.atlasPMA ? "Unlit/Premultiplied Colored" : "Unlit/Transparent Colored");
                mat.shader = shader;
                mat.SetTexture("_MainTex", tex_rgb);
            }

            SetTextureFormat(tex_path, tex_format);
        }

        /// <summary>
        /// 尝试剥离通道
        /// </summary>
        public static bool TryStripAlphaTexture(string png_file_path)
        {
            // 查看该贴图是否需要进行etc处理
            // 读取.png的设置
            // 格式化.png纹理为rgb24
            // 剥离通道
            // 还原.png 贴图纹理
            var file_name = Path.GetFileNameWithoutExtension(png_file_path);
            var format = GetTextureFormatFromConfig(file_name);
            if (format != TextureImporterFormat.ETC_RGB4)
            {
                Debug.Log("This texture can not convert format to ETC_RGB4 ! please check config. ");
                return false;
            }

            SetTextureFormat(png_file_path, TextureImporterFormat.RGBA32);
            TextureImporterFormat tex_format = GetTextureFormatFromConfig(Path.GetFileNameWithoutExtension(png_file_path));
            AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);

            var alpha_tex_path = FormatTextureToRGB4(png_file_path);
            Debug.Log("BuildAlphaTexture:" + png_file_path + "  alpha_tex_path:" + alpha_tex_path);
            SetTextureFormat(alpha_tex_path, TextureImporterFormat.ETC_RGB4);

            SetTextureFormat(png_file_path, tex_format);

            return true;
        }


        /// <summary>
        /// 格式化图片 修改texture的meta文件
        /// 直接设置双平台纹理格式
        /// 当纹理格式为 ETC_RGB4 时会将iphone设置为pv4
        /// </summary>
        private static void SetTextureFormat(string texture_path, TextureImporterFormat format)
        {   
            // 进行贴图的统一的优化设置
            // 再设置纹理格式
            TextureImporter tex_importer = GetTextureImporter(texture_path);
            tex_importer.textureType = TextureImporterType.Default;
            tex_importer.mipmapEnabled = false;
            tex_importer.isReadable = false;
            tex_importer.filterMode = FilterMode.Bilinear;

            if(format == TextureImporterFormat.RGBA32)
            {
                tex_importer.SetPlatformTextureSettings("iPhone",tex_importer.maxTextureSize,TextureImporterFormat.RGBA32);
                tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, TextureImporterFormat.RGBA32);
            }
            else if(format == TextureImporterFormat.RGBA16)
            {
                tex_importer.SetPlatformTextureSettings("iPhone", tex_importer.maxTextureSize, TextureImporterFormat.RGBA16);
                tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, TextureImporterFormat.RGBA16);
            }
            else if(format == TextureImporterFormat.ETC_RGB4)
            {
                tex_importer.SetPlatformTextureSettings("iPhone", tex_importer.maxTextureSize, TextureImporterFormat.PVRTC_RGB4,100,false);
                tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, TextureImporterFormat.ETC_RGB4,100,false);
            }
            else
            {
                Debug.Log("Wrong format : " + format.ToString());
            }

            tex_importer.SaveAndReimport();
        }

        /// <summary>
        /// Floyd–Steinberg dithering
        /// 抖动处理,需要给图片进行抖动处理
        /// 需要注意的是 需要进行抖动处理的是RGBA32的纹理进行抖动处理,再压缩为16位
        /// </summary>
        private static void SetTextureDither(string tex_path)
        {
            SetTextureReadable(tex_path, true);
            var texture = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture2D)) as Texture2D;
            var texw = texture.width;
            var texh = texture.height;

            var pixels = texture.GetPixels ();
            var offs = 0;

            var k1Per15 = 1.0f / 15.0f;
            var k1Per16 = 1.0f / 16.0f;
            var k3Per16 = 3.0f / 16.0f;
            var k5Per16 = 5.0f / 16.0f;
            var k7Per16 = 7.0f / 16.0f;

            for (var y = 0; y < texh; y++) 
            {
                for (var x = 0; x < texw; x++) 
                {
                    float a = pixels [offs].a;
                    float r = pixels [offs].r;
                    float g = pixels [offs].g;
                    float b = pixels [offs].b;

                    var a2 = Mathf.Clamp01 (Mathf.Floor (a * 16) * k1Per15);
                    var r2 = Mathf.Clamp01 (Mathf.Floor (r * 16) * k1Per15);
                    var g2 = Mathf.Clamp01 (Mathf.Floor (g * 16) * k1Per15);
                    var b2 = Mathf.Clamp01 (Mathf.Floor (b * 16) * k1Per15);

                    var ae = a - a2;
                    var re = r - r2;
                    var ge = g - g2;
                    var be = b - b2;

                    pixels [offs].a = a2;
                    pixels [offs].r = r2;
                    pixels [offs].g = g2;
                    pixels [offs].b = b2;

                    var n1 = offs + 1;   // (x+1,y)
                    var n2 = offs + texw - 1; // (x-1 , y+1)
                    var n3 = offs + texw;  // (x, y+1)
                    var n4 = offs + texw + 1; // (x+1 , y+1)

                    if (x < texw - 1) 
                    {
                        pixels [n1].a += ae * k7Per16;
                        pixels [n1].r += re * k7Per16;
                        pixels [n1].g += ge * k7Per16;
                        pixels [n1].b += be * k7Per16;
                    }

                    if (y < texh - 1) 
                    {
                        pixels [n3].a += ae * k5Per16;
                        pixels [n3].r += re * k5Per16;
                        pixels [n3].g += ge * k5Per16;
                        pixels [n3].b += be * k5Per16;

                        if (x > 0) 
                        {
                            pixels [n2].a += ae * k3Per16;
                            pixels [n2].r += re * k3Per16;
                            pixels [n2].g += ge * k3Per16;
                            pixels [n2].b += be * k3Per16;
                        }

                        if (x < texw - 1) 
                        {
                            pixels [n4].a += ae * k1Per16;
                            pixels [n4].r += re * k1Per16;
                            pixels [n4].g += ge * k1Per16;
                            pixels [n4].b += be * k1Per16;
                        }
                    }

                    offs++;
                }
            }
            SetTextureReadable(tex_path, false);
            SaveTexture(tex_path, texture, pixels);
        }


        public static void ConvertTextureFormatToTargetFormat(string tex_path, TextureImporterFormat target_format)
        {
            var file_name = Path.GetFileNameWithoutExtension(tex_path);
            SetTextureReadable(tex_path, true);
            Texture2D tex = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture2D)) as Texture2D;
            if (tex.format != UnityEngine.TextureFormat.RGBA32)
            {
                TextureImporter tex_importer = GetTextureImporter(tex_path);
                tex_importer.textureType = TextureImporterType.Default;
                tex_importer.mipmapEnabled = false;
                tex_importer.isReadable = false;
                tex_importer.filterMode = FilterMode.Bilinear;
                tex_importer.SetPlatformTextureSettings("iPhone", tex_importer.maxTextureSize, target_format);
                tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, target_format);
                tex_importer.SaveAndReimport();
            }
            SetTextureReadable(tex_path, false);
            AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
        }

        /// <summary>
        /// ETC1的特殊处理,分离透明通道并且更改材质为双贴图
        /// 能剥离通道的图片纹理格式只有 rgb24 或者 rgba32
        /// </summary>
        private static string FormatTextureToRGB4(string tex_path)
        {
            // 生成透明通道的png文件
            // 对通道图片进行格式纹理设置
            // 剥离通道并存储
            var alpha_tex_path = tex_path.Replace(".png", "_alpha.png");
            ChangeTextureToAlphaTexture(tex_path,alpha_tex_path);
            return alpha_tex_path;
        }

        /// <summary>
        /// 从RGBA32剥离alpha通道
        /// </summary>
        private static void ChangeTextureToAlphaTexture(string tex_path ,string alpha_tex_path)
        {
            SetTextureReadable(tex_path, true);
            Texture2D sourcetex = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture2D)) as Texture2D;
            Color[] colors = sourcetex.GetPixels();
            SetTextureReadable(tex_path, false);


            // 格式化通道
            Texture2D tex_alpha = new Texture2D(sourcetex.width, sourcetex.height, UnityEngine.TextureFormat.RGB24, false);
            Color[] alpha_colors = new Color[colors.Length];
            for (int i = 0; i < colors.Length; ++i)
            {
                alpha_colors[i].r = colors[i].a;
                alpha_colors[i].g = colors[i].a;
                alpha_colors[i].b = colors[i].a;
            }

            SaveTexture(alpha_tex_path, tex_alpha, alpha_colors);
        }

        private static TextureImporter GetTextureImporter(string path)
        {
            Debug.Log("path: " + path);
            TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
            textureImporter.textureType = TextureImporterType.GUI;
            textureImporter.npotScale = TextureImporterNPOTScale.ToNearest;
            return textureImporter;
        }

        /// <summary>  
        /// 设置图片为可读格式  
        /// </summary>  
        private static void SetTextureReadable(string alpha_tex_path,bool is_allow_read)
        {
            TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(alpha_tex_path);
            ti.isReadable = is_allow_read;
            ti.SaveAndReimport();
            //AssetDatabase.ImportAsset(alpha_tex_path);
        }

        private static void SaveTexture(string tex_path, Texture2D texture, Color[] pixels)
        {
            SetTextureReadable(tex_path, true);
            texture.SetPixels(pixels);
            texture.Apply();
            var bytes = texture.EncodeToPNG();
            SetTextureReadable(tex_path, false);


            if (File.Exists(tex_path)) AssetDatabase.DeleteAsset(tex_path);
            var stream = File.Create(tex_path);
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();

            AssetDatabase.ImportAsset(tex_path);
        }

        /// <summary>
        /// 读取配置表获取贴图要采用什么格式格式化
        /// </summary>
        public static TextureImporterFormat GetTextureFormatFromConfig(string tex_name)
        {
            // 根据配置文件TextureFormat里面的配置寻找图集名称
            // 找到相关项就转换成相关的项的主键并且转换成枚举
            // 如果找不到就直接返回默认类型
            string tex_config_json_str = System.IO.File.ReadAllText(ATLER_FORMAT_CONFIG_PATH, System.Text.Encoding.UTF8);
            JavaScriptObject jsonObj = JavaScriptConvert.DeserializeObject<JavaScriptObject>(tex_config_json_str);

            TextureImporterFormat format = TextureImporterFormat.ETC_RGB4;
            if (jsonObj.ContainsKey("TextureFormat"))
            {
                var item = (Dictionary<string, object>)jsonObj["TextureFormat"];

                foreach (var node in item)
                {
                    var key = node.Key;
                    if (key.CompareTo("DefaultFarmat") != 0)
                    {
                        JavaScriptArray list = (JavaScriptArray)node.Value;
                        foreach (string list_item in list)
                        {
                            if (list_item.CompareTo(tex_name) == 0)
                            {
                                return (TextureImporterFormat)System.Enum.Parse(typeof(TextureImporterFormat), (string)(node.Key));
                            }
                        }
                    }
                }
                if (item.ContainsKey("DefaultFarmat"))
                {
                    return (TextureImporterFormat)System.Enum.Parse(typeof(TextureImporterFormat), (string)(item["DefaultFarmat"]));
                }
            }
            else
            {
                Debug.LogError("Can't find config. config_path: " + ATLER_FORMAT_CONFIG_PATH);
            }

            return format;
        }

        public static void DitherTexture(string png_file_path)
        {
              var file_name = Path.GetFileNameWithoutExtension(png_file_path);
              var format = GetTextureFormatFromConfig(file_name);
              if (format == TextureImporterFormat.RGBA16)
              {
                  SetTextureDither(png_file_path);
              }
              else
              {
                  Debug.Log("can not diter texture ! check config! path: " + png_file_path);
              }
        }

        #endregion


        #region 获取打包图集的图片的大小
        /// <summary>
        /// 获取打包图集的图片的大小
        /// </summary>
        public static int GetTextrueSize(string atler_name)
        {
            //var tex_name = System.IO.Path.GetFileNameWithoutExtension(tex_path);

            // 根据配置文件TextureFormat里面的配置寻找图集名称
            // 找到相关项就转换成相关的项的主键并且转换成枚举
            // 如果找不到就直接返回默认类型
            string tex_config_json_str = System.IO.File.ReadAllText(ATLER_FORMAT_CONFIG_PATH, System.Text.Encoding.UTF8);
            JavaScriptObject jsonObj = JavaScriptConvert.DeserializeObject<JavaScriptObject>(tex_config_json_str);

            TextureImporterFormat format = TextureImporterFormat.ETC_RGB4;
            if (jsonObj.ContainsKey("TextureSize"))
            {
                var item = (Dictionary<string, object>)jsonObj["TextureSize"];

                foreach (var node in item)
                {
                    var key = node.Key;
                    if (key.CompareTo("DefaultSize") != 0)
                    {
                        JavaScriptArray list = (JavaScriptArray)node.Value;
                        foreach (string list_item in list)
                        {
                            if (list_item.CompareTo(atler_name) == 0)
                            {
                                return int.Parse(node.Key);
                            }
                        }
                    }
                }
                if (item.ContainsKey("DefaultFarmat"))
                {
                    return int.Parse((string)item["DefaultFarmat"]);
                }
            }
            else
            {
                Debug.LogError("Can't find config. config_path: " + ATLER_FORMAT_CONFIG_PATH);
            }

            return 1024;
        }
        #endregion


        #region 工具栏

        /// <summary>
        /// 剥离通道
        /// </summary>
        [MenuItem("TextureFormat/BuildAlphaTexture")]
        public static void BuildAlphaTexture()
        {
            GetSelectionPath(".png", (string file_path) =>
            {
                TryStripAlphaTexture(file_path);
            });
        }

        /// <summary>
        /// 抖动处理图片
        /// </summary>
        [MenuItem("TextureFormat/DiterTexture")]
        public static void DitherTexture()
        {
            GetSelectionPath(".png", DitherTexture);
        }

        /// <summary>
        /// 获取选择的文件进行操作
        /// </summary>
        /// <param name="match_end_with_str">结束匹配字段</param>
        /// <param name="func">找到要找到的字段后执行的方法</param>
        public static void GetSelectionPath(string match_end_with_str,Func func)
        {
            if(func == null)
            {
                Debug.Log("no func");
                return;
            }

            if(!string.IsNullOrEmpty(match_end_with_str))
            {
                var objs = UnityEditor.Selection.objects;
                if (objs!= null && objs.Length > 0)
                {
                    for (int i = 0; i < objs.Length; ++i)
                    {
                        var file_path = AssetDatabase.GetAssetPath(objs[i]);
                        if (file_path.EndsWith(match_end_with_str)) func(file_path);
                    }
                }
            }
        }

        public delegate void Func(string selection_path);
        #endregion
    }
}

这里是配置文件记载内容:

{
    "TextureFormat": {
        "RGBA32": [
           "RGBA32Alter" 
        ],
        "RGBA16": [
            "RGBA16Alter"
        ],
        "ETC_RGB4": [

        ],
        "DefaultFarmat": "ETC_RGB4"
    },
    "TextureSize": {
        "2048": [
           "RGBA32"
        ],
        "4086": [
           "RGBA16"
        ],
        "DefaultSize": "1024"
    },
    "注释": {
        "注释": "TextureFormat 是图集图片采用的格式化类型",
        "注释": "TextureFormat 中的 ETC_RGB4 格式代表同时也代表IOS平台的 PV4格式",
        "注释": "TextureSize 是打包Atler 的图集大小限定"
    }
}

接入NGUI实现全自动化

/**
 * UIAtlasMaker.cs
 * 直接修改NGUI的打包图集代码
 * 这里写出修改概要,如果要查看全部源码则可以下载工程
 */
public class UIAtlasMaker
{
// 省略 730 + 行
    void OnGUI ()
    {
        // 省略N行 Ngui源码

        /**
         * 补充代码 在NGUI 处理开始图片时转换格式先
         * NGUI bug 
         * ngui 图集转换格式为 RGBA32 
         * 手动转换格式为etc
         * 再用NGUI 图集追加图片
         * 这时候的图片的部分透明通道就没有掉了。
         * 修复方法,强转一个RGBA32
         */
         if ((delete || update || replace) && NGUISettings.atlas != null && NGUISettings.atlas.texture != null)
         {
                var tex_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.texture);
                TextureFormat.AtlerTextureFormat.ConvertTextureFormatToTargetFormat(tex_path,TextureImporterFormat.RGBA32);
         }


        if (delete)
        {
            // 省略NGUI源码
        }
        else if (update) UpdateAtlas(textures, true);
        else if (replace) UpdateAtlas(textures, false);

        if (NGUISettings.atlas != null && !string.IsNullOrEmpty(selection))
        {
            // 省略NGUI源码
        }
        else if (update || replace)
        {
            // 省略NGUI源码
        }

        // 补充代码 接入图片处理
        if (delete || update || replace)
        {
            if (NGUISettings.atlas != null && NGUISettings.atlas.spriteMaterial != null && NGUISettings.atlas.texture != null)
            {
                // 新图片就要走普通流程
                // 已经更改过纹理的图片就需要走特殊流程
                if (replace && GetIsNewTexture())
                {
                    var tex_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.texture);
                    var mat_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.spriteMaterial);
                    TextureFormat.AtlerTextureFormat.FormatTexture(tex_path, mat_path);
                }
                else
                {
                    var tex_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.texture);
                    var file_name = System.IO.Path.GetFileNameWithoutExtension(tex_path);
                    var format = TextureFormat.AtlerTextureFormat.GetTextureFormatFromConfig(file_name);
                    switch (format)
                    {
                        case TextureImporterFormat.RGBA16:
                            // 先转成rgba32为
                            // 进行抖动处理
                            // 转化成rgba 16 位
                            TextureFormat.AtlerTextureFormat.ConvertTextureFormatToTargetFormat(tex_path, TextureImporterFormat.RGBA32);
                            TextureFormat.AtlerTextureFormat.DitherTexture(tex_path);
                            TextureFormat.AtlerTextureFormat.ConvertTextureFormatToTargetFormat(tex_path, TextureImporterFormat.RGBA16);
                            break;
                        case TextureImporterFormat.ETC_RGB4:
                            TextureFormat.AtlerTextureFormat.TryStripAlphaTexture(tex_path);
                            break;
                        default:
                            Debug.Log("Ignore format!");
                            break;
                    }
                    TextureFormat.AtlerTextureFormat.TryStripAlphaTexture(tex_path);
                }
            }
        }
    }

    static public bool UpdateTexture (UIAtlas atlas, List<SpriteEntry> sprites)
    {

        // 省略NGUI源码
        if (newTexture)
        {
            // Create a new texture for the atlas            
            tex = new Texture2D(1, 1, UnityEngine.TextureFormat.ETC_RGB4, false);

            // 在这里填充代码
            SetIsNewTexture(true);
        }
    }

    /*** 填充代码 ****/
    private static bool m_is_new_tex = false;
    private static void SetIsNewTexture(bool is_new_tex)
    {
            m_is_new_tex = is_new_tex;
    }
    /// <summary> 获取是否新创建的图片,只能读取一次 </summary>
    private static bool GetIsNewTexture()
    {   
        // 只能读取一次
        if(m_is_new_tex)
        {
           m_is_new_tex = false;
           return true;
        }
        return false;
    }
}

项目补充

这份代码目前还存在一定的bug需要修复,其中之一就是UIpanel,具体修改如下。
bug修复

还有一个小功能没有完成,就是图片限定大小的功能,图集越大加载速度越慢,所以有些游戏需要对图集大小有要求,而且也不想依赖项目规范的,可以使用Config中TextureSize字段去补充。

项目工程下载地址

项目工程:http://pan.baidu.com/s/1slsMQex

猜你喜欢

转载自blog.csdn.net/biospc/article/details/78077763