找回密码
 立即注册
查看: 347|回复: 0

Unity实用技巧:利用Mesh绘制圆形和扇形。

[复制链接]
发表于 2022-7-29 21:48 | 显示全部楼层 |阅读模式
Unity实时生成一些简单的模型是比较实用的小技巧。可以方便制作特效和需要进行动态控制模型大小的小效果。例如技能的扇形动态范围检测等等。
先从最简单的三角形来开始画一个片。
形:

三角形:

核心代码:
void DrawTriangle()
    {
        //gameObject.AddComponent<MeshFilter>();
        //gameObject.AddComponent<MeshRenderer>();
        gameObject.GetComponent<MeshRenderer>().material = mat;

        //Mesh mesh = GetComponent<MeshFilter>().mesh;
        //mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        //设置顶点
        mesh.vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0) };
        Vector2[] newUV = { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0) };
        mesh.uv = newUV;
        //设置三角形顶点顺序,顺时针设置
        mesh.triangles = new int[] { 0, 1, 2 };

    }
unity内所有的模型都是由三角面组成的。这一点应该都知道了。因此绘制一个面至少需要3个顶点。只要指定好3个顶点所在的坐标位置,然后告诉cpu绘制的顺序就可以根据顺序绘制出最基础的mesh,然而只有mesh没有shader物体是无法在显示器上显示的。因此需要给与一个shader到mesh上这样才能被渲染出来。


可以非常直观的看到三角形的的数据信息。我们可以利用unity提供的工具给这个mesh加上UV,顶点颜色,法线。等等数据,只要能获得到自己想要的顶点即可。不过缺陷也是很明显。利用unity画模型是需要进行计算的。复杂的模型可能就很考验制作人员的数学功底咯。
矩形:

核心代码:
    void DrawSquare()
    {
        //gameObject.AddComponent<MeshFilter>();
        //gameObject.AddComponent<MeshRenderer>();
        gameObject.GetComponent<MeshRenderer>().material = mat;

        //Mesh mesh = GetComponent<MeshFilter>().mesh;
        //mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        mesh.vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 0) };
        Vector2[] newUV = { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0) };//设置UV。
        mesh.uv = newUV;
        mesh.triangles = new int[]
        { 0, 1, 2,
          0, 2, 3
        };
    }


矩形需要4个顶点2个三角形。因此需要绘制两次。很简单。
圆形:

核心代码:
void DrawCircle(float radius, int segments, Vector3 centerCircle)
    {
        //gameObject.AddComponent<MeshFilter>();
        //gameObject.AddComponent<MeshRenderer>();
        gameObject.GetComponent<MeshRenderer>().material = mat;

        //顶点
        Vector3[] vertices = new Vector3[segments + 1];
        vertices[0] = centerCircle;
        float deltaAngle = Mathf.Deg2Rad * 360f / segments;
        float currentAngle = 0;
        for (int i = 1; i < vertices.Length; i++)
        {
            float cosA = Mathf.Cos(currentAngle);
            float sinA = Mathf.Sin(currentAngle);
            vertices = new Vector3(cosA * radius + centerCircle.x, sinA * radius + centerCircle.y, 0);
            currentAngle += deltaAngle;
        }

        //三角形
        int[] triangles = new int[segments * 3];
        for (int i = 0, j = 1; i < segments * 3 - 3; i += 3, j++)
        {
            triangles = 0;
            triangles[i + 1] = j + 1;
            triangles[i + 2] = j;
        }
        triangles[segments * 3 - 3] = 0;
        triangles[segments * 3 - 2] = 1;
        triangles[segments * 3 - 1] = segments;

        //===========================UV============================
        Vector2[] newUV = new Vector2[vertices.Length];
        for (int i = 0; i < vertices.Length; i++)
        {
            newUV = new Vector2(vertices.x / radius / 2 + 0.5f, vertices.y / radius / 2 + 0.5f);
        }




        //Mesh mesh = GetComponent<MeshFilter>().mesh;
        //mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        mesh.vertices = vertices;
        mesh.uv = newUV;
        mesh.triangles = triangles;
    }


圆形就开始复杂了,需要用到一些计算公式,不过一步步的解答即可。现需要获得圆形的分割数,半径,圆心的位置。接下来就可以套用公式转换成代码,公式真是个好东西。
圆行其实也是由一个个三角形组成,找到绘制的顺序和各个顶点所在的位置就方便了。
依照角度算出各个顶点所在的位置。需要将角度转换一下变成坐标。
        float deltaAngle = Mathf.Deg2Rad * 360f / segments;
        float currentAngle = 0;
        for (int i = 1; i < vertices.Length; i++)
        {
            float cosA = Mathf.Cos(currentAngle);
            float sinA = Mathf.Sin(currentAngle);
            vertices = new Vector3(cosA * radius + centerCircle.x, sinA * radius + centerCircle.y, 0);
            currentAngle += deltaAngle;
        }

把圆想象成一个菱形从0点开始进行绘制。顺序是012,023,034,041,而后在其中不停的增加顶点一个个去描绘即可。
//三角形
        int[] triangles = new int[segments * 3];
        for (int i = 0, j = 1; i < segments * 3 - 3; i += 3, j++)
        {
            triangles = 0;
            triangles[i + 1] = j + 1;
            triangles[i + 2] = j;
        }
//最后三组顶点手写,避免顶点越界。
        triangles[segments * 3 - 3] = 0;
        triangles[segments * 3 - 2] = 1;
        triangles[segments * 3 - 1] = segments;圆环:

圆环和圆形有一点儿不同,圆环是由很多小梯形组成。计算方式需要稍稍修改一下。
核心代码:
void DrawRing(float radius, float innerRadius, int segments, Vector3 centerCircle)
    {
        //gameObject.AddComponent<MeshFilter>();
        //gameObject.AddComponent<MeshRenderer>();
        gameObject.GetComponent<MeshRenderer>().material = mat;

        //顶点
        Vector3[] vertices = new Vector3[segments * 2];
        float deltaAngle = Mathf.Deg2Rad * 360f / segments;
        float currentAngle = 0;
        for (int i = 0; i < vertices.Length; i += 2)
        {
            float cosA = Mathf.Cos(currentAngle);
            float sinA = Mathf.Sin(currentAngle);
            vertices = new Vector3(cosA * innerRadius + centerCircle.x, sinA * innerRadius + centerCircle.y, 0);
            vertices[i + 1] = new Vector3(cosA * radius + centerCircle.x, sinA * radius + centerCircle.y, 0);
            currentAngle += deltaAngle;
        }

        //三角形
        int[] triangles = new int[segments * 6];
        for (int i = 0, j = 0; i < segments * 6; i += 6, j += 2)
        {
            triangles = j;
            triangles[i + 1] = (j + 1) % vertices.Length;
            triangles[i + 2] = (j + 3) % vertices.Length;

            triangles[i + 3] = j;
            triangles[i + 4] = (j + 3) % vertices.Length;
            triangles[i + 5] = (j + 2) % vertices.Length;
        }

        Vector2[] newUV = new Vector2[vertices.Length];
        for (int i = 0; i < vertices.Length; i++)
        {
            newUV = new Vector2(vertices.x / radius / 2 + 0.5f, vertices.y / radius / 2 + 0.5f);
        }


        //Mesh mesh = GetComponent<MeshFilter>().mesh;
        //mesh = GetComponent<MeshFilter>().mesh;
        //mesh = meshFilter.sharedMesh;
        mesh.Clear();

        mesh.vertices = vertices;
        mesh.uv = newUV;
        mesh.triangles = triangles;
    }

和圆区别是圆环需要多绘制一次圆的顶点。
//顶点
        Vector3[] vertices = new Vector3[segments * 2];
        float deltaAngle = Mathf.Deg2Rad * 360f / segments;
        float currentAngle = 0;
        for (int i = 0; i < vertices.Length; i += 2)
        {
            float cosA = Mathf.Cos(currentAngle);
            float sinA = Mathf.Sin(currentAngle);
            vertices = new Vector3(cosA * innerRadius + centerCircle.x, sinA * innerRadius + centerCircle.y, 0);
            vertices[i + 1] = new Vector3(cosA * radius + centerCircle.x, sinA * radius + centerCircle.y, 0);
            currentAngle += deltaAngle;
        }和圆的绘制代码对比会发现多了一行,而原本的画圆的一行多了一个+1的参数。这是因为先要画内圆在画外圆的顶点。
//三角形
        int[] triangles = new int[segments * 6];
        for (int i = 0, j = 0; i < segments * 6; i += 6, j += 2)
        {
            triangles = j;
            triangles[i + 1] = (j + 1) % vertices.Length;
            triangles[i + 2] = (j + 3) % vertices.Length;

            triangles[i + 3] = j;
            triangles[i + 4] = (j + 3) % vertices.Length;
            triangles[i + 5] = (j + 2) % vertices.Length;
        }三角形的绘制和之前也有一些区别,利用的是取余来获得绘制的顺序。也得绘制两次。在纸上试着算算就懂了。
扇形:

扇形有两种绘制方法。一种是顺时针或者逆时针的逐个绘制。另一种则是直接类似翼展一样的绘制。公式和圆类似.这里只列举一下第一种。
核心代码:
    void DrawHalfCycle(float radius, float innerRadius, int segments, float angleDegree, Vector3 centerCircle)
    {
        //顶点
        gameObject.GetComponent<MeshRenderer>().material = mat;



        Vector3[] vertices = new Vector3[segments * 2 + 2];
        vertices[0] = centerCircle;
        float angleRad = Mathf.Deg2Rad * angleDegree;
        float angleCur = angleRad;
        float angledelta = angleRad / segments;

        for (int i = 0; i < vertices.Length; i += 2)
        {
            float cosA = Mathf.Cos(angleCur);
            float sinA = Mathf.Sin(angleCur);

            vertices = new Vector3(radius * cosA, innerRadius, radius * sinA);
            angleCur -= angledelta;

        }
        //三角形
        int[] triangles = new int[segments * 6];
        for (int i = 0, vi = 0; i < triangles.Length; i += 6, vi += 2)
        {
            triangles = vi;
            triangles[i + 1] = vi + 3;
            triangles[i + 2] = vi + 1;
            triangles[i + 3] = vi + 2;
            triangles[i + 4] = vi + 3;
            triangles[i + 5] = vi;
        }

        mesh.Clear();
        mesh.vertices = vertices;
        mesh.triangles = triangles;


    }

顶点计算可以看到和绘制圆不同并没有直接使用整圆的360度而是利用angleDegree的参数进行转换获得角度。后面的计算就和圆没啥区别了。还是挺简单的。三角形的绘制顺序和正圆一模一样。
        vertices[0] = centerCircle;
        float angleRad = Mathf.Deg2Rad * angleDegree;
        float angleCur = angleRad;
        float angledelta = angleRad / segments;

        for (int i = 0; i < vertices.Length; i += 2)
        {
            float cosA = Mathf.Cos(angleCur);
            float sinA = Mathf.Sin(angleCur);

            vertices = new Vector3(radius * cosA, innerRadius, radius * sinA);
            angleCur -= angledelta;

        }体:

体状的mesh相较于形更加复杂。因为想要形成一个体至少需要4个面4个顶点,先从最简单的正八面体试试
正八面体:

核心代码:
void DrawOctahedron()
    {
        gameObject.GetComponent<MeshRenderer>().material = mat;

        mesh.Clear();
        //顶点

        mesh.vertices = new Vector3[]
        {
            Vector3.down,
            Vector3.forward,
            Vector3.left,
            Vector3.back,
            Vector3.right,
            Vector3.up
        };

        //三角形
        mesh.triangles = new int[]
        {
            //0,2,1,
            //0,3,2,
            //0,4,3,
            //0,1,4,

            //5,1,2,
            //5,2,3,
            //5,3,4,
            //5,4,1

            0,1,2,
            0,2,3,
            0,3,4,
            0,4,1,

            5,2,1,
            5,3,2,
            5,4,3,
            5,1,4
        };

        //UV
        mesh.uv = new Vector2[]
        {
            new Vector2(0.25f,0.5f),
            new Vector2(0f,0f),
            new Vector2(0f,1f),
            new Vector2(0.5f,1f),
            new Vector2(0.5f,0f),
            new Vector2(0.75f,0.5f)
        };
    }首先需要定义方向:(其实定义坐标也是可以的,不过方向的坐标更方便。)
        mesh.vertices = new Vector3[]
        {
            Vector3.down,
            Vector3.forward,
            Vector3.left,
            Vector3.back,
            Vector3.right,
            Vector3.up
        };可以发现绘制了8个三角形。正好对应我们写了8次绘制顺序,绘制顺序有正和反,这个需要根据左右手坐标系来判断。是在不清楚就打个灯给一个DIFFUSEshader来看看。法线或者画反了会发现显示不正常的。
        //三角形
        mesh.triangles = new int[]
        {
            //0,2,1,
            //0,3,2,
            //0,4,3,
            //0,1,4,

            //5,1,2,
            //5,2,3,
            //5,3,4,
            //5,4,1

            0,1,2,
            0,2,3,
            0,3,4,
            0,4,1,

            5,2,1,
            5,3,2,
            5,4,3,
            5,1,4
        };

立方体:

立方体和正八面体的绘制差不多。区别大的地方会在UV上,因为6个顶点没法在2维面上绘制出8个面因此需要增加顶点到24个。同样和八面体一样会有顺逆两个绘制方向。这里先书写逆时针的。
核心代码
    void DrawSquares_AntiClockWise()
    {
        //gameObject.AddComponent<MeshFilter>();
        //gameObject.AddComponent<MeshRenderer>();
        gameObject.GetComponent<MeshRenderer>().material = mat;

        //Mesh mesh = GetComponent<MeshFilter>().mesh;
        //mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        mesh.vertices = new Vector3[]
        {
            //front
            new Vector3(0, 0, 0),
            new Vector3(0, 0, 1),
            new Vector3(1, 0, 1),
            new Vector3(1, 0, 0),

            //top
            new Vector3(0, 0, 1),
            new Vector3(0, 1, 1),
            new Vector3(1, 1, 1),
            new Vector3(1, 0, 1),

            //back
            new Vector3(0, 1, 1),
            new Vector3(0, 1, 0),
            new Vector3(1, 1, 0),
            new Vector3(1, 1, 1),

            //bottom
            new Vector3(0, 1, 0),
            new Vector3(0, 0, 0),
            new Vector3(1, 0, 0),
            new Vector3(1, 1, 0),

            //left
            new Vector3(0, 1, 0),
            new Vector3(0, 1, 1),
            new Vector3(0, 0, 1),
            new Vector3(0, 0, 0),

            //right
            new Vector3(1, 0, 0),
            new Vector3(1, 0, 1),
            new Vector3(1, 1, 1),
            new Vector3(1, 1, 0),
        };


        //逆时针绘制。
        mesh.triangles = new int[]
        {
              0,2,1,
              0,3,2,
              4,6,5,
              4,7,6,
              8,10,9,
              8,11,10,
              12,14,13,
              12,15,14,
              16,18,17,
              16,19,18,
              20,22,21,
              20,23,22


        };

        Vector2[] uvs = new Vector2[mesh.vertices.Length];
        for (int i = 0; i < uvs.Length; i += 4)
        {
            //正常贴图
            //uvs = new Vector2(0, 0);
            //uvs[i + 1] = new Vector2(0, 1);
            //uvs[i + 2] = new Vector2(1, 1);
            //uvs[i + 3] = new Vector2(1, 0);
            //翻转处理
            uvs = new Vector2(0, 0);
            uvs[i + 1] = new Vector2(0, 1);
            uvs[i + 2] = new Vector2(1, 1);
            uvs[i + 3] = new Vector2(1, 0);
        };
        mesh.uv = uvs;

        Vector3[] normals = new Vector3[mesh.vertices.Length];
        for (int i = 0; i < normals.Length; i++)
        {
            if (i < 4)
                normals = Vector3.forward;
            if (i >= 4 && i < 8)
                normals = -Vector3.up;
            if (i >= 8 && i < 12)
                normals = Vector3.back;
            if (i >= 12 && i < 16)
                normals = -Vector3.down;
            if (i >= 16 && i < 20)
                normals = Vector3.left;
            if (i >= 20 && i < 24)
                normals = Vector3.right;
        }
        mesh.normals = normals;

        //顶点着色。
        Color[] colors = new Color[mesh.vertices.Length];
        for (int i = 0; i < colors.Length; i++)
        {
            colors = Color.red;
        }


        mesh.colors = colors;
    }

还是挺简单的。(不过都到正方体了其实在MAX或者MAYA里拉一个不是更快么?)
球体:

好吧重头戏来了,球体其实算是最复杂的一个形状。计算公式有很多种。这里列举一个正八面体切割的版本。具体的推导公式笔者也没咋看明白。有空在研究吧。
void DrawSphere(int subdivisions = 0, float Sphereradius = 1)
    {
        //限定最高拆分为4
        if (subdivisions > 4)
        {
            subdivisions = 4;
        }
        //gameObject.AddComponent<MeshFilter>();
        //gameObject.AddComponent<MeshRenderer>();
        gameObject.GetComponent<MeshRenderer>().material = mat;

        //Mesh mesh = GetComponent<MeshFilter>().mesh;
        //mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();

        int resolution = 1 << subdivisions;
        Vector3[] vertices = new Vector3[(resolution + 1) * (resolution + 1) * 4 - 3 * (resolution * 2 + 1)];
        int[] triangles = new int[(1 << (subdivisions * 2 + 3)) * 3];
        CreateOctahedron(vertices, triangles, resolution);
        //在顶点数据输出前归一化处理顶点和法线。


        if (Sphereradius != 1f)
        {
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices *= Sphereradius;
            }
        }

        Vector3[] normals = new Vector3[vertices.Length];
        Normalize(vertices, normals);

        Vector2[] uv = new Vector2[vertices.Length];
        CreateUV(vertices, uv);

        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.normals = normals;

        //赋予顶点顶点颜色
        Color[] colors = new Color[mesh.vertices.Length];
        for (int i = 0; i < colors.Length; i++)
        {
            colors = Color.red;
        }
        mesh.colors = colors;


        mesh.uv = uv;


    }

    private static void CreateOctahedron(Vector3[] vertices, int[] triangles, int resolution)
    {
        int v = 0, vBottom = 0, t = 0;
        vertices[v++] = Vector3.down;

        //for (int i = 0; i < 4; i++)
        //{
        //    vertices[v++] = Vector3.down;
        //}


        for (int i = 1; i <= resolution; i++)
        {
            float progress = (float)i / resolution;
            Vector3 from, to;
            vertices[v++] = to = Vector3.Lerp(Vector3.down, Vector3.forward, progress);
            for (int d = 0; d < 4; d++)
            {
                from = to;
                to = Vector3.Lerp(Vector3.down, directions[d], progress);
                t = CreateLowerStrip(i, v, vBottom, t, triangles);
                v = CreateVertexLine(from, to, i, v, vertices);
                vBottom += i > 1 ? (i - 1) : 0;
                //vBottom += i > 1 ? (i - 1) : 1;
            }
            vBottom = v - 1 - i * 4;
        }

        for (int i = resolution - 1; i >= 1; i--)
        {
            float progress = (float)i / resolution;
            Vector3 from, to;
            vertices[v++] = to = Vector3.Lerp(Vector3.up, Vector3.forward, progress);
            for (int d = 0; d < 4; d++)
            {
                from = to;
                to = Vector3.Lerp(Vector3.up, directions[d], progress);
                t = CreateUpperStrip(i, v, vBottom, t, triangles);
                v = CreateVertexLine(from, to, i, v, vertices);
                vBottom += i + 1;
            }
            vBottom = v - 1 - i * 4;
        }

        vertices[vertices.Length - 1] = Vector3.up;

        for (int i = 0; i < 4; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = v;
            triangles[t++] = ++vBottom;
            //vertices[v++] = Vector3.up;
        }
    }

    private static int CreateVertexLine(Vector3 from, Vector3 to, int steps, int v, Vector3[] vertices)
    {
        for (int i = 1; i <= steps; i++)
        {
            vertices[v++] = Vector3.Lerp(from, to, (float)i / steps);
        }
        return v;
    }

    private static int CreateLowerStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        for (int i = 1; i < steps; i++)
        {
            triangles[t++] = vBottom;
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;

            triangles[t++] = vBottom++;
            triangles[t++] = vTop++;
            triangles[t++] = vBottom;
        }
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = vTop;
        return t;
    }

    private static int CreateUpperStrip(int steps, int vTop, int vBottom, int t, int[] triangles)
    {
        triangles[t++] = vBottom;
        triangles[t++] = vTop - 1;
        triangles[t++] = ++vBottom;
        for (int i = 1; i <= steps; i++)
        {
            triangles[t++] = vTop - 1;
            triangles[t++] = vTop;
            triangles[t++] = vBottom;

            triangles[t++] = vBottom;
            triangles[t++] = vTop++;
            triangles[t++] = ++vBottom;
        }
        return t;
    }


    private static void Normalize(Vector3[] vertices, Vector3[] normals)
    {
        for (int i = 0; i < vertices.Length; i++)
        {
            normals = vertices = vertices.normalized;
        }
    }


    private static void CreateUV(Vector3[] vertices, Vector2[] uv)
    {
        float previousX = 1f;
        for (int i = 0; i < vertices.Length; i++)
        {
            Vector3 v = vertices;
            if (v.x == previousX)
            {
                uv[i - 1].x = 1f;
            }
            previousX = v.x;
            Vector2 textureCoordinates;
            textureCoordinates.x = Mathf.Atan2(v.x, v.z) / (-2f * Mathf.PI);
            if (textureCoordinates.x < 0f)
            {
                textureCoordinates.x += 1f;
            }
            textureCoordinates.y = Mathf.Asin(v.y) / Mathf.PI + 0.5f;
            uv = textureCoordinates;
        }
        uv[vertices.Length - 4].x = uv[0].x = 0.125f;
        uv[vertices.Length - 3].x = uv[1].x = 0.375f;
        uv[vertices.Length - 2].x = uv[2].x = 0.625f;
        uv[vertices.Length - 1].x = uv[3].x = 0.875f;
    }


UV还是有点儿问题。不过问题不大。后续有时间在改改看。

OK记录完毕。写了这么几个现在对在Unity中画MESH又有了比较深的认知和实践,以后画模型大概率是没啥问题咯,当然数学还是该补得补补。
完整资源链接:

MeshDraw.unitypackage
36K
· 百度网盘



                                                                                                                       完成时间2022/7/22

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-18 21:01 , Processed in 0.090820 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表