bzdww

Get answers and suggestions for various questions from here

Unity3D UGUI Optimization: Making Mirror Image (3)

cms

Foreword

This is the last tutorial on the Mirror Image component ( Mirror ).

In the first two we talked about how to use the original 1/2, 1/4 sprite to achieve the final effect, thus reducing the size of the sprite, and ultimately reducing the size of the image.

If you haven't read the previous article, it is recommended to read the first two articles and read this article:

Unity3D UGUI Optimization: Making Mirror Image (1)

Unity3D UGUI Optimization: Making Mirror Image (2)

This article is about how to mirror images in the Tiled state. In fact, Tiled achieves a large background by repeating small textures. In this case, reducing the size of the Sprite may not have much effect. However, it is not excluded that there is a large Tiled texture, and it can be considered as the beginning and the end, so I will complete this article.

Finally, we will also do a performance test of Mirror, and talk about the applicable environment of this component.

statement

  • Welcome to share this article
  • This document does not permit any form of reprint without authorization.


ready

First we prepare a picture that needs to be tiled.


If there is no material, the simplest is to draw a diamond or a circle. Then take out the 1/4 of the lower left corner as before.

Note: The images that apply here are symmetrical.

principle

The method implemented here is not the same as the previous implementation of Simple and Sliced . Simple and Sliced are implemented by scaling the original image and then mirroring one (or three) copies of the same image. Tiled doesn't need to do this, because the copying process, Image.Tiled implementation has helped us. Then all we have to do is flip the picture. Instead of changing the coordinates of the vertices, we flip the vertices directly. (explained later)

We have to judge that the even-numbered pictures on the abscissa (ordinate) need to be flipped.

The principle is to determine whether the center point of the triangle is a long (wide) even number.


Here is explained: the distance from the midpoint of the triangle face to the rightmost (lower) point of the entire Image is divided by the Tiled width (height), and then rounded down. If it cannot be divisible by 2, it needs to be flipped.


Then we calculate the flip of the uv coordinates of the vertex.


Here is explained: u(0) is the leftmost u value of the Sprite, u(1) is the rightmost u value of the Sprite, and u(x) is the u value of the current vertex, for example, 0.3, 0.7, 0.4, respectively, then The value of u after flipping should be 0.6.

PS: The vertices are not modified here because the UV of the four sides of the edge part is still wrong after the vertices are modified. So it is better to flip the UV directly.

Code

Create a new method DrawTiled . Then called in the location reserved by ModifyMesh.

//ModifyMesh()代码内
//......
switch (type)
{
    case Image.Type.Simple:
        DrawSimple(output, count);
        break;
    case Image.Type.Sliced:
        DrawSliced(output, count);
        break;
    case Image.Type.Tiled:
        DrawTiled(output, count);
        break;
    case Image.Type.Filled:

        break;
}
//......

Then we start writing DrawTiled logic.

/// <summary>
/// 绘制Tiled版
/// </summary>
/// <param name="output"></param>
/// <param name="count"></param>
protected void DrawTiled(List<UIVertex> verts, int count)
{
    Sprite overrideSprite = (graphic as Image).overrideSprite;

    if (overrideSprite == null)
    {
        return;
    }

    Rect rect = graphic.GetPixelAdjustedRect();

//此处使用inner是因为Image绘制Tiled时,会把透明区域也绘制了。
    Vector4 inner = DataUtility.GetInnerUV(overrideSprite);

    float w = overrideSprite.rect.width / (graphic as Image).pixelsPerUnit;
    float h = overrideSprite.rect.height / (graphic as Image).pixelsPerUnit;

    int len = count / 3;

    for (int i = 0; i < len; i++)
    {
        UIVertex v1 = verts[i * 3];
        UIVertex v2 = verts[i * 3 + 1];
        UIVertex v3 = verts[i * 3 + 2];

        float centerX = GetCenter(v1.position.x, v2.position.x, v3.position.x);

        float centerY = GetCenter(v1.position.y, v2.position.y, v3.position.y);

        if (m_MirrorType == MirrorType.Horizontal || m_MirrorType == MirrorType.Quarter)
        {
//判断三个点的水平位置是否在偶数矩形内,如果是,则把UV坐标水平翻转
            if (Mathf.FloorToInt((centerX - rect.xMin) / w) % 2 == 1)
            {
                v1.uv0 = GetOverturnUV(v1.uv0, inner.x, inner.z, true);
                v2.uv0 = GetOverturnUV(v2.uv0, inner.x, inner.z, true);
                v3.uv0 = GetOverturnUV(v3.uv0, inner.x, inner.z, true);
            }
        }

        if (m_MirrorType == MirrorType.Vertical || m_MirrorType == MirrorType.Quarter)
        {
//判断三个点的垂直位置是否在偶数矩形内,如果是,则把UV坐标垂直翻转
            if (Mathf.FloorToInt((centerY - rect.yMin) / h) % 2 == 1)
            {
                v1.uv0 = GetOverturnUV(v1.uv0, inner.y, inner.w, false);
                v2.uv0 = GetOverturnUV(v2.uv0, inner.y, inner.w, false);
                v3.uv0 = GetOverturnUV(v3.uv0, inner.y, inner.w, false);
            }
        }

        verts[i * 3] = v1;
        verts[i * 3 + 1] = v2;
        verts[i * 3 + 2] = v3;
    }
}

The GetCenter method here is to calculate the center point position of the three vertices.

The GetOverturnUV method is to flip the UV and get a new UV.

/// <summary>
/// 返回三个点的中心点
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <param name="p3"></param>
/// <returns></returns>
protected float GetCenter(float p1, float p2, float p3)
{
    float max = Mathf.Max(Mathf.Max(p1, p2), p3);

    float min = Mathf.Min(Mathf.Min(p1, p2), p3);

    return (max + min) / 2;
}

/// <summary>
/// 返回翻转UV坐标
/// </summary>
/// <param name="uv"></param>
/// <param name="start"></param>
/// <param name="length"></param>
/// <param name="isHorizontal"></param>
/// <returns></returns>
protected Vector2 GetOverturnUV(Vector2 uv, float start, float end, bool isHorizontal = true)
{
    if (isHorizontal)
    {
        uv.x = end - uv.x + start;
    }
    else
    {
        uv.y = end - uv.y + start;
    }

    return uv;
}

performance

The role of making a mirror image has been mentioned before, in order to reduce the size of the image packaged by the Sprite Package, thereby reducing the time-consuming loading and memory usage.

In other words, we are aiming at optimization. If you don't do performance testing, it is a rogue.


  • DrawSimple

100 Image+Mirrors are used, and then MirrorType is Quarter.

First of all, the use of heap memory is minimal, so don't worry about the increase in heap memory.

Then the CPU is time consuming, because the hardware of each machine is different. So here is the time spent comparing image drawing. Mirror (2.19ms) Image(2.09ms)

Using Mirror.DrawSimple will double the time on the original Image. Time consuming is mainly focused on traversing vertices on vertex processing.


  • DrawSliced

100 Image+Mirrors are used, and then MirrorType is Quarter.

Like DrawSimple, the use of heap memory is minimal.

Then the CPU takes time: Mirror (9.98ms) Image (2.92ms) .

Using Mirror.DrawSliced ​​will take up to three times the original Image. Time consuming is mainly focused on traversing vertices on vertex processing. Mirroring vertices and culling redundant vertices is the most time consuming.


  • DrawTiled

One Image+Mirror is used, then MirrorType is Quarter. Full screen drawing (1920*1080).

Since Tiled is used in a lot of backgrounds, only one is tested.

The use of heap memory is also minimal.

CPU time consumption: Mirror (1.94ms) Image (0.69ms) .

Since the area drawn is relatively large, the Tiled area is relatively small, so the triangular surface has 896, and the time consumption is concentrated on a large number of vertex (896*3) traversal operations.

in conclusion

Looked at the above performance test, in terms of time, in fact, it is quite big. So pay attention to the timing of use.

When should I not use it?

  1. Sprite size is not recommended. Because it doesn't save much space. And CPU consumption has increased a lot.
  2. When there are many places to use at the same time, for example, when there are one hundred sheets at the same time, it is not recommended, especially the Sliced, which will cause the CPU to take time.
  3. When the tiled area is too small and the tile area is too large, it is not recommended, and it will take a lot of time.

When should I use it? I found a UI map on the Internet, which is very suitable for the Mirror component.

For the background, use a special custom Tiled. Then you can save a lot of space, you can try it in the actual project.


Source code

Lawliet-L / UGUIExtend github.com



Finally.........swearing and shameless praise~~~~~~~