---
name: novi-boids
description: "接口姬知识技能包 · 视觉实验室 01「鸟群」：栩栩如生的鸟群，背后只有三条简单规则（分离、对齐、聚合）。教你写 Boids 鸟群、把规则一条条叠加并画出算法辅助线、用网格做邻居搜索，再用旋涡、螺旋画框和目标点让鸟群排出队形、落成一个真能扫的二维码。用户要做鸟群 / 群体动画 / boids / flocking / murmuration、粒子聚成二维码或文字或 logo、生成艺术开场或片尾，或要把它做成视频（Remotion 等）时使用。"
---

# 鸟群：栩栩如生的鸟群，背后只有三条简单规则

> 知识安装包已经准备好了！ —— 接口姬（Novi）

## 要点

- Boids 是 Craig Reynolds 1987 年提出的鸟群模拟（SIGGRAPH 论文《Flocks, Herds, and Schools: A Distributed Behavioral Model》）。每只鸟只看身边的邻居，只守三条规则：**分离**（太近就躲开）、**对齐**（跟邻居朝同一个方向飞）、**聚合**（往邻居的中间靠）。
- 单靠这三条规则，鸟群只会"自然地乱飞"，画不出任何指定的图案。惊艳的效果来自**规则之外的设计**：目标点、节奏、时机。讲解时要说实话：最后排出的形状是指定的，不是涌现出来的。
- 架构原则：**场景只说"去哪儿"，物理负责"怎么飞"**。场景代码只给每只鸟分配目标点、颜色和出发延迟，由同一个步进器把它们飞过去；不要在场景里手写位置动画。

## 怎么用

- 用户要写鸟群：先实现第 1 节的三条规则，按第 2 节的数值起步，再按需求加第 3 节的设计层。
- 用户要讲解 / 做教学视频：按第 4 节的结构，规则逐条叠加，每条画出对应的辅助线。
- 用户要落成二维码：严格遵守第 5 节，交付前用 `scripts/check_qr.py` 对成片截图实测解码。
- 用户要导出视频（Remotion 等逐帧、乱序渲染）：按第 6 节整段预先计算，固定随机种子。
- **先跑演示**：浏览器打开 `examples/boids-qr.html`（单文件、无依赖）就能看到完整效果：鸟群 → 圆环 → 三环 → ∞ 字 → 螺旋画框 + 旋涡 → 落成能扫的二维码。换成自己的网址：`python scripts/make_qr.py <网址> --html examples/boids-qr.html`。加 `?t=25` 可以直接快进到某一秒（截图、测试用）。
- 参考实现：`references/teaching-flock.ts`（规则可开关、环形世界、网格搜索、视角盲区），`references/flock-sim.ts`（视频里用的完整模拟：队形 + 螺旋画框 + 二维码，确定性预计算）。

## 1. 三条规则（自由飞行）

对感知半径 `NR` 内、且不在身后盲区里的邻居（视角 270°，背后 90° 看不见）：

| 规则 | 转向 | 参考系数（1080p、30 fps，每帧） |
|---|---|---|
| 分离 | 离开距离小于 `SR` 的邻居（按单位方向累加） | `+= Σ(−d̂) × 0.35` |
| 对齐 | 朝邻居的平均速度 | `+= (平均速度 − v) × 0.06` |
| 聚合 | 朝邻居的质心 | `+= (质心 − p) × 0.0028` |

让它"活"起来（都乘自由飞行权重）：随机游走（速度方向转 `0.05 × 平滑噪声`）、低频风场、缓慢漂移的吸引点（防止贴边）、速度夹在 `[3, 6]` 像素/帧；自由飞行时世界首尾相接，进入编队后关掉环绕（否则会瞬移）。

**找邻居**：挨个检查是 `n(n−1)`（400 只鸟每帧 159,600 次）。把画面切成边长 ≥ `NR` 的格子，只查自己和周围 3×3 格，实测每帧约 1.2–1.6 万次，少了九成左右。数字要从模拟里实时统计，不要写死。

## 2. 参考数值

`NR = 96`、`SR = 44`、视角 270°；教学场景 400 只（跟随镜头放大 2.2 倍时，感知圈里才经常有好几个邻居）；三角形鸟，尖端 `p + v̂·s`，尾部两点 `p − v̂·0.7s ± 法向·0.55s`。

## 3. 规则之外的设计（惊艳来自这里）

1. **先排队形**（可选，像博客剧场那样）：一个会呼吸的大圆环 → 三个小环各自转、互相绕 → ∞ 字形的鸟流。队形就是"随时间变化的目标点" `dancePoint(i, n, 帧)`，用同一套弹簧拉过去；换队形不需要过渡动画，弹簧的滞后本身就是过渡。
2. **收拢 + 螺旋画框**（博客剧场里最惊艳的一拍，要给足时间）：数据鸟分批（约 0.9 秒）进入目标区域内一个缓慢、带噪声的**旋涡**；多余的鸟沿方形画框排开，整个方框**从 2 倍大小、转着约 126°（2.2 弧度）旋进来再定住**，目标点 = 框上的点绕中心旋转 `2.2k`、放大 `1+k`，`k` 在**约 3 秒**内从 1 缓到 0。画框鸟要用**更硬的弹簧**（`0.02 + 0.16k²`，阻尼 0.74）紧跟目标，否则惯性会把旋转的方框糊掉；画框鸟画得比数据鸟大一点、亮一点。定住后它们沿框绕圈。等画框定住再开始迁移。
3. **分配**：每只数据鸟一个目标点。鸟和目标点都按"绕中心的角度"排序后一一配对，路径不交叉。
4. **迁移**：目标从旋涡位置平滑过渡到各自的点（约 1.4 秒），**按离中心的距离错开出发**（中心先、边缘后，最多约 0.8 秒），由内向外扫开是最好看的一刻。
5. **落位**：靠近后标记"已落位"（只增不减），拉到目标点、速度衰减，并且**直接画在目标点上**（方块），不画物理位置。随后亮起底板、实心定位角，颜色从飞行色变成墨色。

混合方式：`自由权重 = 1 − 编队权重`（编队权重在出发后约 1.5 秒内从 0 升到 1）；弹簧 `0.004 + 0.05·k²`，阻尼 `0.84 − 0.06·k`。排好之后留一点点漂移，别让它变成一张贴图；场景结束时把自由权重调回去，"把方向盘还给 boids"。

## 4. 讲解结构（约 90 秒）

| 段落 | 画面 | 辅助线 |
|---|---|---|
| 钩子 | 标准鸟群；说到"三条规则"时三张规则卡依次弹出 | — |
| 没有规则 | 每只鸟直线飞，互相穿过 | — |
| 感知范围 | 选一只"研究对象"，镜头跟随并放大 | 虚线感知圆、标"盲区"的身后扇区、到每个邻居的连线 |
| ① 分离 | 打开规则 | 红色小圆（分离半径），每个近邻的红色躲避箭头 + 合力箭头 |
| ② 对齐 | 再打开 | 每个邻居的蓝色朝向箭头 + 蓝色平均方向箭头 |
| ③ 聚合 | 再打开；说"就这三条，鸟群就活了"时镜头拉远到全景 | 绿色质心点 + 指向它的箭头 |
| 找邻居 | 400 只鸟 | 先连向全部（计数 159,600），再画网格、高亮 3×3、只连格内（实时计数 + "−9X%" 印章） |
| 上色 | 按飞行方向上色（`hsl(atan2(vy, vx))`），颜色从左往右扫过 | — |
| 收尾 | 队形变换 → 收拢 → 落成二维码，只说一句"最后，来点炫的" | — |

规则是**累加**的（分离 → +对齐 → +聚合），每段单独模拟并预热几秒，第一帧就能看出规则效果。屏幕上始终挂着"分离 / 对齐 / 聚合"三个标签，开启的亮起。

## 5. 落成二维码（不然扫不出来）

- 离线生成矩阵（Python `qrcode`，纠错 **M**，`border=0`），存成 `{url, size, modules: ["0101…"]}`。
- 留 **4 格静区**，模块边长取整数像素（1080p 下 ≥ 16 px），最后是浅底深码。
- **三个 7×7 定位角用实心方块画**，不让鸟去拼（三角形拼出来的定位角扫码器找不到）；鸟只占数据模块。
- 落位后的模块画成约 0.96 格的方块；飞行中用三角形好看，但落位后用方块更好扫。
- 完成的二维码在屏幕上停留 ≥ 5 秒。
- **实测**：`python scripts/check_qr.py video.mp4 90 93 96 --expect <网址>`，对*编码后*的视频截图解码，原尺寸和一半尺寸都要能扫出准确网址才算过。

## 6. 导出视频：确定性预计算

逐帧渲染器（Remotion 等）会乱序、并行地渲染帧，所以不能在帧回调里一步步推进物理：

- 固定随机种子（LCG），固定初始状态。
- **整段轨迹一次性算好**，存进 `Float32Array [帧][鸟][x, y, vx, vy, 已落位]`，按阶段时间做缓存键；每帧只读自己那一片。
- 阶段时间跟着旁白走（比如"加一点设计"那句开始时进入收拢）。
- 编队加二维码约 3000 帧时预计算较慢，Remotion 渲染要加 `--timeout=240000`。
- 深色作品要把场景容器底色也设成深色，否则淡入淡出会闪出默认的浅色。

## 踩过的坑

- 落位时分离规则还开着 → 模块被互相推开。落位前自由权重必须降到 0。
- 落位后画物理位置 → 二维码边缘发虚、扫不出。画目标点。
- 用带字幕的帧做封面背景 → 先裁到只剩画面区域。
- 跟随镜头场景鸟太少 → 感知圈经常是空的，辅助线画不出来。
- 螺旋画框只给 1.4 秒、画框鸟从别的队形直接飞过去 → 看不出螺旋，只剩一个方框。给 3 秒并加硬弹簧。
- 页面里按屏幕比例算的常量（感知半径、速度）要在布局函数里赋值，在算出比例之前定义会变成 NaN。

## 完整提示词

```text
用 Canvas 写一个 Boids 鸟群（三角形的鸟）：分离、对齐、聚合三条规则可以分别开关；选一只鸟画出感知圆、身后盲区、到每个邻居的连线和三种规则的向量；用网格做邻居搜索，并实时显示每帧的距离计算次数。最后加三层设计：旋涡和方形螺旋画框把鸟群收拢；给每只鸟分配二维码的一个模块作为目标点（定位角用实心方块）；按离中心的距离错开出发，一只一只落位。
```

## 资料来源

- Reynolds, C. W. (1987). Flocks, herds and schools: A distributed behavioral model. SIGGRAPH '87, Computer Graphics 21(4).
- 辅助线画法参考 Sebastian Lague《Coding Adventure: Boids》（2019）。
- 编队与落成二维码的做法：《让鸟群按剧本飞：剧场动画是怎么写的》 https://blog.leoimmortal.com/blog/theater-lab/ ，在线演示 https://blog.leoimmortal.com/theater/

## 出处

- 视频：视觉实验室 01 · https://www.bilibili.com/video/BV1e3Wh6zELD
- 更多技能包：https://novi.leoimmortal.com
