文章目录
1.图集相关概念
1.1 图集定义
图集是将许多较小的,独立的纹理合并到一个较大的纹理文件中,从而最小化材质的数量,因此最小化所需使用的Draw Call数量。这是利用动态批处理的有效方法。
每个独特的材质都需要额外的Draw Call,但是每种材质只支持单一的主纹理。当然,它们也可以支持多个二级纹理,比如法线纹理和发射纹理。然而,将多个主纹理合并到一个大的纹理文件中,渲染这个纹理的对象时,可以最小化所使用的Draw Call数量。
⚠️ :图集只是当所有给低昂的纹理需要相同的着色器时采用的一种方法,如果一些纹理需要通过着色器应用独立的图形效果,它们就必须分离到自己的材质中,并在单独的组中打图集。
1.2 图集的意义
- 减少DrawCall:多张图片需要多次DrawCall,合并成一张大图只需要调用一次DrawCall
- 性能优化:图集将图片打包为2的幂次方的素材大小,GPU处理起来会快很多,方便unity渲染合批,降低渲染消耗,小图不可能每张都是2的n次方的大小,所以会提升效率。
- 减小包体大小:多张图片的打包占用会大于一张2的n次方的大图的大小。
1.3 示例
游戏大厅需要这么多张图片:
如果我么单张使用,这就存在上面说的三个问题了:多个DrawCall,效率降低,包体变大。
为了解决这三个问题,图集的存在就变得有意义了。那么可能就有同学要问了,图片是美术给的,那么图集也由美术打包,这样可行吗?
答案是:可行,但有一定的限制。
这是美术将所有用到的图片放在一起打包的图集:
我们在使用的时候需要自己拆分,这样使用起来就不是很方便了。最关键的是以后大厅随便添加或者修改一张图都需要美术给我们一整张图集,这样明显是不合理的。 所以只有当场景资源固定了,以后不会在有任何改动的情况下,是可以由美术出图集的。
但是,稍微有点经验的程序员都不会使用上述这种情况。因为项目本身不被修改那几乎是不可能的,有的时候不但要改,而且还要大改;所以将打包图集放在程序上来出来也是更方便,更合理的,下面我们就来一起学习一下Unity的图集吧。
2.Sprite Atlas打包图集
2.1 开启图集模式
在Unity2020 之后,需在PackageManager中安装2DSprite包,否则无法创建Sprite Atlas
菜单栏 Edit–>Project Settings–>Editor–>EditorSettings–>Sprite packer
Mode:
- Enable For Builds :在游戏发布后图片的引用调整为图集中的元素,在编辑器中仍然使用原图片,因此在Stats中Batches不会发生变化
- Always Enabled:在游戏发布后或者编辑器模式下进入PlayMode后,图片引用为图集中的元素,在运行后Stats中可以看到Batches降低
2.2 创建Sprite Atlas文件
Project视图右键,Create–>Sprite Atlas
2.3 属性面板
属性 | 功能 |
---|---|
Type | 将“Sprite Atlas”类型设置为Master 或Variant ,Master是默认类型设置,Variant设置一般用于高清/低清资源切换。下面会有一个示例帮助理解。 |
Include in build | 选中此框可将Sprite Atlas资源包括在当前构建中。 |
Allow Rotation | 选中此框可以在Unity将精灵打包到精灵地图集时允许精灵旋转。开启后图集会根据计算将一些精灵旋转排列,这样使得图集更小更紧密。被旋转的精灵在场景被使用时也会对应旋转,这样处理起来就会很麻烦,建议禁用 |
Tight Packing | 选中此框可根据精灵轮廓而不是默认矩形轮廓来打包精灵。开启后,精灵在图集中密集排列,甚至有时会出现一张图上有另外一张图的边角的情况,建议禁用 |
Padding | 不同UI元素之间的间隔,单位为像素,避免图片之间过近导致显示出问题 |
Read/Write Enabled | 选中此框可以从脚本函数(例如Texture2D.SetPixels代码访问时需要开启)访问纹理数据。开启此属性,Unity将创建纹理数据的副本。所以会增大内存占用。此属性仅对未压缩或DXT压缩的纹理有效,所以需要开启此属性的图片也不能修改压缩格式。 |
Generate Mip Maps | 选择此选项可启用Mip-Map生成。Mip贴图是Texture的较小版本,当Texture在屏幕上很小时会使用。 |
Filter Mode | 选择如何过滤纹理;选择Unity在变换期间拉伸时过滤压缩纹理的方式。此设置覆盖Atlas中任何打包精灵的过滤模式设置。 |
Objects for Packing | 需要打包的UI元素,可以放文件夹或者单个图片,放文件夹可以将文件夹所有的图片打包进这个图集中,图片的格式需要设置为Sprite(2D and UI),一个图集最大的尺寸是2048*2048 |
2.4 添加要打包的图片或文件夹
Objects For Packing:将此列表中的所有项目打包到当前选定的Sprite图集中,点击+加号,则可选择一张图片放入图集中,所有图集选择完毕后,点击Pack Preview结果如下图所示:
2.5 多平台处理
Platform-specific overrides panel :为Sprite Atlas的每个目标平台设置分辨率、文件大小以及相关内存大小要求、像素尺寸和纹理质量。该面板允许您覆盖精灵图集包含的各个纹理上的这些设置。
后面那些带有自己平台的标识的,都可以通过勾选Override属性,来设置对应平台的图集属性。
比如:在移动的平台(Android,IOS)需要控制包体,那么可以将图集格式设置为ASCT6x6;而在PC端图片的清晰度比包体大小跟重要,则图集可是就可以设置为RGBA32。
3.DrawCall
3.1 什么是DrawCall
DrawCall是指CPU向GPU发送渲染指令的过程。每个DrawCall表示一次绘制操作,包括将一个或多个三角形渲染到屏幕上。对于游戏开发者来说,DrawCall的数量直接影响着游戏的性能,因为每次DrawCall都需要消耗一定的计算资源。
3.2 DrawCall 查看
- Game视口->State
- 菜单栏Window->Analysis->Profilter.再打开的窗口中,点击Rendering
3.3 示例
当没有使用图集时:
4个Iamge的DrawCall为4(其中两个,一个是天空盒一个是默认背景)
使用图集后:
4个Iamge的DrawCall为3
4.代码控制
4.1 动态加载图集并使用
使用图集后加载图片只需加载图片所在图集,通过图集获取图片
GetSprite方法传入对应的Sprite名称,名称为打包时的文件名,如果Sprite的模式是Single,直接传入图片的名字,如果是Multiple,需要加上下划线和对应的索引
using UnityEngine.U2D;
void Start()
{
SpriteAtlas atlas = Resources.Load<SpriteAtlas>("SpriteAtlasTest");
Images[0].sprite = atlas.GetSprite("1");
Images[1].sprite = atlas.GetSprite("2");
Images[2].sprite = atlas.GetSprite("3");
Images[3].sprite = atlas.GetSprite("4");
}
4.2 操作图集
- 创建图集
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);
5.拓展 – 打包图集工具
5.1 图片存放策略
创建在Editor下面一个所有图片的根目录,然后在根据游戏功能或者面板公用等条件,将同一组图片归类到一个文件夹下。比如:Editor/Textures/Login,Editor/Textures/Lobby
这是我下面Demo中使用的图片路径:Assets/CreateAtlas/Editor/Res/SpriteAtlas
PS:按照设计以后新增的图片都会按照功能再分文件夹放到SpriteAtlas文件夹下。
5.2 图集打包逻辑
- 创建图集 --> 图集基础设置 --> 图集纹理设置 --> 按需设置分平台图集格式
- 遍历文件夹 --> 根据格式过滤图片 --> 判断是否存在图集 --> 不存在则加入
5.3 图集打包代码
注意代码放到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);
string str = Application.dataPath.Replace(@"/", @"\");
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(str, ""));
}
}
}
/// <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;
}
}
5.4 图集打包示例
点击打包图集后,就会把文件夹SpriteAtlas下面的图按照文件夹打包成图集。逻辑执行完成后就会在
Textures文件夹下生成新的图集: