1. 概述
在文章Unity3D學習筆記4——創建Mesh高級接口通過高級API的方式創建了一個Mesh,裏面還提到了一個SubMesh的概念。Mesh是對於三維物體對象的封裝概念,一個很容易的需求是,有的地方我希望用到材質A,有的地方我希望用到材質B,我不想把這個Mesh進行拆分,那麼很簡單,就在這個Mesh中劃分兩個子Mesh就可以了。
2. 詳論
2.1. 實現
我們創建如下腳本,並且隨便掛接兩個不同的材質在屬性material1和屬性material2上:
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class Note5Main : MonoBehaviour
{
public Material material1;
public Material material2;
// Start is called before the first frame update
void Start()
{
Mesh mesh = CreateMesh();
MeshFilter mf = gameObject.GetComponent<MeshFilter>();
if (mf == null)
{
mf = gameObject.AddComponent<MeshFilter>();
}
mf.sharedMesh = mesh;
MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
if (meshRenderer == null)
{
meshRenderer = gameObject.AddComponent<MeshRenderer>();
}
Material[] materials = new Material[2];
materials[0] = material1;
materials[1] = material2;
meshRenderer.materials = materials;
}
Mesh CreateMesh()
{
Mesh mesh = new Mesh();
const int vertexCount = 8;
Vector3[] vertices = new Vector3[vertexCount]
{
new Vector3(-5, 0, 0),
new Vector3(-5, 5, 0),
new Vector3(5, 0, 0),
new Vector3(5, 5, 0),
new Vector3(-5, -5, 0),
new Vector3(-5, 0, 0),
new Vector3(5, -5, 0),
new Vector3(5, 0, 0),
};
Vector3[] normals = new Vector3[vertexCount]
{
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
};
Vector2[] uv = new Vector2[vertexCount]
{
new Vector2(0, 0),
new Vector2(0, 1),
new Vector2(1, 0),
new Vector2(1, 1),
new Vector2(0, 0),
new Vector2(0, 1),
new Vector2(1, 0),
new Vector2(1, 1),
};
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uv;
int[] triangles = new int[12] { 0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7, 6 };
MeshUpdateFlags flags = MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontResetBoneBounds
| MeshUpdateFlags.DontNotifyMeshUsers | MeshUpdateFlags.DontRecalculateBounds;
//MeshUpdateFlags flags = MeshUpdateFlags.Default;
int indexCount = triangles.Length;
mesh.SetIndexBufferParams(indexCount, IndexFormat.UInt32);
mesh.SetIndexBufferData(triangles, 0, 0, indexCount, flags);
mesh.subMeshCount = 2;
SubMeshDescriptor subMeshDescriptor1 = new SubMeshDescriptor(0, 6);
mesh.SetSubMesh(0, subMeshDescriptor1, flags);
SubMeshDescriptor subMeshDescriptor2 = new SubMeshDescriptor(6, 6);
mesh.SetSubMesh(1, subMeshDescriptor2, flags);
return mesh;
}
// Update is called once per frame
void Update()
{
}
}
我這裏得到的效果如下:
2.2. 解析
很明顯,我這裏創建了兩個四邊形,並且將其放到一個Mesh下。創建頂點屬性我使用的是簡單接口,創建頂點索引屬性信息使用的是高級接口。關鍵點在於對SubMesh的描述:
mesh.subMeshCount = 2;
SubMeshDescriptor subMeshDescriptor1 = new SubMeshDescriptor(0, 6);
mesh.SetSubMesh(0, subMeshDescriptor1, flags);
SubMeshDescriptor subMeshDescriptor2 = new SubMeshDescriptor(6, 6);
mesh.SetSubMesh(1, subMeshDescriptor2, flags);
SubMeshDescriptor類定義了從那個頂點索引開始,之後多長的空間是一個SubMesh,也就是對Mesh做了一個劃分。另外,GameObject上掛接的材質個數也要對應:
MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
if (meshRenderer == null)
{
meshRenderer = gameObject.AddComponent<MeshRenderer>();
}
Material[] materials = new Material[2];
materials[0] = material1;
materials[1] = material2;
meshRenderer.materials = materials;
MeshRenderer上能掛接多個材質,有多少個SubMesh就應該有多少個材質,它們是一一對應的。數量沒對應上Unity編輯器會報錯。
通過劃分SubMesh的方式來描述一個Mesh通常是用於存在多個材質的情況,如果使用的都是同一個材質,就最好不要作SubMesh劃分。我們打開Frame Debug,可以看到:
一個Mesh分成了居然兩個渲染指令來實現!原因在於圖像引擎通常是一個狀態機,一個材質需要對應一個渲染指令,這就是爲什麼我們往往要儘可能複用材質,減少不同材質的個數。