Showing posts with label CombineChildren. Show all posts
Showing posts with label CombineChildren. Show all posts

08 August 2014

Combine meshes in Unity

If there are a lot of objects in a scene that share the same material but do not share the same meshes you can lower the number of draw calls by combining them into one single mesh.

Unity has a "MeshCombineUtility" in the standard assets that is supposed to do exactly that, but several of our combined meshes were missing parts in an unpredictable way. It uses a hashtable to index the used materials. I replaced it with a typed dictionary and that fixed it. Somehow we must have had hash collisions.

In the documentation of Mesh.Combinemeshes there is a script that intends to do the same, with simpler code. Oddly, that script does combine multiple meshes into one but it generates an object without materials.

So I wrote my own version of the CombineChildren script that combines both into one, working script:

using UnityEngine;
using System.Collections.Generic;

[AddComponentMenu("Mesh/Combine Children")]
public class CombineChildren : MonoBehaviour {

void Start()
{
 Matrix4x4 myTransform = transform.worldToLocalMatrix;
 Dictionary<string, List<CombineInstance>> combines = new Dictionary<string, List<CombineInstance>>();
 Dictionary<string , Material> namedMaterials = new Dictionary<string, Material>();
 MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
 foreach (var meshRenderer in meshRenderers)
 {
  foreach (var material in meshRenderer.sharedMaterials)
    if (material != null && !combines.ContainsKey(material.name)) {
   combines.Add(material.name, new List<CombineInstance>());
   namedMaterials.Add(material.name, material);
    }
 }

 MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
 foreach(var filter in meshFilters)
 {
  if (filter.sharedMesh == null)
   continue;
  var filterRenderer = filter.GetComponent<Renderer>();
  if (filterRenderer.sharedMaterial == null)
   continue;
  if (filterRenderer.sharedMaterials.Length > 1)
   continue;
  CombineInstance ci = new CombineInstance
  {
   mesh = filter.sharedMesh,
   transform = myTransform*filter.transform.localToWorldMatrix
  };
  combines[filterRenderer.sharedMaterial.name].Add(ci);

  Destroy(filterRenderer);
 }

 foreach (Material m in namedMaterials.Values)
 {
  var go = new GameObject("Combined mesh");
  go.transform.parent = transform;
  go.transform.localPosition = Vector3.zero;
  go.transform.localRotation = Quaternion.identity;
  go.transform.localScale = Vector3.one;

  var filter = go.AddComponent<MeshFilter>();
  filter.mesh.CombineMeshes(combines[m.name].ToArray(), true, true);

  var arenderer = go.AddComponent<MeshRenderer>();
  arenderer.material = m;
 }
}}