【博物纳新】超级变变变,万人同屏开源库推荐!

【博物纳新】超级变变变,万人同屏开源库推荐!

【博物纳新】是UWA重磅推出的全新栏目,旨在为开发者推荐新颖、易用、有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性。很多时候,我们并不知道自己想要什么,直到某一天我们遇到了它。

更多精彩内容请关注:lab.uwa4d.com


导读

Unity引擎在其2017.3版本中引入了C# Job System、Burst Compiler以及Entity Component System,使得开发者能够利用CPU的多核硬件并行处理游戏逻辑,大大提升了游戏效率。今天给大家介绍的开源库是Unite Austin会议上展示的Nordeus Demo,该Demo通过利用上述技术实现了PC平台上数以万计角色同屏的效果。传送门
请输入图片描述
本文以该Demo为例,主要介绍了Unity三大功能的使用方法,并对该Demo的性能进行测评,显示了其在提升游戏逻辑执行效率的效果。


一、Job System & ECS & Burst

随着PC和移动平台CPU的核心数增加,如何在游戏开发中利用多核的计算能力提高运行效率成为游戏引擎需要解决的主要问题之一。当开发者能够将原本运行在单一线程的游戏逻辑任务分割成块,并且利用CPU的多核硬件并行处理时,就能大大提高游戏逻辑的执行效率。

Unity引擎在其2017.3版本中引入了C# Job System、Burst Compiler以及Entity Component System。其中,C# Job System为开发者提供了高性能的多线程编程框架,使得开发者能够利用CPU的多核硬件,让游戏运行的更加流畅。在该过程中,各个线程执行的时间(Thread Time)虽然没有发生变化,但并行执行使得实际执行任务的时间(Wall Time)减少了,如下图所示:
请输入图片描述
其中,三个任务(Job 1,Job 2,Job 3)放在了不同线程并行执行,总的线程执行时间是三个任务执行时间之和,即6.9ms。而由于利用了多线程,任务实际执行时间只有4.6ms。

除了使用C# Job System的多线程框架减少Wall Time,配合Burst Compiler和Entity Component System能够减少各线程的Thread Time。Entity Component System将游戏逻辑中使用的内存按照更适合CPU缓存的方式进行排列,通过提高访存时缓存的命中率提高代码执行效率。Burst Compiler则是通过对C#代码编译进行优化提高运行效率。结合两者以及C# Job System能够大大提高游戏逻辑的运行效率。


二、Nordeus Demo

C# Job System中最常用的一种多线程并行编程方式就是使用ParallelFor Jobs,如下文代码段所示:

 1public struct MinionFlightJob : IJobParallelFor
 2{
 3    public ComponentDataArray<UnitTransformData> flyingUnits;
 4    public ComponentDataArray<TextureAnimatorData> textureAnimators;
 5    public ComponentDataArray<RigidbodyData> rigidbodies;
 6    public ComponentDataArray<MinionData> minionData;
 7
 8    [ReadOnly]
 9    public NativeArray<RaycastHit> raycastHits;
10
11    [ReadOnly]
12    public float dt;
13
14    public void Execute(int index)
15    {
16        var rigidbody = rigidbodies[index];
17        var transform = flyingUnits[index];
18        var textureAnimator = textureAnimators[index];
19
20        textureAnimator.NewAnimationId = AnimationName.Falling;
21        transform.Position = transform.Position + rigidbody.Velocity * dt;
22        rigidbody.Velocity.y = rigidbody.Velocity.y + SimulationState.Gravity * dt;
23        // add damping
24
25        if (transform.Position.y < raycastHits[index].point.y)
26        {
27            var minion = minionData[index];
28            minion.Health = 0;
29            minionData[index] = minion;
30        }
31
32        rigidbodies[index] = rigidbody;
33        flyingUnits[index] = transform;
34        textureAnimators[index] = textureAnimator;
35    }
36}

上文代码段显示了Demo中,小怪被炸飞的表现效果代码,其中用到了IJobParallelFor类。ParallelFor Job主要用于处理大量相同逻辑的计算任务。这些计算任务被分配在不同的线程上,利用CPU的多核硬件进行并行计算。通常情况下,需要计算任务的数量远远大于线程数。因此,每个线程会处理多个计算任务。当某一线程完成的速度快于其他线程时,系统会自动从其他线程重新调配计算任务到该线程。

如果配合Entity Component System使用,则通常需要用到JobComponentSystem,如下文代码段所示:

 1public class FlightSystem : JobComponentSystem
 2{
 3    public struct FlyingUnits
 4    {
 5        [ReadOnly]
 6        public ComponentDataArray<FlyingData> flyingSelector;
 7        public ComponentDataArray<UnitTransformData> transforms;
 8        public ComponentDataArray<RigidbodyData> rigidbodies;
 9        public ComponentDataArray<MinionData> data;
10        public ComponentDataArray<TextureAnimatorData> animationData;
11
12        public int Length;
13    }
14
15    [Inject]
16    private FlyingUnits flyingUnits;
17
18    private NativeArray<RaycastHit> raycastHits;
19    private NativeArray<RaycastCommand> raycastCommands;
20
21    protected override void OnDestroyManager()
22    {
23        ...
24    }
25
26    protected override JobHandle OnUpdate(JobHandle inputDeps)
27    {
28        ...
29    }
30}

其中,关键之处在于存储Entity数据的方式与以往不同。在上图显示的Component System代码中,每一个Entity的数据被存储在一系列ComponentDataArray容器里。因此,当CPU需要访问这些数据时,由于其存储的连续性,能够更好的利用CPU与内存之间的缓存机制,提高执行效率。具体的代码实现细节,我们不再赘述,请读者参考Demo的C#脚本。

使用Burst Compiler则更加方便,如下图所示:
请输入图片描述
只需要在编辑器的Jobs菜单中,选择UseBurst Jobs即可。


三、性能测试

为了展示C# Job System、ECS以及Burst Compiler方案的运行效率,以及Burst Compiler带来的性能提升,我们比较了开启Use Burst Jobs之前与之后的性能数据以及场景中创建不同数量角色时的性能数据。由于Burst Compiler目前只能在Editor中使用,因此我们的测试数据均来自Editor运行结果。

测试平台:
系统: Win10
内存: 16G
CPU: Intel(R) Core(TM) i7-6700HQ CPU @2.6GHz
GPU: NVIDIA GeForce GTX 1070

1)开启Use Burst Jobs:

请输入图片描述
请输入图片描述

上图显示了Demo运行时的FPS以及Profiler截取的其中一帧函数耗时堆栈信息。由上图可知,耗时较高的依然是Camera.Render函数,即渲染逻辑。由于使用了多线程、ECS以及Burst Compiler的优化,与角色相关的更新逻辑只占用主线程中非常少的耗时(红色方框)。大量的计算任务过程被分配给了Worker线程,如下图所示:
请输入图片描述

利用大量Worker线程的并行计算结合ECS和Burst Compiler,角色逻辑计算耗时被大量压缩。同时,Demo在渲染角色时采用了GPU Instancing,因此渲染的耗时也被降低到5ms以内。

2)关闭Use Burst Jobs:
请输入图片描述
请输入图片描述

上图显示了不开启Use Burst Jobs时,Demo运行时的FPS以及Profiler截取的其中一帧函数耗时堆栈信息。其中,时间开销最高的函数是CommandSystem的UpdateFunction.Invoke(红色方框)。该System主要负责的是Demo中角色攻击指令相关的逻辑,例如:寻找最近地方单位等。由此可见,在不开启Use Burst Jobs时,游戏逻辑的耗时变得非常高。

请输入图片描述

上图显示了不开启Use Burst Jobs时,Timeline的显示结果。由上图可知,在不使用Burst Compiler进行代码优化的情况下,即使采用多线程进行并行计算以及ECS Cache友好的内存分布,仍然造成了各线程的计算耗时太高,从而导致每一帧总体的耗时过高。因此,Burst Compiler对于C#代码的优化起到非常重要的作用。

3)不同角色数量:
请输入图片描述

上图显示了在开启Use Burst Jobs时,创建不同角色数量对应的帧率。由上图可知,在测试PC上,创建65000个角色同屏依然能够达到33帧的效率。

综合上述Demo中使用的功能介绍和实验结果,可以发现:Burst Compiler对于代码的优化带来的性能提升是非常明显的。配合C# Job System以及ECS则更能将游戏逻辑的执行效率大量提升。同时,为了达到海量角色同屏的效果,在渲染和动画方面可以采用GPU Instancing以及Bake Animation的方式进一步减少渲染逻辑的CPU开销。

开源库传送门:
https://lab.uwa4d.com/lab/5bc550f804617c5805d4e9c1
今天的推荐就到这儿啦,或者它可直接使用,或者它需要您的润色,或者它启发了您的思路~请不要吝啬您的点赞和转发,让我们知道我们在做对的事。当然如果您可以留言给出宝贵的意见,我们会越做越好。


快用UWA Lab合辑Mark好项目!

请输入图片描述

  • 林城明 发表在 6天前 回复

    标题误导人,这个就是利用Job System,Burst Compiler做的一个Demo而已

    • UWA_Xin 发表在 5天前 回复

      并没有误导人,我们在文章有提到:由于Burst Compiler目前只能在Editor中使用,因此我们的测试数据均来自Editor运行结果。同时,Burst将会在后续的Unity版本中加入到Release功能中,而这篇文章是希望让大家看到Job System + ECS + Burst Compiler的运行效率。当然,我们同大家一样,都在期待Release版本中正常使用Burst Compiler的版本出现。