Combine Mesh, Occlusion Cull, Lightmapping All In One

Hi there,

I’m constantly trying to optimise my project and in doing so I’ve been looking into Combining Meshes, Occlusion Culling and Light mapping. All work very well on their own, however when it comes to Combining Meshes and Lightmapping I run into issues as the mesh combining seems to generate bad UVs afterwards.

Has anyone had experience with this? I’ve looked into some old answers on here but there doesn’t seem to be a real solution so far.

Thanks in advance for your help

More Info :

I have a scene made of several objects, I combine the meshes in the editor and bake in the occlusion culling. However when I try to bake lightmaps into these newly created meshes the lightmaps are incorrect. I’m guessing this has something to do with the UVs of the new mesh.

Well I followed up on that and generated new UVs for the new meshes using

Unwrapping.GenerateSecondaryUVSet(filter.sharedMesh);

but the results were still incorrect :frowning:

here’s the code

using UnityEngine;
using System.Collections;
#if UNITY_EDITOR 
using UnityEditor;
#endif

/*
Attach this script as a parent to some game objects. The script will then combine the meshes at startup.
This is useful as a performance optimization since it is faster to render one big mesh than many small meshes. See the docs on graphics performance optimization for more info.

Different materials will cause multiple meshes to be created, thus it is useful to share as many textures/material as you can.
*/
[ExecuteInEditMode()]
[AddComponentMenu("Mesh/Combine Children")]
public class CombineChildren : MonoBehaviour {
	
	/// Usually rendering with triangle strips is faster.
	/// However when combining objects with very low triangle counts, it can be faster to use triangles.
	/// Best is to try out which value is faster in practice.
	public int frameToWait = 0;
	public bool generateTriangleStrips = true, combineOnStart = true, destroyAfterOptimized = false, castShadow = true, receiveShadow = true, keepLayer = true, addMeshCollider = false;
	
	void Start()
	{
		if (combineOnStart && frameToWait == 0) Combine();
		else StartCoroutine(CombineLate());
	}
	
	IEnumerator CombineLate()
	{
		for (int i = 0; i < frameToWait; i++ ) yield return 0;
		Combine();
	}
	
	[ContextMenu("Combine Now on Childs")]
	public void CallCombineOnAllChilds()
	{
		CombineChildren[] c = gameObject.GetComponentsInChildren<CombineChildren>();
		int count = c.Length;
		for (int i = 0; i < count; i++) if(c _!= this)c*.Combine();*_

* combineOnStart = enabled = false;*
* }*

* /// This option has a far longer preprocessing time at startup but leads to better runtime performance.*
* [ContextMenu (“Combine Now”)]*
* public void Combine () {*
* Component[] filters = GetComponentsInChildren(typeof(MeshFilter));*
* Matrix4x4 myTransform = transform.worldToLocalMatrix;*
* Hashtable materialToMesh= new Hashtable();*

* for (int i=0;i<filters.Length;i++) {*
_ MeshFilter filter = (MeshFilter)filters*;
Renderer curRenderer = filters.renderer;
MeshCombineUtility.MeshInstance instance = new MeshCombineUtility.MeshInstance ();
instance.mesh = filter.sharedMesh;
if (curRenderer != null && curRenderer.enabled && instance.mesh != null) {*

instance.transform = myTransform * filter.transform.localToWorldMatrix;_

* Material[] materials = curRenderer.sharedMaterials;*
* for (int m=0;m<materials.Length;m++) {*
* instance.subMeshIndex = System.Math.Min(m, instance.mesh.subMeshCount - 1);*

* ArrayList objects = (ArrayList)materialToMesh[materials[m]];*
* if (objects != null) {*
* objects.Add(instance);*
* }*
* else*
* {*
* objects = new ArrayList ();*
* objects.Add(instance);*
* materialToMesh.Add(materials[m], objects);*
* }*
* }*
* if (Application.isPlaying && destroyAfterOptimized && combineOnStart) Destroy(curRenderer.gameObject);*
* else if (destroyAfterOptimized) DestroyImmediate(curRenderer.gameObject);*
* else curRenderer.enabled = false;*
* }*
* }*

* foreach (DictionaryEntry de in materialToMesh) {*
* ArrayList elements = (ArrayList)de.Value;*
* MeshCombineUtility.MeshInstance[] instances = (MeshCombineUtility.MeshInstance[])elements.ToArray(typeof(MeshCombineUtility.MeshInstance));*

* // We have a maximum of one material, so just attach the mesh to our own game object*
* if (materialToMesh.Count == 1)*
* {*
* // Make sure we have a mesh filter & renderer*
* if (GetComponent(typeof(MeshFilter)) == null)*
* gameObject.AddComponent(typeof(MeshFilter));*
* if (!GetComponent(“MeshRenderer”))*
* gameObject.AddComponent(“MeshRenderer”);*

* MeshFilter filter = (MeshFilter)GetComponent(typeof(MeshFilter));*
* if (Application.isPlaying) filter.mesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);*
* else filter.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);*
* renderer.material = (Material)de.Key;*
* renderer.enabled = true;*
* if (addMeshCollider) gameObject.AddComponent();*
* renderer.castShadows = castShadow;*
* renderer.receiveShadows = receiveShadow;*

* #if UNITY_EDITOR*
* //Unwrapping.GenerateSecondaryUVSet(filter.sharedMesh);*
* //Unwrapping.GeneratePerTriangleUV(filter.sharedMesh);*
* Vector2[] uvs = new Vector2[mesh.vertices.Length];*
* int i = 0;*
* while (i < uvs.Length)*
* {*
uvs = new Vector2(vertices_.x, vertices*.z);
i++;
}
mesh.uv = uvs;
#endif*

* }
// We have multiple materials to take care of, build one mesh / gameobject for each material*

* // and parent it to this object*
* else*
* {
GameObject go = new GameObject(“Combined mesh”);
if (keepLayer) go.layer = gameObject.layer;
go.transform.parent = transform;
go.transform.localScale = Vector3.one;
go.transform.localRotation = Quaternion.identity;
go.transform.localPosition = Vector3.zero;
go.AddComponent(typeof(MeshFilter));
go.AddComponent(“MeshRenderer”);
go.renderer.material = (Material)de.Key;
MeshFilter filter = (MeshFilter)go.GetComponent(typeof(MeshFilter));
if(Application.isPlaying)filter.mesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
else filter.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
go.renderer.castShadows = castShadow;
go.renderer.receiveShadows = receiveShadow;
if (addMeshCollider) go.AddComponent();_

#if UNITY_EDITOR*

* //Unwrapping.GenerateSecondaryUVSet(filter.sharedMesh);*
* //Unwrapping.GeneratePerTriangleUV(filter.sharedMesh);*
* Vector2[] uvs = new Vector2[mesh.vertices.Length];*
* int i = 0;*
* while (i < uvs.Length)*
* {*
uvs = new Vector2(vertices_.x, vertices*.z);
i++;
}
mesh.uv = uvs;
#endif*

* }
}
}
}*_

After you’ve generated the mesh, try this:

 Vector2[] uvs = new Vector2[mesh.vertices.Length];
        int i = 0;
        while (i < uvs.Length) 
        {
            uvs <em>= new Vector2(vertices_.x, vertices*.z);*_</em>

i++;
}
mesh.uv = uvs;