SpriteAtlas图集预览算法:MaxRectsBinPack

前段时间对unity spriteAtlas预览功能比较感兴趣,就去查询了一下unity是如何实现的。

unity SpriteAtlas预览算法接口调用

探查相关知识后可以发现,unity spriteAtlas的预览调用接口很隐蔽,在SpriteAtlas.GetPreviewTextures中。(有个网址可以学习unity源码,还是非常推荐的)EditorSpriteAtlas.bindings下的接口
当然我们可以选择直接new一个Texture2D作为atlas,直接调用Texture2D.PackTextures(Texture2D[] textures, int padding, int maximumAtlasSize, bool makeNoLongerReadable)即可。

SpriteAtlas预览算法:MaxRectsBinPack

可以看到真正的算法再GetPreviewTextures里,但是unity用了NativeHeader链接了相应方法到C++编写的本地库,所以暂时是无法看到了。但是本人搜索了一遍之后看到了相关算法关键词:MaxRectsBinPack算法,并且有大佬完成并开源了相关算法流程,在这里就通过阅读这套源码详细解释spriteAtlas处理多texture的步骤。

对所有贴图进行排序

按照贴图某个较长的边进行比较,矩形的宽高最大值越大,越在数组的前面,保证处理贴图数据的顺序是从较大贴图开始处理的。比如4x6的贴图要放在5x5的贴图前面,7x3的贴图放在4x6的贴图的前面。

设置为最小的2的n次方的图集使其装得下最大的贴图

我们都知道图集将图片打包为2的幂次方的素材大小,借以减少drawcall。所以我们直接取第一张贴图(也就是最大的那张贴图),生成一张最接近它的height和width的2次幂图集作为起始图集(模拟演算的数据,并非从现在开始就直接操作texture,而是一个数据块存储了必要的宽/高信息)已完成初期准备工作。

public void Init(int width, int height, bool rotations = true)
{
    binWidth = width;
    binHeight = height;
    allowRotations = rotations;
    Rect n = new Rect(); //一个矩形,左上角起始,宽高
    n.x = 0;
    n.y = 0;
    n.width = width;
    n.height = height;
    usedRectangles.Clear();
    freeRectangles.Clear();
    freeRectangles.Add(n); //把初始化的矩形添加进来
}

比如最大的一张贴图是240x600,那第一步首先生成一张256x1024的图集,至少要能放下这张最大的贴图。
在这里插入图片描述

开始循环,遍历贴图数组,往这一特定大小的图集里插入贴图

这一步是算法核心,我们需要往这个特定大小的图集里插入贴图。匹配空闲空间插入贴图的算法有很多种,开源者在这里提供了五种算法:

public enum FreeRectChoiceHeuristic
{ //匹配规则
    RectBestShortSideFit, // BSSF: 短边最接近
    RectBestLongSideFit, // BLSF: 长边最接近
    RectBestAreaFit, // BAF: 面积最接近
    RectBottomLeftRule, /// BL: 放在最左下
    RectContactPointRule // CP: 尽可能与更多矩形相邻
};

代表的就是现在有一堆空闲矩形,那这个待插入的贴图应该往哪里插入比较好,至少要插入到其中一个最合适的矩形中才算准确。
在这里以“面积最接近算法”作为示例算法继续讲解,其实就是这个等待插入的贴图A的大小和所有的空闲矩形做比较,能插入的条件就是这个空闲矩形的面积首先要比A的面积要大,宽度、高度也要比A要大(这是肯定的,否则就塞不下A,就要跳过),满足这个条件下的最小面积的空闲矩形就是最终要放进去的矩形。

Rect FindPositionForNewNodeBestAreaFit(int width, int height)
{
    Rect bestNode = new Rect();//创建匹配矩形
    int bestAreaFit = int.MaxValue;
    int bestShortSideFit = 0;
    for (int i = 0; i < freeRectangles.Count; ++i)
    { //遍历空闲矩形列表
        int areaFit = (int)freeRectangles[i].width * (int)freeRectangles[i].height - width * height; //计算面积匹配度,轮询空闲矩形的面积减掉目标匹配矩形的面积
        // 查找到一个可以容纳目标矩形的空闲矩形
        if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
        {
            int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);//目标矩形宽度相比查找的矩形多余的部分,宽度匹配度
            int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);//目标矩形高度相比查找的矩形多余的部分,高度匹配度
            int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);//短边的匹配度
            if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
            { // 面积小于上次匹配的矩形,或者面积相等但短边更加合适
                bestNode.x = freeRectangles[i].x;//保存匹配到的矩形数据
                bestNode.y = freeRectangles[i].y;
                bestNode.width = width;
                bestNode.height = height;
                bestShortSideFit = shortSideFit;//短边匹配度
                bestAreaFit = areaFit;//面积匹配度
            }
        }
    }

打个比方,现在有个贴图A大小是240x160,要在300x170、400x100、280x180的空闲矩形中挑选一个进行放置,那就应当选280x180的空闲矩形,因为第二个矩形放不下,第三个相比第一个面积又会更小一点,所以是候选者里最适合放下这个贴图的。
如果是2个面积同样最小的矩形,则以某条边更“适配”贴图的矩形作为最终候选者来供插入。比如这个240x160的贴图A要在260x280和280x260的空闲矩形中选,那应该选260x280的,因为做差值运算后会发现前者宽度剩20,高度剩120,后者宽度剩40高度剩100,会发现前者的宽度差值更小,也就更适配了。

添加插入贴图后的空闲矩形信息

选好能放入贴图的空闲矩形后,就要对这个空闲矩形进行拆分。代码里是直接把剩下的上下左右四个部分直接加入空闲矩形列表里供下次遍历的,如下图:
在这里插入图片描述
以上只是比喻,通常情况下是不会这么分割的,一般情况A的左下角会和空闲矩形的左下角重合:
在这里插入图片描述
当然,之后会越来越多的小碎片存在,所以代码块里有一个优化算法,就是每一次裁剪后判断矩形之间是否有相互包含的,如果有则剔除。这个相互包含的情况是大概率会出现的,比如上述这么划分之后有个贴图B把矩形3的大部分都占有了,留下矩形3和4的共同区域:
在这里插入图片描述
这时候就需要将矩形3剔除了。

循环结束,判定是否所有贴图都已插入进图集中

判定这个有很多种方法,传送门里的算法流程里是根据最新插入图集的这个贴图返回的Rect信息来判断的。如果返回的Rect信息的长度或者宽度为0,意味着这张贴图根本没插进去,
那就代表这个图集(模拟演算出来的)大小本身不够大,就要继续加宽/加高处理。新建一个加宽/加高后的图集数据,然后继续下个循环,回到上一步,插入所有贴图,直到所有贴图插入完全,或者碰到有贴图无法插入。

 for (int i = 0; i < textures.Length; ++i)
 { //忽略数组0的元素
     Texture2D tex = textures[i]; //贴图数据
     Rect rect = packer.Insert(tex.width, tex.height, FreeRectChoiceHeuristic.RectBestAreaFit); //使用面积最接近的方式进行矩形的插入
     aryRects[i] = rect;
     if (rect.width == 0 || rect.height == 0)
     {
         //不通过
         successed = false;
         break;
     }
 }
 if (successed)
 {
     break;
 }
 //先往高度发展,如果发现高度大于1024,则向宽度发展
 if (maxHeight >= AtlasPreviewer.ATLAS_MAX_SIZE && maxWidth < AtlasPreviewer.ATLAS_MAX_SIZE)
 {
     //不允许超过2048
     maxWidth *= 2;
 }
 else if (maxHeight >= AtlasPreviewer.FAVOR_ATLAS_SIZE && maxWidth < AtlasPreviewer.FAVOR_ATLAS_SIZE)
 {
     //尽量不能超过1024
     maxWidth *= 2;
 }
 else if (maxHeight >= maxWidth * 2)
 {
     //尽量使宽和高之间的比例不要相差太大,防止IOS上会扩展成更大的正方形
     maxWidth *= 2;
 }
 else
 {
     maxHeight *= 2;
 }

返回这个图集并显示

将上述计算得出的所有贴图数据(就是每个贴图的Rect数据,此数据保存了这个贴图的长度、高度、在图集的坐标)传进图集里,通过Texture.SetPixels来绘制出来,就得到一张完美的图集预览texture了。

  • 36
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: Python的Atlas图集拆分可以用来将大型的图集文件切分成较小的单个图片文件,以便方便地使用它们在不同的游戏引擎或应用程序中。以下是关于如何使用Python进行Atlas图集的拆分。 首先,需要安装Pillow库和xml.etree.ElementTree库,这可以通过在终端中运行“pip install pillow”和“pip install xml.etree.ElementTree”来完成。这两个库都是Python图像处理和XML解析的标准库。 接下来,需要准备Atlas图集,并将其保存为XML格式。我们可以使用一些图集编辑器(如TexturePacker)来创建Atlas图集,并导出为XML格式。 在Python脚本中,可以使用xml.etree.ElementTree库来解析XML文件,然后提取每个纹理的位置和大小。然后,可以使用Pillow库来裁剪图像数据,并将其保存到单独的输出文件中。 最后,将所有的单独的图像合并成一个Atlas图集,以便在应用程序中使用。可以使用TexturePacker等工具来执行此操作,也可以使用代码来实现。 总之,Python Atlas图集拆分是一个很有用的工具,可以简化图像管理和优化应用程序性能。通过使用Pillow和xml.etree.ElementTree库,可以实现这个功能,并获得更好的控制和灵活性。 ### 回答2: Python Atlas图集拆分通常是针对游戏引擎和其他需要大量图像资源的应用程序进行的,它将大型图像文件分解为小图像块,以便在需要时加载和使用图像资源。 要使用Python拆分图集,我们可以使用Pillow库或OpenCV库等图像处理库来实现。我们首先需要将含有所有图像的大型图像文件加载到Python中。然后,我们可以使用库中可用的裁剪函数来划分图像块。我们需要提供裁剪参数,例如每个块的大小,每个块之间的间隔,以及需要裁剪的图像的位置。然后将块保存到单独的文件中,以便我们可以随时在程序中读取和使用它们。 我们还可以使用Python脚本来自动化此过程,以便同时处理多个图像文件。我们可以编写一个Python脚本来迭代处理文件夹中的所有图像文件,并将它们拆分为块。这样可以节省时间和精力,并使我们能够快速创建大量的图像资源。 在实际应用中,我们通常需要执行额外的处理步骤,例如为拆分的图像块打标记和元数据或重新整理图像文件的名称和路径。这些都可以通过Python脚本和其他库来实现。 总之,Python Atlas图集拆分是一种方便快捷的方式来处理大量图像资源。它可以帮助增加程序的性能,减少加载时间,并提高用户体验。 ### 回答3: Atlas图集(也称为纹理图集)是一种将多个小型纹理合并成单个大型纹理的技术。这种技术可以显著减少内存使用量,因此成为许多游戏和应用程序的常见技术。 在Python中,有许多库可以帮助我们拆分Atlas图集。其中一种流行的库是Pygame,在Pygame中,我们可以使用Surface.subsurface(rect)方法从Atlas图集中提取小型纹理。 此外,还有一些第三方库可用于Atlas图集拆分,例如Pillow和OpenCV。这些库提供了更多的功能和配置选项,使我们可以更轻松地进行Atlas图集拆分。 在使用这些库拆分Atlas图集时,我们需要先找到Atlas图集中各个小型纹理的矩形区域。这可以通过阅读Atlas图集中的元数据文件来完成,或者使用图像处理算法来寻找区域。一旦我们找到了这些区域,就可以使用所选的库从Atlas图集中提取小型纹理,并将它们保存到单独的图像文件中以供使用。 总体而言,Atlas图集拆分是一项重要的任务,它可以帮助我们提高游戏和应用程序的性能和内存使用。使用Python和适当的库,我们可以轻松地进行Atlas图集拆分,并从中获得许多好处。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值