INSANE tree optimisations
Went from 60-80 fps @ 4K to 400-450 (in game). Yes a 6 to 7x increase.
How you ask?
GPU Mesh Instance drawing offloading the CPU work of handling thousands of game objects.
In English, I’m no longer spawning thousands of tree objects, but instead telling the GPU to draw the tree instances every frame. This seems quite unintuitive because you’re now drawing thousands of things every second. BUT this means that there are no game objects, so the CPU doesn’t even know that these exist. Also the GPU draws it thousands of times anyways.
A game object handles transforms, rotations, etc. that the CPU has to keep track of whether it is active or not. This had the added benefit of the trees having no overhead when the trees are disabled, so you regain all of your framerate!
Also I used some neat little script that combined the meshes all together in the GameObject prefab.
Now it really should be able to run on your potato PC/Laptop.
Combine script
using UnityEngine;
public class MeshCombineUtility : MonoBehaviour
{
[ContextMenu("Combine Children Meshes")]
public void Combine()
{
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
Matrix4x4 matrix = transform.worldToLocalMatrix;
for (int i = 0; i < meshFilters.Length; i++)
{
if (meshFilters[i].gameObject == gameObject) continue;
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = matrix * meshFilters[i].transform.localToWorldMatrix;
}
Mesh combinedMesh = new Mesh();
combinedMesh.CombineMeshes(combine, true, true);
#if UNITY_EDITOR
string directory = $"Assets/Assets/Mesh/Tree/{gameObject.name}.asset";
UnityEditor.AssetDatabase.CreateAsset(combinedMesh, directory);
UnityEditor.AssetDatabase.SaveAssets();
Debug.Log($"Combined mesh saved to {directory}");
#endif
}
}
Draw section of GPU Mesh Instancing
private void DrawTreeMeshes()
{
if (treeMesh == null || treeMaterial == null) return;
foreach (var kvp in generatedChunks)
{
ChunkData chunk = kvp.Value;
if (chunk.spawnedTrees == null || chunk.spawnedTrees.Count == 0) continue;
int batchSize = 1023;
int totalTrees = chunk.spawnedTrees.Count;
for(int i = 0; i < totalTrees; i+=batchSize)
{
int length = Mathf.Min(batchSize, totalTrees - i);
Matrix4x4[] subMatrices = new Matrix4x4[length];
for (int j = 0; j < length; j++)
{
subMatrices[j] = chunk.spawnedTrees[i + j].matrix;
}
int meshIndex = Mathf.Abs(chunk.chunkCord.GetHashCode() + i) % treeMesh.Length;
Graphics.DrawMeshInstanced(treeMesh[meshIndex], 0, treeMaterial, subMatrices, length);
}
}
}