• Unity | Image 自定义顶点数据实现圆角矩形


    1 圆角方案简介

    UGUI 中的 Image 实现圆角效果通常有三种方式,Mask、Shader以及自定义顶点数据,相比于前两者,自定义顶点数据的使用方式更加灵活,同时可以减少 DrawCall,但是会增加顶点及三角形数量。最终实现方案可根据实际情况选择,水不深,自己把握

    2 实现方案

    1 修改顶点数据

    渲染流程这里不再赘述,可以简单回顾下渲染管线的每个阶段:

    我们要修改的就是发送给 GPU 的顶点数据, 在 Unity 的 Image 组件中,可以使用 OnPopulateMesh 函数来修改顶点数据

    关于 OnPopulateMesh,在之前的 强制新手引导 中也介绍过,这里再重复一遍

    API:

    protected virtual void OnPopulateMesh(VertexHelper vh);
    

    UI 元素需要生成顶点时的回调函数,通常用于自定义 UI 元素的渲染,可以通过重写该方法来实现自定义的 UI 元素渲染效果

    vh 参数是一个 VertexHelper 类型的对象,用于生成网格数据。在该方法中,可以通过调用 VertexHelper 的方法来添加顶点、三角形和颜色等信息,从而生成网格数据

    在重写该方法时,需要注意以下几点:

    • 在方法中添加顶点、三角形和颜色等信息时,需要按照一定的顺序添加,以确保生成的网格数据正确无误

    • 在方法中添加顶点、三角形和颜色等信息时,需要注意坐标系的转换,以确保生成的网格数据与 UI 元素的位置和大小一致

    • 在方法中添加顶点、三角形和颜色等信息时,需要注意性能问题,尽量避免生成过多的网格数据,以提高渲染效率

    2 填充三角形

    将矩形分割成三个矩形(左边、中间、右边)和四个扇形(四个角)

    先将所有顶点都放入 VertexHelper 中:

    1. vh.Clear();
    2. // 0 1 2 3
    3. vh.AddVert(new Vector3(lefttop), color32, new Vector2(outerUV.x, outerUV.w));
    4. vh.AddVert(new Vector3(lefttop - r), color32, new Vector2(outerUV.x, outerUV.w - uvRadiusY));
    5. vh.AddVert(new Vector3(leftbottom + r), color32, new Vector2(outerUV.x, outerUV.y + uvRadiusY));
    6. vh.AddVert(new Vector3(leftbottom), color32, new Vector2(outerUV.x, outerUV.y));
    7. // 4 5 6 7
    8. vh.AddVert(new Vector3(left + r, top), color32, new Vector2(outerUV.x + uvRadiusX, outerUV.w));
    9. vh.AddVert(new Vector3(left + r, top - r), color32,
    10.     new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
    11. vh.AddVert(new Vector3(left + r, bottom + r), color32,
    12.     new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
    13. vh.AddVert(new Vector3(left + r, bottom), color32, new Vector2(outerUV.x + uvRadiusX, outerUV.y));
    14. // 8 9 10 11
    15. vh.AddVert(new Vector3(right - r, top), color32, new Vector2(outerUV.z - uvRadiusX, outerUV.w));
    16. vh.AddVert(new Vector3(right - r, top - r), color32,
    17.     new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
    18. vh.AddVert(new Vector3(right - r, bottom + r), color32,
    19.     new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
    20. vh.AddVert(new Vector3(right - r, bottom), color32, new Vector2(outerUV.z - uvRadiusX, outerUV.y));
    21. // 12 13 14 15
    22. vh.AddVert(new Vector3(righttop), color32, new Vector2(outerUV.z, outerUV.w));
    23. vh.AddVert(new Vector3(righttop - r), color32, new Vector2(outerUV.z, outerUV.w - uvRadiusY));
    24. vh.AddVert(new Vector3(rightbottom + r), color32, new Vector2(outerUV.z, outerUV.y + uvRadiusY));
    25. vh.AddVert(new Vector3(rightbottom), 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)

    1. // 左边矩形
    2. vh.AddTriangle(251);
    3. vh.AddTriangle(256);
    4. // 中间矩形
    5. vh.AddTriangle(784);
    6. vh.AddTriangle(7811);
    7. // 右边矩形
    8. vh.AddTriangle(10139);
    9. vh.AddTriangle(101314);

    组装四个扇形,分别是:(1,5,4)、(2,6,7)、(8,9,13)、(11,10,14),每个扇形需要用若干个三角形来模拟,三角形数量越多,边缘越平滑,但对应的开销越大

    1. List<Vector2> positionList = new List<Vector2>();
    2. List<Vector2> uvList = new List<Vector2>();
    3. List<int> vertexList = new List<int>();
    4. // 右上角圆心
    5. positionList.Add(new Vector2(right - r, top - r));
    6. uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
    7. vertexList.Add(9);
    8. // 左上角的圆心
    9. positionList.Add(new Vector2(left + r, top - r));
    10. uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
    11. vertexList.Add(5);
    12. // 左下角圆心
    13. positionList.Add(new Vector2(left + r, bottom + r));
    14. uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
    15. vertexList.Add(6);
    16. // 右下角圆心
    17. positionList.Add(new Vector2(right - r, bottom + r));
    18. uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
    19. vertexList.Add(10);
    20. // 每个三角形角度
    21. float degreeDelta = Mathf.PI / 2 / this.cornerSegments;
    22. // 当前的角度
    23. float degree = 0;
    24. for (int i = 0; i < vertexList.Count; i++)
    25. {
    26.     int currentVertCount = vh.currentVertCount;
    27.     for (int j = 0; j <= this.cornerSegments; j++)
    28.     {
    29.         float cos = Mathf.Cos(degree);
    30.         float sin = Mathf.Sin(degree);
    31.         Vector3 position = new Vector3(positionList[i].x + cos * r, positionList[i].y + sin * r);
    32.         Vector3 uv0 = new Vector2(uvList[i].x + cos * uvRadiusX,
    33.             uvList[i].y + sin * uvRadiusY);
    34.         vh.AddVert(position, color32, uv0);
    35.         degree += degreeDelta;
    36.     }
    37.     degree -= degreeDelta;
    38.     for (int j = 0; j <= this.cornerSegments - 1; j++)
    39.     {
    40.         vh.AddTriangle(vertexList[i], currentVertCount + j + 1, currentVertCount + j);
    41.     }
    42. }

    3 扩展 Inspector

    由于脚本是直接继承 Image,脚本中定义的 public 变量不会在 Inspector 面板上显示,所以需要自己扩展面板,方便调节参数:

    1. #if UNITY_EDITOR
    2. [CustomEditor(typeof(BorderRadius), true)]
    3. public class BorderRadiusEditor : ImageEditor
    4. {
    5.     SerializedProperty _sprite;
    6.     SerializedProperty _cornerRadius;
    7.     SerializedProperty _cornerSegments;
    8.     protected override void OnEnable()
    9.     {
    10.         base.OnEnable();
    11.         this._sprite = this.serializedObject.FindProperty("m_Sprite");
    12.         this._cornerRadius = this.serializedObject.FindProperty("cornerRadius");
    13.         this._cornerSegments = this.serializedObject.FindProperty("cornerSegments");
    14.     }
    15.     public override void OnInspectorGUI()
    16.     {
    17.         this.serializedObject.Update();
    18.         this.SpriteGUI();
    19.         this.AppearanceControlsGUI();
    20.         this.RaycastControlsGUI();
    21.         bool showNativeSize = this._sprite.objectReferenceValue != null;
    22.         this.m_ShowNativeSize.target = showNativeSize;
    23.         this.MaskableControlsGUI();
    24.         this.NativeSizeButtonGUI();
    25.         EditorGUILayout.PropertyField(this._cornerRadius);
    26.         EditorGUILayout.PropertyField(this._cornerSegments);
    27.         this.serializedObject.ApplyModifiedProperties();
    28.     }
    29. }
    30. #endif

    圆角效果:

    4 定制圆角

    为了视觉体验,多数情况下矩形并非四个角都是圆角,实现该效果只需要在进行圆角的顶点计算时,判断如果是非圆角,则直接填充该扇形对应的矩形:

    1. if (i == 0 && !this.rightTop)
    2. {
    3.     vh.AddTriangle(vertexList[i], 812);
    4.     vh.AddTriangle(vertexList[i], 1213);
    5.     continue;
    6. }
    7. if (i == 1 && !this.leftTop)
    8. {
    9.     vh.AddTriangle(vertexList[i], 04);
    10.     vh.AddTriangle(vertexList[i], 01);
    11.     continue;
    12. }
    13. if (i == 2 && !this.leftBottom)
    14. {
    15.     vh.AddTriangle(vertexList[i], 32);
    16.     vh.AddTriangle(vertexList[i], 37);
    17.     continue;
    18. }
    19. if (i == 3 && !this.rightBottom)
    20. {
    21.     vh.AddTriangle(vertexList[i], 1514);
    22.     vh.AddTriangle(vertexList[i], 1511);
    23.     continue;
    24. }

    左上和右上非圆角:

    左下和右上非圆角:

    「完整代码公众号回复:圆角矩形」

    更多源码,请扫码获取

    更多源码,请扫码获取

  • 相关阅读:
    网上选课系统的设计与实现(ASP.NET)
    【JavaEE】TCP/IP协议
    SSRF服务器端请求伪造
    相机卡格式化了还能恢复吗?答案在这!(附带恢复教程)
    UWB 定位技术方案选择
    Sophon CE社区版上线,免费Get轻量易用、高效智能的数据分析工具
    Servlet规范之The Request
    深入剖析 RocketMQ 源码 - 负载均衡机制
    c++ web框架实现之静态反射实现
    利用ImportBeanDefinitionRegistrar和BeanPostProcessor实现Spring对自定义注解bean的管理
  • 原文地址:https://blog.csdn.net/u010799737/article/details/133988052