Graphics & Optimization #3: Combining static objects

Rendering multiple meshes and objects is not a good choice for optimization. The starting point of optimization should be decreasing number of draw call and batches. For this, we decreased the number of vertices and materials as possible as we can. There were two method we used for our application: combining mesh in 3D software Blender and joining meshes through Unity scripting.

Firstly, we could make separated meshes share one single material through Blender. The easiest way is  just joining mesh objects each other. But, If there are the objects  which are not sharing UV map, we used a technic called “Texture Atlas”.

The second method was using Unity scripting. This method is manually copying all meshes vertex information and appending in a single large array. below is the script of that.

Untitled-7

private Mesh CombineMeshes_fixed() {
MeshRenderer[] meshRenderers = this.gameObject.GetComponentsInChildren(false);
int totalVertexCount = 0;
int totalMeshCount = 0;

//
if(meshRenderers != null && meshRenderers.Length > 0) {
foreach(MeshRenderer meshRenderer in meshRenderers) {
MeshFilter filter = meshRenderer.gameObject.GetComponent();
if(filter != null && filter.sharedMesh != null) {
totalVertexCount += filter.sharedMesh.vertexCount;
totalMeshCount++;
}
}
}

if(totalMeshCount == 0) {
Debug.Log(“SpaceInfo Exporter :: [Error] No meshes found in children.”);
return null;
}
if(totalMeshCount == 1) {
Debug.Log(“SpaceInfo Exporter :: [Error] Only 1 mesh found in children.”);
return null;
}
if(totalVertexCount > 65535) {
Debug.Log(“SpaceInfo Exporter :: [Error] There are too many vertices to combine into 1 mesh (“+totalVertexCount+”). The max. limit is 65535″);
return null;
}

Mesh mesh = new Mesh();
Matrix4x4 myTransform = this.gameObject.transform.worldToLocalMatrix;
List vertices = new List();
List normals = new List();
List uv1s = new List();
List uv2s = new List();
Dictionary<Material, List> subMeshes = new Dictionary<Material, List>();

if(meshRenderers != null && meshRenderers.Length > 0) {
foreach(MeshRenderer meshRenderer in meshRenderers) {
MeshFilter filter = meshRenderer.gameObject.GetComponent();
if(filter != null && filter.sharedMesh != null) {
MergeMeshInto(filter.sharedMesh, meshRenderer.sharedMaterials, myTransform * filter.transform.localToWorldMatrix, vertices, normals, uv1s, uv2s, subMeshes);
if(filter.gameObject != this.gameObject) {
filter.gameObject.SetActive(false);
}
}
}
}

mesh.vertices = vertices.ToArray();
if(normals.Count>0) mesh.normals = normals.ToArray();
if(uv1s.Count>0) mesh.uv = uv1s.ToArray();
if(uv2s.Count>0) mesh.uv2 = uv2s.ToArray();
mesh.subMeshCount = subMeshes.Keys.Count;
Material[] materials = new Material[subMeshes.Keys.Count];
int mIdx = 0;
foreach(Material m in subMeshes.Keys) {
materials[mIdx] = m;
mesh.SetTriangles(subMeshes[m].ToArray(), mIdx++);
}

if(meshRenderers != null && meshRenderers.Length > 0) {
MeshRenderer meshRend = this.gameObject.GetComponent();
if(meshRend == null) meshRend = this.gameObject.AddComponent();
meshRend.sharedMaterials = materials;

MeshFilter meshFilter = this.gameObject.GetComponent();
if(meshFilter == null) meshFilter = this.gameObject.AddComponent();
meshFilter.sharedMesh = mesh;
}
return mesh;
}

private static void MergeMeshInto(Mesh meshToMerge, Material[] ms, Matrix4x4 transformMatrix, List vertices, List normals, List uv1s, List uv2s, Dictionary<Material, List> subMeshes) {
if(meshToMerge == null) return;
int vertexOffset = vertices.Count;
Vector3[] vs = meshToMerge.vertices;

for(int i=0;i<vs.Length;i++) { vs[i] = transformMatrix.MultiplyPoint3x4(vs[i]); } vertices.AddRange(vs); Quaternion rotation = Quaternion.LookRotation(transformMatrix.GetColumn(2), transformMatrix.GetColumn(1)); Vector3[] ns = meshToMerge.normals; if(ns!=null && ns.Length>0) {
for(int i=0;i<ns.Length;i++) ns[i] = rotation * ns[i]; normals.AddRange(ns); } Vector2[] uvs = meshToMerge.uv; if(uvs!=null && uvs.Length>0) uv1s.AddRange(uvs);
uvs = meshToMerge.uv2;
if(uvs!=null && uvs.Length>0) uv2s.AddRange(uvs);

for(int i=0;i<ms.Length;i++) {
if(i<meshToMerge.subMeshCount) { int[] ts = meshToMerge.GetTriangles(i); if(ts.Length>0) {
if(ms[i]!=null && !subMeshes.ContainsKey(ms[i])) {
subMeshes.Add(ms[i], new List());
}
List subMesh = subMeshes[ms[i]];
for(int t=0;t<ts.Length;t++) {
ts[t] += vertexOffset;
}
subMesh.AddRange(ts);
}
}
}
}