鱼群模拟效果 | 骑虎鲸、抓海鱼!揭秘刺激海底游的背后技术
夏天哪能不玩水?逆水寒全新的南海海域已经在正式服全服上线啦!冲向南海,成为一个沉迷玩水的大侠!除了瑰丽美景,还有危险刺激的深海巨怪、南海龙女等神秘生物,为了让广大同门能够体验到最真实的海底世界,“会呼吸的江湖”要让鱼群也会“呼吸”!
随着技术的提升,现在游戏中模拟的场景越来越贴近于真实的自然环境,无论是天空、大地还是植被、海洋,都能让人身临其境。那除了这些“静止”的美,还有什么是一个真实自然环境中必不可少的哪?那自然就是各种各样的动物群落了,无论是天上的鸟群、地上的牛群还是海里的鱼群,都能为环境增添一种“动态”的美。
今天我们邀请了一位来自益达平台的资深引擎专家——明睿,来给大家分享怎么模拟海底鱼群的运动更多详细操作方法关注益达https://www.yarongsy.com
1. 鱼群的模拟
**个部分我们首先来谈谈动物集群模拟采用的算法。
集群模拟采用的基本算法是Boid算法,这个算法是1987年提出的,最早是用来模拟鸟群的运动,Boid并不是人名,它是Bird-oid object的缩写(-oid表示属于某一类的,比如humanoid)。这个算法将集群中每一个个体受到的力分为了三个部分:Cohesion凝聚力、Alignment对齐力和Separation分离力。下面为了方便解释,我都将用鱼群进行举例。
首先来介绍下Cohesion(凝聚力),鱼群中的每条鱼会受到一个向周围一定范围内其他鱼的平均位置去靠拢的力。大家注意下面的图中,绿色的三角形表示当前正在计算的鱼,灰色的圈表示我们只关心这个范围内的其他鱼,绿色的点表示这些鱼的平均位置,红色的箭头说明当前鱼会向这个平均位置去靠拢。Cohesion让整个集群向一个中心聚拢。
然后是Alignment(对齐力),集群中的个体会向周围一定范围内其他个体的平均移动方向去靠近。下面这张图中,益达平台仍然只关心灰色圈中的鱼,红色的箭头表示这些鱼的平均移动方向,当前的鱼就会像这一个平均移动方向靠近。Alignment让整个集群向一个方向运动。

最后是Separation(分离力),周围一定范围内的每条鱼都会产生一个推开远离的力,将这些力按照距离为权重叠加在一起就得到了Separation。这个力让集群的个体之间有合适的距离而不会黏在一起。

大家应该可以注意到,Boid算法的三个基本力都是在当前个体的一定范围内去进行计算的,这个距离被称为邻近距离,是Boid算法中一个非常重要的参量,它表明了Boid算法受到的刺激不是一下子就影响到集群中的所有个体的,而是一小圈一小圈扩散出去的,像下面右图,假设每个黑色的三角形表示一只沙丁鱼,红色的大箭头表示一只鲨鱼来了,那么首先受到影响开始逃散的只是最接近鲨鱼的一小个红圈,但由于每条鱼又处于不同的小圈中,这种影响就一层一层扩散出去,直到到达整个鱼群。更多详细操作方法关注益达https://www.yarongsy.com

下面给大家看一个具体的简单实现:

首先我们对于每条鱼都定义Boid算法的三个基本力,separation、alignment初始化为0向量,cohesion初始化为鱼群的默认中心位置。

然后开始跑大循环,对于鱼群中的每条鱼,都判断它是否是当前鱼的邻近鱼,如果是,就加上separation将它推开远离的力,alignment和它方向保持一致的力,以及cohesion向平均中心靠近的力。这里可以注意到,如果当前鱼的周围没有任何一只别的鱼,它的separation和alignment都是0,但cohesion仍然还是在的,它仍然会向默认的中心位置去靠近。更多详细操作方法关注益达https://www.yarongsy.com

循环跑完之后,除一下邻近数量,得到平均的移动方向和平均的中心位置。再把这三个力加在一起,就得到了Boid算法计算出来的下一时刻的方向。

这个方向并不会立刻就更新上去,而是和当前的方向之间做一个插值,插值的权重是我们定义的每条鱼的转向速度。有了移动方向,再乘一下移动速度,就能得到下一时刻的位置信息。
2. 鱼群的改进
用Boid算法模拟出来的鱼群类似下图,能够看出来有点鱼群的感觉了,但运动的模式有些固定,最终的整体形状也都是一个球状,看起来有些boring,本节就介绍一些针对Boid算法的改进。
2.1形状
【大漩涡鱼群】
首先是整体形状上的,美术同学提出的**点需求就是想要一个中间有大空洞的大漩涡鱼群,这种样子的鱼群有个专用的术语叫做海狼风暴。

那我们首先要解决的就是鱼群的漩涡效果。当我们在初始化一个鱼群的时候,如果给每条鱼的移动方向都是随机的,那最终得到的大概率是下图这样一坨向一个方向移动的鱼群,也有小概率得到一个漩涡鱼群,那么是什么因素导致这两种不同的稳定状态那?经过摸索,发现鱼的转向速度和移动速度会很大影响到最终的稳定状态。


当鱼的转向速度特别大时,也就是说每条鱼的移动方向会很快趋向于我们用Boid算法更新出来的方向,这时如果有一个方向纯随机的初始化状态,多半在某一个方向上的鱼数量是稍微占据优势的。由于alignment对齐力的影响,这优势方向周围的鱼用Boid算法计算出来的方向也会向这样一个优势方向靠近,而又因为每条鱼的转向速度特别大,这些鱼实际的运动方向很快就更新成了Boid算法计算的方向,这个优势方向上的鱼就越来越多,直到整个鱼群都向一个方向运动。

而当鱼的移动速度特别大时,整个鱼群会快聚拢成一坨,每条鱼的移动方向偏差也很快被抹去掉。

所以只要适当减小鱼的转向速度和移动速度,就能得到一个漩涡状的鱼群,而当达到了漩涡形状,你再增加它的移动速度和转向速度,它仍然能保持这样一个形状,如果按照博弈论的说法,我们可以认为Boid集群最终的稳定形态有两个Nash均衡点,至于落在哪一个均衡点其实取决于我们初态的设置,就像下面这一个台子,我们从上面任意一个位置扔下一个球,它最终静止于左端还是静止于右端其实取决于这个平台的尖角分布情况。

有了这样一个漩涡状的鱼群,我们肯定希望它的形状是能够保留的,所以还加入了快照功能,相当于我们能保存这一时刻鱼群中每条鱼的位置和方向信息,下次初始化就直接拿这份数据去用。

有了这样一个漩涡,还是不太够的,因为默认的漩涡就是下图这样1:1有点矮胖的样子,我们实际想要的效果是更为细长的。
那么是什么因素让整个鱼群取向于一个球那,这一定就是三种基本力里的Cohesion凝聚力了,这个力让整个鱼群向一个中心聚拢,大家可以想象万有引力将人造卫星拉向一个个圆形轨道。如果我们想要形状变得更加细长,就需要对Cohesion的竖直分量动下手脚,简单来说,我们直接在y分量上乘上一个衰减系数,这样就能得到一个更为细长的鱼群,就像如果万有引力在某一个方向上有衰弱,那卫星的轨道也会相应地发生拉伸。更多详细操作方法关注益达https://www.yarongsy.com

有了这个形状还是不太够的,因为我们需要中间有个大空洞,甚至还要站人。

要做到这一点,有两种很简单的方式,**种就是直接减小每条鱼的转向速度,大家如果看过大货车过弯,就应该知道,如果你的转向非常困难,最后你转过的弯一定是很大的。还有一种方法就是直接增大每条鱼的移动速度,如果你以120km/h漂移下山,你转过的弯一定是不会小的。
但这两种方式都会实际修改到每条鱼的表现效果,我最终采用的是另外一种方法,增大alignment对齐力的权重。这样做的原理是什么那?我们看看下面的两个圆。下面两个圆中,如果在左边这个半径很小的圆上,我们取相邻一定距离的两个点,这两个点在圆周上的切线方向差异一定是比较大的;而在右边这个半径很大的圆上,我们取相隔相同距离的两个点,它们在圆周切线方向上的差异就相对较小。而alignment对齐力的效果就是要让周围的鱼的运动方向趋向一致,所以只要增大alignment对齐力的影响就能将鱼的运动从小圆推向大圆。更多详细操作方法关注益达游戏平台https://www.yarongsy.com
经过这些调整,我们最终就能得到下面这样一个稍微像样的海狼风暴效果了。
【包围盒】
基于形状改进的第二点是包围盒效果,这个效果类似水族馆里的水族箱;除了形状上的限制,包围盒还有很重要的一点功能,它给出了每个鱼群的边界情况。正常情况下,我们是没法知道一个鱼群的具体空间分布的,给你一堆鱼群的参数:移动速度、转向速度、cohesionlignmentseparation权重,让你计算这个鱼群可能的活动空间,这是没办法做到的,除非你每一时刻都遍历整个鱼群中的每一条鱼。有了包围盒之后,对后面的裁剪也很有帮助。
【路点功能】
针对形状改进的最后一点是路点功能。
最简单的版本中我们直接在空间上撒一连串的目标点,鱼群中的鱼除了受到Boid算法三种基本力的影响,还会受到一个向目标点靠近的力,达到了一号路点一定距离内,就会向二号路点出发。


更进一步,我们撒多套轨迹点,每条鱼就在自己的那套轨迹上运动。
再进一步,我们可以定时刷新轨迹点,来让整个鱼群的运动更加多变。

2.2交互
【避障】
下面进入鱼群改进的第二部分交互,首先我们是要有一条底裤的,玩家和鱼之间要有一个最基本的防穿模避障,我采用的是最简单的球形避障,当鱼进入障碍球后,就会受到一个远离的力。可以看到上面的效果演示中,鱼的表现有点太云淡风轻了,我们实际想要的效果应该是下面右边这种。所以当鱼进入障碍圈后,我还会增大它的移动速度和转向速度。
但由于只是最简单的球形避障,从下面的视频中可以看到(密集恐惧症警告),鱼的表现还是有点呆,它要实际进到这个障碍球之后才会突然抬头躲开。

但由于只是最简单的球形避障,从下面的视频中可以看到(密集恐惧症警告),鱼的表现还是有点呆,它要实际进到这个障碍球之后才会突然抬头躲开。

所以我还加入了一个简单的VO避障,当鱼发现沿着当前轨迹运动会撞上障碍球,它就会逐渐受到一个切线方向避开的力。

【亲近】
下面介绍交互部分的第二点——亲近效果。很直观的要实现亲近效果,我们就让鱼进入目标物一定距离内时,给它一个向目标物靠近的力。


但要注意的是随着离目标物越近,目标力的权重就应该越低,不然就会出现下图右边这种黏在一起的情况。

恐惧传播效应最早是用来研究牧羊犬和羊群关系的,当牧羊犬靠近后,羊群会聚拢、远离牧羊犬并加速。下图的白色点表示羊群中的杨,黑色的点表示牧羊犬,可以发现当牧羊犬靠近后,右图中的羊就聚拢在一起了。更多详细操作方法关注益达游戏平台https://www.yarongsy.com
如果放到Boid算法当中,
聚拢 -->Cohesion权重加大
远离牧羊犬 -->产生一个远离的力
加速 -->增大移动速度和转向速度

具体来讲,当威胁源靠近时,我们会计算每条鱼Emotion State,它的情绪情况,如果它离威胁源越近,它的恐惧情绪就越明显。之后我们再根据Emotion State来增大Cohesion权重、向远离的方向运动并获得移速加成。
有了基本的恐惧效应,我们还能加入恐惧传播效果,听上去比较高端,实际也挺简单。当我们计算每条鱼的Emotion State的时候,不再只仅仅却取决于它和恐惧源的位置,还会考虑到周围鱼的恐惧情绪,在这两者之间做一个插值。

2.3外观
下面是鱼群改进的最后一个部分,鱼的外观改进。这部分主要是一些随机值的引入,像贴图随机、大小随机、移动速度随机,这些随机值的位置和结构我放到下一个板块来介绍。
【小总结】
针对鱼群的改进,主要需要理解Boid算法三种基本力的效果,再在其基础上小小动些手脚,添加一些辅助力,就能让整个鱼群的效果发生很大的变化,出现一些“惊喜”,当然,惊的时候多一点,喜的时候少一点就是了。
3. 鱼群的接入
鱼群的接入主要有两块,鱼群的算法和鱼群的绘制。
对于鱼群的算法,我们既可以放在GPU用ComputeShader跑,也可以放在CPU用c++跑。对于鱼群的绘制,就是一个简单的instance化过程。
最理想的状态,我们希望整套流程都能在GPU上进行,先用ComputeShader跑Boid算法,然后把跑好的数据直接喂给实例化阶段。但这里面有个问题,骨骼动画。
下面简单介绍下骨骼动画。有基础的同学可以直接跳过这段。
【【骨骼动画是一种常用的动画技术。对于一个模型,我们先定义一套骨架,骨架由一系列关节组成,关节之间有父子关系,每一个关节表示了一个坐标的转换矩阵,这个转换矩阵由Translation(位移)、Rotation(旋转)、Scale(放缩)组成。


每个关节表示的转换矩阵都是基于父关节坐标系的,也就是说上图2号关节表示的变换矩阵Matrix2是基于1号关节坐标系的,1号的又是基于0号的,0号的又是基于自身模型坐标系的。这样我们要计算任意一个关节基于模型坐标系的转换矩阵,就只需要从根节点,一层一层矩阵乘下来,直到当前关节的矩阵。
有了这样一套骨骼,那又怎么和我们实际的模型关联上那?动画师会定义模型上每一个顶点受到哪几个关节的影响,每个关节影响的权重又分别是多少,这样当关节运动时,受到这个关节影响的模型顶点也会跟着运动。这个过程就是蒙皮,就像我们手上的皮肤会受到腕关节的影响,当手腕转动时,手上的皮肤也会跟着动。

有了骨骼,动画师就能通过各个关节的变换,来实现一个动画。而有了关节的父子关系,我们就能得到每个关节基于模型坐标系的变换矩阵,这些关节的矩阵常常组成一个“调色盘”扔到顶点着色器中。
有了蒙皮,在顶点着色器中我们就能知道每个顶点受到哪几个关节的变换矩阵影响,每个关节的影响权重又是多少,这样就能得到顶点受到关节影响的变换矩阵,从而得到骨骼动画后的坐标位置。】】
下面继续回到鱼群的绘制,如果鱼的游动也采用骨骼动画,因为现有动画系统大多在CPU上进行(因为还有IK、Ragdoll这种更为复杂的机制),这就造成了没法整套流程都放在GPU上跑,要解决这一点,就需要在GPU上直接实现鱼的动画。**种方案就是GPU Skinning,将骨骼动画搬到GPU上实现,但即使这样,也会牺牲显存来存储动画数据,而且由于鱼群的数量一般很大,一个3000只的鱼群,一条鱼10个关节,每帧就需要更新30000个关节矩阵信息。并且鱼的动画并不像人物那么细腻敏感,对于一个大鱼群就更难以注意每条鱼的细节游动了。所以对于鱼的游动动画,参考ABZU中的实现,是用类似模拟简单旗帜摆动的方法,用一些基本的正余弦动画在顶点着色器去合成。更多详细操作方法关注益达游戏平台https://www.yarongsy.com
直接看下面这四种基本运动的表达式肯定有些晕乎,我觉得最好的方法是大家先在脑海中想清楚每种运动是怎么摆怎么扭的,能清晰浮现出画面,再根据扭动的画面反推出在xyz轴上的运动。

有了这四种基本运动,只要将它们按照一定权重混合在一起,就得到了一条活蹦乱跳的小鱼。
需要注意的一点是鱼的摆动主要集中在尾部,所以越靠近尾巴鱼的③蛇形扭动④绕脊旋转就要越强,越靠近头部这2个动画就越弱。这四种动画的振动频率是和我们传入的鱼游动速度有关的,鱼游得越快,合成出来的动画就摆动得越快。
有了鱼的动画,整套GPU流程就比较简单了。首先我们在ComputeShader上跑Boid算法,跑好的数据从UAV buffer拷贝到SRV中,留给实例化阶段使用。到了真正的实例化阶段,我们首先在VertexShader上跑上面的游动动画合成,然后拿上ComputeShader算好的数据,更新每条鱼的位置和方向信息,这样整个鱼群就绘制好了。下面是益达平台InstanceBuffer中的数据结构,除了位置和方向信息,剩下的2个32位uint用来存一些随机值,像贴图id随机、scale大小随机。


4. 鱼群的优化
最后谈一谈针对鱼群的性能优化问题,这里主要说一下裁剪方面。因为鱼群的流程都在GPU上进行,我们是不知道鱼群中每只鱼的具体位置信息的,除非map回CPU来一个一个判断,如果一个3000只的鱼群,就要判断3000次,加上map的开销不是太行。所以在这套流程当中裁剪是基于包围盒以鱼群为单位的,如果包围盒太远看不见,就直接把这个包围盒代表的鱼群隐藏掉。这里鱼群的算法更新和鱼群的绘制是同时终止的。当然,如果你是在CPU上实现Boid算法,你也可以加入划分格子的方法,将一整个鱼群空间拆分开,从而降低Boid算法n平方的复杂度。本篇关于鱼群的分享就到这里了,谢谢你的阅读!
如果您对本站有任何建议,欢迎您提出来!本站部分信息来源于网络,如果侵犯了您权益,请联系我们删除!
下一篇:没有了!
微信客服