1. 简介
我们都知道在UI中使用贴图图集可以有效地减少drawcall提升渲染效率,在Unity中制作图集或者通过第三方工具制作图集已经成为标准开发流程。
但是在某些使用场景中,离线制作的静态图集并不是最优做法,比如我们一个界面上要显示两个人物头像,四个物品图标,以及四个技能图标。按照静态图集的流程,我们会制作人物、物品、技能三个图集,运行时同时将这三个图集加载进内存。如果我们的图标数量较大的话,很显然会造成内存浪费。
这时,动态图集就有明显的优势了,我们如果能在运行时将需要的少数图标打包到一张贴图中,既能满足ui合批的需求,又不会造成内存的浪费。
下面我们来讨论一下动态图集的实现。
2. 动态图集的制作
动态图集的制作,主要涉及到两个方面的知识,一是贴图打包算法(贴图该放到什么位置),二是贴图拷贝方法(怎么将贴图拷贝到图集特定位置)
2.1 贴图打包算法
贴图打包算法是一个NP难的问题[1],在[2]中对解决算法有详细的讨论。对于我们动态图集的打包,一是要求实时,二是贴图数量不大,所以不必在这里对算法优劣过多纠结。
在示例中我们实现的是按行列切割矩形的贪婪打包算法,有兴趣的同学可以直接看代码,在这里不多做讨论。
2.2 贴图拷贝方法
Unity中可以将贴图归为两大类,一类是以CPU端操作为主的Texture2D,第二类是以GPU端操作为主的RenderTexture。两种贴图类型各自都有优缺点。Texture2D的使用范围最广,但是修改起来慢,而且有着诸多限制。RenderTexture操作快,但是不支持贴图压缩。
在这里我们使用RenderTexture来做图集贴图类型。那么现在我们需要实现一种将任意贴图渲染到RenderTexture特定位置的方法。由于几次实验一直没办法理解Graphics.DrawTexture的正确用法,所以这里我只好通过魔改 Graphics.Blit 投影矩阵[3]的方法来实现了。
c#端主要代码:
public static void DrawTexture(Texture source, RenderTexture target, RectInt position)
{
// 构建变换矩阵
float l = position.x * 2.0f / target.width - 1;
float r = (position.x + position.width) * 2.0f / target.width - 1;
float b = position.y * 2.0f / target.height - 1;
float t = (position.y + position.height) * 2.0f / target.height - 1;
var mat = new Matrix4x4();
mat.m00 = r - l;
mat.m03 = l;
mat.m11 = t - b;
mat.m13 = b;
mat.m23 = -1;
mat.m33 = 1;
// 绘制贴图
uploadMateral.SetMatrix(Shader.PropertyToID("_ImageMVP"), GL.GetGPUProjectionMatrix(mat, true));
Graphics.Blit(source, target, uploadMateral);
}
Shader主要代码:
float4x4 _ImageMVP;
v2f_img vert (appdata_img v)
{
v2f_img o;
o.pos = mul(_ImageMVP, v.vertex);
o.uv = v.texcoord;
return o;
}
sampler2D _MainTex;
fixed4 frag (v2f_img i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
2.3 示例
在下图,我们将6张尺寸不一的贴图动态打成图集:
3. 动态图集的使用
由于我们使用RenderTexture来储存图集贴图,而Unity的Image控件只支持Texture2D,所以我们只能忍痛放弃Image。
RawImage控件是支持RenderTexture的,在使用时我们需要设置RawImage的uvRect属性,使之与图集子贴图位置对应即可。
var image = GetComponent<RawImage>();
image.texture = textureAtlas.texture;
image.uvRect = new Rect(
(float)position.x / (float)textureAtlas.texture.width,
(float)position.y / (float)textureAtlas.texture.height,
(float)position.width / (float)textureAtlas.texture.width,
(float)position.height / (float)textureAtlas.texture.height);
4. 综合示例
jintiao/RuntimeTextureAtlasgithub.com引用
[1] Bin Packing Problem
[2] Optimal Rectangle Packing
[3] The Perspective and Orthographic Projection Matrix