Answers:
我花了几个月时间完成一项更好的纹理打包算法。
我们开始的算法很简单。收集所有输入项。按消耗的总像素(从大到小)对它们进行排序。按扫描线顺序将它们放置在纹理中,只需测试从左上像素到右上像素的内容,向下移动一行,然后重复一次,即可在每次成功放置后重置为左上像素。
您要么需要对宽度进行硬编码,要么为此提出另一种启发式方法。为了保持矩形性,我们的算法将从128开始,然后增加128s,直到得出的结果不深于宽度。
因此,我们有了该算法,因此我决定对其进行改进。我尝试了一堆古怪的启发式方法-试图找到适合的对象,对一堆所需的空间填充属性进行一些加权,然后旋转和翻转。在完成所有工作后,实际上是三个月的工作,我最终节省了3%的空间。
是的 3%。
在我们对它执行了压缩例程之后,它实际上最终变大了(我仍然无法解释),因此我们将整个过程都扔掉了,回到了旧算法。
排序项目,按扫描线顺序塞入纹理。有你的算法。它易于编码,运行速度快,而且如果不做大量工作,您将不会变得更好。除非您的公司至少有 50名员工,甚至更多,否则这项工作就不值得了。
另外,我只是针对您正在做的完全相同的应用程序(没有ftgles,而是由opengl渲染的自由类型字形)实现了该算法(固定宽度512像素)。由于我使用的是Valve的基于距离场的文本渲染算法,因此看起来很模糊,该算法还考虑了字形之间的额外空间。显然,这里没有太多的空余空间,并且可以很好地将东西塞满空旷的地方。
所有这些代码都是BSD许可的,可以在github上找到。
Andrea Lodi的博士学位论文题为二维装箱和分配问题的算法。
本文讨论了该问题的一些较难形式。幸运的是,纹理打包是最简单的版本。他发现的最好算法是Touching Perimeter。
引用第52页:
该算法称为Touching Perimeter(TPRF),首先根据不增加的区域对项目进行排序(通过不增加min {wj,hj}值来打破联系),然后对它们进行水平定位。然后计算最优解值的下限L,并初始化L个空箱。(在上一节中定义的连续下界L0显然也适用于2BP | R | F; Dell'Amico,Martello和Vigo提出了更好的界限[56]。)算法一次打包一项,或者在现有的垃圾箱中,或通过初始化一个新的垃圾箱。装在垃圾箱中的第一个物品始终放在左下角。每个后续项目都以所谓的正常位置包装(请参见Christofins和Whitlock [41]),即
箱子和包装位置的选择是通过评估分数来完成的,分数定义为接触箱子和其他已包装物品的物品周长的百分比。这种策略倾向于使用包装物品不会“困住”小区域的模式,因为这些区域可能很难用于后续放置。对于每个候选包装位置,针对两个项目方向(如果两者均可行)对得分进行两次评估,并选择最高值。通过选择包装面积最大的储物箱来打破领带。总体算法如下。touching_perimeter: sort the items by nonincreaseing w,h values, and horizontally orient them; comment: Phase 1; compute a lower bound L on the optimal solution value, and open L empty bins; comment: Phase 2; for j := 1 to n do score := 0; for each normal packing position in an open bin do let score1 and score2 be scores with tow orientations; score := max{score,score1,score2}; end for; if score > 0 then pack item j in the bin, position and orientation corresponding to score; else open a new bin and horizontally pack item j into i; end if; end for; end;
同样令人感兴趣的是,本文描述了一种确定最佳包装纹理贴图大小的算法。这对于确定是否有可能在一个1024x1024地图集中拟合所有纹理将很有用。
如果仍然有人感兴趣,我已经完全重写了rectpack2D库,以便更高效。
它的工作原理是std::vector
在地图集中保留一个空白空间,从一些初始的最大大小(通常是特定GPU上允许的最大纹理大小)开始,分割第一个可行的空白空间,并将分割后的内容保存回向量。
性能突破来自仅使用向量,而不是像以前那样保留整个树。
自述文件中详细描述了该过程。
该库在麻省理工学院管理下,如果您觉得它有用,我将为您感到高兴!
测试是在3.50GHz的Intel Core i7-4770K CPU上进行的。二进制文件是用lang 6.0.0使用-03开关构建的。
运行时间:4毫秒
浪费像素:15538(0.31%-相当于125 x 125平方)
输出(2116 x 2382):
颜色:(
黑色浪费空间)
运行时间:3.5-7毫秒
浪费像素:9288(1.23%-相当于96 x 96正方形)
输出(866 x 871):
颜色:(
黑色浪费空间)
一个很好的启发式算法可以在这里找到。最近,当我尝试类似的操作时,我发现这是我所看到的大多数实现的基本起点。
与大量规则形状,类似尺寸的项目,或小而少的大图像的良好组合,效果特别好。要获得良好结果的最佳建议是记住按照图像大小对输入进行排序,然后将较大的图像打包到最小的图像,因为较小的图像将打包到较大图像周围的空间中。您如何进行分类取决于您的目标。我使用周长而不是面积作为一阶近似值,因为我认为以后很难将高+薄/短+宽的图像(可能具有较小的区域)放置在包装中,因此通过使用周长,您可以按这些奇怪的形状朝向订单的前面。
这是我的打包程序在我的网站图像转储目录中的一组随机图像上输出的示例化:)。
正方形中的数字是树中包含的块的ID,因此,您可以了解插入顺序。第一个是ID“ 3”,因为它是第一个叶子节点(仅叶子包含图像),因此有2个父节点。
Root[0]
/ \
Child[1] Child[2]
|
Leaf[3]
我们最近发布了一个python脚本,它将纹理打包到给定大小的多个图像文件中。
从我们的博客中引用:
“虽然可以在网上找到很多打包机,但是我们的困难是要找到可以处理多个目录中大量图像的打包机。因此,我们自己的地图集打包机诞生了!
照原样,我们的小脚本将在基本目录中启动,并将所有.PNG加载到地图集中。如果该图集已满,它将创建一个新图集。然后,它将尝试在所有以前的地图集中拟合其余图像,然后在新地图集中找到一个位置。这样,每个地图集都尽可能地紧凑。地图集是根据其图像所来自的文件夹来命名的。
您可以轻松地更改图集的大小(第65行),要打包的图像格式(第67行),加载目录(第10行)和保存目录(第13行),而无需使用Python。作为一个小的免责声明,它在几天内就被鞭打在一起,专门用于我们的引擎。我鼓励您请求功能,用自己的版本发表评论并报告任何错误,但是对脚本的任何更改都将在我的空闲时间进行。”
请随时在此处查看完整的源代码:http : //www.retroaffect.com/blog/159/Image_Atlas_Packer/#b
打包字体非常容易,因为所有(或绝大多数)字形纹理的大小几乎相同。做最简单的事情,这将非常接近最优。
当打包尺寸非常不同的图像时,聪明就显得尤为重要。然后,您希望能够缩小差距,等等。尽管如此,即使像前面讨论的扫描线顺序搜索这样的简单算法也会产生非常合理的结果。
先进的算法都不是魔术。它们的效率不会比Simpel算法高50%,除非您拥有惊人数量的纹理表,否则您将不会从中获得一致的收益。这是因为更好的算法所做的细微改进只会在总体上体现出来。
变得简单,然后继续前进,使自己的努力得到更好的回报