Unity | Image Custom vertex data to implement rounded rectangle

1 Introduction to the rounded corner solution

There are usually three ways to achieve rounded corner effect on Image in UGUI, Mask, Shader and custom vertex data. Compared with the first two, the use of custom vertex data is more flexible and can reduce DrawCall, but it will increase vertices and triangles. quantity. The final implementation plan can be selected according to the actual situation. The water is not deep and you can control it yourself.

2 Implementation plan

1 Modify vertex data

The rendering process will not be described in detail here. You can briefly review each stage of the rendering pipeline:

What we want to modify is the vertex data sent to the GPU. In Unity’s Image component, you can use the OnPopulateMesh function to modify the vertex data.

Regarding OnPopulateMesh, it was also introduced in the previous mandatory novice guide. I will repeat it here.

API:

protected virtual void OnPopulateMesh(VertexHelper vh);

The callback function when UI elements need to generate vertices is usually used to customize the rendering of UI elements. You can achieve customized UI element rendering effects by overriding this method.

The vh parameter is an object of type VertexHelper, used to generate grid data. In this method, you can add information such as vertices, triangles, and colors by calling the VertexHelper method to generate mesh data.

When overriding this method, you need to pay attention to the following points:

  • When adding information such as vertices, triangles, and colors in the method, it needs to be added in a certain order to ensure that the generated mesh data is correct.

  • When adding information such as vertices, triangles, and colors in the method, you need to pay attention to the conversion of the coordinate system to ensure that the generated mesh data is consistent with the position and size of the UI elements.

  • When adding information such as vertices, triangles, and colors in the method, you need to pay attention to performance issues and try to avoid generating too much mesh data to improve rendering efficiency.

2 filled triangle

Divide the rectangle into three rectangles (left, middle, right) and four sectors (four corners)

First put all vertices into 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));

Assemble three rectangles, and the corresponding six triangles are: (2, 5, 1), (2, 5, 6), (7, 8, 4), (7, 8, 11), (10, 13 , 9), (10, 13, 14)

//left rectangle
vh.AddTriangle(2, 5, 1);
vh.AddTriangle(2, 5, 6);
// middle rectangle
vh.AddTriangle(7, 8, 4);
vh.AddTriangle(7, 8, 11);
//right rectangle
vh.AddTriangle(10, 13, 9);
vh.AddTriangle(10, 13, 14);

Assemble four sectors, namely: (1,5,4), (2,6,7), (8,9,13), (11,10,14). Each sector needs to be simulated by several triangles. , the greater the number of triangles, the smoother the edges, but the corresponding cost is greater

List<Vector2> positionList = new List<Vector2>();
List<Vector2> uvList = new List<Vector2>();
List<int> vertexList = new List<int>();

//Center of upper right corner
positionList.Add(new Vector2(right - r, top - r));
uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
vertexList.Add(9);

//The center of the upper left corner
positionList.Add(new Vector2(left + r, top - r));
uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
vertexList.Add(5);

//The center of the lower left corner
positionList.Add(new Vector2(left + r, bottom + r));
uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
vertexList.Add(6);

// Center of the lower right corner
positionList.Add(new Vector2(right - r, bottom + r));
uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
vertexList.Add(10);

//Each triangle angle
float degreeDelta = Mathf.PI / 2 / this.cornerSegments;

// current angle
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);
    }
}

3 Extended Inspector

Since the script directly inherits Image, the public variables defined in the script will not be displayed on the Inspector panel, so you need to extend the panel yourself to facilitate parameter adjustment:

#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

Rounded corners effect:

4 Customized rounded corners

For the sake of visual experience, in most cases the four corners of a rectangle are not rounded. To achieve this effect, you only need to determine if the corners are non-rounded when calculating the vertices of the rounded corners, and then directly fill the rectangle corresponding to the sector:

if (i == 0 & amp; & amp; !this.rightTop)
{
    vh.AddTriangle(vertexList[i], 8, 12);
    vh.AddTriangle(vertexList[i], 12, 13);
    continue;
}

if (i == 1 & amp; & amp; !this.leftTop)
{
    vh.AddTriangle(vertexList[i], 0, 4);
    vh.AddTriangle(vertexList[i], 0, 1);
    continue;
}

if (i == 2 & amp; & amp; !this.leftBottom)
{
    vh.AddTriangle(vertexList[i], 3, 2);
    vh.AddTriangle(vertexList[i], 3, 7);
    continue;
}

if (i == 3 & amp; & amp; !this.rightBottom)
{
    vh.AddTriangle(vertexList[i], 15, 14);
    vh.AddTriangle(vertexList[i], 15, 11);
    continue;
}

Top left and top right non-rounded corners:

Lower left and upper right non-rounded corners:

“Complete code public account reply: rounded rectangle”

?

For more source code, please scan the code to obtain

For more source code, please scan the code to obtain

?