UGUI 中的 Image 实现圆角效果通常有三种方式,Mask、Shader以及自定义顶点数据,相比于前两者,自定义顶点数据的使用方式更加灵活,同时可以减少 DrawCall,但是会增加顶点及三角形数量。最终实现方案可根据实际情况选择,水不深,自己把握
渲染流程这里不再赘述,可以简单回顾下渲染管线的每个阶段:
我们要修改的就是发送给 GPU 的顶点数据, 在 Unity 的 Image 组件中,可以使用 OnPopulateMesh 函数来修改顶点数据
关于 OnPopulateMesh,在之前的 强制新手引导 中也介绍过,这里再重复一遍
API:
protected virtual void OnPopulateMesh(VertexHelper vh);
UI 元素需要生成顶点时的回调函数,通常用于自定义 UI 元素的渲染,可以通过重写该方法来实现自定义的 UI 元素渲染效果
vh 参数是一个 VertexHelper 类型的对象,用于生成网格数据。在该方法中,可以通过调用 VertexHelper 的方法来添加顶点、三角形和颜色等信息,从而生成网格数据
在重写该方法时,需要注意以下几点:
在方法中添加顶点、三角形和颜色等信息时,需要按照一定的顺序添加,以确保生成的网格数据正确无误
在方法中添加顶点、三角形和颜色等信息时,需要注意坐标系的转换,以确保生成的网格数据与 UI 元素的位置和大小一致
在方法中添加顶点、三角形和颜色等信息时,需要注意性能问题,尽量避免生成过多的网格数据,以提高渲染效率
将矩形分割成三个矩形(左边、中间、右边)和四个扇形(四个角)
先将所有顶点都放入 VertexHelper 中:
vh.Clear();
// 0 1 2 3
vh.AddVert(new Vector3(left, top), color32, new Vector2(outerUV.x, outerUV.w));
vh.AddVert(new Vector3(left, top - r), color32, new Vector2(outerUV.x, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(left, bottom + r), color32, new Vector2(outerUV.x, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(left, bottom), color32, new Vector2(outerUV.x, outerUV.y));
// 4 5 6 7
vh.AddVert(new Vector3(left + r, top), color32, new Vector2(outerUV.x + uvRadiusX, outerUV.w));
vh.AddVert(new Vector3(left + r, top - r), color32,
new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(left + r, bottom + r), color32,
new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(left + r, bottom), color32, new Vector2(outerUV.x + uvRadiusX, outerUV.y));
// 8 9 10 11
vh.AddVert(new Vector3(right - r, top), color32, new Vector2(outerUV.z - uvRadiusX, outerUV.w));
vh.AddVert(new Vector3(right - r, top - r), color32,
new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(right - r, bottom + r), color32,
new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(right - r, bottom), color32, new Vector2(outerUV.z - uvRadiusX, outerUV.y));
// 12 13 14 15
vh.AddVert(new Vector3(right, top), color32, new Vector2(outerUV.z, outerUV.w));
vh.AddVert(new Vector3(right, top - r), color32, new Vector2(outerUV.z, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(right, bottom + r), color32, new Vector2(outerUV.z, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(right, bottom), color32, new Vector2(outerUV.z, outerUV.y));
组装三个矩形,其对应的六个三角形分别是:(2, 5, 1)、(2, 5, 6)、(7, 8, 4)、(7, 8, 11)、(10, 13, 9)、(10, 13, 14)
// 左边矩形
vh.AddTriangle(2, 5, 1);
vh.AddTriangle(2, 5, 6);
// 中间矩形
vh.AddTriangle(7, 8, 4);
vh.AddTriangle(7, 8, 11);
// 右边矩形
vh.AddTriangle(10, 13, 9);
vh.AddTriangle(10, 13, 14);
组装四个扇形,分别是:(1,5,4)、(2,6,7)、(8,9,13)、(11,10,14),每个扇形需要用若干个三角形来模拟,三角形数量越多,边缘越平滑,但对应的开销越大
List positionList = new List();
List uvList = new List();
List vertexList = new List();
// 右上角圆心
positionList.Add(new Vector2(right - r, top - r));
uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
vertexList.Add(9);
// 左上角的圆心
positionList.Add(new Vector2(left + r, top - r));
uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
vertexList.Add(5);
// 左下角圆心
positionList.Add(new Vector2(left + r, bottom + r));
uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
vertexList.Add(6);
// 右下角圆心
positionList.Add(new Vector2(right - r, bottom + r));
uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
vertexList.Add(10);
// 每个三角形角度
float degreeDelta = Mathf.PI / 2 / this.cornerSegments;
// 当前的角度
float degree = 0;
for (int i = 0; i < vertexList.Count; i++)
{
int currentVertCount = vh.currentVertCount;
for (int j = 0; j <= this.cornerSegments; j++)
{
float cos = Mathf.Cos(degree);
float sin = Mathf.Sin(degree);
Vector3 position = new Vector3(positionList[i].x + cos * r, positionList[i].y + sin * r);
Vector3 uv0 = new Vector2(uvList[i].x + cos * uvRadiusX,
uvList[i].y + sin * uvRadiusY);
vh.AddVert(position, color32, uv0);
degree += degreeDelta;
}
degree -= degreeDelta;
for (int j = 0; j <= this.cornerSegments - 1; j++)
{
vh.AddTriangle(vertexList[i], currentVertCount + j + 1, currentVertCount + j);
}
}
由于脚本是直接继承 Image,脚本中定义的 public 变量不会在 Inspector 面板上显示,所以需要自己扩展面板,方便调节参数:
#if UNITY_EDITOR
[CustomEditor(typeof(BorderRadius), true)]
public class BorderRadiusEditor : ImageEditor
{
SerializedProperty _sprite;
SerializedProperty _cornerRadius;
SerializedProperty _cornerSegments;
protected override void OnEnable()
{
base.OnEnable();
this._sprite = this.serializedObject.FindProperty("m_Sprite");
this._cornerRadius = this.serializedObject.FindProperty("cornerRadius");
this._cornerSegments = this.serializedObject.FindProperty("cornerSegments");
}
public override void OnInspectorGUI()
{
this.serializedObject.Update();
this.SpriteGUI();
this.AppearanceControlsGUI();
this.RaycastControlsGUI();
bool showNativeSize = this._sprite.objectReferenceValue != null;
this.m_ShowNativeSize.target = showNativeSize;
this.MaskableControlsGUI();
this.NativeSizeButtonGUI();
EditorGUILayout.PropertyField(this._cornerRadius);
EditorGUILayout.PropertyField(this._cornerSegments);
this.serializedObject.ApplyModifiedProperties();
}
}
#endif
圆角效果:
为了视觉体验,多数情况下矩形并非四个角都是圆角,实现该效果只需要在进行圆角的顶点计算时,判断如果是非圆角,则直接填充该扇形对应的矩形:
if (i == 0 && !this.rightTop)
{
vh.AddTriangle(vertexList[i], 8, 12);
vh.AddTriangle(vertexList[i], 12, 13);
continue;
}
if (i == 1 && !this.leftTop)
{
vh.AddTriangle(vertexList[i], 0, 4);
vh.AddTriangle(vertexList[i], 0, 1);
continue;
}
if (i == 2 && !this.leftBottom)
{
vh.AddTriangle(vertexList[i], 3, 2);
vh.AddTriangle(vertexList[i], 3, 7);
continue;
}
if (i == 3 && !this.rightBottom)
{
vh.AddTriangle(vertexList[i], 15, 14);
vh.AddTriangle(vertexList[i], 15, 11);
continue;
}
左上和右上非圆角:
左下和右上非圆角:
「完整代码公众号回复:圆角矩形」
❝更多源码,请扫码获取
❞