UGUI降低填充率技巧两则

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但是不参与绘制的组件即可,改完之后帧率瞬间正常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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](http://forum.unity3d.com/threads/ugui-image-with-
polygon-sprites.390039/),Texture Packer作者也表示很感兴趣~

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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);



        }



    }



}

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

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×