为什么要介绍VertexHelper(顶点帮助器)这个类。他在UGUI中起到了至关重要的地位。当然不了解这个类,对于使用UGUI没有任何影响。他只是看懂UGUI源码的一个基础。不看点源码,怎敢说自己精通。
前言
UGUI是图形学渲染和射线检测这两个技术结合的典型例子。图形学渲染来处理UI的显示,射线用来交互。
今天我们主要看看UI的渲染。
首先介绍一点图形学的基础知识:
1、所有的渲染对象都是由网格(Mesh)构成,Mesh的绘制单位是图元(点、线、三角形)。意思也就是说所有的游戏对象都是在绘制一些点、线、三角形的集合。
具体是如何一步一步显示到屏幕上的,可以看之前的一篇博文(实时渲染(第四版)_第2章|图形渲染管线总结):https://www.jianshu.com/p/2d8eeec9d0ef
2、顶点数据中最主要的是位置,颜色,纹理坐标,法线等。也就是说顶点中保存着其位置、颜色、纹理坐标、法线信息等。
3、贴纹理是指,将一张纹理(可以称为图片),根据纹理坐标,贴在模型上面。就好比同一个模型人,穿上了不同的衣服。纹理坐标指的是根据坐标将图片信息覆盖在Mesh表面,就相当于,柚子就贴在手腕处,裤子就贴在腿上。
正文
UGUI提供了我们自己构建顶点、三角形的类,那就是VertexHelper类。通过这个类,我们可以创建顶点,构成三角形,填充到一张mesh上,然后用MeshRenderer渲染到屏幕上,实际上我们可以直接操作Mesh类添加顶点、三角形等操作,这里的vertexHelper只是UGUI与Mesh之间的一座桥梁。
实践
接下来 我们尝试用这种办法在空物体上画出一张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();
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();
render= this.GetComponent();
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;
}
看看效果:
上面分别是10面和1000面的效果。
实际上36个三角形已经可以构造出很圆的圆形了。大量的三角形渲染会增加GPU负担。这也是在游戏开发中,美术为什么要限制模型的顶点与三角形数量的原因。
我们还可以搞点不一样的。想想下面是如何实现的。
小结
知识点:了解VertexHelper的用法。VertexHelper有助于我们理解UGUI源码。而且我们熟悉了VertexHelper的用法后,甚至可以随心所欲的制作出独特的UI。