1,顶点
顶点是网格最基础的组成部分,可通过mesh.vertices获取和赋值。mesh.vertices是一个Vector3的数组,每个Vector3代表了此顶点在世界空间中的位置每个Vector3为此顶点与此游戏物体的相对坐标(local position)。每个顶点的位置,总共顶点的数量没有任何限制。几个同样的顶点可以组合成若干不同形状,不同数量的三角形(例如在白纸上画5个点最多可以连成9个不同的三角形)。
手动设置顶点:
mesh = new Mesh ();
Vector3[] v3s=new Vector3[3];
v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);
mesh.vertices = v3s;
2,由顶点组成三角形
mesh.triangles决定了网格中的三角形的形状和朝向。triangles是一个int数组,此数组长度永远是3的倍数,每三个int代表的是由哪三个顶点并由什么顺序(朝向)来组成一个三角形,例如:
手动设置三角形:
int[] index = new int[3];
index [0] = 0;
index [1] = 1;
index [2] = 2;
mesh.triangles = index;
等号右侧的0,1,2表明网格中将会有一个以 mesh.vertices[0],mesh.vertices[1],和mesh.vertices[2]组成的三角形。此三角形的形状有此三个顶点的位置决定,而它的朝向则由此三个顶点的旋转方向决定。
Unity是左手坐标系,如下图三维空间,z轴方向与视线方向相同,由a-b-c组成的三角形将会背对z轴,既背对我们的视线,
而由b-a-c组成的三角形将会正对z轴,既面对我们的视线:
b-a-c,a-c-b,c-b-a,这些顺序效果相同。任何一个三角形必须单独设定顶点组合顺序,且此顺序与其他三角形不发生关系。mesh内的顶点可以用来组合三角形,也可不用来组合三角形,triangles数组与vertices数组无绝对的对应关系。
3,法线
给网格赋值顶点并组成三角形,将此网格赋值给场景中的meshfilter,meshrenderer就可以将此三角形渲染出来了,
但是法线并不一定是正确的,例如上面声明的三个顶点
v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);
不论是由0-1-2还是2-1-0的顺序组成三角形,此三角形的法线总是指向z轴方向,在有光照的场景中会出现错误的效果,这里unity提供了一个方法:
mesh.RecalculateNormals();
此方法将会自动将每个顶点的法线调整到正确方向,既是与面的朝向一致。还提供了一个方法是:mesh.RecalculateBounds();。将会重新计算mesh.bounds,通过mesh.bounds可访问size,center等一些网格的属性。所以在将手动设置的mesh赋值给meshfilter前需要先调用此两方法。
4,UV
uv是为了防止与xy混淆的另一种写法。u为x轴,v为y轴,最小值为0,最大值为1无数值限制(材质上的uv值一般是0-1,当进行采样的uv值大于1时会只取小数部分)。uv值用处很多,例如材质texture的采样,也既是贴图。或是利用它的一些特性(范围0-1;大于1时去小数部分)进行GPU内的一些计算。
当每个顶点被赋予uv值后,在片段着色阶段,片段着色器将会根据顶点的uv值将此三角形覆盖的每个像素的uv值进行自动插值:
例如此三角形中,假设三角形ABC的边bc由10个像素块组成,ABC三个顶点的UV值如括号内所示,那么BC直线(假设与x轴完全平行)上每个像素的uv值将左起依次为(0,0),(0,0.1),(0,0.2),(0,0.3)…(0,1)。
uv插值是以三角形为单位进行的,既三角形ABC内各个像素的uv值与其他周边三角形各顶点的uv值是无关的。假如出现D点,那么ACD内各像素的uv值将会由A,C,D的uv值来决定:
当D的uv值为(1,1)时进行的2D采样贴图:
Vector2[] uvs = new Vector2[4];
uvs [0] = Vector2.zero; //(0,0)
uvs [1] = Vector2.up; //(1,0)
uvs [2] = Vector2.right;//(0,1)
uvs [3] = Vector2.one; //(1,1)
mesh.uv = uvs;
mesh.uv作为一个Vector2数组与mesh.vertices的长度必须一致,每个uv的顺序与vertices的顺序也是一一对应,既uv[0]代表的是vertices[0]的uv值。而与三角形组成的顺序无关。
(下图)当D的uv值为(1,1)时:
(下图)当D的uv值为(0,0)时进行的2D采样贴图:
(下图)当D的世界坐标位置为(3,3,0),uv值为(0,0)时进行的2D采样贴图:
顶点uv值的赋值除了必须在0-1之内以外没有任何其他限制,如有特殊需要可以都为(0,0),或没有上下左右顺序。但很多GPU的方法的实现都需要一个有秩序的uv,例如上面作为例子的2D采样贴图,之所以会出现"正确的"和“错误的”效果,是由于原图材质作为sampler2D纹理缓存入GPU之后,纹理的每个像素点都有一个独立的uv值(由左下角(0,0)开始插值直至右上角(1,1))。
而在进入片段着色器后,tex2D命令将会根据当前三角形内像素的uv值去寻找sampler2D上对应uv的颜色,再根据此颜色对三角形内的此像素进行渲染。而由于顶点uv值的不同而导致的三角形内uv值与纹理uv映射关系的改变,以上例子中出现了不同的效果。
附:双面网格
可以利用上面的知识实现一个双面的网格,既是利用四个顶点以顺时针方向组成两个三角形再以逆时针方向组成两个三角形,四个顶点赋uv值后每个点的正反两面的uv皆相同,进而出现镜像效果。此效果与shader调用cull off选项后的效果相同,以下为实现方法:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Triangle : MonoBehaviour {
public MeshFilter meshFilter;
private Mesh mesh;
// Use this for initialization
void Start () {
mesh = new Mesh ();
//四个顶点位置
Vector3[] v3s = new Vector3[4];
v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);
v3s [3] = new Vector3 (1, 1, 0);
mesh.vertices = v3s;
//组合成四个三角形
int[] index = new int[12];
index [0] = 0;
index [1] = 1;
index [2] = 2;
index [3] = 3;
index [4] = 2;
index [5] = 1;
index [6] = 1;
index [7] = 0;
index [8] = 2;
index [9] = 3;
index [10] = 1;
index [11] = 2;
mesh.triangles = index;
//Debug.Log (mesh.uv.Length);
//uv赋值
Vector2[] uvs = new Vector2[4];
uvs [0] = Vector2.zero;
uvs [1] = Vector2.up;
uvs [2] = Vector2.right;
uvs [3] = Vector2.one;
mesh.uv = uvs;
//手动计算顶点法线。在此双面网格中,只用4个顶点会产生一个与Unity实时光照相关的bug,因为mesh.vertices数组长度要与mesh.normals数组长度对应,所以最多只能提供四个法线,那么网格两边参与实时光的渲染结果是相同的(正反两侧永远都是同样的亮度),既有一边是不正确的。如果需要正确响应实时光照,需要使用8个vertice以容纳8个normal。
Vector3[] normals = new Vector3[4];
normals[0] = Vector3.Normalize(Vector3.Cross(v3s[0] - v3s[1],v3s[0]-v3s[2]));
normals[1] = Vector3.Normalize(Vector3.Cross(v3s[1] - v3s[3], v3s[1] - v3s[0]));
normals[2] = Vector3.Normalize(Vector3.Cross(v3s[2] - v3s[0], v3s[2] - v3s[3]));
normals[3] = Vector3.Normalize(Vector3.Cross(v3s[3] - v3s[2], v3s[3] - v3s[1]));
mesh.normals = normals;
// mesh.RecalculateBounds();
// mesh.RecalculateNormals();
// mesh.RecalculateTangents();
meshFilter.mesh = mesh;
}
// Update is called once per frame
void Update () {
}
}
————————————————————————————
维护日志:
2017-8-22:更改了标题
2017-10-11:Review,更改了一些错误。
2018-1-9: 更新了代码,增加手动计算法线部分。
2020-2-7:review