The code example you provided is a great way to create and render entities efficiently in Unity using the Entities Graphics package, which leverages DOTS. This example efficiently generates entities in bulk, applies transforms and materials, and instantiates them using GPU instancing for efficient rendering.
Here’s a breakdown of the key parts of this example and how it works:
The example creates a prototype entity (prototype) with all necessary components (like RenderMesh, MaterialMeshInfo, and MaterialColor). This prototype serves as a blueprint for creating multiple entities efficiently via instantiation.
var prototype = entityManager.CreateEntity();
RenderMeshUtility.AddComponents(
MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0));
entityManager.AddComponentData(prototype, new MaterialColor());
The RenderMeshUtility.AddComponents method is used to add rendering-related components to the entity, such as the RenderMesh, MaterialMeshInfo, and others, which are required for the Entities Graphics system to render the entity properly.
The SpawnJob is a Burst-compatible job that runs in parallel using the IJobParallelFor interface. The job is responsible for:
- Instantiating Entities: Clones the prototype entity (
Ecb.Instantiate) to create new entities.
- Setting Transform and Color: It computes a transform and color based on the index of the entity. The position is determined using some simple math to arrange the entities in a twisted spiral shape, and the color is interpolated based on the entity’s index.
- Setting Mesh Information: For each entity, the job sets the appropriate mesh from the
RenderMeshArray, ensuring that the correct mesh is used for rendering.
The job uses an EntityCommandBuffer (ECB) to safely instantiate and modify entities in a parallel job. This is crucial for ensuring thread safety since entities can’t be directly modified within jobs that run in parallel. The ECB collects all entity creation and modification commands and then executes them later, once the job has finished.
public struct SpawnJob : IJobParallelFor
public float ObjectScale;
public EntityCommandBuffer.ParallelWriter Ecb;
[ReadOnly] public NativeArray<RenderBounds> MeshBounds;
public void Execute(int index)
var e = Ecb.Instantiate(index, Prototype);
Ecb.SetComponent(index, e, new LocalToWorld { Value = ComputeTransform(index) });
Ecb.SetComponent(index, e, new MaterialColor() { Value = ComputeColor(index) });
int meshIndex = index % MeshCount;
Ecb.SetComponent(index, e, MaterialMeshInfo.FromRenderMeshArrayIndices(0, meshIndex));
Ecb.SetComponent(index, e, MeshBounds[meshIndex]);
// Helper methods to compute the color and transform for each entity.
By using IJobParallelFor, the job processes each entity index (index) in parallel, allowing thousands of entities to be created very quickly. This reduces the overhead of having to loop over each entity sequentially, which would otherwise be slow when dealing with large numbers of entities.
RenderMeshArray: This is an optimized way of handling multiple meshes and materials. It holds references to the materials and meshes, and the system dynamically assigns them to entities during instantiation.
RenderBounds: This component defines the axis-aligned bounding box (AABB) for the mesh, which helps with efficient culling. Entities outside the camera’s view will not be rendered.
var renderMeshArray = new RenderMeshArray(new[] { Material }, Meshes.ToArray());
var bounds = new NativeArray<RenderBounds>(Meshes.Count, Allocator.TempJob);
for (int i = 0; i < bounds.Length; ++i)
bounds[i] = new RenderBounds { Value = Meshes[i].bounds.ToAABB() };
The job is scheduled using the Schedule method, which runs the job in parallel across multiple cores. After the job completes, the EntityCommandBuffer is played back to apply all entity creation and modification commands to the EntityManager.
var spawnHandle = spawnJob.Schedule(EntityCount, 128);
bounds.Dispose(spawnHandle);
ecbJob.Playback(entityManager);
entityManager.DestroyEntity(prototype);
If you want to render spheres, as per your original scenario, you can adapt this example by making the following changes:
-
Change Mesh to Spheres: Replace the meshes in RenderMeshArray with sphere meshes. You can either generate these spheres dynamically or use Unity’s built-in sphere primitive.
var sphereMesh = GameObject.CreatePrimitive(PrimitiveType.Sphere).GetComponent<MeshFilter>().sharedMesh;
var renderMeshArray = new RenderMeshArray(new[] { Material }, new[] { sphereMesh });
-
Adjust Transform for Spheres: In the ComputeTransform function, ensure that the transformations you compute match how you want the spheres to be arranged in the scene. For example, you can arrange them in a grid, a circle, or any other pattern.
-
Entity Count: Adjust the EntityCount to control how many spheres are created. You can tweak this number to match the performance you desire.
- Batching: Unity’s ECS + Entities Graphics approach automatically batches entities for efficient rendering.
- Instancing: With Entities Graphics, mesh instancing is handled efficiently by Unity. You can handle many entities with a single draw call if they share the same material and mesh.
- Burst Compilation: The job system is Burst-compatible, which greatly improves performance by compiling the job into highly optimized native code.
This example is a high-performance, optimized way of creating and rendering a large number of entities using DOTS and Entities Graphics. It demonstrates how to instantiate entities in parallel, configure rendering components, and leverage ECS’s powerful multithreaded architecture to efficiently manage and render thousands of entities.
By adapting the mesh and material to suit your requirements (e.g., using spheres instead of random meshes), you can render spheres efficiently while benefiting from the performance optimizations provided by Unity’s DOTS and Entities Graphics.