UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法

前言

最近制作的一个需求类似于一个吸尘器,他在地上移动的同时会把周围的丧尸吸到自己这边,并且要不断地缩小,到达身边之后没有。参考视频里那只是个灰尘,但是部门具体需求是吸入丧尸,同时这个丧尸还要执行每tick的随机徘徊任务。最开始做了一版用了character作为丧尸的主体,自认为使用character会简化开发工作,毕竟每次往场景里拖几个或者用生成器多生成几个就OK了。但是场景中大概存在2000个的时候已经需要眨眼补帧了,差不多运行起来只能达到10帧或者更低。并且在吸入的过程中使用的方法是Timeline+Lerp的形式,在线程里阻塞的要死,不知道人更多的时候线程会不会直接爆掉。组长提醒换了个方法,改成使用Instance Mesh+顶点动画+Wander徘徊算法的方法去实现。下面着重讲一下InstanceMesh和Wander徘徊。

InstanceMesh的性能问题

几何实例化是现代计算机图形学中的一个概念,是一次在场景中渲染同一网格的多个副本的做法,也就是所谓的“一次提交,多次渲染”。表现在UE4的指标就是DrawCall的次数。对于InstanceMesh来说,不论你生成多少个,只要是同一套材质和网格体,只会使用一次DrawCall。也就是说,假如我要在场景中生成10000个立方体,我一个一个拖或者使用循环Spawn一万个,DrawCall的指令都被调用了一万次。但是使用InstanceMesh只会调用一次DrawCall指令,相当于节约了9999次。

参考这个链接的数据:zwcloud

空场景下DrawCall指令数:

UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第1张图片

生成1000个A类型石头和1000个B类型石头后的DrawCall执行数:

UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第2张图片

 仅从4变成了6,增加了2.

 除此之外还有LOD计算、剔除、顶点信息和shader的问题,文章里都有一一提到。

但是这里还存在一个问题:目前ISM对用户操作单个实例的支持很有限,那么是如何做到吸入每个实例、并且能让每个实例执行徘徊算法呢?

操作单个InstanceMesh实例

先看一下ISM在UE4中的表现形式(数据存储):

UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第3张图片

 在ISM/HISM组件下找到Instances,并且注意官方已经让你小心性能了,点开之后会显示你创建的InstanceMesh们以及他们的Transform。每个Instance拥有自己的Index(索引/指针)用于操作单个实例。

首先构建一个300*300,共9万个的InstanceMesh:

UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第4张图片

之后我们的思路很清晰,因为InstanceMeshComponent提供了Overlapping sphere/box的方法,能够获取在指定box/sphere重叠下的InstanceIndex,因此我们理所应当的使用他返回的数组对重叠的指定ISM进行位移、缩放、删除操作。蓝图如下:
UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第5张图片

这里先说一下GetInstancesOverlappingSphere这个数组。他是一个队列结构,先重叠的会先进入数组,后进入的会排在数组后边。同时他的返回数组还拥有Set的性质,也就是不会出现重复的Index。所以我们只需要设置好这个节点的参数,之后用ForEachLoop进行遍历,对每一个重叠的元素每Tick进行Vinterp进行位移和缩小,最后update给指定Instance的Transform即可,这块很简单。

接下来着重说一下内存管理,为什么我的内存管理要写成这样。首先,移动一个物体,不管你是使用施加速度也好,Timeline+Lerp也好,Interp也好,他们都要有时间,比如位移几秒才能到达人物的位置。相比于删除操作,位移操作和删除操作是完全异步的,我必须先位移到指定位置或缩小到0之后才能删除。同时,Overlap返回的Index数组不能堆积,因为只要这边数据堆积必然造成卡顿,也不能检测到Overlap就立马删除,这样就没有位移和缩小的过程了。之后为什么要写进来300个删掉前50个。RemoveInstance(删除单个Instance)这个节点,假设你传入的是一个空值,他会直接把前边Instance数组的index0删掉,也就是说只要Overlap数组空了,在每tick的值传过去就是空的,就会不断地删掉Instance数组的头元素,过一会你的Instance就都不见了。也就是说我可以一直保证我能看见位移和缩小的过程,同时保证Overlap数组数量维持在250-300个,也不会删错Instance数组中的元素,达到一个完全的异步操作而不适用多线程。

这里RemoveInstance还有一个性质,假如你删除了index0,那么之前的index1会变成删除后的index0,有自动扩容的功能。同时在Instance数组变化之后也会同步变化Overlap数组,这一点不知道UE是怎么实现的,要去查一下源码。

至此,吸入和位移操作就都完成了。

Wander徘徊

Wander徘徊算法在我个人理解里,是优化了随机取点之后SetLocation的突兀性。如果用NavBounds+GetRandomReachablePosition节点人物很有可能出现突然回头或左转后突然右转的效果。这对于徘徊的人/羊/生物都是不合常理的。

关于Wander徘徊的解释在这个链接:22 操控行为——Wander(徘徊) - 豆丁网

简单来说,Wander徘徊有以下几个量:徘徊半径WanderRadius、徘徊距离WanderDistance、还有一个每tick位移的大小。在开始的时候要在物体的本地坐标系下以自己为中心绘制一个圆(或者表示一个圆,都可以,不必非得绘制),之后在圆上随便取一个起点,之后在这个起点上随机加偏移量(真的是随机,随便加减,但是要考虑你的移动速度和tick抖动问题),之后为了防止这个加完的点在圆外边,要把他投影到圆周上,具体操作是将其归一化后乘徘徊半径大小。这就是你下一tick要到达的点的本地坐标。

下一步我们要把他转换到世界坐标系下,同时加上徘徊距离(WanderDistance),之后计算每帧的移动位置Set回去就可以了。这样可以避免角色在随机取点位移时造成的突然回头或转向问题,蓝图如下:

UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第6张图片

首先将每个Instance的本地坐标下的第一个起点添加到一个Map中,但是要自己定一个前进方向,因为Instance不是Actor,不能获取到ForwardVector,这里自己选一个方向的单位坐标就行了。 

 UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第7张图片

 之后是一个随机徘徊函数,最后计算出来的是世界坐标下的位移点坐标。将本地坐标系下的点随机加偏移量,之后投影到圆周上,在转换到世界坐标,加上Wander距离,设置到一个新的Map中。这两个Map都是Index和坐标的Key-Value关系。

UE4开发记录-集群操作的性能问题&InstanceMesh&随机徘徊算法_第8张图片

最后每Tick更新对应Instance的Transform,直接把最后世界坐标系下的Map找到对应index设置就好了。效果:

 

 

 结语

虽然InstanceMesh确实节省了很多渲染时的性能,但对于想要操作其中单个的需求还是有些棘手。还好UE没给挖什么坑,要是我Remove了之后Overlap数组里没更新,现在已经就裂开了:)

你可能感兴趣的:(UE4开发记录,ue4,算法)