Unity工程里图片的RGB和Alpha通道的分离,及显示带有Alpha通道贴图的Material

发表于2017-11-03
评论0 2.3k浏览

背景:ETC1图片格式的罪孽,不支持Alpha通道。于是程序员们将一些气力浪费在Alpha通道的处理上。 为了能使用ETC1,同时某些透明效果必须有Alpha通道,一般的处理方式是将RGB和Alpha分为两张图片分别储存。 只存Alpha通道的图片及RGB都为要存的Alpha值,因为熵比较小,图片尺寸也可以相应减小一些。


要做的工作:

1. 将带有Alpha通道的图片,另存为两张图片,一张只存RGB信息,另一张只存Alpha信息。建议保持为图片原目录,名称加后缀“_RGB”, "_Alpha"。

2. 带有Alpha通道的图片,所用的Shader要更新为支持RGB和Alpha信息分别从两张不同图片读取的shader。这个功能,因为不能的Material的Shader会很不一样,因此,不用程序来强硬指定了。但程序起码需要给出提示,工程中哪些Material用到了哪些带有Alpha通道的图片。

不罗嗦了,直接上代码。

using UnityEngine;  
using System.Collections;  
using System.Collections.Generic;  
using UnityEditor;  
using System.IO;  
using System.Reflection;  
public class MaterialTextureForETC1{  
    public static float sizeScale = 0.5f;   //the size decrease scale for alphaTexture  
    public static Dictionary<string, bool> texturesAlphaDic = new Dictionary<string, bool>();  
    [MenuItem("EffortForETC1/Seperate RGB and Alpha Channel for All Textures")]  
    static void SeperateAllTexturesRGBandAlphaChannel()  
    {  
        string[] paths = Directory.GetFiles(Application.dataPath, "*.*", SearchOption.AllDirectories);  
        foreach (string path in paths)  
        {  
            if (!string.IsNullOrEmpty(path) && IsTextureFile(path))   //full name  
            {  
                SeperateRGBAandlphaChannel(path);  
            }  
        }   
    }  
    [MenuItem("EffortForETC1/Show Materials Having Textures with Alpha Chanel")]  
    static void ShowMaterialsHavingTextureswithAlphaChanel()  
    {  
        CalculateTexturesAlphaChannelDic();  
        string[] matpaths = Directory.GetFiles(Application.dataPath, "*.mat", SearchOption.AllDirectories);  
        foreach (string matpath in matpaths)  
        {  
            string propermatpath = GetRelativeAssetPath(matpath);  
            Material mat = (Material)AssetDatabase.LoadAssetAtPath(propermatpath, typeof(Material));  
            if (mat)  
            {  
                string[] alphatexpaths = GetMaterialTexturesHavingAlphaChannel(mat);  
                if (alphatexpaths.Length == 0)  
                {  
                    continue;  
                }  
                Debug.Log("Material having texture(s) with Alpha channel : "   propermatpath);  
                foreach (string alphatexpath in alphatexpaths)  
                {  
                    Debug.Log(alphatexpath   " in "   propermatpath);  
                }  
            }  
            else  
            {  
                Debug.LogError("Load material failed : "   matpath);  
            }  
        }  
        Debug.Log("Finish!");       
    }  
    #region inspect material  
    static string[] GetMaterialTexturesHavingAlphaChannel(Material _mat)  
    {  
        List<string> alphatexpaths = new List<string>();  
        string[] texpaths = GetMaterialTexturePaths(_mat);  
        foreach (string texpath in texpaths)  
        {  
            if (texturesAlphaDic[texpath])  
            {  
                alphatexpaths.Add(texpath);  
            }  
        }  
        return alphatexpaths.ToArray();  
    }  
    static string[] GetMaterialTexturePaths(Material _mat)  
    {  
        List<string> results = new List<string>();  
        Object[] roots = new Object[] { _mat };  
        Object[] dependObjs = EditorUtility.CollectDependencies(roots);  
        foreach (Object dependObj in dependObjs)  
        {  
            if (dependObj.GetType() == typeof(Texture2D))  
            {  
                string texpath = AssetDatabase.GetAssetPath(dependObj.GetInstanceID());  
                results.Add(texpath);  
            }  
        }  
        return results.ToArray();  
    }  
    #endregion  
    static void CalculateTexturesAlphaChannelDic()  
    {  
        string[] paths = Directory.GetFiles(Application.dataPath, "*.*", SearchOption.AllDirectories);  
        foreach (string path in paths)  
        {  
            if (!string.IsNullOrEmpty(path) && IsTextureFile(path))   //full name  
            {  
                string assetRelativePath = GetRelativeAssetPath(path);  
                SetTextureReadable(assetRelativePath);  
                Texture2D sourcetex = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(Texture2D)) as Texture2D;    
                if (!sourcetex)  //make sure the file is really Texture2D which can be loaded as Texture2D.  
                {  
                    continue;  
                }  
                if (HasAlphaChannel(sourcetex))  
                {  
                    AddValueToDic(assetRelativePath, true);                                      
                }  
                else  
                {  
                    AddValueToDic(assetRelativePath, false);    
                }  
            }  
        }   
    }  
    static void AddValueToDic(string _key, bool _val)  
    {  
        if (texturesAlphaDic.ContainsKey(_key))  
        {  
            texturesAlphaDic[_key] = _val;  
        }  
        else  
        {  
            texturesAlphaDic.Add(_key, _val);  
        }  
    }  
    #region process texture  
    static void SeperateRGBAandlphaChannel(string _texPath)  
    {  
        string assetRelativePath = GetRelativeAssetPath(_texPath);  
        SetTextureReadable(assetRelativePath);  
        Texture2D sourcetex = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(Texture2D)) as Texture2D;  //not just the textures under Resources file  
        if (!sourcetex)  
        {  
            Debug.Log("Load Texture Failed : "   assetRelativePath);  
            return;  
        }  
        if (!HasAlphaChannel(sourcetex))  
        {  
            Debug.Log("Texture does not have Alpha channel : "   assetRelativePath);  
            return;  
        }  
        Texture2D rgbTex = new Texture2D(sourcetex.width, sourcetex.height, TextureFormat.RGB24, true);  
        Texture2D alphaTex = new Texture2D((int)(sourcetex.width * sizeScale), (int)(sourcetex.height * sizeScale), TextureFormat.RGB24, true);  
        for (int i = 0; i < sourcetex.width;  i)  
            for (int j = 0; j < sourcetex.height;  j)  
            {  
                Color color = sourcetex.GetPixel(i, j);  
                Color rgbColor = color;  
                Color alphaColor = color;  
                alphaColor.r = color.a;  
                alphaColor.g = color.a;  
                alphaColor.b = color.a;  
                rgbTex.SetPixel(i, j, rgbColor);  
                alphaTex.SetPixel((int)(i * sizeScale), (int)(j * sizeScale), alphaColor);  
            }  
        rgbTex.Apply();  
        alphaTex.Apply();  
        byte[] bytes = rgbTex.EncodeToPNG();  
        File.WriteAllBytes(GetRGBTexPath(_texPath), bytes);  
        bytes = alphaTex.EncodeToPNG();  
        File.WriteAllBytes(GetAlphaTexPath(_texPath), bytes);  
        Debug.Log("Succeed to seperate RGB and Alpha channel for texture : "   assetRelativePath);  
    }  
    static bool HasAlphaChannel(Texture2D _tex)  
    {  
        for (int i = 0; i < _tex.width;  i)  
            for (int j = 0; j < _tex.height;  j)  
            {  
                Color color = _tex.GetPixel(i, j);  
                float alpha = color.a;  
                if (alpha < 1.0f - 0.001f)  
                {  
                    return true;  
                }  
            }  
        return false;  
    }  
    static void SetTextureReadable(string _relativeAssetPath)  
    {  
        string postfix = GetFilePostfix(_relativeAssetPath);  
        if (postfix == ".dds")    // no need to set .dds file.  Using TextureImporter to .dds file would get casting type error.  
        {  
            return;  
        }  
        TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(_relativeAssetPath);  
        ti.isReadable = true;  
        AssetDatabase.ImportAsset(_relativeAssetPath);  
    }  
    #endregion  
    #region string or path helper  
    static bool IsTextureFile(string _path)  
    {  
        string path = _path.ToLower();  
        return path.EndsWith(".psd") || path.EndsWith(".tga") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".dds") || path.EndsWith(".bmp") || path.EndsWith(".tif") || path.EndsWith(".gif");  
    }  
    static string GetRGBTexPath(string _texPath)  
    {  
        return GetTexPath(_texPath, "_RGB.");  
    }  
    static string GetAlphaTexPath(string _texPath)  
    {  
        return GetTexPath(_texPath, "_Alpha.");  
    }  
    static string GetTexPath(string _texPath, string _texRole)  
    {  
        string result = _texPath.Replace(".", _texRole);  
        string postfix = GetFilePostfix(_texPath);  
        return result.Replace(postfix, ".png");  
    }  
    static string GetRelativeAssetPath(string _fullPath)  
    {  
        _fullPath = GetRightFormatPath(_fullPath);  
        int idx = _fullPath.IndexOf("Assets");  
        string assetRelativePath = _fullPath.Substring(idx);  
        return assetRelativePath;  
    }  
    static string GetRightFormatPath(string _path)  
    {  
        return _path.Replace("\\", "/");  
    }  
    static string GetFilePostfix(string _filepath)   //including '.' eg ".tga", ".dds"  
    {  
        string postfix = "";  
        int idx = _filepath.LastIndexOf('.');  
        if (idx > 0 && idx < _filepath.Length)  
            postfix = _filepath.Substring(idx, _filepath.Length - idx);  
        return postfix;  
    }  
    #endregion     
} 


这篇文章里有两个明显的问题:
1. 处理Alpha贴图时是一个像素一个像素地处理,用Texture.SetPixel()函数。推荐批量处理,用Texture.SetPixels()函数。推荐批量处理,用Texture.SetPixels()函数。
2. Alpha贴图只存储Alpha通道信息,信息熵较低,理论上可以缩减一些尺寸来存储,但简单地按比例缩小可能会有问题,例如Alpha通道像素相邻一直为黑白黑白的情况,会严重失真,这里的取样需要考虑下。


【改进版】Unity工程里图片的RGB和Alpha通道的分离

http://blog.csdn.net/e295166319/article/details/52624155

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引