实现圆形Image的方式(编写CircleImage组件,比Mask更高效好用?)
(一) 官方案例中利用Mask实现圆形Image的弊端
1. Mask组件造成渲染消耗
许多游戏项目里免不了有很多图片是以圆形形式展示的,如头像,技能Icon等,一般做法是使用Image组件,再加上一个圆形的Mask。实现非常简单,但因为影响效率,许多关于ui方面的Unity效率优化文章,都会建议开发者少用Mask。
使用Mask会额外消耗多一个Drawcall来创建Mask,做像素剔除。
Mask不利于层级合并。原本同一图集里的ui可以合并层级,仅需一个Drawcall渲染,如果加入Mask,就会将一个ui整体分割成了Mask下的子ui与其他ui,两者只能各自进行层级合并,至少要两个Drawcall。Mask用得多了,一个ui整体会被分割得四分五裂,就会严重影响层次合并的效率了。
2. 无法精确点击 (下节博客细讲)
Image+Mask的实现的圆形,点击判断不精确,点击到圆形外的四个边角仍会触发点击,虽然可以通过另外设置eventAlphaThreshold实现像素级判断,但这个方法有天生缺陷,并不是好的选择。
关于实现规则与不规则图形Button精准点击效果链接地址:
UGUI案例篇第(二)节拓展篇二 :三种实现规则与不规则图形Button精准点击效果的策略
(二) 手动编写CircleImage组件
了解了原有做法的缺陷后,我们希望自制圆形Image组件,解决这些问题,并且尽量简单易用。
1. 将Mask替代掉的CircleImage组件实现原理
从渲染层面思考,Image组件默认以矩形形式渲染,如果有办法定制一个特殊Image组件,重新写入圆形形状的渲染顶点、三角面片信息,根本不需要Mask就能渲染出圆形Image。
因为我们看到的屏幕显示,都是通过GPU渲染出来的,而GPU渲染以三角面片为最小单元。所有的图形画面,本质是由无数三角面片组成的,例如矩形是由两个直角三角面片组成的;圆形可以由若干个相同的以圆心为顶点的等腰三角面片组成正多边形,近似模拟出来。三角面片分得多了,多边形的边越多,夹角越大,就越近似圆形。
如下图所示:
2. 实现CircleImage组件
Image类继承自MaskableGraphic,实现了ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter这三个接口。最关键的是MaskableGraphic类,MaskableGraphic负责绘制逻辑,MaskableGraphic继承自Graphic,Graphic里有个OnPopulateMesh函数,这正是我们需要的函数。
(1) 实现CircleImage组件的想法概述
当UI元素生成顶点数据时会调用OnPopulateMesh(VertexHelper vh)函数,我们只要继承改写OnPopulateMesh函数,将原先的矩形顶点数据清除,改写入圆形顶点数据,这样渲染出来的自然是圆形图片。
(2) 实现CircleImage组件步骤:
步骤一:清除VertexHelper原有顶点信息,获得uvCenter,uv转换系数
OnPopulateMesh方法的VertexHelper参数(顶点数据集成类对象,面片数据的载体),保存着原来的顶点信息,因为要重新传入顶点信息,需先调用Clear方法,清除VertexHelper原有顶点信息。在计算顶点前,通过DataUtility.GetOuterUV(overrideSprite)获取贴图uv信息,简单计算获得中心点Center,UV转换系数等信息。
步骤二:计算radian(每个3角面片的弧度) 与 radius(圆的半径)
知道了等分面片数segementCount,我们可以算出每个面片的顶点夹角弧度(radian);以及利用Pivot和Image的Width或Height算出半径(radius).
步骤三:创建所有圆的顶点信息(UIVertex), 再将UIVertex传给VertexHelper
已经有了半径,夹角信息,根据圆形点坐标公式(radius * cosA,radius * sinA)可以算出顶点坐标,每次迭代新建UIVertex,将求出的顶点的position,color,uv等参数传入,再将UIVertex传给VertexHelper。重复迭代n次,VertexHelper就获得了多边形顶点及圆心点信息了。
步骤四:创建所有三角形信息, 再将信息传给VertexHelper
知道了所有顶点信息,仍不足以渲染图形,因为GPU还不知道顶点之间的关系,不知道这些顶点分成了多少个三角面片,所以还需要把所有三角形信息一一告诉GPU。
VertexHelper接受三角形信息原理:
VertexHelper是通过AddTriangle接口接受三角形信息:
public void AddTriangle(int idx0, int idx1, int idx2)
接口的传入参数并不是UIVertex类型,而是int类型