纹理打包算法


52

什么是好的纹理打包算法?从技术上讲,bin打包NP-hard的,所以启发式就是我真正追求的。


我以为您正在使用它来优化uv贴图,但是我很好奇这个应用程序是什么。
乔纳森·费斯霍夫

ftgles是使用opengl和freetype呈现字体的库。但是,每个字形都存储在其自己的纹理中。我想将它们打包成一个纹理。
deft_code 2010年

Answers:


58

我花了几个月时间完成一项更好的纹理打包算法。

我们开始的算法很简单。收集所有输入项。按消耗的总像素(从大到小)对它们进行排序。按扫描线顺序将它们放置在纹理中,只需测试从左上像素到右上像素的内容,向下移动一行,然后重复一次,即可在每次成功放置后重置为左上像素。

您要么需要对宽度进行硬编码,要么为此提出另一种启发式方法。为了保持矩形性,我们的算法将从128开始,然后增加128s,直到得出的结果不深于宽度。

因此,我们有了该算法,因此我决定对其进行改进。我尝试了一堆古怪的启发式方法-试图找到适合的对象,对一堆所需的空间填充属性进行一些加权,然后旋转和翻转。在完成所有工作后,实际上是三个月的工作,我最终节省了3%的空间。

是的 3%。

在我们对它执行了压缩例程之后,它实际上最终变大了(我仍然无法解释),因此我们将整个过程都扔掉了,回到了旧算法。

排序项目,按扫描线顺序塞入纹理。有你的算法。它易于编码,运行速度快,而且如果不做大量工作,您将不会变得更好。除非您的公司至少有 50名员工,甚至更多,否则这项工作就不值得了。

替代文字

另外,我只是针对您正在做的完全相同的应用程序(没有ftgles,而是由opengl渲染的自由类型字形)实现了该算法(固定宽度512像素)。由于我使用的是Valve的基于距离场的文本渲染算法,因此看起来很模糊,该算法还考虑了字形之间的额外空间。显然,这里没有太多的空余空间,并且可以很好地将东西塞满空旷的地方。

所有这些代码都是BSD许可的,可以在github上找到


我看着您的纹理,然后对自己说:“我敢肯定我们的纹理打包器比这更好。” 然后我去看了一下,意识到我在一段时间前把它弄坏了,却没有注意到(因为一旦工作了,谁在看输出纹理?)...所以谢谢你发帖-找不到否则,将导致该错误:)(一旦我修复了该错误,它看起来就非常相似-也许阴影会更好,但是要准确地告诉它是很棘手的。“尽可能好”可能是最安全的描述)。
JasonD

@JasonD,我很想知道您的算法做了什么,即使它获得更好的输出:)即使它以不同的方式获得了大致等效的输出。
ZorbaTHut 2010年

1
感谢您的算法描述+允许的失败+源代码。很棒的帖子。
Calvin1602

1
压缩后变大的原因可能是由于压缩算法。由于压缩通常依赖于散列并查找二进制模式,因此,如果算法能够识别出足够的模式,它将生成一整堆模式,这可能会导致大小扩大。一种很好的测试方法,它可以简单地一遍又一遍地重新压缩文件,由于缺少模式,最终它将再次变得更大。
汉娜(Hanna)

1
对于如何搜索最新版本的ZorbaTHut的包装代码(font_baker.cpp),你可以在这里找到:github.com/zorbathut/glorp/blob/...
MEMS

20

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地图集中拟合所有纹理将很有用。


该算法假定纹理为矩形,对吗?
user1767754

17

如果仍然有人感兴趣,我已经完全重写了rectpack2D库,以便更高效。

它的工作原理是std::vector在地图集中保留一个空白空间,从一些初始的最大大小(通常是特定GPU上允许的最大纹理大小)开始,分割第一个可行的空白空间,并将分割后的内容保存回向量。

性能突破来自仅使用向量,而不是像以前那样保留整个树。

自述文件中详细描述了该过程。

该库在麻省理工学院管理下,如果您觉得它有用,我将为您感到高兴!

结果示例:

测试是在3.50GHz的Intel Core i7-4770K CPU上进行的。二进制文件是用lang 6.0.0使用-03开关构建的。

任意游戏精灵+日本字形:总共3264个主题。

运行时间:4毫秒
浪费像素:15538(0.31%-相当于125 x 125平方)

输出(2116 x 2382):

3

颜色:(
黑色浪费空间)

4

日语字形和一些GUI精灵:3122个主题。

运行时间:3.5-7毫秒
浪费像素:9288(1.23%-相当于96 x 96正方形)

输出(866 x 871):

5

颜色:(
黑色浪费空间)

6


2
我已经下载了您的代码。只需阅读struct定义:什么是怪物?它看起来像代码高尔夫球。
akaltar

3
尽管如此,它仍然有效,并有所帮助,非常感谢。我不想变得无礼。
akaltar

不知道为什么我跳过这个答案,因为它比我自己的算法O_O更快,而且打包程序也更好O
O

@akaltar我可以想象,在这段时间里我仍在学习语言:)
Patryk Czachurski

非常简单的方法,可以快速实施并获得良好的结果,谢谢:)
FlintZA 2016年

5

一个很好的启发式算法可以在这里找到。最近,当我尝试类似的操作时,我发现这是我所看到的大多数实现的基本起点。

与大量规则形状,类似尺寸的项目,或小而少的大图像的良好组合,效果特别好。要获得良好结果的最佳建议是记住按照图像大小对输入进行排序,然后将较大的图像打包到最小的图像,因为较小的图像将打包到较大图像周围的空间中。您如何进行分类取决于您的目标。我使用周长而不是面积作为一阶近似值,因为我认为以后很难将高+薄/短+宽的图像(可能具有较小的区域)放置在包装中,因此通过使用周长,您可以按这些奇怪的形状朝向订单的前面。

这是我的打包程序在我的网站图像转储目录中的一组随机图像上输出的示例化:)。 打包机输出

正方形中的数字是树中包含的块的ID,因此,您可以了解插入顺序。第一个是ID“ 3”,因为它是第一个叶子节点(仅叶子包含图像),因此有2个父节点。

        Root[0]
       /      \
   Child[1]  Child[2]
      |
    Leaf[3]

3

我个人只是使​​用适合第一个系统的贪婪的最大块。这不是最佳选择,但可以解决问题。

请注意,如果您有合理数量的纹理块,则即使问题本身是NP,也可以穷举搜索最佳顺序。


3

我使用过的方法(即使对于不规则的UV贴图也适用)是将UV贴图变成位图蒙版,并为纹理本身保留一个蒙版,以寻找UV贴图适合的第一个位置。我根据一些简单的启发式方法(高度,宽度,大小等)对块进行排序,并且允许块的旋转以最小化或最大化所选的启发式方法。这为蛮力提供了一个易于管理的搜索空间。

然后,如果可以迭代尝试几种启发式算法,和/或在选择顺序时应用随机因素,然后迭代直到某个时间限制用完。

通过此方案,您将在较小的UV岛中填入较大的UV岛,甚至将其留在单个UV贴片本身中留下的孔中。


1

我们最近发布了一个python脚本,它将纹理打包到给定大小的多个图像文件中。

从我们的博客中引用:

“虽然可以在网上找到很多打包机,但是我们的困难是要找到可以处理多个目录中大量图像的打包机。因此,我们自己的地图集打包机诞生了!

照原样,我们的小脚本将在基本目录中启动,并将所有.PNG加载到地图集中。如果该图集已满,它将创建一个新图集。然后,它将尝试在所有以前的地图集中拟合其余图像,然后在新地图集中找到一个位置。这样,每个地图集都尽可能地紧凑。地图集是根据其图像所来自的文件夹来命名的。

您可以轻松地更改图集的大小(第65行),要打包的图像格式(第67行),加载目录(第10行)和保存目录(第13行),而无需使用Python。作为一个小的免责声明,它在几天内就被鞭打在一起,专门用于我们的引擎。我鼓励您请求功能,用自己的版本发表评论并报告任何错误,但是对脚本的任何更改都将在我的空闲时间进行。”

请随时在此处查看完整的源代码:http : //www.retroaffect.com/blog/159/Image_Atlas_Packer/#b


1

打包字体非常容易,因为所有(或绝大多数)字形纹理的大小几乎相同。做最简单的事情,这将非常接近最优。

当打包尺寸非常不同的图像时,聪明就显得尤为重要。然后,您希望能够缩小差距,等等。尽管如此,即使像前面讨论的扫描线顺序搜索这样的简单算法也会产生非常合理的结果。

先进的算法都不是魔术。它们的效率不会比Simpel算法高50%,除非您拥有惊人数量的纹理表,否则您将不会从中获得一致的收益。这是因为更好的算法所做的细微改进只会在总体上体现出来。

变得简单,然后继续前进,使自己的努力得到更好的回报


0

如果它是专门用于字体纹理的,那么您可能会做一些非最佳但又好又简单的操作:

按高度排序字符,最高的优先

从0,0开始在当前坐标处放置第一个字符,前进X,下一个放置,重复直到我们无法容纳另一个

将X重置为0,将Y向下移动行中最高字符的高度,然后填充另一行

重复直到我们用完字符或无法容纳另一行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.