uv信息

image-1687004818730

我就可以用x、y、z、w这四个点的信息,来表示这张图的uv信息。

比如我想知道这张图的中心点的uv位置 ​\begin{array}{c}{(\LARGE \frac{z-x}{2}, \frac{w-y}{2})} \end{array}

uv坐标和rect之间的换算比例

我们需要去知道 他们之间的换算系数。
image-1687010843742

GPU如何判断一个三角面是正面还是背面?

通过传入顶点的顺序,如果传入的点的顺序是顺时针就是正面,如果是逆时针就是反面。
比如:
image-1687006199168这里有一个三角面。
如果传入的是(1,0,2)就是顺时针,GPU就会认为是正面,如果是(2,0,1)就是逆时针,GPU就会认为是反面。

对于一个圆来说
image-1687006499343
传入GPU的顶点不就是 (1,0,2)(2,0,3) (3,0,4) ......
也就是 (i, 0 i + 1) 其中i从1开始不断递增。

判断点击区域是否在区域内的算法

image-1687137239702

脚本代码

可以通过修改下面的代码来完成各种想要实现的效果

OnPopulateMesh

通过重写Image类的OnPopulateMesh方法。那么GPU将会从这个方法中获取顶点信息和三角面信息;
所以说:可以通过改变顶点信息和三角面的信息来重写渲染想要的图片效果

DataUtility.GetOuterUV

获取精灵的uv信息,是一个Vector4

UIVertex

这是一个顶点类,里面封装了各种顶点信息
color:顶点的颜色
position:点在rect中的坐标
uv0:点映射在uv坐标系上的坐标

VertexHelper的AddVert方法和AddTriangle方法

添加顶点信息到 VertexHelper中,GPU从这里获取

求两点的直线

k = \frac{y_2-y_1}{x_2-x_1}
x_2 = \frac{y_2 - y1}{k} + x_1
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;

public class CircleImage : Image
{
    /// <summary>
    /// 圆形由多少块三角形拼成
    /// </summary>
    [SerializeField]
    private int segements = 100;


    //显示部分占圆形的百分比.
    [SerializeField]
    private float showPercent = 1;


    // 灰色颜色
    private readonly Color32 GRAY_COLOR = new Color32(60, 60, 60, 255);
    private List<Vector3> _vertexList; 

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        // VertexHelper里面就是存放了数据载体,GPU就会从VertexHelper里获取它的顶点数
        vh.Clear();

        _vertexList = new List<Vector3>();

        AddVertex(vh, segements); // 顶点数

        AddTriangle(vh, segements); // 三角面
    }

    private void AddVertex(VertexHelper vh, int segements)
    {
        // 要想获取图片的顶点数,首先需要获取图片的uv信息
        /**
         * 1. 
         * 2. 这个UIVertex类,就表示一个顶点
         * 3. GetUIVertex这是获取顶点的方法
         * 3. 
         */
        float width = rectTransform.rect.width;
        float heigth = rectTransform.rect.height;
        int realSegments = (int)(segements * showPercent);
        Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;// 获取精灵的uv坐标
        float uvWidth = uv.z - uv.x;// uv的宽高
        float uvHeight = uv.w - uv.y;
        Vector2 uvCenter = new Vector2(uvWidth * 0.5f, uvHeight * 0.5f);
        Vector2 convertRatio = new Vector2(uvWidth / width, uvHeight / heigth);
        float radian = (2 * Mathf.PI) / segements; // 每一个的弧度值
        float radius = width * 0.5f;


        // 获取圆的中心点(如果改变轴心点,中心点也会改变,所以让其中心点偏移)
        Vector2 originPos = new Vector2((0.5f - rectTransform.pivot.x) * width, (0.5f - rectTransform.pivot.y) * heigth);
        Vector2 vertPos = Vector2.zero;
        Color32 colorTemp = GetOriginColor();
        UIVertex origin = GetUIVertex(colorTemp, originPos, vertPos, uvCenter, convertRatio);
        vh.AddVert(origin);

        int vertexCount = realSegments + 1;
        float curRadian = 0; // 当前弧度值
        Vector2 posTermp = Vector2.zero;
        for (int i = 0; i < segements + 1; i++)
        {
            float x = Mathf.Cos(curRadian) * radius;
            float y = Mathf.Sin(curRadian) * radius;
            curRadian += radian;

            if (i < vertexCount)
            {
                colorTemp = color;
            }
            else
            {
                colorTemp = GRAY_COLOR;
            }
            posTermp = new Vector2(x, y); 
            UIVertex vertexTemp = GetUIVertex(colorTemp, posTermp + originPos, posTermp, uvCenter, convertRatio);
            vh.AddVert(vertexTemp);
            _vertexList.Add(posTermp + originPos);
        }
    }

    private Color32 GetOriginColor()
    {
        Color32 colorTemp = (Color.white - GRAY_COLOR) * showPercent;
        return new Color32(
            (byte) (GRAY_COLOR.r + colorTemp.r), 
            (byte) (GRAY_COLOR.g + colorTemp.g),
            (byte) (GRAY_COLOR.b + colorTemp.b),
            255);
    }

    private void AddTriangle(VertexHelper vh, int realSegements)
    {
        int id = 1;
        for (int i = 0; i < realSegements; i++)
        {
            vh.AddTriangle(id, 0, id + 1);
            id++;
        }
    }

    /// <summary>
    /// 获取顶点信息
    /// </summary>
    /// <param name="col">当前的颜色颜色值</param>
    /// <param name="pos">当前点在rect的位置</param>
    /// <param name="uvPos">uv的坐标</param>
    /// <param name="uvCenter">uv中心点的位置</param>
    /// <param name="uvScale">缩放比例</param>
    /// <returns></returns>
    private UIVertex GetUIVertex(Color32 col, Vector3 pos, Vector2 uvPos, Vector2 uvCenter, Vector2 uvScale)
    {
        UIVertex vertexTemp = new UIVertex();
        vertexTemp.color = col;
        vertexTemp.position = pos;
        vertexTemp.uv0 = new Vector2(uvPos.x * uvScale.x + uvCenter.x, uvPos.y * uvScale.y + uvCenter.y);
        return vertexTemp;
    }

    /// <summary>
    /// 当前点击射线是否有效
    /// </summary>
    /// <param name="screenPoint">当前点击的屏幕坐标</param>
    /// <param name="eventCamera">相机事件</param>
    /// <returns></returns>
    public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
        Vector2 localPoint;
        // 参数1:你想要以谁为中心
        // 参数2:点击的屏幕坐标
        // 参数3:事件相机
        // 参数4:子节点在父节点的局部坐标
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out localPoint);
        return IsValid(localPoint);
    }

    private bool IsValid(Vector2 localPoint)
    {
        return GetCrossPointNum(localPoint, _vertexList) %2 == 1;
    }

    private int GetCrossPointNum(Vector2 localPoint, List<Vector3> vertexList)
    {
        int count = 0;
        Vector3 vert1 = Vector3.zero;
        Vector3 vert2 = Vector3.zero;
        int vertCount = vertexList.Count;

        for (int i = 0; i < vertCount; i++)
        {
            vert1 = vertexList[i];
            vert2 = vertexList[(i + 1)% vertCount];

            if (IsYInRang(localPoint, vert1, vert2))
            {
                if (localPoint.x < GetX(vert1, vert2, localPoint.y))
                {
                    count++;
                }
            }
        }

        return count;
    }

    private bool IsYInRang(Vector2 localPoint, Vector3 vert1, Vector3 vert2)
    {
        if (vert1.y > vert2.y)
        {
            return localPoint.y < vert1.y && localPoint.y > vert2.y;
        }
        else
        {
            return localPoint.y < vert2.y && localPoint.y > vert1.y;
        }
    }

    private float GetX(Vector3 vert1, Vector3 vert2, float y)
    {
        float k = (vert1.y - vert2.y)/(vert1.x - vert2.x); // 斜率k
        return vert1.x + (y - vert1.y)/k;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(CircleImage), true)]
[CanEditMultipleObjects]
public class CircleImageEditor : UnityEditor.UI.ImageEditor
{
    SerializedProperty _fillPercent;
    SerializedProperty _segements;

    protected override void OnEnable()
    {
        base.OnEnable();
        _fillPercent = serializedObject.FindProperty("showPercent");
        _segements = serializedObject.FindProperty("segements");
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        serializedObject.Update();
        EditorGUILayout.Slider(_fillPercent, 0, 1, new GUIContent("showPercent"));

        EditorGUILayout.PropertyField(_segements);

        serializedObject.ApplyModifiedProperties();
        if (GUI.changed)
        {
            EditorUtility.SetDirty(target);
        }

    }
}