Hello!
I’m using Unity 2022.3.62f3 with Entities 1.4.3 and Entities Graphics 1.4.16 , and I’m struggling to get multiple submeshes with different materials to render as a single Entity — both at runtime and via baking.
According to the changelog entry [1.1.0-exp.1] - 2023-09-18 :
“Add support for multiple submeshes per entity. Stop creating one entity per submesh unless skinning is used.”
Changelog | Entities Graphics | 1.4.16
This strongly suggests it’s possible — and indeed preferred — to have one Entity for multi-submesh meshes. However, I have been unable to find any working code examples or documentation covering this (the runtime-entity-creation page only shows a single-material case).
What I’ve tried
1. Runtime creation
Example code
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
[AddComponentMenu("Entities/Graphics/Multi Material Entity Spawner")]
public class MultiMaterialEntitySpawner : MonoBehaviour
{
[Header("Mesh Configuration")]
public Mesh sourceMesh; // Mesh with multiple submeshes
public Material[] materials; // Array of materials (one per submesh)
[Header("Rendering Settings")]
public ShadowCastingMode shadowCastingMode = ShadowCastingMode.On;
public bool receiveShadows = true;
[Header("Entity Settings")]
public int spawnCount = 10;
public float spawnRadius = 5.0f;
private EntityManager _entityManager;
private Entity _prototypeEntity;
private void Start()
{
if (sourceMesh == null || materials == null || materials.Length == 0)
{
Debug.LogError("Please assign a mesh and at least one material in the inspector.");
return;
}
int subMeshCount = sourceMesh.subMeshCount;
if (materials.Length < subMeshCount)
{
Debug.LogWarning($"Mesh has {subMeshCount} submeshes but only {materials.Length} materials provided. " +
"Some submeshes may not render correctly.");
}
_entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
// Create prototype with multiple materials
CreateMultiMaterialPrototype();
// Spawn entities
SpawnEntities();
// Destroy prototype after use
if (_prototypeEntity != Entity.Null)
{
_entityManager.DestroyEntity(_prototypeEntity);
}
}
private void CreateMultiMaterialPrototype()
{
// Check number of submeshes
int subMeshCount = sourceMesh.subMeshCount;
// Create RenderMeshArray with multiple materials
// For a mesh with multiple submeshes, materials must match the number of submeshes
Material[] actualMaterials = new Material[subMeshCount];
for (int i = 0; i < subMeshCount; i++)
{
if (i < materials.Length)
{
actualMaterials[i] = materials[i];
}
else
{
actualMaterials[i] = materials[0]; // Use first material as fallback
Debug.LogWarning($"No material provided for submesh {i}, using first material as fallback");
}
}
var renderMeshArray = new RenderMeshArray(actualMaterials, new Mesh[] { sourceMesh });
// Correct creation of RenderMeshDescription for version 1.4.x
// In version 1.4.x, RenderMeshDescription accepts a Renderer, not RenderFilterSettings
var renderer = gameObject.GetComponent<Renderer>();
if (renderer == null)
{
renderer = gameObject.AddComponent<MeshRenderer>();
}
// Configure renderer for correct rendering settings
renderer.shadowCastingMode = shadowCastingMode;
renderer.receiveShadows = receiveShadows;
// Create RenderMeshDescription from renderer
var renderMeshDescription = new RenderMeshDescription(renderer);
// Create prototype
_prototypeEntity = _entityManager.CreateEntity();
// Correct AddComponents call for version 1.4.x
// MaterialMeshInfo.FromRenderMeshArrayIndices accepts only two parameters: materialIndex and meshIndex
RenderMeshUtility.AddComponents(
_prototypeEntity,
_entityManager,
renderMeshDescription,
renderMeshArray,
MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0) // materialIndex=0, meshIndex=0
);
// Add transform components
_entityManager.AddComponentData(_prototypeEntity, new LocalTransform
{
Position = float3.zero,
Rotation = quaternion.identity,
Scale = 1.0f
});
_entityManager.AddComponentData(_prototypeEntity, new LocalToWorld());
Debug.Log($"Created prototype entity with {subMeshCount} submeshes and {actualMaterials.Length} materials");
}
private void SpawnEntities()
{
using (var ecb = new EntityCommandBuffer(Allocator.TempJob))
{
var spawnJob = new SpawnJob
{
Prototype = _prototypeEntity,
SpawnCount = spawnCount,
SpawnRadius = spawnRadius,
Ecb = ecb.AsParallelWriter()
};
var spawnHandle = spawnJob.Schedule(spawnCount, 64);
spawnHandle.Complete();
ecb.Playback(_entityManager);
ecb.Dispose();
}
}
[GenerateTestsForBurstCompatibility]
private struct SpawnJob : IJobParallelFor
{
public Entity Prototype;
public int SpawnCount;
public float SpawnRadius;
public EntityCommandBuffer.ParallelWriter Ecb;
public void Execute(int index)
{
Entity entity = Ecb.Instantiate(index, Prototype);
// Calculate position on a circle
float angle = (index / (float)SpawnCount) * 360f;
float x = Mathf.Cos(angle * Mathf.Deg2Rad) * SpawnRadius;
float z = Mathf.Sin(angle * Mathf.Deg2Rad) * SpawnRadius;
Ecb.SetComponent(index, entity, new LocalTransform
{
Position = new float3(x, 0, z),
Rotation = quaternion.Euler(0, angle, 0),
Scale = 1.0f
});
}
}
// For visual debugging in the editor
private void OnDrawGizmosSelected()
{
if (Application.isPlaying || sourceMesh == null)
return;
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, spawnRadius);
// Display number of submeshes
Handles.Label(transform.position + Vector3.up * 2,
$"Submeshes: {(sourceMesh != null ? sourceMesh.subMeshCount.ToString() : "0")}\n" +
$"Materials: {(materials != null ? materials.Length.ToString() : "0")}");
}
[ContextMenu("Validate Mesh Submeshes")]
private void ValidateMeshSubmeshes()
{
if (sourceMesh == null)
{
Debug.LogWarning("No mesh assigned.");
return;
}
Debug.Log($"Mesh '{sourceMesh.name}' has {sourceMesh.subMeshCount} submeshes.");
for (int i = 0; i < sourceMesh.subMeshCount; i++)
{
var subMesh = sourceMesh.GetSubMesh(i);
Debug.Log($"Submesh {i}: {subMesh.indexCount} triangles, topology: {subMesh.topology}");
}
}
}
→ Only the first submesh renders (with first material).
2. Baking via SubScene
I placed a Prefab to a Subscene. Entity Debugger shows 2 separate Entities were created (one per submesh).
This contradicts the changelog — unless I’m misunderstanding something (e.g., perhaps ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING define or import settings are required?).
Core questions
- Is it actually possible today (in 1.4.16) to get a single Entity with multiple submeshes — both for static meshes and animated ones?
- If yes:
- What is the exact API usage? (e.g., special
MaterialMeshInfo,RenderMeshArrayconstruction, flags?) - Are there hidden requirements (e.g., mesh import settings, defines, SRP compatibility)?
- For animated characters with 4–6 materials (e.g. skin, cloth, metal, faction-colored decal):
- Must we really manage 4–6 Entities per character?
- How do people handle runtime material changes (e.g. faction recoloring) across such a group efficiently?
I’m hoping to avoid the “one entity per submesh” fallback.
Any working example would be hugely appreciated!
Best regards.