• 【UGUI源码 —— VertexHelper 】 极其重要的辅助类


    参考链接:

    https://zhuanlan.zhihu.com/p/136279221

    https://www.jianshu.com/p/2245969a9173

    先上源码

    public class VertexHelper : IDisposable
    {
        //保存每个顶点的位置、颜色、UV、法线、切线
        private List<Vector3> m_Positions = ListPool<Vector3>.Get();
        private List<Color32> m_Colors = ListPool<Color32>.Get();
        private List<Vector2> m_Uv0S = ListPool<Vector2>.Get();
        private List<Vector2> m_Uv1S = ListPool<Vector2>.Get();
        private List<Vector2> m_Uv2S = ListPool<Vector2>.Get();
        private List<Vector2> m_Uv3S = ListPool<Vector2>.Get();
        private List<Vector3> m_Normals = ListPool<Vector3>.Get();
        private List<Vector4> m_Tangents = ListPool<Vector4>.Get();
        //记录三角形的索引
        private List<int> m_Indices = ListPool<int>.Get();
    
        //开始添加顶点的位置、颜色、UV、法线、切线数据
        public void AddVert(Vector3 position, Color32 color, Vector2 uv0, Vector2 uv1, Vector3 normal, Vector4 tangent)
        {
            InitializeListIfRequired();
            m_Positions.Add(position);
            m_Colors.Add(color);
            m_Uv0S.Add(uv0);
            m_Uv1S.Add(uv1);
            m_Uv2S.Add(Vector2.zero);
            m_Uv3S.Add(Vector2.zero);
            m_Normals.Add(normal);
            m_Tangents.Add(tangent);
        }
        //添加三角形的索引
        public void AddTriangle(int idx0, int idx1, int idx2)
        {
            InitializeListIfRequired();
            m_Indices.Add(idx0);
            m_Indices.Add(idx1);
            m_Indices.Add(idx2);
        }
    
    一个知识点注意一下:mesh的顶点不能超过65000,Unity中顶点数量不能超过65000个
            public void FillMesh(Mesh mesh)
            {
                mesh.Clear();
    
                if (m_Positions.Count >= 65000)
                    throw new ArgumentException("Mesh can not have more than 65000 vertices");
    
                mesh.SetVertices(m_Positions);
                mesh.SetColors(m_Colors);
                mesh.SetUVs(0, m_Uv0S);
                mesh.SetUVs(1, m_Uv1S);
                mesh.SetUVs(2, m_Uv2S);
                mesh.SetUVs(3, m_Uv3S);
                mesh.SetNormals(m_Normals);
                mesh.SetTangents(m_Tangents);
                mesh.SetTriangles(m_Indices, 0);
                mesh.RecalculateBounds();
            }

    介绍:

    VertexHelpr是UGUI中的代码,在自定义UI控件的时候,需要大量用到。

    VertexHelper类。通过这个类,我们可以创建顶点,构成三角形,填充到一张mesh上,然后用MeshRenderer渲染到屏幕上,实际上我们可以直接操作Mesh类添加顶点、三角形等操作,这里的vertexHelper只是UGUI与Mesh之间的一座桥梁。


    应用例子01:

    Graphic中有个静态对象s_VertexHelper保存每次生成的网格信息,使用完后会立即清理掉等待下个Graphic对象使用。

    public abstract class Graphic : UIBehaviour, ICanvasElement
    {
        //...略
        [NonSerialized] private static readonly VertexHelper s_VertexHelper = new VertexHelper();
        
        private void DoMeshGeneration()
        {
            //...略
            //s_VertexHelper中的数据信息,调用FillMesh()方法生成真正的网格信息。
            s_VertexHelper.FillMesh(workerMesh);
    
            //最终提交网格信息,在C++底层中合并网格
            canvasRenderer.SetMesh(workerMesh);
        }
    }

    应用例子02: 用来帮助我们快速创建网格。

    // 创建一个正方形面片
    VertexHelper vh = new VertexHelper();
    vh.Clear();
    
    // 添加顶点
    vh.AddVert(new Vector3(0, 0, 0), Color.red, new Vector2(0, 0));
    vh.AddVert(new Vector3(1, 0, 0), Color.green, new Vector2(1, 0));
    vh.AddVert(new Vector3(1, 1, 0), Color.yellow, new Vector2(1, 1));
    vh.AddVert(new Vector3(0, 1, 0), Color.cyan, new Vector2(0, 1));
    
    // 设置三角形顺序
    vh.AddTriangle(0, 2, 1);
    vh.AddTriangle(0, 3, 2);
    
    // 将结果展示出来
    MeshFilter meshFilter = gameObject.GetOrAddComponent<MeshFilter>();
    Mesh mesh = new Mesh();
    mesh.name = "Quad";
    vh.FillMesh(mesh);
    meshFilter.mesh = mesh;
    
    MeshRenderer meshRenderer = gameObject.GetOrAddComponent<MeshRenderer>();
    meshRenderer.material = material;
    meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    meshRenderer.receiveShadows = false;

    最后再来看看效果:

    应用例子03:

    接下来 我们尝试用这种办法在空物体上画出一张image图片。

    首先在Unity中添加一个空对象,命名Image,Image对象需要添加MeshFilter组件,用来添加Mesh,还需要添加MeshRenderer组件,用来渲染Mesh,使其能够在屏幕中显示。

    创建CreateImage.cs脚本,并挂载在Image上。
    在脚本中添加Create函数,并有vertexHelper构造4个顶点(矩形):


            VertexHelper vp = new VertexHelper();
            vp.Clear();
            //这里采用的添加顶点函数,函数参数分别对应了顶点位置,顶点颜色,纹理坐标
            vp.AddVert(new Vector3(0, 0, 0), Color.red, new Vector2(0, 0));
            vp.AddVert(new Vector3(0, 1, 0), Color.green, new Vector2(0, 1));
            vp.AddVert(new Vector3(1, 1, 0), Color.blue, new Vector2(1, 1));
            vp.AddVert(new Vector3(1, 0, 0), Color.cyan, new Vector2(1, 0));

    接下来,我们用4个顶点构造出矩形的两个三角形:

            vp.AddTriangle(0, 1, 2);
            vp.AddTriangle(2, 3, 0);

    我们知道矩形最少是由两个三角形构成的,当然我们可以构造更多的三角形去显示一个矩形,但是这样不仅显得多余,而且对于计算机来说计算量就会上升。同时我们要注意:

    Unity默认是只渲染物体的正面,背面是不渲染的。这里就涉及到构造三角形的顺序。在Unity中以顺时针方向构造三角形,这个三角形就是正面,就被会参与渲染的计算,逆时针就是背面,最终不会显示到屏幕上。

    如何看三角形是顺时针还是逆时针呢。从构造三角形的三个顶点顺序(A、B、C),第一个顶点A为基准,然后指向第二个顶点B划线AB,这根线沿着第二个顶点B到第三个顶点C的连线BC扫描,扫描的方向是顺时针即为正面,逆时针即为背面(当然这个规定只是Unity的规定,其他地方要看所用的图形接口是如何规定的)。

    接下来,我们填充到一张Mesh上,并将这个Mesh赋值为空物体Image的MeshFilter上。

            meshFilter = this.GetComponent<MeshFilter>();
            Mesh mesh = new Mesh();
            vp.FillMesh(mesh);
            meshFilter.mesh = mesh;

    然后我们运行游戏:

    可以看到有一个紫色的方块从无到有。可能有小伙伴就有疑问了,创建的顶点不是紫色的啊,这是因为MeshRender渲染时候无材质的默认颜色。
    意思就是大部分的shder都不会显示顶点颜色,用一个可以显示顶点颜色的shader比如, partice shader去观察顶点颜色。
    这时候随便用partice下的shader创建一张材质,赋给meshrender,再运行一遍就可以看到顶点颜色了。
    4个顶点的颜色是已知的,中间的颜色是插值得到的。

     当然我们也可以用Material中的颜色或者一张图片,给mesh贴上纹理,就相当于给Mesh穿上纯色或者有图案的衣服。全部代码如下:

        private MeshFilter meshFilter;
        private MeshRenderer render;
        public Texture tex;
    
        void Start()
        {        
            meshFilter = this.GetComponent<MeshFilter>();
            render= this.GetComponent<MeshRenderer>();
            CreateAImage();
        }
    
        void CreateAImage()
        {
            VertexHelper vp = new VertexHelper();
            vp.Clear();
            //这里采用的添加顶点函数,函数参数分别对应了顶点位置,顶点颜色,纹理坐标
            vp.AddVert(new Vector3(0, 0, 0), Color.red, new Vector2(0, 0));
            vp.AddVert(new Vector3(0, 1, 0), Color.green, new Vector2(0, 1));
            vp.AddVert(new Vector3(1, 1, 0), Color.blue, new Vector2(1, 1));
            vp.AddVert(new Vector3(1, 0, 0), Color.cyan, new Vector2(1, 0));
    
            vp.AddTriangle(0, 1, 2);
            vp.AddTriangle(2, 3, 0);
    
            Mesh mesh = new Mesh();
            vp.FillMesh(mesh);
            meshFilter.mesh = mesh;
    
            //设置颜色或者纹理(这里的设置会覆盖Mesh的颜色)
            //想象你穿衣服是不是就把你的肤色盖着了
            //render.material.color = Color.red;
            render.material.mainTexture = tex;
        }

    举一反三:

    想象一下圆,他同样是有三角形来构成的,我们可以用10个三角形构成一个圆,也可以用1000个三角形构成。下面我们看看三角形数量有什么差别。
    直接上主要代码:

    void CreateCircle()
        {
            VertexHelper toFill = new VertexHelper();
            toFill.Clear();
            //首先添加圆心
            toFill.AddVert(new Vector3(0, 0, 0), Color.white, new Vector2(0.5f, 0.5f));
            //计算单位三角形的角度
            float theta = 2 * Mathf.PI / triCount;
    
            for (int i = 0; i < triCount; i++)
            {
                //玩的特殊的,顶点颜色都不一样
                Color color = Color.Lerp(Color.blue, Color.red,  (float)i / (triCount-1));
                //计算每个顶点的位置
                float X = Mathf.Sin(2 * Mathf.PI-i * theta);
                float Y= Mathf.Cos(2 * Mathf.PI-i * theta);
                //radius为半径
                toFill.AddVert(new Vector3(X*radius, Y*radius, 0), color, new Vector2(X, Y));
            }
            //添加三角形
            for (int i = 1; i < triCount; i++)
            {
                toFill.AddTriangle(0, i, i + 1);
            }
            toFill.AddTriangle(0, triCount, 1);
            Mesh mesh = new Mesh();
            toFill.FillMesh(mesh);
            meshFilter.mesh = mesh;
        }
    实际上36个三角形已经可以构造出很圆的圆形了。大量的三角形渲染会增加GPU负担。这也是在游戏开发中,美术为什么要限制模型的顶点与三角形数量的原因。

    00

  • 相关阅读:
    [转]深度理解依赖注入(Dependence Injection)
    [转]控制反转(IOC)和依赖注入(DI)
    [转]依赖注入的概念
    [转]struct实例字段的内存布局(Layout)和大小(Size)
    异步编程模式
    HTTP协议返回代码含义
    [转]StructLayout特性
    Stack的三种含义
    FineUI登入的例子中遇到的一些问题
    编程以外积累: 如何给项目生成类似VS2008的说明文档
  • 原文地址:https://www.cnblogs.com/rollingyouandme/p/15350869.html
Copyright © 2020-2023  润新知