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]