欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UI优化的一些想法

程序员文章站 2022-03-01 17:47:32
...

初始情况

我们采取的是树形结构的UI,你可以理解成跟HTML类似的层级结构。做法也很传统,每一帧都从根节点出发遍历所有节点,对有渲染用途的节点会收集顶点数据并判断是否能合并批次。渲染也中规中矩,从后往前不开深度测试和深度写进行渲染

瓶颈一:大量无渲染功能的UI节点

UI树大部分情况下需要渲染的节点只占整棵树的一小部分,剩下的很大一部分要么隐藏了,要么只是起到布局作用,类似HTML中DIV的功能。当时就出现节点太多,遍历起来特别慢的情况。庆幸的是隐藏的节点可以大部分可以通过判断子树根节点是否显示来剔除整颗子树,所以只有布局功能没有渲染功能的节点就成了罪魁祸首,需要着手去优化

优化的方式是,给每个UI节点,新增以下几个字段用于加速遍历。思路是,假设当前节点是子树根节点,如果它的子树没发生任何变化,那么dirty为false,begin是子树第一个有渲染功能的节点(可能是子树根节点),end是子树最后一个有渲染功能的节点,next指向下一个有渲染功能的节点。那么只需要通过begin,一路next下去,直到end,就可以收集完整棵子树有渲染功能的节点。

struct
{
    bool dirty;
    UIElem* begin;
    UIElem* end;
    UIElem* next;
};

因为UI是变化的,随时有插入删除,所以我们只能在线Lazy处理,做法就是当有子节点状态发生改变或者插入删除时,会从该子节点的父节点出发一路dirty标记为true直到根节点。然后在遍历的时候对上面提到的字段进行动态维护。对于dirty为false,直接采用上面的做法。为true的话,因为我们是先序遍历节点,也就意味着如果当前子树根节点没有渲染作用不能作为begin的话,我们只需要遍历它的下一层节点,第一个有效的begin就可以作为子树根节点的begin,最后一个end可以作为子树根节点的end,只要一次遍历就可以将子树根节点的dirty标记为false。

这样优化之后性能消耗将至原来的1/4

瓶颈二:不同的图片和文字间交叉渲染

如果UI使用的不同的图片的话,它们是没法在一个批次里进行渲染的。文字也是一样,文字其实也是一张大贴图,不同字号的其实用的贴图也不一样。所以想优化这个问题的话,首先要做的,就是将用到的图片打到一张图集里,采样一张图里不同的部分即可,这样同一张图片的东西就能合到一起。

但其实这里还有一个文字,例如两个带文字的按钮,虽然按钮的背景是一样的原本可以合到一起,但由于遍历用的是先序遍历,所以只能是按这个顺序渲染:按钮背景、文字、按钮背景、文字。然后我们就直接优化掉这种情况,人为控制了所有文字都是最后画的,也就一般存在于树的根节点,并且它们不需要在文字间的遮挡关系,将除文字以外的东西都按原来的先后顺序进行渲染(这里依旧按先序遍历的方式,是因为图片间有遮挡关系),并收集文字,文字根据字号和批次最后进行渲染。所以问题就退化成了,除文字部分都在一张图集里就能1个批次解决问题。然后不同字号的文字也分别一个批次解决。

该方法不完美的地方在于,有时候一张图片放不下所有图片,依旧可能会出现交叉渲染

瓶颈三:图片相互叠加带了的像素填充率瓶颈

因为我们采用的是无深度测试从前往后画的做法,所以必然会带来这个后果。要优化的话,我有以下想法,但还没时间去实践。

因为UI是渲染完场景之后渲染的,所以渲染UI前,先把深度清空成1.0,因为我们是先序遍历,所以我们可以从根节点出发,在遍历过程中从大到小赋予深度值,这个值可以假设最大的节点数量,然后1.0除一下,一个个分配就好了。

因为顶点数据有深度值的存在,所以我们可以尽可能的去合批次,只要图片相同混合模式,用的shader是一样的就能合了,唯一需要注意的就是裁剪范围是否一致,不一致的话合不到一起。

渲染的时候跟之前就不一样了,需要倒着画,也就是从前往后画,需要开深度写和深度测试

这是我认知里最好的优化方案了,即不需要担心Drawcall太多的问题,又没有像素填充率的问题,而且深度测试本身就很廉价,有空再实现吧

题外话

再有一个是跟性能无关但跟UI相关的话题,那就是打图集的问题,打图集本身库和方法比较多,我也就随便推荐一个,可以用stb_rect_pack这个库。当然我不是来推销代码,我想说的是实际游戏中,可能会使用到一些比较廉价的UI做法,例如纯色块,只有横向或者纵向渐变的矩形,以及中间镂空的边框。偷懒的做法,往往是控件多大,图片就按一比一的比例塞到图集中。这其实对图集空间带来了很大的浪费,直接导致的后果就是图片位置不够用需要多一张图片,以至于会交叉渲染。所以要想办法提交图片利用率

纯色块:直接压成3x3的像素放在图集中,为什么不是1x1,是因为采样会有误差,以至于采样到周围的其他像素。虽然是3x3像素在图片中,但我们uv只指定1x1的部分

横向渐变的矩形:一般横向渐变,其实有效的也就1行像素,假设它的宽是N,那我们就将3xN放到图集中,原因和上面一样,所以分成3行,采样的时候只采样一行,由GPU自己去拉伸。

纵向渐变的矩形:道理同横向渐变,只需要图集保留3列

镂空图片:拆分成8张图片,8张图片可以根据情况看能不能用上面3种方式优化掉。由于是九宫格的做法,所以也就意味着UI控件需要支持九宫格

在制作图集工具时,别妄想根据图片自动选择优化方案了,还是老老实实人工选方案吧,因为美术给的图,大概率是会有误差的,纯色块并不一定每个像素都一样可能只是接近,横向渐变也并不是每一行都一样,可能会有误差。

相关标签: 图形 UI