OnPopulateMesh function and VertexHelper class of UGUI in Unity

Graphics class

When a UGUI UI element generates vertex data, it will call the Graphics class OnPopulateMesh(VertexHelper vh) function, we can modify the vertex data or get the vertex in this function The data.

When to call, refer to details: Simple reading of UGUI source code for drawing Mesh and custom drawing bazyd

Rebuild method
It is called before the Canvas is rendered. In this method, UpdateGeometry and UpdateMaterial are called to update vertices and materials.

UpdateGeometry method
Call the DoMeshGeneration method, if the rectTransform is not empty, and the width and height are greater than 0, call OnPopulateMesh, in fact, just save the vertex and triangle information to s_VertexHelper. Then get all the components of IMeshModifier type (IMeshModifier is an interface, components that need to be based on vertex information inherit from it, such as Shadow inherits from it indirectly), call the ModifyMesh method of IMeshModifier to modify the Mesh information. Finally, assign the modified information in s_VertexHelper to workerMesh, and set workerMesh to canvasRenderer.

UpdateMaterial method
Update the material and texture of canvasRenderer. When setting the material for the canvasRenderer, it will traverse all IMaterialModifier type components, and call the IMaterialModifier.GetModifiedMaterial method to obtain the modified Material when rebuilding the image to achieve the mask effect.

OnPopulateMesh method
During CanvasUpdateRegistry layout reconstruction and graphics reconstruction, the Rebuild method of Graphic in the reconstruction sequence will be called, and the Rebuild method will call the OnPopulateMesh method to provide the vertex position, vertex color, UV and triangle information for the CanvasRenderer’s Mesh. The OnPopulateMesh method saves vertex data and triangle information to VertexHelper. This method will be overridden in RawImage, Image and Text to draw pictures and text.

The display-related controls in UGUI, such as Image, Text, RawImage, etc., all inherit from the MaskableGraphic class, and the MaskableGraphic class inherits from the Graphic class

The shape, color, normal and other information of the control in the screen are all controlled by the OnPopulateMesh function in the Graphics class
Whenever a vertex or texture is changed, the OnPopulateMesh function is called
The required input parameter VertexHelper is a vertex auxiliary class that stores the basic information for generating Mesh

Drawing UGUI can also pass Mesh, but it is generally not used very much
Just like a 3D object can be displayed because of its MeshRender and MeshFilter, a 2D Sprite can be displayed because of its SpriteRender, the same is true for UGUI elements, each UI element has a CanvasRender component

 CanvasRenderer render;
    Vector3[] vertices;
    int[] triangles;
 
    void Update()
    {
        Mesh mesh = new Mesh();
        mesh.vertices = vertices;
        mesh. triangles = triangles;
 
        render = GetComponent<CanvasRenderer>();
        render. SetMesh(mesh);
    }

Mouse line drawing function

Use the rewritten OnPopulateMesh( VertexHelper vh ) method in UGUI, and then use it to draw, change it, it can be used to achieve mouse drawing line function. Detailed reference:

Unity drawing line OnPopulateMesh function VertexHelper_Peter_Gao_’s blog – CSDN blog

VertexHelper class structure

In OnPopulateMesh( VertexHelper vh ) function, the vertex data of UI elements will fill this parameter strong> VertexHelper data structure, we can modify the data in this data structure to affect some attributes of vertices. Properties and methods of the VertexHelper class:

Methods for drawing basic graphics of the VertexHelper class

AddVert: Add a vertex (the index of the first added vertex is 0, the second added vertex is 1, in order…)
AddTriangle: Draw a triangle (the GPU will draw a triangle in the order of the input vertex subscripts when drawing)
currentIndexCount: There are several vertex indexes in the VertexHelper structure (coincident vertices are counted as 2 vertices, for example, a triangle has at least 3 vertices, and a square has at least 6 vertices)
currentVertCount: How many vertices are there in the VertexHelper structure (the number of vertices added by AddVert)
PopulateUIVertex: Get the vertex data of a certain index
SetUIVertex: Set the vertex data of a certain index
AddUIVertexQuad: Encapsulate AddVert and AddTriangle into one function
AddUIVertexStream: Encapsulate AddVert and AddTriangle into one function

Draw a triangle

We draw a triangle with AddVert and AddTriangle

We create a script TestVertexHelper.cs and inherit Graphic, because the OnPopulateMesh function is defined in Graphic

using UnityEngine.UI;
using UnityEngine;
 
public class Test : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        base.OnPopulateMesh(vh);
 
        vh. Clear();
        vh.AddVert(new Vector3(0, 0), Color.black, Vector2.zero);
        vh.AddVert(new Vector3(0, 100), Color.black, Vector2.zero);
        vh.AddVert(new Vector3(100, 0), Color.black, Vector2.zero);
 
        vh.AddTriangle(0, 1, 2);
    }
}

First, we use AddVert to add three vertices. The relationship between the three vertices is shown in the figure above. Then we add a triangle with AddTriangle. The parameter is the index of the triangle vertex. When drawing, the GPU will draw a triangle according to vertex 0->vertex 1->vertex 2

Draw a square

We draw a square with AddVert and AddTriangle

public class TestVertexHelper : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        //Add four vertices
        vh.AddVert(new Vector3(0, 0), Color.red, Vector2.zero);
        vh.AddVert(new Vector3(0, 100), Color.green, Vector2.zero);
        vh.AddVert(new Vector3(100, 100), Color.black, Vector2.zero);
        vh.AddVert(new Vector3(100, 0), Color.blue, Vector2.zero);
        // add two triangles
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(2, 3, 0);
    }
}

We first added four vertices with AddVert, and the order of the vertices is shown in the figure; then we added two triangles with AddTriangle, where vh.AddTriangle(0, 1, 2) means to draw a triangle with vertices 0, 1, 2, vh .AddTriangle(2, 3, 0) means to draw a triangle with vertices 2, 3, 0.
Properties currentIndexCount and currentVertCount

currentVertCount indicates how many vertices there are in the VertexHelper structure

currentIndexCount indicates how many vertex indexes are in the VertexHelper structure

Let’s print the information of the square we just drew

public class TestVertexHelper : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        //Add four vertices
        vh.AddVert(new Vector3(0, 0), Color.red, Vector2.zero);
        vh.AddVert(new Vector3(0, 100), Color.green, Vector2.zero);
        vh.AddVert(new Vector3(100, 100), Color.black, Vector2.zero);
        vh.AddVert(new Vector3(100, 0), Color.blue, Vector2.zero);
        // add two triangles
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(2, 3, 0);
 
        Debug.Log("currentIndexCount" + vh.currentIndexCount);
        Debug.Log("currentVertCount" + vh.currentVertCount);
    }
}

We see that the vertex index has four and the vertex has six

It is easy to understand that there are four vertex indexes (0, 1, 2, 3), but we added four vertices with AddVert. How do we show that there are six? Because unity will divide the vertices at the junction of the triangle into two. That is, the vertices 0, 2 where the triangle (0,1,2) and the triangle (2,3,0) coincide will be divided into two vertices for processing.

Other functions

PopulateUIVertex function

Return the vertex data of the specified index, and the returned vertex data will fill the UIVertex data structure

example:

We use the PopulateUIVertex function to get the vertex data with index 2 and print it out

public class TestVertexHelper : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        //Add four vertices
        vh.AddVert(new Vector3(0, 0), Color.red, Vector2.zero);
        vh.AddVert(new Vector3(0, 100), Color.green, Vector2.zero);
        vh.AddVert(new Vector3(100, 100), Color.black, Vector2.zero);
        vh.AddVert(new Vector3(100, 0), Color.blue, Vector2.zero);
        // add two triangles
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(2, 3, 0);
 
        UIVertex vertex = new UIVertex();
        vh.PopulateUIVertex(ref vertex, 2);
        Debug.Log("color " + vertex.color + "position " + vertex.position + "uv0 " + vertex.uv0);
    }
}

We see that this data is the same as the data of the third vertex we set

SetUIVertex function

Set the data of a vertex

We set the color of the third vertex to black (vh.AddVert(new Vector3(100, 100), Color.black, Vector2.zero)?, we use the SetUIVertex function to set the third vertex (vertex with index 2 , the index starts from 0) the tricolor is set to yellow

public class TestVertexHelper : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        //Add four vertices
        vh.AddVert(new Vector3(0, 0), Color.red, Vector2.zero);
        vh.AddVert(new Vector3(0, 100), Color.green, Vector2.zero);
        vh.AddVert(new Vector3(100, 100), Color.black, Vector2.zero);
        vh.AddVert(new Vector3(100, 0), Color.blue, Vector2.zero);
        // add two triangles
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(2, 3, 0);
 
        //Get the color of the third vertex (vertex index is 2, starting from 0)
        UIVertex vertex = new UIVertex();
        vh.PopulateUIVertex(ref vertex, 2);
        // set the color to yellow
        vertex.color = Color.yellow;
        vh. SetUIVertex(vertex, 2);
    }
}


We see that the vertex with index 2 turns yellow

AddUIVertexQuad(UIVertex[] verts) function

add a rectangle

public class TestVertexHelper : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        UIVertex[] verts = new UIVertex[4];
        verts[0].position = new Vector3(0, 0);
        verts[0].color = Color.red;
        verts[0].uv0 = Vector2.zero;
 
        verts[1].position = new Vector3(0, 100);
        verts[1].color = Color.green;
        verts[1].uv0 = Vector2.zero;
 
        verts[2].position = new Vector3(100, 100);
        verts[2].color = Color.black;
        verts[2].uv0 = Vector2.zero;
 
        verts[3].position = new Vector3(100, 0);
        verts[3].color = Color.blue;
        verts[3].uv0 = Vector2.zero;
 
        vh. AddUIVertexQuad(verts);
    }
}

We see that this method produces the same effect as drawing two triangles

public void AddUIVertexStream(List verts, List indices);

This method adds vertex data to VertexHelper in batches. The first parameter is the vertex data, and the second parameter is the vertex index directory that constitutes the primitive.

If we want to draw two triangles, add that we have two triangles, and the vertex indices are (0,1,2), (2,3,0), then our indices should be defined as

List indices = new List() { 0, 1, 2, 2, 3, 0 };

public class TestVertexHelper : Graphic
{
protected override void OnPopulateMesh(VertexHelper vh)
{
vh. Clear();
List verts = new List();

 UIVertex vert0 = new UIVertex();
    vert0.position = new Vector3(0, 0);
    vert0.color = Color.red;
    vert0.uv0 = Vector2.zero;
    verts. Add(vert0);

    UIVertex vert1 = new UIVertex();
    vert1.position = new Vector3(0, 100);
    vert1.color = Color.green;
    vert1.uv0 = Vector2.zero;
    verts. Add(vert1);

    UIVertex vert2 = new UIVertex();
    vert2.position = new Vector3(100, 100);
    vert2.color = Color.black;
    vert2.uv0 = Vector2.zero;
    verts. Add(vert2);

    UIVertex vert3 = new UIVertex();
    vert3.position = new Vector3(100, 0);
    vert3.color = Color.blue;
    vert3.uv0 = Vector2.zero;
    verts. Add(vert3);

    List<int> indices = new List<int>() { 0, 1, 2, 2, 3, 0 };
    vh.AddUIVertexStream(verts, indices);
}

Effect:

void AddUIVertexTriangleStream(List verts) function

This method adds triangle vertex data to VertexHelper in batches, and the length of the parameter must be a multiple of three

public class TestVertexHelper : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        List<UIVertex> verts = new List<UIVertex>();
 
        UIVertex vert0 = new UIVertex();
        vert0.position = new Vector3(0, 0);
        vert0.color = Color.red;
        vert0.uv0 = Vector2.zero;
        verts. Add(vert0);
 
        UIVertex vert1 = new UIVertex();
        vert1.position = new Vector3(0, 100);
        vert1.color = Color.green;
        vert1.uv0 = Vector2.zero;
        verts. Add(vert1);
 
        UIVertex vert2 = new UIVertex();
        vert2.position = new Vector3(100, 100);
        vert2.color = Color.black;
        vert2.uv0 = Vector2.zero;
        verts. Add(vert2);
 
        vh.AddUIVertexTriangleStream(verts);
    }
}

GetUIVertexStream(List stream) function

Get information about all vertices in the current VertexHelper

public class TestVertexHelper : Graphic
{
 
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh. Clear();
        // add three vertices
        vh.AddVert(new Vector3(0, 0), Color.red, Vector2.zero);
        vh.AddVert(new Vector3(0, 100), Color.green, Vector2.zero);
        vh.AddVert(new Vector3(100, 100), Color.black, Vector2.zero);
        //add triangle
        vh.AddTriangle(0, 1, 2);
        //get information about all vertices
        List<UIVertex> stream = new List<UIVertex>();
        vh. GetUIVertexStream(stream);
        foreach (UIVertex v in stream) {
            Debug.Log("color " + v.color + "position " + v.position + "uv0 " + v.uv0);
        }
    }
}