Skip to content

Instantly share code, notes, and snippets.

@PopupAsylum
Last active October 6, 2016 08:22
Show Gist options
  • Save PopupAsylum/1a682b336b774d2616e51e08200f090c to your computer and use it in GitHub Desktop.
Save PopupAsylum/1a682b336b774d2616e51e08200f090c to your computer and use it in GitHub Desktop.
Combines multiple meshes based on materials and lightmap index
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MeshBatcher : MonoBehaviour {
class MeshStrip
{
public List<int> strip = new List<int>();
}
public List<GameObject> batchedResults = new List<GameObject>();
public bool batchOnStart = false;
/// <summary>
/// Batchs the index of the lightmap.
/// Meshes are divided into mesh strips (one strip per material)
/// Meshes are given a lightmap index;
/// Meshes with the same lightmap index and strips that use the same material can have thier strips batched.
/// Lightmapped meshes have a second (full 01) uv set and renderers contain the offset/scale of it in the lightmap
/// UV2s must therefore be adjusted to account for the fact that we can only have one offset
/// </summary>
/// <param name='lightmapIndex'>
/// Lightmap index.
/// </param>
void BatchLightmapIndex(int lightmapIndex)
{
MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
List<Material> materials = UniqueMaterialsForRenderers(meshRenderers);
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
meshFilters = RemoveDisabledRenderersAndNoneLightmap(meshFilters, lightmapIndex);
List<Vector3> verts = new List<Vector3>();
List<Vector3> norms = new List<Vector3>();
List<Vector2> uv = new List<Vector2>();
List<Vector2> uv1 = new List<Vector2>();
List<Color> col = new List<Color>();
List<MeshStrip> strips = new List<MeshStrip>();
for (int i = 0; i<materials.Count; i++){
strips.Add(new MeshStrip());
}
foreach (MeshFilter mf in meshFilters)
{
int vertCount = verts.Count;
// if the mesh filter has no mesh ignore it
if (!mf || !mf.sharedMesh){
break;
}
//if the result of adding this mesh would produce a mesh with a vertex count thats too high, create the current mesh and begin a new one
if (mf.sharedMesh.vertexCount + vertCount > 65000)
{
MakeMeshChild("MeshLightmapIndex_" + lightmapIndex + "_" + vertCount,
verts.ToArray(),
norms.ToArray(),
uv.ToArray(),
uv1.ToArray(),
col.ToArray(),
strips.ToArray(),
materials.ToArray(),
lightmapIndex);
verts.Clear();
norms.Clear();
uv.Clear();
uv1.Clear();
col.Clear();
foreach (MeshStrip ms in strips)
{
ms.strip.Clear();
}
vertCount = 0;
}
//ADDING VERTICIES
Vector3[] newVerts = new Vector3[mf.sharedMesh.vertices.Length];
for (int i = 0; i<mf.sharedMesh.vertices.Length; i++)
{
newVerts[i] = transform.InverseTransformPoint(mf.transform.TransformPoint(mf.sharedMesh.vertices[i]));
}
verts.AddRange(newVerts);
//ADDING STANDARD NORMALS
norms.AddRange(mf.sharedMesh.normals);
//ADDING STANDARD UVS
uv.AddRange(mf.sharedMesh.uv);
//CONVERTING INDIVIDUAL LIGHTMAP UVS TO THIS RENDERERS LIGHTMAP COORDS
if (lightmapIndex>=0 && lightmapIndex<255){
Vector2[] lightmapUVs;
if (mf.sharedMesh.uv2 != null && mf.sharedMesh.uv2.Length > 0){
lightmapUVs = mf.sharedMesh.uv2;
} else {
lightmapUVs = mf.sharedMesh.uv;
}
Vector4 lightmapTilingOffset = mf.renderer.lightmapTilingOffset;
Vector2 uvscale = new Vector2( lightmapTilingOffset.x, lightmapTilingOffset.y );
Vector2 uvoffset = new Vector2( lightmapTilingOffset.z, lightmapTilingOffset.w );
for ( int j = 0; j < lightmapUVs.Length; j++ ) {
lightmapUVs[j] = uvoffset + new Vector2( uvscale.x * lightmapUVs[j].x, uvscale.y * lightmapUVs[j].y );
}
uv1.AddRange(lightmapUVs);
}
//ACCOUNTING FOR MESHES WITHOUT VERTEX COLORS
if (mf.sharedMesh.colors.Length == 0){
Color[] replacementColors = new Color[mf.sharedMesh.vertexCount];
for (int i = 0; i < mf.sharedMesh.vertexCount; i++){replacementColors[i] = Color.white;}
col.AddRange(replacementColors);
}
else{
col.AddRange(mf.sharedMesh.colors);
}
//ASSEMBLING TRIANGLE STRIPS
for (int subMeshIndex = 0; subMeshIndex < mf.sharedMesh.subMeshCount && subMeshIndex < mf.renderer.sharedMaterials.Length; subMeshIndex++)
{
int[] mfStrip = mf.sharedMesh.GetTriangles(subMeshIndex);
if (MeshIsFlipped(mf)){
mfStrip = ReverseTriangleWinding(mfStrip);
}
for(int i = 0; i < mfStrip.Length; i++)
{
mfStrip[i] = mfStrip[i] + vertCount;
}
int stripIndex = materials.IndexOf(mf.renderer.sharedMaterials[subMeshIndex]);
strips[stripIndex].strip.AddRange(mfStrip);
}
}
MakeMeshChild("MeshLightmapIndex_" + lightmapIndex + "_" + verts.Count,
verts.ToArray(),
norms.ToArray(),
uv.ToArray(),
uv1.ToArray(),
col.ToArray(),
strips.ToArray(),
materials.ToArray(),
lightmapIndex);
foreach (MeshFilter r in meshFilters){
DestroyImmediate(r.renderer);
DestroyImmediate(r);
}
}
void MakeMeshChild(string name, Vector3[] verts, Vector3[] norms, Vector2[] uv0s, Vector2[] uv1s, Color[] cols, MeshStrip[] subMeshes, Material[] materials, int lightmapIndex)
{
//MAKE NEW MESH
Mesh newMesh = new Mesh();
newMesh.name = name;
//APPLYING VERTICIES< NORMALS< UVS< UV2S< COLORS
newMesh.vertices = verts;
newMesh.normals = norms;
newMesh.uv = uv0s;
if (uv1s.Length == verts.Length){
newMesh.uv2 = uv1s;
}
newMesh.colors = cols;
//APPLYING SUBMESH TRIANGLE STRIPS
newMesh.subMeshCount = materials.Length;
for (int i = 0; i<subMeshes.Length; i++)
{
newMesh.SetTriangles(subMeshes[i].strip.ToArray(), i);
}
GameObject childMesh = new GameObject(name);
Transform cT = childMesh.transform;
cT.parent = transform;
cT.localPosition = Vector3.zero;
cT.localScale = Vector3.one;
cT.localRotation = Quaternion.identity;
MeshFilter mfr = childMesh.AddComponent<MeshFilter>();
mfr.sharedMesh = newMesh;
MeshRenderer mr = childMesh.AddComponent<MeshRenderer>();
mr.sharedMaterials = materials;
mr.lightmapIndex = lightmapIndex;
childMesh.layer = this.gameObject.layer;
batchedResults.Add(childMesh);
}
int[] ReverseTriangleWinding(int[] triangleStrip)
{
int[] copy = new int[triangleStrip.Length];
for (int i = 0; i<triangleStrip.Length; i+=3)
{
copy[i] = triangleStrip[i];
copy[i+1] = triangleStrip[i+2];
copy[i+2] = triangleStrip[i+1];
}
return copy;
}
bool MeshIsFlipped(MeshFilter m)
{
Vector3 mScale = m.transform.lossyScale;
return (mScale.x*mScale.y*mScale.z < 0);
}
MeshFilter[] RemoveDisabledRenderersAndNoneLightmap(MeshFilter[] mfs, int removeMeshesWithLightmapIndexesThatDontMatchThisValue)
{
List<MeshFilter> filterList = new List<MeshFilter>();
filterList.AddRange(mfs);
List<MeshFilter> dupFilterList = new List<MeshFilter>();
dupFilterList.AddRange(mfs);
for (int i = 0; i < mfs.Length; i++){
if (dupFilterList[i].renderer.enabled == false || dupFilterList[i].renderer.lightmapIndex!=removeMeshesWithLightmapIndexesThatDontMatchThisValue) {
filterList.Remove(dupFilterList[i]);
}
}
return filterList.ToArray();
}
List<Material> UniqueMaterialsForRenderers(MeshRenderer[] meshRenderers)
{
List<Material> materials = new List<Material>();
foreach (MeshRenderer mr in meshRenderers){
foreach (Material mat in mr.sharedMaterials){
if (mat && !materials.Contains(mat)){
materials.Add(mat);
}
}
}
return materials;
}
/// <summary>
/// Gets the lightmap indicies of mesh renderers in children
/// </summary>
/// <returns>
/// The lightmap indicies.
/// </returns>
int[] GetLightmapIndicies()
{
MeshRenderer[] mrs = gameObject.GetComponentsInChildren<MeshRenderer>();
List<int> lightmapIndexes = new List<int>();
foreach (MeshRenderer mr in mrs){
if (!lightmapIndexes.Contains(mr.lightmapIndex)){
lightmapIndexes.Add(mr.lightmapIndex);
}
}
return lightmapIndexes.ToArray();
}
public void Start()
{
if (batchOnStart && (batchedResults == null || batchedResults.Count == 0)){
Batch();
}
}
public void Batch()
{
//if (DuplicateBatch()){return;}
foreach (int index in GetLightmapIndicies())
{
if (index>=-1 && index<=255){ BatchLightmapIndex(index);}
}
}
public bool DuplicateBatch()
{
foreach (MeshBatcher mb in FindObjectsOfType(typeof(MeshBatcher)) as MeshBatcher[]) {
if (mb.name == gameObject.name && mb.batchedResults.Count>0){
foreach (GameObject r in mb.batchedResults){
GameObject doop = Instantiate(r, this.transform.position, this.transform.rotation) as GameObject;
doop.transform.parent = this.transform;
}
return true;
}
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment