参考链接:
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控件的时候,需要大量用到。
应用例子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渲染时候无材质的默认颜色。这时候随便用partice下的shader创建一张材质,赋给meshrender,再运行一遍就可以看到顶点颜色了。
当然我们也可以用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; }
00