Стаття Роєвий інтелект · ≈ 9 хв читання

Флокінг у 3D: Boids з перешкодами та лідером

Три оригінальні правила Рейнольдса були створені для плоскої площини. Перенесіть ту саму зграю у повне 3D — крізь систему печер, навколо хмарочоса, за мігруючим лідером — і кожна частина алгоритму, від пошуку сусідів до орієнтації, потребує додаткової уваги через новий вимір.

Коротко: Перехід boids з 2D у 3D додає третю вісь швидкості, вимагає кватерніонної орієнтації з креном та уникнення перешкод через промені чи sphere-cast, а також силу слідування за лідером, щоб зграя огинала перешкоди й тримала курс за лідером — усе це комбінується через пріоритетні правила, щоб уникнення перешкод завжди переважало соціальні сили.

1. Від 2D до 3D: додатковий ступінь свободи

Оригінальна демонстрація Boids 1986 року працювала на плоскій площині. Додавання третього просторового виміру — це більше, ніж "просто додати z". Кожен запит сусідів тепер має шукати в об'ємі простору замість площі, тобто рівномірна сітка перевіряє 27 сусідніх комірок (3×3×3) замість 9, а орієнтація — вже не один кут напряму θ, а повне 3D-обертання.

Птахи, риби та дрони також кренять під час поворотів — вони обертаються навколо напряму руху, а не просто повертаються до нього. Щоб це відобразити, потрібні кути Ейлера (прості, але страждають від шарнірного замка) або кватерніони (числово стабільні, стандартний вибір у промислових 3D-рушіях).

Ті самі три правила все ще застосовуються

Розлучення, вирівнювання і зближення переносяться незмінними як 3D-векторні операції — див. нашу статтю про 2D Boids щодо базових правил. Ця стаття зосереджується на тому, що дійсно нове у 3D: перешкодах, лідері та орієнтації.

2. Уникнення перешкод за допомогою променів

Статична геометрія — стіни печер, будівлі, рельєф — має відхиляти зграю, не руйнуючи її цілісності. Стандартна техніка випромінює один або кілька зондувальних променів вперед від кожного boid:

Зондування перешкод ray = pos[i] + t · normalize(vel[i]), t ∈ [0, LOOKAHEAD]

hit = intersect(ray, obstacles)
if hit: v_avoid = reflect(vel[i], hit.normal) · weight_avoid
  • Один прямий промінь — дешевий, але пропускає перешкоди, які boid зачепить збоку.
  • Віяло з трьох променів (центральний + два зміщені на невеликий кут) — виявляє близькі промахи за помірну додаткову ціну.
  • Sphere-cast — прокидає сферу власного радіуса boid уздовж шляху, гарантуючи, що жодна частина тіла не зачепить перешкоду.

Самі перешкоди майже завжди спрощують до сфер чи капсул для швидких перевірок перетину, навіть коли візуальний меш складний — фізичний колайдер і рендерний меш зберігаються окремо, як в ігрових рушіях.

3. Слідування за лідером

Щоб зграя слідувала конкретним маршрутом — міграція через гірський хребет, супровід персонажа гравця — один або кілька boids призначаються лідерами. Лідери рухаються за незалежно скриптованим шляхом (сплайн Безьє чи Catmull-Rom, або пряме керування гравцем/ШІ) і виключені зі стандартних сил флокінгу.

Сила слідування за лідером v_leader(i) = (pos[leader] − offset − pos[i]) · w_leader

де offset розташовує послідовників трохи позаду і збоку від лідера (уникаючи "конга-лінії" boids, що вишиковуються прямо за ним), а w_leader зазвичай важить більше, ніж звичайне зближення.

Послідовники все ще застосовують розлучення та вирівнювання між собою, тож зграя зберігає свою органічну, "дихаючу" форму, відстежуючи загальну траєкторію лідера — це техніка, що лежить в основі більшості поведінок "слідуй за птахом-провідником" чи супроводу в іграх.

4. Комбінування правил і пріоритети

З п'ятьма чи шістьма конкуруючими силами (розлучення, вирівнювання, зближення, уникнення перешкод, слідування за лідером, обмеження меж) наївне сумування може дати сумарну силу, яка все ще заганяє boid прямо у стіну, якщо зближення достатньо сильне. Дві надійні стратегії комбінування:

  • Зважена сума з пріоритетними вагами — уникнення перешкод отримує вагу на порядок більшу за соціальні правила, тож завжди домінує, коли активне.
  • Пріоритетне перекриття — правила оцінюються по порядку; якщо уникнення перешкод дає ненульову силу вище порогу, використовуємо лише її, пропускаючи решту правил на цьому тику.

Пріоритетне перекриття передбачуваніше і саме його на практиці використовують більшість промислових систем флокінгу (включно з симуляцією натовпу в ігрових рушіях), бо гарантує, що жорсткі обмеження (ніколи не вдарятись об стіну) не можуть бути розбавлені м'якими перевагами (триматися ближче до зграї).

5. Крен і кватерніонна орієнтація

Візуальна орієнтація boid має відстежувати і куди він рухається (вектор вперед = нормалізована швидкість), і наскільки різко він повертає (крен пропорційний швидкості повороту). Стандартна побудова:

Орієнтація зі швидкості forward = normalize(vel[i])
right = normalize(cross(worldUp, forward))
up = cross(forward, right)
bankAngle = clamp(turnRate · BANK_FACTOR, -MAX_BANK, MAX_BANK)
orientation = lookRotation(forward, up) · rotateAroundForward(bankAngle)

Подання результату як кватерніона та використання сферичної лінійної інтерполяції (slerp) між кадрами уникає розривів і шарнірного замка, що псують інтерполяцію сирих кутів Ейлера, даючи плавний крен навіть під час різких маневрів.

6. Просторовий поділ у 3D

Той самий трюк з рівномірною сіткою з 2D boids поширюється напряму: розмір комірки ≈ радіус огляду, і кожен boid перевіряє 27 (3×3×3) сусідніх комірок замість 9. Для дуже нерівномірної щільності зграї (щільне ядро, помножене на розсіяних відсталих) октодерево адаптує розмір комірки локально й може перевершити рівномірну сітку, ціною складнішого обліку вставки/видалення щокадру.

Компонування, дружнє до GPU

Для 10 000+ boids у WebGL позиції кодуються через код Мортона (крива Z-порядку, що перемежовує біти x, y, z) і сортуються radix-сортуванням щокадру, тож просторово близькі boids опиняються поруч у GPU-буфері — це зберігає суміжність пошуку сусідів навіть у трьох вимірах.

7. Псевдокод

function stepBoids3D(boids, obstacles, leader, dt):

  for each boid i:

    // 1. Перевірка пріоритету: спершу уникнення перешкод
    avoidForce = probeObstacles(boid[i], obstacles, LOOKAHEAD)
    if length(avoidForce) > AVOID_THRESHOLD:
      a = avoidForce
    else:
      // 2. Стандартний флокінг (3D вектори)
      neighbors = getNeighbors3D(i, VIEW_RADIUS)
      v_sep   = separation(i, neighbors)
      v_align = alignment(i, neighbors)
      v_coh   = cohesion(i, neighbors)

      // 3. Слідування за лідером, якщо призначено
      v_leader = leader ? followLeader(i, leader) : 0

      a = W_SEP*v_sep + W_ALIGN*v_align + W_COH*v_coh + W_LEADER*v_leader

    // 4. Інтегрування руху
    vel[i] = clampLength(vel[i] + a * dt, MAX_SPEED)
    pos[i] += vel[i] * dt

    // 5. Оновлення орієнтації з креном
    orientation[i] = computeBankedOrientation(vel[i], prevVel[i])

Типові константи: VIEW_RADIUS = 12, LOOKAHEAD = 6, AVOID_THRESHOLD = 0.1, W_LEADER = 1.5–2.0 (вище за зближення, щоб зграя надійно відстежувала лідера крізь повороти).

Часті запитання

Що змінюється, коли boids переходять з 2D до 3D?

Позиція та швидкість отримують z-компоненту, пошук сусідів має сканувати об'єм комірок (27 замість 9 для рівномірної сітки), а орієнтація — вже не один кут напряму, а повне 3D-обертання, зазвичай кватерніон, яке має відстежувати і напрям руху, і крен.

Як працює уникнення перешкод у 3D для boids?

Найпоширеніший підхід випромінює один або кілька зондувальних променів від boid у напрямі руху і перевіряє їх проти сферичних чи капсульних колайдерів. Якщо промінь перетинає перешкоду на відстані передбачення, додається сила уникнення перпендикулярно поверхні перешкоди з достатньою вагою, щоб перекрити зближення та вирівнювання.

Чим слідування за лідером відрізняється від звичайного флокінгу?

Один або кілька призначених boids рухаються за незалежно скриптованим маршрутом і виключені зі стандартних правил флокінгу. Звичайні члени зграї додають додатковий член, схожий на зближення, що тягне їх до недавньої позиції лідера, тож уся зграя слідує маршруту лідера, все ще застосовуючи розлучення та вирівнювання між собою.

Чому для орієнтації boid використовують кватерніони, а не кути Ейлера?
Кути Ейлера страждають від шарнірного замка — втрати одного обертального ступеня свободи при певних орієнтаціях, що спричиняє видимі стрибки чи заблоковані осі обертання. Кватерніони представляють обертання без цієї сингулярності і підтримують плавну сферичну інтерполяцію (slerp) між кадрами, що критично важливо для візуально плавного крену під час різких поворотів.
Чим відрізняється зважена сума від пріоритетного перекриття при комбінуванні сил?
Зважена сума просто додає всі сили разом з фіксованими вагами, що все ще може дозволити boid зачепити перешкоду, якщо соціальні сили достатньо сильні. Пріоритетне перекриття спершу оцінює уникнення перешкод; якщо воно дає значну силу, ця сила використовується виключно на цьому тику, гарантуючи, що жорстке обмеження (ніколи не вдарятись об стіну) ніколи не розбавляється м'якими перевагами, як триматися ближче до зграї.
Чому для виявлення перешкод випромінюють кілька променів, а не один?
Один прямий промінь виявляє лише перешкоди прямо попереду центру boid і може пропустити геометрію, яку тіло boid все одно зачепить збоку, особливо на високій швидкості чи біля кутів. Невелике віяло з двох-трьох променів, зміщених на помірний кут, або повний sphere-cast, що прокидає власний радіус зіткнення boid уздовж шляху, виявляє ці близькі промахи лише за помірну додаткову обчислювальну ціну.
Чому зміщення при слідуванні за лідером уникає прямолінійної "конга-лінії"?
Якщо послідовники просто цілять у точну позицію лідера, вони вишиковуються прямо за ним у колону по одному, що виглядає неприродно і викликає постійні близькі зіткнення. Додавання невеликого бокового та вертикального зміщення — часто рандомізованого для кожного послідовника — розподіляє зграю у природну V-подібну чи кластерну формацію навколо маршруту лідера.
Чим октодерево покращує рівномірну сітку для 3D-флокінгу?
Рівномірна сітка ділить простір на комірки однакового розміру незалежно від щільності boids, що марнує пам'ять і час ітерації у розріджених регіонах. Октодерево рекурсивно поділяє лише там, де boids дійсно скупчені, адаптуючи розмір комірки до локальної щільності — це окупається для зграй з дуже нерівномірним розподілом (щільне ядро плюс розсіяні відсталі) ціною складнішого обліку вставки й видалення щокадру.
Чи можуть уникнення перешкод і слідування за лідером конфліктувати, і як це вирішується?
Так — скриптований шлях лідера може вести прямо до перешкоди, яку послідовник інакше уникнув би. Оскільки уникнення перешкод стоїть на вершині порядку пріоритетів (або несе найбільшу вагу), послідовники завжди відхиляються навколо перешкод навіть під впливом слідування за лідером, а потім відновлюють відстеження лідера, щойно перешкода залишається позаду.
Скільки boids може обробити 3D-симуляція флокінгу в реальному часі?
З хешуванням простору через код Мортона та radix-сортуванням пошуку сусідів, реалізованими у WebGL шейдерах у стилі compute, сучасні браузерні 3D-симуляції флокінгу можуть підтримувати 10 000 і більше boids на 60 fps на споживчих GPU, оскільки пошук сусідів залишається O(N), а оновлення кожного boid повністю паралельне за даними.
▶ Жива демонстрація

🐦 Спробуйте симуляцію 3D boids

Спостерігайте, як зграя обходить перешкоди та слідує за лідером у реальному часі — тисячі агентів, прискорені WebGL.

Відкрити симуляцію →

🔗 Пов'язані симуляції

🐦Боїди 🦤Зграя птахів