UGUI 降低填充率技巧两则

来源:互联网 发布:淘宝怎么卖演唱会门票 编辑:程序博客网 时间:2024/05/22 13:06
原文链接:http://blog.uwa4d.com/archives/fillrate.html

Fill Rate(填充率)是指显卡每帧每秒能够渲染的像素数。在每帧绘制中,如果一个像素被反复绘制的次数越多,那么它占用的资源也必然更多。目前在移动设备上,FillRate 的压力主要来自半透明物体。因为多数情况下,半透明物体需要开启 Alpha Blend 且关闭 ZTest和 ZWrite,同时如果我们绘制像 alpha=0 这种实际上不会产生效果的颜色上去,也同样有 Blend 操作,这是一种极大的浪费。因此,今天我们为大家推荐两则UGUI 降低填充率的技巧,希望大家能受用。

这是侑虎科技第50篇原创文章,感谢作者钱康来供稿。欢迎转发分享,未经作者授权请勿转载。同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

作者博客:http://qiankanglai.me
知乎专栏:https://zhuanlan.zhihu.com/soulgame


在Unity中,与能直接看到的Verts/Tris/Batches数据不同,填充率并不能被直接统计到,但是我们可以通过查看OverDraw来大致查看:

对于UI来说,后者其实是很容易被忽视的热点(特别是对于中低端移动设备来说)。下面我就以具体两个例子为例,并探讨其解决思路。


滥用不可见组件

之前在Profile手头项目的时候发现红米上一个奇怪的现象:战斗界面维持60fps没问题;进入UI界面之后瞬间掉到45fps,甚至有的复杂界面掉到30fps。但战斗场景的Tris/Verts比UI高不少。

通过工具很方便的就定位到了瓶颈在于FillRate爆了,最后发现新手教学部分用了很多“不可见”的Image作为交互响应的控件;但这些东西虽然画上去没有效果,依然占用了显卡资源,特别是有很多大块的区域...找到问题之后就解决起来很方便:实现一个只在逻辑上响应Raycast但是不参与绘制的组件即可,改完之后帧率瞬间正常。

using UnityEngine;using System.Collections;namespace UnityEngine.UI{    public class Empty4Raycast : MaskableGraphic    {        protected Empty4Raycast()        {            useLegacyMeshGeneration = false;        }        protected override void OnPopulateMesh(VertexHelper toFill)        {            toFill.Clear();        }    }} 

这里顺便提一句,显卡资源消耗在没有到瓶颈的时候,大概是随着使用的增加正相关,但是到瓶颈之后很多时候是“崩盘”节奏。


Polygon Mode Sprites

在UI部分中我们会大量使用图片作为元素,如果图片边缘有大片留白就会和上面那个问题一样,产生很多无用填充。Unity和Texture Packer目前都支持了Polygon Mode,也就是说将原来的矩形Sprite用更加紧致的Polygon来描述,从而能更有效的利用空白空间(顺便也减小了打包出来的图资源)。

当然,目前Unity只在Sprite Render里支持了这个模式,在UGUI的Image中还无法正常使用。我自己实现了一个挂官方论坛UGUI Image with polygon sprites,Texture Packer作者也表示很感兴趣~

可以看到同样的一个图片,新的模式下顶点数变多了,但是绘制的范围变小了不少;同时在打包的时候图片也更加的紧致了,因为在不规则大图周围能塞进去不少小的元素。

下面这个脚本是针对Image的扩展,使其支持Polygon Mode Sprite...不过精力有限,只支持了Simple而且没做Preserve Aspect,有兴趣的朋友如果实现了别的模式还望多多交流(主要是Filled和Sliced下要自己重新划分三角形,想想就麻烦...)

using System.Collections.Generic;namespace UnityEngine.UI{    [AddComponentMenu("UI/Effects/PolygonImage", 16)]    [RequireComponent(typeof(Image))]    public class PolygonImage : BaseMeshEffect    {        protected PolygonImage()        { }        // GC Friendly        private static Vector3[] fourCorners = new Vector3[4];        private static UIVertex vertice = new UIVertex();        private RectTransform rectTransform = null;        private Image image = null;        public override void ModifyMesh(VertexHelper vh)        {            if (!isActiveAndEnabled) return;            if (rectTransform == null)            {                rectTransform = GetComponent<RectTransform>();            }            if (image == null)            {                image = GetComponent<Image>();            }            if (image.type != Image.Type.Simple)            {                return;            }            Sprite sprite = image.overrideSprite;            if (sprite == null || sprite.triangles.Length == 6)            {                // only 2 triangles                return;            }            // Kanglai: at first I copy codes from Image.GetDrawingDimensions            // to calculate Image's dimensions. But now for easy to read, I just take usage of corners.            if (vh.currentVertCount != 4)            {                return;            }            rectTransform.GetLocalCorners(fourCorners);            // Kanglai: recalculate vertices from Sprite!            int len = sprite.vertices.Length;            var vertices = new List<UIVertex>(len);            Vector2 Center = sprite.bounds.center;            Vector2 invExtend = new Vector2(1 / sprite.bounds.size.x, 1 / sprite.bounds.size.y);            for (int i = 0; i < len; i++)            {                // normalize                float x = (sprite.vertices[i].x - Center.x) * invExtend.x + 0.5f;                float y = (sprite.vertices[i].y - Center.y) * invExtend.y + 0.5f;                // lerp to position                vertice.position = new Vector2(Mathf.Lerp(fourCorners[0].x, fourCorners[2].x, x), Mathf.Lerp(fourCorners[0].y, fourCorners[2].y, y));                vertice.color = image.color;                vertice.uv0 = sprite.uv[i];                vertices.Add(vertice);            }            len = sprite.triangles.Length;            var triangles = new List<int>(len);            for (int i = 0; i < len; i++)            {                triangles.Add(sprite.triangles[i]);            }            vh.Clear();            vh.AddUIVertexStream(vertices, triangles);        }    }} 

这个做法是用顶点数来换填充率,具体是否这么干还要看项目本身的瓶颈。这一点在官方论坛的帖子里我也和别人讨论过,这里就不再赘述了。

原创粉丝点击