为了降低DrawCall,我们需要将多个图片构建在图集上。构建图集可以自动将图片补齐2的幂次方或者正方形图,这样就可以进行ETC(安卓)和PVRTC(苹果)的压缩了,PC主机设备一般使用Unity默认DXT压缩。

随着移动平台更新迭代,最新的贴图推荐压缩方式是ASTC

关于图片压缩的文章

Unity中图像设置与常用压缩算法

unity贴图压缩浅析

UI图集的压缩

Sprite Packer 在 Unity 2020.1 及更高版本中已弃用,并且将不再作为 Sprite Packer 模式的选项提供。已经使用 Sprite Packer 的现有项目仍然可以继续使用它,但是在 2020.1 之后创建的任何新项目都将默认使用 Sprite Atlas 打包纹理。

如果是UI图片,设置了相同PackingTag的Sprite会合并在一个图集,游戏中有一些图,比如说背景图或者大头像图,由于它们本身可能不是2的幂次方或者正方形图,所以我们可以自动给它们设置成唯一的PackingTag,这样它们也可以进行压缩了。

需要注意的是,某些大图因为需要设置成了RGBA32或者RGBA16,并且本身不是2的幂次方或者正方形图的话,再去加PackingTag可能就不合适了,否则会让图片变得更大,所以,在自动构建图集时可以加个判断,让这种图片不参与构建图集。

在Sprite Packer中,可以自定义图集的构建策略,这样就可以进一步灵活地控制图集的构建方式,可以调节图集的压缩类型以及大小等。

新建CustomPackerPolicySample脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using UnityEditor.Sprites;
using System;

public class CustomPackerPolicySample : UnityEditor.Sprites.IPackerPolicy
{
public bool AllowSequentialPacking {
get { return true; } }

protected class Entry
{
public Sprite sprite;
public AtlasSettings settings;
public string atlasName;
public SpritePackingMode packingMode;
public int anisoLevel;//各项异性过滤等级
}
private const uint kDefaultPaddingPower = 3;
protected string TagPrefix => "[RECT]";
protected bool AllowTightWhenTagged => true;
protected bool AllowRotationFlipping => true;

//判断图片是否为压缩格式
public static bool IsCompressedFormat(TextureFormat fmt)
{
if (fmt >= TextureFormat.DXT1 && fmt <= TextureFormat.DXT5)
return true;
if (fmt >= TextureFormat.DXT1Crunched && fmt <= TextureFormat.DXT5Crunched)
return true;
if (fmt >= TextureFormat.PVRTC_RGB2 && fmt <= TextureFormat.PVRTC_RGBA4)
return true;
if (fmt == TextureFormat.ETC_RGB4)
return true;
if (fmt >= TextureFormat.ETC_RGB4 && fmt <= TextureFormat.ETC2_RGBA8)
return true;
if (fmt >= TextureFormat.EAC_R && fmt <= TextureFormat.EAC_RG_SIGNED)
return true;
if (fmt >= TextureFormat.ETC2_RGB && fmt <= TextureFormat.ETC2_RGBA8)
return true;
if (fmt >= TextureFormat.ASTC_4x4 && fmt <= TextureFormat.ASTC_HDR_12x12)
return true;
return false;
}
public int GetVersion()
{
throw new System.NotImplementedException();
}

public void OnGroupAtlases(BuildTarget target, PackerJob job, int[] textureImporterInstanceIDs)
{
List<Entry> entries = new List<Entry>();
foreach (int instanceID in textureImporterInstanceIDs)
{
TextureImporter ti = EditorUtility.InstanceIDToObject(instanceID) as TextureImporter;

TextureFormat desiredFormat;
ColorSpace colorSpace;
int compressionQuality;
ti.ReadTextureImportInstructions(target, out desiredFormat, out colorSpace, out compressionQuality);
TextureImporterSettings tis = new TextureImporterSettings();
ti.ReadTextureSettings(tis);

Sprite[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(ti.assetPath)
.Select(x => x as Sprite)
.Where(x => x != null)
.ToArray();

foreach (Sprite sprite in sprites)
{
//初始化图片压缩参数
Entry entry = new Entry();
entry.sprite = sprite;
entry.settings.format = desiredFormat;
entry.settings.colorSpace = colorSpace;
entry.settings.compressionQuality = IsCompressedFormat(desiredFormat) ? compressionQuality : 0;
entry.settings.filterMode = Enum.IsDefined(typeof(FilterMode), ti.filterMode) ? ti.filterMode : FilterMode.Bilinear;
entry.settings.maxWidth = 2048;
entry.settings.maxHeight = 2048;
entry.settings.generateMipMaps = false;
entry.settings.enableRotation = AllowRotationFlipping;
entry.settings.paddingPower = (uint)EditorSettings.spritePackerPaddingPower;
entry.settings.allowsAlphaSplitting = ti.GetAllowsAlphaSplitting();//已弃用
entry.atlasName = ParseAtlasName(ti.spritePackingTag);
entry.packingMode = GetPackingMode(ti.spritePackingTag, tis.spriteMeshType);
entry.anisoLevel = ti.anisoLevel;

entries.Add(entry);
}

Resources.UnloadAsset(ti);
}
//将相同图集的名称划分成不同图集
var atlasGroups =
from e in entries
group e by e.atlasName;
foreach (var atlasGroup in atlasGroups)
{
int page = 0;
//相同的图集中压缩格式不同的图划分成不同的组
var settingGroups =
from t in atlasGroup
group t by t.settings;
foreach (var settingGroup in settingGroups)
{
//已经设置了非压缩的图,不做成图集,避免图片变大
//非压缩的图有唯一的PackingTag并且不在CompressedFormat内,所以需要如此判断
bool skipAtlas = (settingGroup.Count() == 1 && !IsCompressedFormat(settingGroup.ElementAt(0).settings.format));
if (skipAtlas) continue;

string atlasName = atlasGroup.Key;

if (settingGroups.Count() > 1)
atlasName += string.Format("(Group {0})", page);

AtlasSettings settings = settingGroup.Key;
settings.anisoLevel = 1;
if (settings.generateMipMaps)
foreach (Entry entry in settingGroup)
if (entry.anisoLevel > settings.anisoLevel)
settings.anisoLevel = entry.anisoLevel;

job.AddAtlas(atlasName, settings);
foreach (Entry entry1 in settingGroup)
{
job.AssignToAtlas(atlasName, entry1.sprite, entry1.packingMode, SpritePackingRotation.None);
}

++page;
}
}
}

protected bool IsTagPrefixed(string packingTag)//检测PackingTag是否都带"[RECT]"字符
{
packingTag = packingTag.Trim();
if (packingTag.Length < TagPrefix.Length) return false;
return (packingTag.Substring(0, TagPrefix.Length) == TagPrefix);
}

private string ParseAtlasName(string packingTag)
{
string name = packingTag.Trim();
if (IsTagPrefixed(name))
name = name.Substring(TagPrefix.Length).Trim();//去掉"[RECT]"字符
return (name.Length == 0) ? "(unnamed)" : name;
}

private SpritePackingMode GetPackingMode(string packingTag,SpriteMeshType meshType)
{
if (meshType == SpriteMeshType.Tight)
if (IsTagPrefixed(packingTag) == AllowTightWhenTagged)
return SpritePackingMode.Tight;
return SpritePackingMode.Rectangle;
}
}

如果打包需要自动指定策略,可以强制设置本地打包使用什么图集策略,然后将策略以及打包平台传入即可

1
2
3
4
static private void BuildAtlas(BuildTarget buildTarget){
Packer.SelectedPolicy = typeof(CustomPackerPolicySample).Name;
Packer.RebuildAtlasCacheIfNeeded(buildTarget);
}

非UI图集的压缩

角色、特效和场景等图片并不属于UI图片,如果美术人员并没有提供2的幂次方或者正方形图,就无法压缩了。在图片的导入面板——Advance下拉面板中选择合适的Non Power of 2(直译过来的意思是不是2的幂次方)可以强制设置图片为2的幂次方。其中ToNearest、ToLarger、ToSmaller表示自动将图片拉伸成对应的规则大小,这样图片就可以进行压缩了。