Dynamic Mesh Combining in Unity with C#

Combine trees into larger meshes

In this section you will learn how to create a random forest and how to combine the trees into larger meshes to increase the performance.

Part 1. Create a forest

Begin by removing the trees you added in the last tutorial (but keep the prefap Tree). Then create two new game objects and add a Mesh Renderer and a Mesh Filter to them. Rename them "Tree combined wood" and "Tree combined leaf" and add materials to them. These objects will hold the combined tree meshes. To get a cleaner work space, create a new game object called "Tree parent" and as a child to this object, create 2 new game objects called "Individual trees" and "Combined parent." So we will parent all individual trees to the parent "Individual trees" because we have to save them after creating the combined tree mesh so we can remove them later on from the combined mesh.

Now create a script called "TutorialCreateForest" and add it to the "_Controller" game object. Deactivate the "TutorialCombineAll" script if you haven't already done so (the script we created in the last part). Then add the following to the new script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TutorialCreateForest : MonoBehaviour {

    public static TutorialCreateForest current;

    //Drags
    //The main tree
    public GameObject treeObj;
    //What we are going to parent everything to, to get a clean workspace
    public Transform individualTreeParent;
    public Transform combinedMeshParent;
    //The combined meshes
    public GameObject combinedWoodObj;
    public GameObject combinedLeafObj;


    //How many vertices per combined mesh (max 65535)
    //Should sometimes be smaller because smaller meshes are faster to modify
    [System.NonSerialized]
    public int vertexLimit = 30000;

    //Lists to make it easier to remove/add trees from/to combined meshes
    //Will contain references to all combined meshes
    [System.NonSerialized]
    public List<GameObject> combinedWoodList = new List<GameObject>();
    [System.NonSerialized]
    public List<GameObject> combinedLeafList = new List<GameObject>();

    //We also need a list of all trees we created before combining them
    //to make it easier to remove them from the combined meshes
    [System.NonSerialized]
    public List<GameObject> allTrees = new List<GameObject>();

    void Awake()
    {
        current = this;
    }

    void Start() 
    {
        CreateForest();
        //CombineTrees();
    }
	
    //Creates a random forest
    void CreateForest()
    {
        for (int i = 0; i < 2000; i++)
        {
            //Generate random map coordinates
            float halfMapSize = 300f;

            float x = Random.Range(-halfMapSize, halfMapSize);
            float z = Random.Range(-halfMapSize, halfMapSize);

            Vector3 treePos = new Vector3(x, 0f, z);

            //Create a tree
            GameObject newTree = Instantiate(treeObj, treePos, Quaternion.identity) as GameObject;

            //Parent it to get a clean workspace
            newTree.transform.parent = individualTreeParent;

            //Add it to the list of trees
            allTrees.Add(newTree);
        }
    }
}

Add everything needed to the script and press Play. If you click to display the Stats window and if you zoom out, you should see that the number of batches has grown to almost 4000:

The number of batches before we combine the trees

Part 2. Combine the meshes

Let's reduce the number of batches. So add the method "CombineTrees()" to the script:

//Combines the trees in the forest into combined meshes
void CombineTrees()
{
	//Lists needed to create the combined meshes of each type
	List<CombineInstance> woodList = new List<CombineInstance>();
	List<CombineInstance> leafList = new List<CombineInstance>();

	//Used in the loop, but better to create it here
	CombineInstance combine = new CombineInstance();

	//Keep track of the vertices in the list so we know when we have reached the limit
	int verticesSoFar = 0;
	//Keep track of which gameobject the mesh has been combined into
	int meshListCounter = 0;

	//Loop through all trees
	for (int i = 0; i < allTrees.Count; i++)
	{
		allTrees[i].SetActive(false);

		//Add to which list och combined meshes this tree belongs
		allTrees[i].GetComponent<TreeData>().listPos = meshListCounter;

		//Get all children
		MeshFilter[] meshFilters = allTrees[i].GetComponentsInChildren<MeshFilter>(true);

		//Loop through all children and add them to respective list
		for (int j = 0; j < meshFilters.Length; j++)
		{
			//We need to get both the mesh's mesh renderer and mesh filter
			MeshFilter meshFilter = meshFilters[j];

			//To find out if this is mesh is a wood or a leaf we need the name of the material
			//Modify the material name, because Unity adds (Instance) to the end of the name
			string materialName = meshFilter.GetComponent<MeshRenderer>().material.name.Replace(" (Instance)", "");

			if (materialName == "Leaf")
			{
				combine.mesh = meshFilter.mesh;
				combine.transform = meshFilter.transform.localToWorldMatrix;

				//Add it to the list of leaf mesh data
				leafList.Add(combine);

				//The leafs have more vertices, so we need to keep track of them
				verticesSoFar += meshFilter.mesh.vertexCount;
			}
			else if (materialName == "Wood")
			{
				combine.mesh = meshFilter.mesh;
				combine.transform = meshFilter.transform.localToWorldMatrix;

				//Add it to the list of wood mesh data
				woodList.Add(combine);
			}
		}

		//Have we reached the limit?
		if (verticesSoFar > vertexLimit)
		{
			//If so we have added to many vertices, so we undo the last step
			i -= 1;

			//And remove the last mesh we added to the lists
			leafList.RemoveAt(leafList.Count - 1);
			woodList.RemoveAt(woodList.Count - 1);

			//Now we can create a combined mesh of the meshes we have collected so far
			CreateCombinedMesh(woodList, combinedWoodObj, combinedWoodList);
			CreateCombinedMesh(leafList, combinedLeafObj, combinedLeafList);

			//Reset the lists with mesh data
			leafList.Clear();
			woodList.Clear();

			verticesSoFar = 0;

			//Change how many combined meshes we have generated so far
			meshListCounter += 1;
		}
	}

	//When the main loop is finished we have to add what didnt reach the vertice limit
	CreateCombinedMesh(woodList, combinedWoodObj, combinedWoodList);
	CreateCombinedMesh(leafList, combinedLeafObj, combinedLeafList);
}



//Creates a combined mesh from a list and adds it to a game object
void CreateCombinedMesh(List<CombineInstance> meshDataList, GameObject meshHolderObj, List<GameObject> combinedHolderList)
{
	//Create the new combined mesh
	Mesh newMesh = new Mesh();
	newMesh.CombineMeshes(meshDataList.ToArray());

	//Create new game object that will hold the combined mesh
	GameObject combinedMeshHolder = Instantiate(meshHolderObj, Vector3.zero, Quaternion.identity) as GameObject;

	combinedMeshHolder.transform.parent = combinedMeshParent;

	//Add the mesh
	combinedMeshHolder.GetComponent<MeshFilter>().mesh = newMesh;

	//Add the combined holder to the list
	combinedHolderList.Add(combinedMeshHolder);
}

The basic idea is that we loop through all trees we created and combine them into meshes. The problem is that the limit of one mesh is 65535 vertices (a vertice is like a corner in the mesh), so when we have reached that limit we need to create a new combined mesh and begin all over again. I've also decided to lower the limit from 65535 to 30000. The reason is that when we begin to add and remove trees, it will be much faster to search through the combined mesh if we have fewer trees combined into one mesh. But if you are not going to remove or add something to the mesh, you can safely leave the limit at 65535.

To be able to easier add and remove trees, we also need to know which combined mesh the individual tree has been combined into. So we have to save the list number in a script called "TreeData." So create such a script, add it to the Tree prefab, and add the following:

using UnityEngine;
using System.Collections;

//Holds data about a tree
public class TreeData : MonoBehaviour
{
    public int listPos;

    void Awake()
    {
        //-1 means not in any list at all
        listPos = -1;
    }
}

I've also been lazy by assuming that the wood part of the tree and the leaf part of the tree have the same amount of vertices. But in reality the cylinder has fewer vertices than a sphere. So if you want to squeeze out every inch of performance out of your game, you should not change to a new combined wood mesh when the combined mesh that holds the leafs is full, because you can fit more cylinders in one combined mesh than what you can fit spheres. But to get a cleaner code, I've decided to ignore that fact.

If you now press Play and zoom out you should be able to see that the number of batches have decreased from 4000 to 200:

The number of batches after we combine the trees

Now let's move on and learn how to add and remove trees. It might sound easy, but it will be complicated, so take a break before you begin!