Boids算法
在Unity中实现了 Boids 算法,实现类鸟运动行为模拟。该模拟从单个 boid 遵循一组规中产生复杂的涌现行为。下面程序实现行为模拟,加ComputerShader运算优化。
主要规则
Boids 算法由 Craig Reynolds 于 1986 年创建,是用于描述“鸟类物体”的术语。在这个模拟中,复杂的涌现行为来自简单的规则:
● 分离:检测某个范围内的所有类鸟的位置,计算出质心,然后产生一个远离质心的速度
● 平行:检测某个范围内的所有类鸟的速度,计算出平均速度,然后产生一个与平均速度方向一致的速度
● 内聚:检测某个范围内(与分离规则的范围不同)的所有类鸟的位置,计算出质心,然后产生一个指向质心的速度
同时可以应用额外的规则来创建更复杂、更有趣的行为。在本工程中添加了3条额外的规则:
- boid会加速或减速以匹配目标速度
- 鸟群会被方框边缘排斥
- 鸟群会避开被标记障碍的物体
Boids 模型代码
Boid类
变量avgFlockHeading、avgAvoidanceHeading、centreOfFlockmates是三条基本规则的核心数据。
/// <summary>
/// 当前速度
/// </summary>
Vector3 velocity;
// To update:
/// <summary>
/// 鸟群平均航向
/// </summary>
public Vector3 avgFlockHeading;
/// <summary>
/// 鸟群平均躲避速度
/// </summary>
public Vector3 avgAvoidanceHeading;
/// <summary>
/// 鸟群中心点
/// </summary>
public Vector3 centreOfFlockmates;
/// <summary>
/// 鸟群数量
/// </summary>
public int numPerceivedFlockmates;
该方法应用所有规则并及时推进 boids 模型。每个规则的距离和权重在参数中定义。
public void UpdateBoid () {
// 当前帧检测所产生的速度
Vector3 acceleration = Vector3.zero;
if (target != null) {
// 计算目标速度,方向*飞向目标的速度
Vector3 offsetToTarget = (target.position - position);
acceleration = SteerTowards (offsetToTarget) * settings.targetWeight;
}
// 如果观察半径内有鸟,则构成鸟群
if (numPerceivedFlockmates != 0) {
// 获取偏移值
Vector3 offsetToFlockmatesCentre = (centreOfFlockmates - position);
// 获取对齐力
var alignmentForce = SteerTowards (avgFlockHeading) * settings.alignWeight;
// 获取内聚力
var cohesionForce = SteerTowards (offsetToFlockmatesCentre) * settings.cohesionWeight;
// 获取分离力
var seperationForce = SteerTowards (avgAvoidanceHeading) * settings.seperateWeight;
// 计算帧当前速度
acceleration += alignmentForce;
acceleration += cohesionForce;
acceleration += seperationForce;
}
// 当前飞向是否碰撞障碍
if (IsHeadingForCollision ()) {
// 获取避免碰撞分享
Vector3 collisionAvoidDir = ObstacleRays ();
// 计算避免碰撞的力
Vector3 collisionAvoidForce = SteerTowards (collisionAvoidDir) * settings.avoidCollisionWeight;
acceleration += collisionAvoidForce;
}
// 当前帧速度
var frameVelocity = velocity + acceleration * Time.deltaTime;
float speed = frameVelocity.magnitude;
Vector3 dir = frameVelocity / speed;
// 限制最大速度
speed = Mathf.Clamp (speed, settings.minSpeed, settings.maxSpeed);
velocity = dir * speed;
forward = dir;
}
private void LateUpdate()
{
// 修改当前帧位置和朝向
cachedTransform.position += velocity * Time.deltaTime;
cachedTransform.forward = forward;
position = cachedTransform.position;
}
bool IsHeadingForCollision () {
// 检测是否碰撞
RaycastHit hit;
if (Physics.SphereCast (position, settings.boundsRadius, forward, out hit, settings.collisionAvoidDst, settings.obstacleMask)) {
return true;
} else { }
return false;
}
Vector3 ObstacleRays () {
// 存储了n个方向
Vector3[] rayDirections = BoidHelper.directions;
// 检测那个分享可以避免碰撞
for (int i = 0; i < rayDirections.Length; i++) {
Vector3 dir = cachedTransform.TransformDirection (rayDirections[i]);
Ray ray = new Ray (position, dir);
if (!Physics.SphereCast (ray, settings.boundsRadius, settings.collisionAvoidDst, settings.obstacleMask)) {
return dir;
}
}
return forward;
}
Vector3 SteerTowards (Vector3 vector) {
// 速度与当前阶段计算的速度相减,得到一个合速度
Vector3 v = vector.normalized * settings.maxSpeed - velocity;
return Vector3.ClampMagnitude (v, settings.maxSteerForce);
}
Boid管理类
计算各boid的平行、分离、内聚速度。
void Update () {
if (boids != null) {
int numBoids = boids.Length;
// 设置所有的boid数据
var boidData = new BoidData[numBoids];
for (int i = 0; i < boids.Length; i++) {
boidData[i].position = boids[i].position;
boidData[i].direction = boids[i].forward;
}
// 创建一个computerbuff,并添加boid数据
var boidBuffer = new ComputeBuffer (numBoids, BoidData.Size);
boidBuffer.SetData (boidData);
compute.SetBuffer (0, "boids", boidBuffer);
compute.SetInt ("numBoids", boids.Length);
compute.SetFloat ("viewRadius", settings.perceptionRadius);
compute.SetFloat ("avoidRadius", settings.avoidanceRadius);
// 设置computershader 数据
int threadGroups = Mathf.CeilToInt (numBoids / (float) threadGroupSize);
compute.Dispatch (0, threadGroups, 1, 1);
// 从computerbuff中获取数据
boidBuffer.GetData (boidData);
// 设置回数据 并更新boid
for (int i = 0; i < boids.Length; i++) {
boids[i].avgFlockHeading = boidData[i].flockHeading;
boids[i].centreOfFlockmates = boidData[i].flockCentre;
boids[i].avgAvoidanceHeading = boidData[i].avoidanceHeading;
boids[i].numPerceivedFlockmates = boidData[i].numFlockmates;
boids[i].UpdateBoid ();
}
boidBuffer.Release ();
}
}
/// <summary>
/// ComputerShader需要的数据
/// </summary>
public struct BoidData {
public Vector3 position;
public Vector3 direction;
public Vector3 flockHeading;
public Vector3 flockCentre;
public Vector3 avoidanceHeading;
public int numFlockmates;
public static int Size {
get {
return sizeof (float) * 3 * 5 + sizeof (int);
}
}
}
ComputerShader,每个线程计算1个boid数据
#pragma kernel CSMain
static const int threadGroupSize = 1024;
struct Boid {
float3 position;
float3 direction;
float3 flockHeading;
float3 flockCentre;
float3 separationHeading;
int numFlockmates;
};
RWStructuredBuffer<Boid> boids;
int numBoids;
float viewRadius;
float avoidRadius;
[numthreads(threadGroupSize,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
// 计算boid感知半径是否存在其他鸟,后计算鸟群中心点、该鸟所在鸟群数量和鸟群的合方向
float3 centerSize;
for (int indexB = 0; indexB < numBoids; indexB ++) {
if (id.x != indexB) {
Boid boidB = boids[indexB];
float3 offset = boidB.position - boids[id.x].position;
float sqrDst = offset.x * offset.x + offset.y * offset.y + offset.z * offset.z;
if (sqrDst < viewRadius * viewRadius) {
boids[id.x].numFlockmates += 1;
boids[id.x].flockHeading += boidB.direction;
centerSize += boidB.position;
if (sqrDst < avoidRadius * avoidRadius) {
boids[id.x].separationHeading -= offset / sqrDst;
}
}
}
}
boids[id.x].flockCentre = centerSize / boids[id.x].numFlockmates;
}
BoidSetting
变量的大小关系,是所偏向的力,代表着不同的规则,存在不同的表现。
// Settings
public float minSpeed = 2;
public float maxSpeed = 5;
// 感知半径
public float perceptionRadius = 2.5f;
// 躲避boid半径
public float avoidanceRadius = 1;
// 最大转向力
public float maxSteerForce = 3;
// 对齐力
public float alignWeight = 1;
// 内聚力
public float cohesionWeight = 1;
// 分离力
public float seperateWeight = 1;
// 偏向目标力
public float targetWeight = 1;
[Header ("Collisions")]
public LayerMask obstacleMask;
public float boundsRadius = .27f;
public float avoidCollisionWeight = 10;
public float collisionAvoidDst = 5;
}

浙公网安备 33010602011771号