Quick answer: Group meshes by identical vertex layout before calling CombineMeshes. Or pre-process: add the missing streams (zero-filled) on each input so all share the same layout. Set mesh.indexFormat = IndexFormat.UInt32 if the combined vertex count exceeds 65,535.
CombineMeshes works for one batch and fails for another with “has different vertex layouts.” Some imported meshes have tangents, others don’t; some have color, others UV1. The combiner needs them all to look identical.
The Symptom
CombineMeshes throws or silently produces an empty mesh. Console message about layout, vertex count exceeded, or index format. Affected meshes look fine individually.
What Causes This
Mesh.SetVertexBufferParams declares which streams a mesh has. CombineMeshes requires matching layouts because the output is a single buffer with one consistent layout. Mismatched inputs can’t be packed together without padding the smaller layout up.
The Fix
Step 1: Group by layout, combine within group.
var groups = sources.GroupBy(m => VertexLayoutKey(m));
foreach (var g in groups)
{
var combine = g.Select(m => new CombineInstance { mesh = m, transform = Matrix4x4.identity }).ToArray();
var result = new Mesh { indexFormat = IndexFormat.UInt32 };
result.CombineMeshes(combine, mergeSubMeshes: true, useMatrices: false);
}
string VertexLayoutKey(Mesh m)
{
return $"{m.vertexBufferCount}|{(int)m.GetVertexAttributes()[0].format}|{m.HasVertexAttribute(VertexAttribute.Color)}|{m.HasVertexAttribute(VertexAttribute.Tangent)}|{m.HasVertexAttribute(VertexAttribute.TexCoord1)}";
}
Step 2: Or normalize all to one layout. Pre-process every input to have the union of streams, zero-fill missing ones:
void EnsureColors(Mesh m)
{
if (!m.HasVertexAttribute(VertexAttribute.Color))
m.colors = Enumerable.Repeat(Color.white, m.vertexCount).ToArray();
}
Step 3: 32-bit index format for big combines. 16-bit max 65,535 vertices. Above, set:
result.indexFormat = IndexFormat.UInt32;
Memory usage doubles for the index buffer; vertex buffer unchanged.
Combine Then Apply
For runtime mesh combination (procedural levels, batch optimization), call CombineMeshes once at scene load and assign the result to a single MeshFilter. Don’t recombine every frame; it’s slow.
Verifying
Combine; check vertex count and submesh count on the result. Visualize in Scene view; the merged mesh should look identical to the sum of inputs.
“Match layouts. UInt32 for big combines. Group meshes that pair cleanly.”
Related Issues
For SRP Batcher, see SRP Batcher. For occlusion culling, see occlusion.
Layouts align. Indices grow. Combines succeed.