Skip to content

Instantly share code, notes, and snippets.

@romainPechot
Created December 8, 2015 15:30
Show Gist options
  • Save romainPechot/168249eff6dbfac22b9a to your computer and use it in GitHub Desktop.
Save romainPechot/168249eff6dbfac22b9a to your computer and use it in GitHub Desktop.
Quick tool for batching a lot of small meshes inside a SkinnedMesh. The genuine meshes have to use the same Material because of the SkinnedMeshRenderer.
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
[System.Serializable]
public class RendererShadowLightSetup
{
[SerializeField]
private ShadowCastingMode shadowCastingMode = ShadowCastingMode.TwoSided;
public ShadowCastingMode SHADOW_CASTIN_MODE { get { return shadowCastingMode; } }
[SerializeField]
private bool receiveShadows = true;
public bool RECEIVE_SHADOWS { get { return receiveShadows; } }
[SerializeField]
private bool useLightProbes = false;
public bool USE_LIGHT_PROBES { get { return useLightProbes; } }
[SerializeField]
private ReflectionProbeUsage reflectionProbeUsage = ReflectionProbeUsage.Off;
public ReflectionProbeUsage REFLECTION_PROBE_USAGE { get { return reflectionProbeUsage; } }
public void Apply(Renderer[] renderers)
{
if(renderers != null && renderers.Length > 0)
{
for(int i = 0; i < renderers.Length; i++)
{
Apply(renderers[i]);
}
}
else Debug.LogWarning("Missing Renderer[] ref, or array is empty!");
}// Apply()
public void Apply(Renderer renderer)
{
if(renderer)
{
// shadow casting
renderer.shadowCastingMode = shadowCastingMode;
// receive shadow
renderer.receiveShadows = receiveShadows;
// use light probe
renderer.useLightProbes = useLightProbes;
// reflection probe usage
renderer.reflectionProbeUsage = reflectionProbeUsage;
}
else Debug.LogWarning("Missing Renderer ref!");
}// Apply()
}// RendererShadowLightSetup
public static class SkinnedMeshRendererMerger
{
public const int MAX_VERTICES_PER_MESH = 65536;
private static int sub_meshes_index_start = 0;
private static int sub_meshes_index_end = 0;
public static SkinnedMeshRenderer[] Generate(MeshFilter[] genuine_mesh_filters, Material material = null, Transform root_skinned_meshes = null, SkinQuality skin_quality = SkinQuality.Bone1, bool destroy_genuine = true, RendererShadowLightSetup renderer_shadow_ligth_setup = null)
{
// temp list skinned meshrenderer
List<SkinnedMeshRenderer> list_skinned_mesh_merges = new List<SkinnedMeshRenderer>();
while(sub_meshes_index_start < genuine_mesh_filters.Length)
{
// new skin mesh
SkinnedMeshRenderer skinned_mesh_renderer = new GameObject("SkinnedMeshRenderer " + list_skinned_mesh_merges.Count.ToString(), typeof(SkinnedMeshRenderer)).GetComponent<SkinnedMeshRenderer>();
Transform transform_skinned_mesh_renderer = skinned_mesh_renderer.transform;
// reset transform
transform_skinned_mesh_renderer.SetParent(root_skinned_meshes);
transform_skinned_mesh_renderer.localPosition = Vector3.zero;
transform_skinned_mesh_renderer.localRotation = Quaternion.identity;
transform_skinned_mesh_renderer.localScale = Vector3.one;
// skinmesh values
int vert_count = 0;
int norm_count = 0;
int tri_count = 0;
int uv_count = 0;
sub_meshes_index_end = sub_meshes_index_start;
int sub_meshes_count = 0;
bool count_done = false;
while(!count_done)
{
// NOT out of bounds ?
if(sub_meshes_index_end < genuine_mesh_filters.Length)
{
// NOT null ?
if(genuine_mesh_filters[sub_meshes_index_end])
{
// fetch new submesh
Mesh mesh = genuine_mesh_filters[sub_meshes_index_end].mesh;
// mesh is readable ?
if(mesh.isReadable)
{
// still inside vertices limit ?
if(vert_count + mesh.vertexCount < MAX_VERTICES_PER_MESH)
{
// add to count
vert_count += mesh.vertices.Length;
norm_count += mesh.normals.Length;
tri_count += mesh.triangles.Length;
uv_count += mesh.uv.Length;
// sub index up
sub_meshes_index_end++;
// update count
sub_meshes_count++;
}
else
{
count_done = true;
}
}
else
{
Debug.LogWarning("Mesh \"" + mesh.name + "\" is marked as not readable!");
// sub index up
sub_meshes_index_end++;
}
}
else
{
Debug.LogWarning("Missing MeshFilter ref at index " + sub_meshes_index_end);
// sub index up
sub_meshes_index_end++;
}
}
else
{
count_done = true;
}
}
if(sub_meshes_count > 0)
{
// allocate arrays
Vector3[] verts = new Vector3[vert_count];
Vector3[] norms = new Vector3[norm_count];
Transform[] tBones = new Transform[sub_meshes_count];
Matrix4x4[] bindPoses = new Matrix4x4[sub_meshes_count];
BoneWeight[] weights = new BoneWeight[vert_count];
int[] tris = new int[tri_count];
Vector2[] uvs = new Vector2[uv_count];
int vertOffset = 0;
int normOffset = 0;
int triOffset = 0;
int uvOffset = 0;
int meshOffset = 0;
// merge the meshes and set up bones
for(int i = sub_meshes_index_start; i < sub_meshes_index_end; i++)
{
MeshFilter meshFilter = genuine_mesh_filters[i];
Mesh mesh = meshFilter.mesh;
int[] tri = mesh.triangles;
for(int j = 0; j < tri.Length; j++)
{
tris[triOffset++] = tri[j] + vertOffset;
}
tBones[meshOffset] = meshFilter.transform;
bindPoses[meshOffset] = Matrix4x4.identity;
Vector3[] vtr3 = mesh.vertices;
for(int j = 0; j < vtr3.Length; j++)
{
weights[vertOffset].weight0 = 1f;
weights[vertOffset].boneIndex0 = meshOffset;
verts[vertOffset++] = vtr3[j];
}
Vector3[] nrm = mesh.normals;
for(int j = 0; j < nrm.Length; j++)
{
norms[normOffset++] = nrm[j];
}
Vector2[] uv = mesh.uv;
for(int j = 0; j < uv.Length; j++)
{
uvs[uvOffset++] = uv[j];
}
meshOffset++;
}
// generate skinned mesh
Mesh skinned_mesh = new Mesh();
// setup mesh
skinned_mesh.name = transform_skinned_mesh_renderer.name;
skinned_mesh.vertices = verts;
skinned_mesh.normals = norms;
skinned_mesh.boneWeights = weights;
skinned_mesh.uv = uvs;
skinned_mesh.triangles = tris;
skinned_mesh.bindposes = bindPoses;
// hook up to the skin mesh renderer
skinned_mesh_renderer.quality = skin_quality;
skinned_mesh_renderer.sharedMesh = skinned_mesh;
skinned_mesh_renderer.bones = tBones;
skinned_mesh_renderer.material = material;
// setup ligth/shadow ?
if(renderer_shadow_ligth_setup != null)
{
renderer_shadow_ligth_setup.Apply(skinned_mesh_renderer);
}
// recalculate bounds
skinned_mesh_renderer.sharedMesh.RecalculateBounds();
// add to temp list
list_skinned_mesh_merges.Add(skinned_mesh_renderer);
// update main index
sub_meshes_index_start = sub_meshes_index_end;
}
else
{
sub_meshes_index_start = sub_meshes_index_end = int.MaxValue;
}
}// while()
// destroy sub mesh filter/renderer ?
if(destroy_genuine)
{
for(int i = 0; i < genuine_mesh_filters.Length; i++)
{
if(genuine_mesh_filters[i])
{
// mesh renderer ?
MeshRenderer meshRenderer = genuine_mesh_filters[i].GetComponent<MeshRenderer>();
if(meshRenderer)
{
GameObject.Destroy(meshRenderer);
}
// mesh
Mesh mesh = genuine_mesh_filters[i].mesh;
if(mesh)
{
GameObject.Destroy(mesh);
}
GameObject.Destroy(genuine_mesh_filters[i]);
}
}
}
return list_skinned_mesh_merges.ToArray();
}// Generate()
}// SkinnedMeshMerger
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment