一,图集的相关概念
1.1 图集的定义
  图集是将许多较小的,独立的纹理合并到一个较大的纹理文件中,从而最小化材质的数量,因此最小化所需使用的 Draw Call 数量。这是利用动态批处理的有效方法。
  每个独特的材质都需要额外的 Draw Call,但是每种材质只支持单一的主纹理。当然,它们也可以支持多个二级纹理,比如法线纹理和发射纹理。然而,将多个主纹理合并到一个大的纹理文件中,渲染这个纹理的对象时,可以最小化所使用的 Draw Call 数量。
⚠️ :图集只是当所有给低昂的纹理需要相同的着色器时采用的一种方法,如果一些纹理需要通过着色器应用独立的图形效果,它们就必须分离到自己的材质中,并在单独的组中打图集。
1.2 图集的意义
- 减少 DrawCall:多张图片需要多次 DrawCall,合并成一张大图只需要调用一次 DrawCall 
- 性能优化:图集将图片打包为 2 的幂次方的素材大小,GPU 处理起来会快很多,方便 unity 渲染合批,降低渲染消耗,小图不可能每张都是 2 的 n 次方的大小,所以会提升效率。 
- 减小包体大小:多张图片的打包占用会大于一张 2 的 n 次方的大图的大小。 
 
 
游戏大厅需要这么多张图片: 
如果我么单张使用,这就存在上面说的三个问题了:多个 DrawCall,效率降低,包体变大。
为了解决这三个问题,图集的存在就变得有意义了。那么可能就有同学要问了,图片是美术给的,那么图集也由美术打包,这样可行吗?
答案是:可行,但有一定的限制。
这是美术将所有用到的图片放在一起打包的图集: 
  我们在使用的时候需要自己拆分,这样使用起来就不是很方便了。最关键的是以后大厅随便添加或者修改一张图都需要美术给我们一整张图集,这样明显是不合理的。 所以只有当场景资源固定了,以后不会在有任何改动的情况下,是可以由美术出图集的。
  但是,稍微有点经验的程序员都不会使用上述这种情况。因为项目本身不被修改那几乎是不可能的,有的时候不但要改,而且还要大改;所以将打包图集放在程序上来出来也是更方便,更合理的,下面我们就来一起学习一下 Unity 的图集吧。
二,图集的属性介绍
2.1 属性面板
Objects For Packing:将此列表中的所有项目打包到当前选定的 Sprite 图集中,点击+加号,则可选择一张图片放入图集中,所有图集选择完毕后,点击Pack Preview结果如下图所示: 
创建新的 Sprite Atlas,然后设置 Type 为Variant类型,并设置关联的 Master Atlas,修改 Scale 即可改变分辨率,可以看到当 Scale 设置为 0.5 时,图片以及变的模糊了: 
2.2 格式处理
Platform-specific overrides panel :为 Sprite Atlas 的每个目标平台设置分辨率、文件大小以及相关内存大小要求、像素尺寸和纹理质量。该面板允许您覆盖精灵图集包含的各个纹理上的这些设置。
后面那些带有自己平台的标识的,都可以通过勾选 Override 属性,来设置对应平台的图集属性。 比如:在移动的平台(Android,IOS)需要控制包体,那么可以将图集格式设置为 ASCT6x6;而在 PC 端图片的清晰度比包体大小跟重要,则图集可是就可以设置为 RGBA32。 
2.3 代码操作
- 创建图集 
 SpriteAtlas spriteAtlas = new SpriteAtlas();AssetDatabase.CreateAsset(spriteAtlas, "Assets/CzhenyaTest.spriteatlas");
   复制代码
 - 添加图片到 Sprite Atlas 图集 
 SpriteAtlas spriteAtlas = new SpriteAtlas();// 获取图集下图片List<Object> packables = new List<Object>(spriteAtlas.GetPackables());
// 每个图集的所有图片路径private static List<string> textureFullName = new List<string>();foreach (string item in textureFullName){   // 加载指定目录下的图片   Object spriteObj = AssetDatabase.LoadAssetAtPath(item, typeof(Object));   if (!packables.Contains(spriteObj))   {       // 添加到图集中      spriteAtlas.Add(new Object[] {spriteObj});   }}
   复制代码
 - 图集基础设置 
 SpriteAtlas spriteAtlas = new SpriteAtlas();
SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings(){    blockOffset = 1,    enableRotation = false,    enableTightPacking = false,    padding = 8,};
spriteAtlas.SetPackingSettings(packSetting);      
   复制代码
 - 图集纹理设置 
 SpriteAtlas spriteAtlas = new SpriteAtlas();
SpriteAtlasTextureSettings textureSettings = new SpriteAtlasTextureSettings(){    readable = false,    generateMipMaps = false,    sRGB = true,    filterMode = FilterMode.Bilinear,};
spriteAtlas.SetTextureSettings(textureSettings);        
   复制代码
 - 分平台设置纹理 
 SpriteAtlas spriteAtlas = new SpriteAtlas();
TextureImporterPlatformSettings platformSetting = atlas.GetPlatformSettings("Android");platformSetting.overridden = true;platformSetting.maxTextureSize = 2048;platformSetting.textureCompression = TextureImporterCompression.Compressed;platformSetting.format = TextureImporterFormat.ASTC_6x6;
spriteAtlas.SetPlatformSettings(platformSetting);
   复制代码
 
三,拓展 -- 打包图集工具
3.1 图片存放策略
创建在Editor下面一个所有图片的根目录,然后在根据游戏功能或者面板公用等条件,将同一组图片归类到一个文件夹下。比如:Editor/Textures/Login,Editor/Textures/Lobby
这是我下面 Demo 中使用的图片路径:Assets/CreateAtlas/Editor/Res/SpriteAtlas 
PS:按照设计以后新增的图片都会按照功能再分文件夹放到 SpriteAtlas 文件夹下。
3.2 图集打包逻辑
3.3 图集打包代码
按照 3.2 的逻辑编写代码,注释写的已很详细:
注意代码放到Editor文件夹下。
 using System;using System.Collections;using System.Collections.Generic;using System.IO;using UnityEditor;using UnityEditor.U2D;using UnityEngine;using UnityEngine.U2D;using Object = UnityEngine.Object;
public class CreateAtlas : MonoBehaviour{    /// <summary>    /// 图片根目录 -- 需要打包图集的文件夹父级    /// 适用目录结构:根部文件夹    ///                -> 图片文件夹1    ///                -> 图片文件夹2    ///                ...     /// </summary>    private static string pathRoot = Application.dataPath + "/CreateAtlas/Editor/Res/SpriteAtlas/";        /// <summary>    /// 图集存储路径    /// </summary>Assets/CreateAtlas/Editor/Res/Textures    private static string atlasStoragePath = "Assets/CreateAtlas/Editor/Res/Textures/";        /// <summary>    /// 每个需要打图集的文件夹名 -- 即图集名    /// </summary>    private static string spritefilePathName;
    [MenuItem("Tools/打包图集")]    public static void CreateAllSpriteAtlas()    {        Debug.Log("打包图集开始执行");
        DirectoryInfo info = new DirectoryInfo(pathRoot);        int index = 0;        // 遍历根目录        foreach (DirectoryInfo item in info.GetDirectories())        {            spritefilePathName = item.Name;                         SpriteAtlas spriteAtlas = AssetDatabase.LoadAssetAtPath(atlasStoragePath + "/" +  spritefilePathName + ".spriteatlas", typeof(Object)) as SpriteAtlas;               // 不存在则创建后更新图集            if (spriteAtlas == null)            {                spriteAtlas = CreateSpriteAtlas(spritefilePathName);            }
            string spriteFilePath = pathRoot + "/" + spritefilePathName;            UpdateAtlas(spriteAtlas, spriteFilePath);                        // 打包进度            EditorUtility.DisplayProgressBar("打包图集中...", "正在处理:" + item, index / info.GetDirectories().Length);            index++;        }
        EditorUtility.ClearProgressBar();        AssetDatabase.Refresh();                Debug.Log("打包图集执行结束");    }         /// <summary>    /// 创建图集    /// </summary>    /// <param name="atlasName">图集名字</param>    private static SpriteAtlas CreateSpriteAtlas(string atlasName)    {        SpriteAtlas atlas = new SpriteAtlas();
        #region 图集基础设置                SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings()        {            blockOffset = 1,            enableRotation = false,            enableTightPacking = false,            padding = 8,        };        atlas.SetPackingSettings(packSetting);                #endregion                #region 图集纹理设置                SpriteAtlasTextureSettings textureSettings = new SpriteAtlasTextureSettings()        {            readable = false,            generateMipMaps = false,            sRGB = true,            filterMode = FilterMode.Bilinear,        };        atlas.SetTextureSettings(textureSettings);                #endregion                #region 分平台设置图集格式                TextureImporterPlatformSettings platformSetting = atlas.GetPlatformSettings(GetPlatformName(BuildTarget.iOS));        platformSetting.overridden = true;        platformSetting.maxTextureSize = 2048;        platformSetting.textureCompression = TextureImporterCompression.Compressed;        platformSetting.format = TextureImporterFormat.PVRTC_RGB4;        atlas.SetPlatformSettings(platformSetting);                // 需要多端同步,就在写一份        platformSetting = atlas.GetPlatformSettings(GetPlatformName(BuildTarget.Android));        platformSetting.overridden = true;        platformSetting.maxTextureSize = 2048;        platformSetting.textureCompression = TextureImporterCompression.Compressed;        platformSetting.format = TextureImporterFormat.ASTC_6x6;        atlas.SetPlatformSettings(platformSetting);                 #endregion
        string atlasPath = atlasStoragePath + "/" + atlasName + ".spriteatlas";        AssetDatabase.CreateAsset(atlas, atlasPath);        AssetDatabase.SaveAssets();
        return atlas;    }
    /// <summary>    /// 每个图集的所有图片路径  --  记得用之前清空    /// </summary>    private static List<string> textureFullName = new List<string>();
    /// <summary>    /// 更新图集内容    /// </summary>    /// <param name="atlas">图集</param>    static void UpdateAtlas(SpriteAtlas atlas, string spriteFilePath)    {        textureFullName.Clear();        FileName(spriteFilePath);
        // 获取图集下图片        List<Object> packables = new List<Object>(atlas.GetPackables());
        foreach (string item in textureFullName)        {            // 加载指定目录            Object spriteObj = AssetDatabase.LoadAssetAtPath(item, typeof(Object));            Debug.Log("存png和jpg后缀的图片: " +item + " , " + !packables.Contains(spriteObj));            if (!packables.Contains(spriteObj))            {                atlas.Add(new Object[] {spriteObj});            }        }    }
    /// <summary>    /// 递归文件夹下的图    /// </summary>    /// <param name="folderPath"></param>    static void FileName(string folderPath)    {        DirectoryInfo info = new DirectoryInfo(folderPath);        foreach (DirectoryInfo item in info.GetDirectories())        {            FileName(item.FullName);        }        foreach (FileInfo item in info.GetFiles())        {             // 存png和jpg后缀的图片            if (item.FullName.EndsWith(".png", StringComparison.Ordinal)                || item.FullName.EndsWith(".jpg", StringComparison.Ordinal))            {                textureFullName.Add("Assets" + item.FullName.Replace(Application.dataPath, ""));            }        }    }     /// <summary>    /// 不同平台枚举对应的值    /// </summary>    /// <param name="target"></param>    /// <returns></returns>    static string GetPlatformName(BuildTarget target)    {        string platformName = "";        switch (target)        {            case BuildTarget.Android:                platformName = "Android";                break;            case BuildTarget.iOS:                platformName = "iPhone";                break;            case BuildTarget.PS4:                platformName = "PS4";                break;            case BuildTarget.XboxOne:                platformName = "XboxOne";                break;            case BuildTarget.NoTarget:                platformName = "DefaultTexturePlatform";                break;            default:                platformName = "Standalone";                break;        }        return platformName;    }    }
   复制代码
 
3.4 图集打包示例
- 将 3.3 代码添加到工程之后,当代码编译完成后,会在上方工具栏中多出一个:Tools/打包图集 菜单,如下图:  
- 点击打包图集后,就会把文件夹- SpriteAtlas下面的图按照文件夹打包成图集。逻辑执行完成后就会在- Textures文件夹下生成新的图集:
 
- 代码打包后的图集效果:  
至此,关于 Unity 图集 SpriteAtlas 的相关介绍和一键打包工具就全部介绍完了。
评论