Entities Graphics 运行时高效创建实体:EntityCreation 样本深度解析
2026/9/16 19:31:27 网站建设 项目流程

Entities Graphics 运行时高效创建实体:EntityCreation 样本深度解析

【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples

本技术指南围绕 Unity DOTS 官方样本仓库 EntityComponentSystemSamples 中的 EntityCreation 场景展开,讲解如何基于RenderMeshUtility.AddComponentsRenderMeshDescriptionAPI,在运行时通过 Burst 并行 Job 批量实例化实体,并通过MaterialColor等 ECS 组件实现逐实体着色。读完本文,你将掌握一套"原型实体 + RenderMeshArray + EntityCommandBuffer"的高效实体生成范式,并理解其渲染裁剪、内存与性能设计背后的原理。

样本概览

EntityCreation 是 HDRP Entities Graphics 功能样本之一,位于 GraphicsSamples/HDRPSamples/Assets/SampleScenes/4. API Examples/EntityCreation,对应的场景文件为EntityCreation.unity。该样本的核心目标是:在运行期高效创建经由 Entities Graphics 渲染的实体,并通过 ECS 组件设置颜色。进入 Play 模式即可看到生成结果——数量可配的实体沿螺旋状排列,色相沿路径平滑渐变。

仓库同时提供了功能一致的 URP 版本样本(GraphicsSamples/URPSamples/Assets/SampleScenes/4. API Examples/EntityCreation),两处 TestEntityCreationAPI.cs 源码完全一致,说明该方案与渲染管线(HDRP/URP)无关,属于 Entities Graphics 的通用运行时 API。

样本的运行结果如上图所示:一万个实体沿扭曲的螺旋柱面分布,颜色由 HSV 色相随索引位置线性变化,直观展示了"单个原型批量实例化 + ECS 组件逐实体差异化"的组合效果。

核心 API:RenderMeshUtility 与 RenderMeshDescription

样本 README 明确指出,核心逻辑由TestEntityCreationAPI脚本完成,它组合使用了RenderMeshDescriptionRenderMeshUtility.AddComponentsInstantiate(实体实例化)三个要素。

RenderMeshUtility.AddComponents是 Entities Graphics 提供的运行时实体渲染组件装配 API,其作用等价于把 GameObject 转实体(GameObject Conversion)流程中的渲染相关组件一次性附加到目标实体上,避免手写一长串AddComponent调用。调用时通常配合两个数据载体:

  • RenderMeshArray:维护一组材质(Material)与一组网格(Mesh)的扁平索引数组,供实体以"数组下标"方式引用,避免为每个实体单独持有资源引用;
  • RenderMeshDescription:描述实体的渲染设置,包括阴影投射/接收、光照探针(Light Probe)等过滤选项。

从源码可见其典型用法(TestEntityCreationAPI.cs):

var filterSettings = RenderFilterSettings.Default; filterSettings.ShadowCastingMode = ShadowCastingMode.Off; filterSettings.ReceiveShadows = false; var renderMeshArray = new RenderMeshArray(new[] {Material}, Meshes.ToArray()); var renderMeshDescription = new RenderMeshDescription { FilterSettings = filterSettings, LightProbeUsage = LightProbeUsage.Off, }; var prototype = entityManager.CreateEntity(); RenderMeshUtility.AddComponents( prototype, entityManager, renderMeshDescription, renderMeshArray, MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0)); entityManager.AddComponentData(prototype, new MaterialColor());

这段代码先以默认值构造RenderFilterSettings,关闭阴影投射与阴影接收(样本为性能演示场景,不需要阴影),再基于单一材质与多网格构建RenderMeshArray,随后创建一个"裸"实体作为原型,把渲染组件一次性装配到原型上。其中MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0)表示该实体默认引用 RenderMeshArray 中下标为 0 的材质与 0 号网格——注意这里的网格下标是运行时可通过SetComponent动态改写的(见下文 SpawnJob)。

与该样本同目录下的 RenderMeshUtilityExample 展示了RenderMeshUtility.AddComponents的另一类用法——通过运行时创建的材质与材质属性设置实现着色,可作为本样本的进阶参考。

原型实体 + 并行 Job 的批量生成范式

样本的生成策略分为两个阶段:先在主线程准备好一个带完整渲染组件的原型实体,再在 Burst 并行 Job 中通过EntityCommandBuffer.ParallelWriter批量克隆该原型。注释对此做了明确说明:"通过克隆预先创建的原型实体来生成大部分实体,原型可以是 Prefab,也可以是像本样本这样在运行时创建的实体,这是运行时创建实体最快、最高效的方式"(TestEntityCreationAPI.cs)。

SpawnJob:一次 Job 完成实例化与差异化

SpawnJob是一个IJobParallelFor结构体,每个 index 对应一个待生成的实体:

[GenerateTestsForBurstCompatibility] public struct SpawnJob : IJobParallelFor { public Entity Prototype; public int EntityCount; public int MeshCount; public float ObjectScale; public float Radius; public float Twists; public EntityCommandBuffer.ParallelWriter Ecb; [ReadOnly] public NativeArray<RenderBounds> MeshBounds; public void Execute(int index) { var e = Ecb.Instantiate(index, Prototype); // Prototype has all correct components up front, can use SetComponent Ecb.SetComponent(index, e, new LocalToWorld {Value = ComputeTransform(index)}); Ecb.SetComponent(index, e, new MaterialColor() {Value = ComputeColor(index)}); // MeshBounds must be set according to the actual mesh for culling to work. int meshIndex = index % MeshCount; Ecb.SetComponent(index, e, MaterialMeshInfo.FromRenderMeshArrayIndices(0, meshIndex)); Ecb.SetComponent(index, e, MeshBounds[meshIndex]); } // ComputeTransform / ComputeColor 见源码 }

关键点逐一拆解:

  • Ecb.Instantiate(index, Prototype):克隆原型实体。由于原型已具备全部渲染组件,克隆体会自动继承这些组件,因此后续只需用SetComponent覆盖差异,无需再走AddComponent
  • LocalToWorld:由ComputeTransform(index)计算。从源码看,位置采用球坐标变换——r = sin(t·π)·Radius让实体沿柱面半径收缩、phi = t·Twists·2π产生扭转、y方向线性铺开,最终构成螺旋扭曲的立体分布。
  • MaterialColor:由ComputeColor(index)基于Color.HSVToRGB(t, 1, 1)计算,t = index / (EntityCount - 1)把色相从 0 线性映射到 1,形成彩虹渐变。这正是样本 README 提到的"通过 ECS 组件设置颜色"的实现——颜色被写入MaterialColor组件,由 Entities Graphics 自动驱动材质_BaseColor/_Color属性。
  • MaterialMeshInfo.FromRenderMeshArrayIndices(0, meshIndex):按index % MeshCount循环切换网格,让不同实体引用 RenderMeshArray 中的不同 Mesh,验证了"网格下标可逐实体运行时改写"。
  • MeshBounds[meshIndex]:把每个网格的RenderBounds(渲染包围盒)写入实体。源码注释明确指出:"MeshBounds 必须根据实际网格设置,裁剪(culling)才能正常工作"(TestEntityCreationAPI.cs)。渲染器靠该组件做视锥/遮挡剔除,若缺失或错误,实体会被错误裁剪掉。

包围盒预计算与 Job 调度

主线程在启动 Job 前,先把每个 Mesh 的包围盒转成 ECS 的RenderBounds(AABB)存入NativeArray,Job 中以[ReadOnly]只读引用:

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()}; var spawnHandle = spawnJob.Schedule(EntityCount, 128); bounds.Dispose(spawnHandle); // 让 Job 完成后自动回收 spawnHandle.Complete();

spawnJob.Schedule(EntityCount, 128)以批大小 128 将EntityCount次迭代并行化,全部由 Burst 编译执行,因此可以轻松在单帧内生成上万个实体。bounds.Dispose(spawnHandle)把原生内存释放挂接到 Job 句柄上,Job 执行完毕即自动释放,避免主线程Complete()前手动管理。

ECB 回放与原型销毁

Job 本身不直接操作EntityManager,而是写入EntityCommandBuffer.ParallelWriter,保证线程安全:

ecbJob.Playback(entityManager); // 将 ECB 中的操作应用回 EntityManager ecbJob.Dispose(); // 释放 ECB 自身内存 entityManager.DestroyEntity(prototype); // 原型实体不再需要,销毁

Playback在主线程执行,统一完成实例化与组件设置;随后销毁原型实体,避免其残留场景中产生多余渲染。整个流程体现了 ECS 的核心纪律:写操作集中在主线程回放,并行 Job 只做无冲突的并行记录

场景配置与操作步骤

场景EntityCreation.unity中挂载TestEntityCreationAPISpawnerGameObject 已预置了样本默认参数(EntityCreation.unity):

参数默认值作用
EntityCount10000生成实体总数,直接决定 Job 迭代次数与实例数量
ObjectScale0.1实体缩放,float3(ObjectScale)作为 TRS 缩放入参
Radius10螺旋柱面的最大半径
Twists16螺旋扭转圈数(phi角随t的倍数)
Meshes4 个 Mesh供实体轮换引用的网格集合
Material1 个材质渲染材质,实体共用一个材质实例

样本 README 给出的使用步骤非常简洁,按如下操作即可复现:

  1. 在 Hierarchy 中选中Spawner
  2. 在 Inspector 中配置上述参数(可调整实体数量、缩放、半径、扭转圈数,或增删 Mesh);
  3. 点击 Play。

进入 Play 后,Start()一次性完成原型创建、Job 调度与回放,随后所有实体持续由 Entities Graphics 渲染。注意该脚本只负责"一次性生成",Update()为空,实体生成后不会继续更新变换;如需动态改变,可另写 System 修改LocalToWorldMaterialColorMaterialMeshInfo组件。

性能设计要点总结

综合源码可以提炼出这套"运行时批量建实体"方案的四个关键实践:

  1. 原型复用代替逐个装配:先装配好一个原型实体(可来自 Prefab 或运行时创建),再批量Instantiate,把成本高昂的组件装配只执行一次;
  2. 并行 Job 代替主线程循环:通过IJobParallelFor+EntityCommandBuffer.ParallelWriter,实体创建本身被 Burst 并行化,主线程只做最后的 ECB 回放;
  3. 数组索引代替资源引用RenderMeshArray让所有实体共享同一份材质/网格索引,配合MaterialMeshInfo运行时切换,既节省内存又支持差异化外观;
  4. 组件契约完整:原型实体"组件齐全、只改值不改结构",配合正确的RenderBounds,保证渲染与裁剪行为符合预期。

需要说明的是:本样本的TestEntityCreationAPI是演示用MonoBehaviour(通过World.DefaultGameObjectInjectionWorld获取 EntityManager),生产级代码通常会把生成逻辑放入 ECS System(如SystemBase+JobHandle)中管理,但"原型 + 并行 Job + ECB"的核心模式完全一致,可直接迁移。仓库中同目录的 RenderMeshUtilityExample 与 MaterialMeshChange 等样本,分别覆盖了运行时材质属性与运行时换材换网格的场景,与本样本互补,适合继续深入阅读。

【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询