Mesh
FBX
文件在Unity中会实例化成GameObject,MeshFilter 中的 Mesh 存储了网格的顶点,三角形索引,UV,顶点颜色,切线,法线,骨骼等渲染所需的必要数据。
从头开始构建mesh
- 先设置顶点(vertices),再设置三角形(triangles)。
1
2
3
4
5
6
7
8
9
10
11
12
Vector3[] newVertices;
Vector2[] newUV;
int[] newTriangles;
void Start()
{
Mesh mesh = new Mesh();
mesh.vertices = newVertices;
mesh.uv = newUV;
mesh.triangles = newTriangles;
GetComponent<MeshFilter>().mesh = mesh;
}
仅修改顶点属性, 不改变顶点数目
- 获取顶点属性,修改,重新设置到mesh中。
1
2
3
4
5
6
7
8
9
10
11
12
13
private void Update()
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
Vector3[] normals = mesh.normals;
for (var i = 0; i < vertices.Length; i++)
{
vertices[i] += normals[i] * Mathf.Sin(Time.time);
}
mesh.vertices = vertices;
}
连续不断的增减三角形和顶点
- 先调用Clear清空整个mesh, 再设置顶点(vertices)和其他属性,最后设置三角形索引(triangles indices)。
- Unity总是检查三角形索引看他们是否引用了数组外的顶点。调用Clear然后再设置顶点,最后设置三角形,可以确保三角形不会引用非法的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private Vector3[] vertices;
private int verticesCount;
private Vector2[] uvs;
private int uvsCount;
private int[] triangles;
private int trianglesCount;
private Mesh mesh;
private void Awake()
{
mesh = GetComponent<MeshFilter>().sharedMesh = new Mesh();
mesh.name = "CustomMesh";
vertices = new Vector3[128];
uvs = new Vector2[128];
triangles = new int[128];
}
private void Update()
{
// Do some calculations...
// update vertices, uvs, triangles...
mesh.Clear();
mesh.SetVertices(vertices, 0, verticesCount);
mesh.SetUVs(0, uvs, 0, uvsCount);
mesh.SetTriangles(triangles, 0, trianglesCount, 0);
}
SubMesh
- Unity中所有的submesh存储在一个mesh中,materials中记录每个子网格用到的材质。
- 需要一个网格使用多个材质时就拆分成多个子网格,每个子网格赋予不同的材质。
- 每个子网格都有自己材质球,导致子网格越多,增加的 drawcall越多,且子网格无法与其他网格合并,导致优化的一个重要环节被阻断。
- 有时可以选择把网格完全拆分成其他网格来代替子网格。
![[Pasted image 20221203145759.png]] 这种不叫子网格,这种还是拆分好的独立的网格。
MeshFilter
- MeshFilter 承载网格数据,Mesh 实例化后存储在 MeshFilter 类中。
- MeshFilter 对
mesh
操作将生成新的网格实例,对sharedMesh
操作将会改变与其他模型共同拥有的那个指定的网格数据实例。
mesh & sharedMesh
mesh
是实例型的变量,对mesh执行任何操作,都会额外复制一份后再重新赋值,即使只是get操作。sharedMesh
是共享型变量,多个 3D 模型可以公用同一个指定的 sharedMesh,修改 sharedMesh,指向同一个 sharedMesh 的多个模型会同时发生变化。- 优先使用
sharedMesh
。
MeshRenderer
- 一个 Renderer 只能渲染一个网格。
- MeshRenderer 提取 MeshFilter 中的网格数据,用给定的 [[Material]] 绘制。
- MeshRenderer 对
material
操作将生成新的材质实例,对sharedMaterial
操作将会改变与其他模型共同拥有的那个指定的材质实例。
material & sharedMaterial
material
的工作原理类似mesh
,是实例型的变量。sharedMaterial
的工作原理类似sharedMaterial
,是共享型变量。- 优先使用
sharedMaterial
。
materials & sharedMaterials
materials
和sharedMaterials
可以针对不同的子网格。material = materials[0]
sharedMaterial = sharedMaterials[0]
动态替换 sharedMaterials[0]
- sharedMaterials 的 get 方法返回的是一份拷贝的材质数组。
- sharedMaterial 拿到的是 m_Materials 数组的第一个元素。
- 两者的 set 方法都是对整个数组进行重新赋值。
1
2
3
4
5
6
7
8
9
10
11
12
Renderer renderer = gameObject.GetComponent<Renderer>();
// 错误方法,不生效
renderer.sharedMaterials[0] = newMat;
// 正确赋值 materials & sharedMaterials 的方法
Material[] arrMat = renderer.sharedMaterials;
arrMat[0] = newMat;
renderer.sharedMaterials = arrMat;
// 单独赋值 material & sharedMaterial 也是生效的
renderer.sharedMaterial = newMat;
网格包围盒
- AABB包围盒基本概念见:[[包围盒]]
轴对齐包围盒
1
2
3
4
// 本地空间的轴对齐包围盒
mesh.bounds
// 世界空间的轴对齐包围盒
meshRenderer.bounds
通过某包围盒扩大本包围盒
1
2
Bounds b = new Bounds(Vector3.zero, Vector3.zero);
b.Encapsulate(childMeshRenderer.bounds);
编辑器中画出网格包围盒
1
2
3
4
5
6
7
8
private void OnDrawGizmosSelected () {
// A sphere that fully encloses the bounding box
var center = renderer.bounds.center;
var radius = renderer.bounds.extents.magnitude;
// Draw it
Gizmos.color = Color.white;
Gizmos.DrawWireSphere (center, radius);
}