NGUI打图集工具Alpha通道分离

工具设计目的

NGUI中图集默认使用"Unlit/Transparent Colored" Shader来创建材质。这样的话需要一张RGBA32的带透明通道的贴图,一张515x512的图占用空间1M,加载到内存后变成2M。在手机内存还是比较宝贵的时代这个是不大能接受的。一般项目的做法是分隔成两张图一张color图包含RGB通道,一张alpha图包含A通道。通过shader来组合长RGBA的图,来实现接近于RGBA的效果。通常Android使用ETC格式,IOS使用PVRTC格式。一张ETC格式的512x512的图占用128kb,两张一起256kb。这样相对于RGBA32格式的只占用其四分之一的空间。

NGUI自带的图集工具并不支持打ETC通道分离。通常一般做法采用第三方工具TexturePacker来做这个,但是使用第三方工具来做这个也会比较麻烦。所以通过修改NGUI打图集工具来实现这个通道分离。

图集增加通道分离

通道分离的做法是生成一张完整RGBA32的图片,然后读取RGBA分别生成两张和RGBA32一样大小的RGB图片和Alpha图片。最后替换shader,生成两张图片合成的材质 。代码如下:

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using com.geargames.common.utils;

public class UIAtlasChangeShaderTool : EditorWindow
{
    private bool _mSearched;
    private Object[] mObjects;
    private Vector2 mScroll = Vector2.zero;
    public static bool useAlpha = true;
    const string defShader = "Unlit/Transparent Colored";
    const string newShader = "这里填合成两张图的shader名字";

    private readonly string[] atlasPaths = { "这里填图集所在路径"};

    [MenuItem("Tool/Open UIAtlas Shader Change Window")]
    public static void OpenWindow()
    {
        UIAtlasChangeShaderTool window = (UIAtlasChangeShaderTool)EditorWindow.GetWindow(typeof(UIAtlasChangeShaderTool));
        window.Show();
    }
    void OnGUI()
    {
        if (mObjects != null && mObjects.Length != 0)
        {
            mScroll = GUILayout.BeginScrollView(mScroll);
            foreach (Object o in mObjects)
            {
                DrawUIAtalsObject(o);
            }
            GUILayout.EndScrollView();
        }
        GUILayout.Space(6f);
        GUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        bool search = GUILayout.Button("Show All", "LargeButton", GUILayout.Width(120f));
        bool Normal = GUILayout.Button("Normal All", "LargeButton", GUILayout.Width(120f));
        bool Etc = GUILayout.Button("Etc All", "LargeButton", GUILayout.Width(120f));
        bool CreateTexture = GUILayout.Button("CreateTexture All", "LargeButton", GUILayout.Width(200f));
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();
        if (search) Search(typeof(UIAtlas));

        if (mObjects == null || mObjects.Length == 0) return;
        if (Normal) foreach (Object o in mObjects) _ChangeShaderToNormal(o as UIAtlas);
        if (Etc) foreach (Object o in mObjects) _ChangeShaderToEtc(o as UIAtlas);
        if (CreateTexture)foreach (Object o in mObjects) SeperateRGBAandlphaChannel((o as UIAtlas).spriteMaterial.mainTexture as Texture2D);
    }

    private void DrawUIAtalsObject(Object obj)
    {
        if (obj == null) return;
        Component comp = obj as Component;
        UIAtlas at = obj as UIAtlas;
        GUILayout.BeginHorizontal();
        {
            string path = AssetDatabase.GetAssetPath(obj);

            if (string.IsNullOrEmpty(path))
            {
                path = "[Embedded]";
                GUI.contentColor = new Color(0.7f, 0.7f, 0.7f);
            }
            else if (comp != null && EditorUtility.IsPersistent(comp.gameObject))
                GUI.contentColor = new Color(0.6f, 0.8f, 1f);

            GUILayout.Label(obj.name, "TextArea", GUILayout.Width(160f), GUILayout.Height(20f));
            GUILayout.Label(path.Replace("Assets/", ""), "TextArea", GUILayout.Width(300f), GUILayout.Height(20f));
            GUILayout.Label(at.spriteMaterial.shader.name, "TextArea", GUILayout.Width(200f), GUILayout.Height(20f));
            GUI.contentColor = Color.white;
            if (GUILayout.Button("Normal", "ButtonLeft", GUILayout.Width(60f), GUILayout.Height(16f)))
            {
                _ChangeShaderToNormal(at);
                AssetDatabase.Refresh();
            }

            if (GUILayout.Button("Etc", "ButtonLeft", GUILayout.Width(60f), GUILayout.Height(16f)))
            {
                _ChangeShaderToEtc(at);
                AssetDatabase.Refresh();
            }
            
            if (GUILayout.Button("CreateTexture", "ButtonLeft", GUILayout.Width(100f), GUILayout.Height(16f)))
            {
               string assetPath =  AssetDatabase.GetAssetPath(at).Replace(".prefab",".png")  ;
                Texture2D texture = AssetDatabase.LoadAssetAtPath(assetPath,typeof(Texture2D)) as Texture2D;
                SeperateRGBAandlphaChannel(at.texture as Texture2D);
                AssetDatabase.Refresh();
            }
        }
        GUILayout.EndHorizontal();

    }
    protected void Search(System.Type mType)
    {
        _mSearched = true;
        List<string> pathList = new List<string>();
        _GetAllAtlasPath(pathList);
        bool isComponent = mType.IsSubclassOf(typeof(Component));
        List<Object> list = new List<Object>();

        if (mObjects != null)
        {
            for (int i = 0; i < mObjects.Length; ++i)
                if (mObjects[i] != null)
                    list.Add(mObjects[i]);
        }

        string path = "";

        string[] paths = pathList.ToArray();
        for (int i = 0; i < paths.Length; ++i)
        {
            path = paths[i];

            EditorUtility.DisplayProgressBar("Loading", "Searching assets, please wait...", (float)i / paths.Length);
            Object obj = AssetDatabase.LoadMainAssetAtPath(path);
            if (obj == null || list.Contains(obj)) continue;

            if (!isComponent)
            {
                System.Type t = obj.GetType();
                if (t == mType || t.IsSubclassOf(mType) && !list.Contains(obj))
                    list.Add(obj);
            }
            else if (PrefabUtility.GetPrefabType(obj) == PrefabType.Prefab)
            {
                Object t = (obj as GameObject).GetComponent(mType);
                if (t != null && !list.Contains(t)) list.Add(t);
            }
        }
        list.Sort(delegate (Object a, Object b) { return a.name.CompareTo(b.name); });
        mObjects = list.ToArray();

        EditorUtility.ClearProgressBar();
    }
    private void _GetAllAtlasPath(List<string> pathList)
    {
        for (int i = 0; i < atlasPaths.Length; i++)
        {
            FileUtils.GetAllFiles(Application.dataPath + atlasPaths[i], pathList);
        }

        for (int i = 0; i < pathList.Count; i++)
        {
            pathList[i] = pathList[i].Replace("\\", "/").Replace(Application.dataPath, "Assets");
        }
    }

    static string c_flg = "_C";
    static string a_flg = "_A";
    static string exp_flg = ".png";
    static private void _ChangeShaderToNormal(UIAtlas obj)
    {
        if (obj.spriteMaterial.shader.name != defShader)
        {
            obj.spriteMaterial.shader = Shader.Find(defShader);
            string path = AssetDatabase.GetAssetPath(obj.spriteMaterial.mainTexture);
            string p = path.Substring(0, path.IndexOf(c_flg + "."))+ ".png";
            obj.spriteMaterial.mainTexture = AssetDatabase.LoadAssetAtPath(p , typeof(Texture2D)) as Texture2D;
            AssetDatabase.SaveAssets();
        }
    }
    static private void _ChangeShaderToEtc(UIAtlas obj)
    {
        if (obj.spriteMaterial.shader.name != newShader)
        {
            obj.spriteMaterial.shader = Shader.Find(newShader);
            string path = AssetDatabase.GetAssetPath(obj.spriteMaterial.mainTexture);
            string p = path.Substring(0, path.IndexOf("."));
            string cpath = p + c_flg + exp_flg;
            string apath = p + a_flg + exp_flg;
            obj.spriteMaterial.mainTexture = AssetDatabase.LoadAssetAtPath(cpath, typeof(Texture2D)) as Texture2D;
            obj.spriteMaterial.SetTexture("_MaskTex", AssetDatabase.LoadAssetAtPath(apath, typeof(Texture2D)) as Texture2D);
            Debug.Log("path = " + cpath + "  " + apath);
            AssetDatabase.SaveAssets();
        }
    }
    static void SeperateRGBAandlphaChannel(Texture2D sourcetex)
    {
        string path = AssetDatabase.GetAssetPath(sourcetex);
        AssetDatabase.ImportAsset(path);
        string p = path.Substring(0, path.IndexOf("."));
        string cpath = p + c_flg + exp_flg;
        string apath = p + a_flg + exp_flg;
        AssetDatabase.DeleteAsset(cpath);
        AssetDatabase.DeleteAsset(apath);
        TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
        MakeTextureReadable(ti, path, false);
        Color[] sc = sourcetex.GetPixels();
        int sw = sourcetex.width;
        int sh = sourcetex.height;
        Texture2D rgbTex = new Texture2D(sw, sh, TextureFormat.RGB24, false);
        Texture2D alphaTex = new Texture2D(sw, sh, TextureFormat.RGB24, false);
        Color[] ac = new Color[sw * sh];
        for (int i = 0; i <sw; i++)
        {
            for (int j = 0; j < sh; j++)
            {
                int ind = j * sw + i;
                Color color = sc[ind];
                color.r = color.a;
                color.g = color.a;
                color.b = color.a;
                ac[ind] = color;
            }
        }
        rgbTex.SetPixels(sc);
        rgbTex.Apply();
        alphaTex.SetPixels(ac);
        alphaTex.Apply();
        byte[] bytes = rgbTex.EncodeToPNG();
        File.WriteAllBytes(cpath, bytes);
        bytes = alphaTex.EncodeToPNG();
        File.WriteAllBytes(apath, bytes);

        AssetDatabase.ImportAsset(cpath);
        AssetDatabase.ImportAsset(apath);
        TextureImporter tic = AssetImporter.GetAtPath(cpath) as TextureImporter;
        TextureImporter tia = AssetImporter.GetAtPath(apath) as TextureImporter;
        SetTextureFormat(tic, tia);
        MakeTextureAnAtlas(ti, path, false, true);
        MakeTextureAnAtlas(tic, cpath, false, false);
        MakeTextureAnAtlas(tia, apath, false, true);
    }
    static public bool MakeTextureReadable(TextureImporter ti, string path, bool force)
    {
        TextureImporterSettings settings = new TextureImporterSettings();
        ti.ReadTextureSettings(settings);

        if (force || !settings.readable || settings.npotScale != TextureImporterNPOTScale.None || settings.alphaIsTransparency)
        {
            settings.readable = true;
            if (NGUISettings.trueColorAtlas) settings.textureFormat = TextureImporterFormat.AutomaticTruecolor;
            settings.npotScale = TextureImporterNPOTScale.None;
            settings.alphaIsTransparency = false;
            ti.SetTextureSettings(settings);
            AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
        }
        return true;
    }
    static bool MakeTextureAnAtlas(TextureImporter ti, string path, bool force, bool alphaTransparency)
    {
        TextureImporterSettings settings = new TextureImporterSettings();
        ti.ReadTextureSettings(settings);

        if (force ||
            settings.readable ||
            settings.maxTextureSize < 4096 ||
            settings.wrapMode != TextureWrapMode.Clamp ||
            settings.npotScale != TextureImporterNPOTScale.ToNearest)
        {
            settings.readable = false;
            settings.maxTextureSize = 4096;
            settings.wrapMode = TextureWrapMode.Clamp;
            settings.npotScale = TextureImporterNPOTScale.ToNearest;

            if (NGUISettings.trueColorAtlas)
            {
                settings.textureFormat = TextureImporterFormat.ARGB32;
                settings.filterMode = FilterMode.Trilinear;
            }

            settings.aniso = 4;
            settings.alphaIsTransparency = alphaTransparency;
            ti.SetTextureSettings(settings);
            AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
        }
        return true;
    }
    static public void SetTextureFormat(TextureImporter tic, TextureImporter tia)
    {
        tic.alphaIsTransparency = false;
        tic.isReadable = tia.isReadable = false;
        tic.mipmapEnabled = tia.mipmapEnabled = false;
        // TextureImporterPlatformSettings textureImporterPlatformSettings = tic.
        // UnityEditor.TextureImporter.SetPlatformTextureSettings();
        
        TextureImporterPlatformSettings tips = new TextureImporterPlatformSettings();
        tips.name = "Android";
        tips.overridden = true;
        tips.maxTextureSize = 2048;
        tips.format = TextureImporterFormat.ETC_RGB4;
        tips.textureCompression = (int)UnityEditor.TextureCompressionQuality.Fast;

        tic.SetPlatformTextureSettings(tips);
        tia.SetPlatformTextureSettings(tips);
        tips.overridden = true;
        tips.name = "iPhone";
        tips.format = TextureImporterFormat.PVRTC_RGB4;
        tic.SetPlatformTextureSettings(tips);
        tia.SetPlatformTextureSettings(tips);
#if UNITY_ANDROID || UNITY_STANDALONE
        tips.name = "Standalone";
        tips.format = TextureImporterFormat.ETC_RGB4;
        tips.overridden = true;
        tic.SetPlatformTextureSettings(tips);
        tia.SetPlatformTextureSettings(tips);
        // tic.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.ETC_RGB4, (int)TextureCompressionQuality.Fast);
        // tia.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.ETC_RGB4, (int)TextureCompressionQuality.Fast);
        // tic.textureFormat = TextureImporterFormat.ETC_RGB4;
        // tia.textureFormat = TextureImporterFormat.ETC_RGB4;
#else
        tips.name = "Standalone";
        tips.format = TextureImporterFormat.PVRTC_RGB4;
        tips.overridden = true;
        tic.SetPlatformTextureSettings(tips);
        tia.SetPlatformTextureSettings(tips);
        // tic.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.PVRTC_RGB4, (int)TextureCompressionQuality.Fast);
        // tia.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.PVRTC_RGB4, (int)TextureCompressionQuality.Fast);
        // tic.textureFormat = TextureImporterFormat.PVRTC_RGB4;
        // tia.textureFormat = TextureImporterFormat.PVRTC_RGB4;
#endif
    }

    public static bool flg = false;
    static public void OnInspectorGUI(UIAtlas mLastAtlas)
    {
        GUILayout.BeginHorizontal();
        UIAtlasChangeShaderTool.flg = EditorGUILayout.Toggle("spaceAlpha", UIAtlasChangeShaderTool.flg, GUILayout.Width(100f));
        GUILayout.Label("分隔color,Alpha两通道");
        GUILayout.EndHorizontal();
    }
    static public void updateAtlas(UIAtlas mLastAtlas)
    {
        updateAtlas(mLastAtlas, UIAtlasChangeShaderTool.flg);
    }
    static public void updateAtlas(UIAtlas mLastAtlas, bool flg)
    {
        if (flg)
        {
            SeperateRGBAandlphaChannel(mLastAtlas.texture as Texture2D);
            _ChangeShaderToEtc(mLastAtlas);
        }
        else
        {
            _ChangeShaderToNormal(mLastAtlas);
        }
        AssetDatabase.Refresh();
    }
}

注意,我这用的是Unity2018.3,比较早期的版本在图集格式设置那块需要修改 。就是SetPlatformTextureSettings这个方法。
NGUI中打图集的工具写在UIAtlasMaker.cs里面。我们需要在系统打图集的选项里面增加一个选择打分离通道的。如下:


image.png

UIAtlasMaker.cs需要增加代码位置为:

    void OnEnable () { instance = this;
                //默认勾选上
        UIAtlasChangeShaderTool.flg = true;
    }
void OnGUI ()
    {
//省略之前代码。在布局的最后添加这个。
        UIAtlasChangeShaderTool.OnInspectorGUI(NGUISettings.atlas);
        NGUIEditorTools.EndContents();

                if (delete)
                {
                    List<SpriteEntry> sprites = new List<SpriteEntry>();
                    ExtractSprites(NGUISettings.atlas, sprites);

                    for (int i = sprites.Count; i > 0; )
                    {
                        SpriteEntry ent = sprites[--i];
                        if (mDelNames.Contains(ent.name))
                            sprites.RemoveAt(i);
                    }
                    UpdateAtlas(NGUISettings.atlas, sprites);
                    mDelNames.Clear();
                    NGUIEditorTools.RepaintSprites();
                }
                else if (update){
                     //更新的位置还原默认图集,用来生成RGBA32
                    UIAtlasChangeShaderTool.updateAtlas(NGUISettings.atlas, false);
                    UpdateAtlas(textures, true);
                } 
                else if (replace) UpdateAtlas(textures, false);

                if (NGUISettings.atlas != null && !string.IsNullOrEmpty(selection))
                {
                    NGUIEditorTools.SelectSprite(selection);
                }
                else if (NGUISettings.autoUpgradeSprites && (update || replace))
                {
                    NGUIEditorTools.UpgradeTexturesToSprites(NGUISettings.atlas);
                    NGUIEditorTools.RepaintSprites();
                  //这里生成两个图集
                    UIAtlasChangeShaderTool.updateAtlas(NGUISettings.atlas);
                }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容