Unity3D学习笔记5——创建子Mesh


目录
  • 1. 概述
  • 2. 详论
    • 2.1. 实现
    • 2.2. 解析
  • 3. 参考

1. 概述

在文章通过高级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();
        if (mf == null)
        {
            mf = gameObject.AddComponent();
        }
        mf.sharedMesh = mesh;

        MeshRenderer meshRenderer = gameObject.GetComponent();
        if (meshRenderer == null)
        {
            meshRenderer = gameObject.AddComponent();
        }

        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();
if (meshRenderer == null)
{
    meshRenderer = gameObject.AddComponent();
}

Material[] materials = new Material[2];       
materials[0] = material1;
materials[1] = material2;
meshRenderer.materials = materials;

MeshRenderer上能挂接多个材质,有多少个SubMesh就应该有多少个材质,它们是一一对应的。数量没对应上Unity编辑器会报错。

通过划分SubMesh的方式来描述一个Mesh通常是用于存在多个材质的情况,如果使用的都是同一个材质,就最好不要作SubMesh划分。我们打开Frame Debug,可以看到:

一个Mesh分成了居然两个渲染指令来实现!原因在于图像引擎通常是一个状态机,一个材质需要对应一个渲染指令,这就是为什么我们往往要尽可能复用材质,减少不同材质的个数。

3. 参考