Baste 发表于 2022-7-29 21:48

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

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;
      vertices = 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;
      for (int i = 0, j = 1; i < segments * 3 - 3; i += 3, j++)
      {
            triangles = 0;
            triangles = j + 1;
            triangles = j;
      }
      triangles = 0;
      triangles = 1;
      triangles = segments;

      //===========================UV============================
      Vector2[] newUV = new Vector2;
      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;
      for (int i = 0, j = 1; i < segments * 3 - 3; i += 3, j++)
      {
            triangles = 0;
            triangles = j + 1;
            triangles = j;
      }
//最后三组顶点手写,避免顶点越界。
      triangles = 0;
      triangles = 1;
      triangles = 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;
      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 = new Vector3(cosA * radius + centerCircle.x, sinA * radius + centerCircle.y, 0);
            currentAngle += deltaAngle;
      }

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

            triangles = j;
            triangles = (j + 3) % vertices.Length;
            triangles = (j + 2) % vertices.Length;
      }

      Vector2[] newUV = new Vector2;
      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;
      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 = new Vector3(cosA * radius + centerCircle.x, sinA * radius + centerCircle.y, 0);
            currentAngle += deltaAngle;
      }和圆的绘制代码对比会发现多了一行,而原本的画圆的一行多了一个+1的参数。这是因为先要画内圆在画外圆的顶点。
//三角形
      int[] triangles = new int;
      for (int i = 0, j = 0; i < segments * 6; i += 6, j += 2)
      {
            triangles = j;
            triangles = (j + 1) % vertices.Length;
            triangles = (j + 3) % vertices.Length;

            triangles = j;
            triangles = (j + 3) % vertices.Length;
            triangles = (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;
      vertices = 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;
      for (int i = 0, vi = 0; i < triangles.Length; i += 6, vi += 2)
      {
            triangles = vi;
            triangles = vi + 3;
            triangles = vi + 1;
            triangles = vi + 2;
            triangles = vi + 3;
            triangles = vi;
      }

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


    }

顶点计算可以看到和绘制圆不同并没有直接使用整圆的360度而是利用angleDegree的参数进行转换获得角度。后面的计算就和圆没啥区别了。还是挺简单的。三角形的绘制顺序和正圆一模一样。
      vertices = 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;
      for (int i = 0; i < uvs.Length; i += 4)
      {
            //正常贴图
            //uvs = new Vector2(0, 0);
            //uvs = new Vector2(0, 1);
            //uvs = new Vector2(1, 1);
            //uvs = new Vector2(1, 0);
            //翻转处理
            uvs = new Vector2(0, 0);
            uvs = new Vector2(0, 1);
            uvs = new Vector2(1, 1);
            uvs = new Vector2(1, 0);
      };
      mesh.uv = uvs;

      Vector3[] normals = new Vector3;
      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;
      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;
      Normalize(vertices, normals);

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

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

      //赋予顶点顶点颜色
      Color[] colors = new Color;
      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 = Vector3.down;

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


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

      vertices = Vector3.up;

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

    private static int CreateVertexLine(Vector3 from, Vector3 to, int steps, int v, Vector3[] vertices)
    {
      for (int i = 1; i <= steps; i++)
      {
            vertices = 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 = vBottom;
            triangles = vTop - 1;
            triangles = vTop;

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

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

            triangles = vBottom;
            triangles = vTop++;
            triangles = ++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.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.x = uv.x = 0.125f;
      uv.x = uv.x = 0.375f;
      uv.x = uv.x = 0.625f;
      uv.x = uv.x = 0.875f;
    }


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

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

http://zhstatic.zhihu.com/assets/zhihu-components/file-icon/zhimg_answer_editor_file_other.svgMeshDraw.unitypackage
36K
· 百度网盘



                                                                                                                     完成时间2022/7/22
页: [1]
查看完整版本: Unity实用技巧:利用Mesh绘制圆形和扇形。