Boids算法

在Unity中实现了 Boids 算法,实现类鸟运动行为模拟。该模拟从单个 boid 遵循一组规中产生复杂的涌现行为。下面程序实现行为模拟,加ComputerShader运算优化。

主要规则
Boids 算法由 Craig Reynolds 于 1986 年创建,是用于描述“鸟类物体”的术语。在这个模拟中,复杂的涌现行为来自简单的规则:
● 分离:检测某个范围内的所有类鸟的位置,计算出质心,然后产生一个远离质心的速度
● 平行:检测某个范围内的所有类鸟的速度,计算出平均速度,然后产生一个与平均速度方向一致的速度
● 内聚:检测某个范围内(与分离规则的范围不同)的所有类鸟的位置,计算出质心,然后产生一个指向质心的速度

同时可以应用额外的规则来创建更复杂、更有趣的行为。在本工程中添加了3条额外的规则:

  1. boid会加速或减速以匹配目标速度
  2. 鸟群会被方框边缘排斥
  3. 鸟群会避开被标记障碍的物体
    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;
}
posted @ 2025-10-19 17:59  有间猫  阅读(23)  评论(0)    收藏  举报