前提介绍
之前的角色血条实现较为简单。画一个血条需要4个批次,有图有真相:
(GPA截取,粉红色为当前drawcall对应的绘制区域)
乍看好像没什么问题,但是游戏是10 vs 10的游戏,极端情况下(两方英雄加小兵进行团P)仅是血条这部分就有上百个批次。虽说图片小、顶点少,但是批次多引起的状态切换开销,大家都懂得。
于是乎,就要想办法合并批次。
之前血条各部分的贴图资源全是分开出的,如果让美术重新出图,程序结构和配置文件都要改。最终美术、程序,策划三方都不爽。
还是发扬优良传统已最小的代价,程序自己把这件事给悄悄做了吧。
因此就有了这篇文章的重点,动态贴图合并算法。
核心算法
把小贴图合并成大贴图也不是什么新鲜事,为了packing lightmaps,图形学前辈就想出了很多绝妙点子。
google “texture atlas”文章一大堆。这里讲一种二叉树算法实现。
思想很简单:递归得把空白大图片切割成小区域,在合适的区域填上小图片。
比如,把图片1填到大图片A的过程:
-
把A根据图片1的高度上下切割成两部分,上部分B的高度等于图片1的高度。
高度差大于宽度差的前提下。否则,A切割成左右两部分,左边的宽度等于图片1的宽度。
-
把B切割成左右两部分。左边的宽度等于图片1 的宽度。
同上依据宽度差大于高度差(此时高度差为0)
-
图片1填充到左边部分。
此时对应的区域结构和数据结构:
继续把图片2填到大图片A:
-
A已被切割,转到B;B已被切割,转到1.
-
1已被填充,转到1的兄弟节点;空间不够,转到B的兄弟节点
-
切割B的下部分C,根据宽度差大于高度差,左右切割成两部分。左边部分D的宽度等于图片2的宽度。
-
继续根据高度差大于宽度差上下切割D节点。上部分的高度等于图片2的高度。
-
图片2填充到上部分。
此时对应的区域结构和数据结构:
优化成果:
由于层次表现需要,血条被分成了5种: 己方英雄,敌方英雄,己方小兵,敌方小兵,自己。分5个批次画出。
下图为画敌方英雄的批次:
GPA截图一局游戏中同一时刻的某一常规帧进行对比:
批次由1075降为1013
GPU时间比较:
补充说明
为提高插入命中率并高效利用空间,可以事先对所有的小图片进行排序。
但也不一定,比如这次就没做,原因两点:变动和影响尽可能小,小图片数量少压根没必要。
下图是程序运行中,生成的texture cache。