# Flowing Fireworks 3D Pass (流光烟花 3D 通道)

- **Summary**: `FlowingFireworks3DPass` 与 [`FlowingLight3DPass`](./flowing-light-3d-pass.md) 配套使用,在每条流光线条的「尾部位置」(渐变淡出处)持续喷射出烟花状向四周发散的粒子,适合为流光拖尾增加飞溅火花、星点爆裂等增强效果。
- **Schema**: `FlowingFireworks3DPass` 类及 `FireworksLine` 数据结构。
- **Concepts**: GPU 粒子运动积分、环形缓冲发射、与流光尾部位置同步、加法混合发光。
- **Configuration**: 3D 路径点、颜色、动画周期、尾长比例、粒子数量/发射率/扩散/重力等调优参数。
- **Examples**: 流光拖尾火花、多条 3D 路径烟花、与 FlowingLight3DPass 配合、参数调优。

## Schema

> **Definition**: [FlowingFireworks3DPass](../../five/renderer/postprocessing/passes/flowing-fireworks-3d-pass.d.ts)

`FlowingFireworks3DPass` 继承自 `FivePass`,与 `FlowingLight3DPass` 共享同一套线条数据结构(`points` / `totalLength` / `duration` / `startTime` / `tailLengthRatio`),并额外提供烟花粒子调优参数。核心接口如下:

```ts
import * as THREE from 'three';
import { FivePass } from './pass';
import { type Camera } from '../../../core/camera';

type FireworksLine = {
  id?: string;                   // 可选的唯一标识符,不传则自动生成 UUID
  points: THREE.Vector3[];       // 世界坐标系路径点
  totalLength: number;           // 路径总长度(世界单位)
  color: THREE.Color;            // 粒子颜色
  opacity?: number;              // 粒子不透明度(0-1)
  tailLengthRatio?: number;      // 尾巴长度比例(0-1),默认 0.2,与 FlowingLight3DPass 含义一致
  duration?: number;             // 动画周期(毫秒),默认 1000
  startTime?: number;            // 线条开始时间(毫秒),默认使用当前内部时间

  // ---- 烟花粒子调优参数(可选) ----
  particleCount?: number;        // 该线条的粒子池大小,默认 200
  emissionRate?: number;         // 每秒发射的粒子数量,默认 300
  spread?: number;               // 扩散尺度(世界单位),默认 0.1
  particleSize?: number;         // 粒子尺寸(世界单位),默认 spread * 0.12
  particleLife?: number;         // 粒子最大寿命(秒),默认 0.5
  gravity?: THREE.Vector3;       // 粒子加速度(世界单位/秒²),默认沿 -Y 缓慢下落
  leadRatio?: number;            // 发射点相对尾部向前(头部方向)的提前比例,默认 0.04
};

export class FlowingFireworks3DPass extends FivePass {
  constructor(camera: Camera);

  // 批量设置路径列表(替换所有现有线条)
  public setLines(lines: FireworksLine[]): void;

  // 追加一条或多条线,返回生成的 id 数组
  public addLine(...lines: FireworksLine[]): string[];

  // 按 id 移除一条或多条线,返回实际移除的数量
  public removeLine(...ids: string[]): number;

  // 修改单条线的参数(通过 id),支持部分更新
  public setLine(params: { id: string } & Partial<Omit<FireworksLine, 'id'>>): void;

  public render(
    renderer: THREE.WebGLRenderer,
    writeBuffer: THREE.WebGLRenderTarget,
    readBuffer: THREE.WebGLRenderTarget,
    deltaTime: number,
    maskActive?: boolean,
  ): void;

  public dispose(): void;
}
```

## Concepts

### 与流光尾部同步 (Tail-synced Emission)
该 Pass 的粒子发射点与 `FlowingLight3DPass` 的「尾部位置」(`tailPosWorld`)保持一致:

- 流光的头部(Head)是前进方向的亮端,尾部(Tail)是渐变淡出的末端。
- `tailLengthRatio` 表示从头部到渐变结束所占路径比例(默认 0.2)。粒子在「渐变结束的位置」喷射,即 `headPos - tailLengthRatio * totalLength`。
- 把本 Pass 与 `FlowingLight3DPass` 配置**完全相同**的线条数据(`points`/`totalLength`/`duration`/`startTime`/`tailLengthRatio`),即可让烟花精准跟随流光尾部。

### 提前量 (Lead Ratio)
由于尾部沿路径滞后于头部,在视觉上粒子有时会显得「晚一拍」。`leadRatio` 让发射点相对尾部向头部方向提前一小段(占 `totalLength` 的比例,默认 0.04),使火花出现时机更早。提前量会被限制在当前尾长以内,不会越过头部。设为 `0` 即严格在尾部位置喷射。

### GPU 粒子运动积分 (GPU Particle Integration)
每个粒子在 CPU 端发射时仅写入「出生世界坐标 / 初速度 / 加速度 / 出生时间 / 寿命」等属性,后续运动完全在顶点着色器中按 `p = p0 + v·t + ½·a·t²` 积分计算,并随寿命淡出与缩小。CPU 每帧只需计算尾部位置并发射少量新粒子,性能开销低。

### 环形缓冲发射 (Ring-buffer Emission)
每条线条拥有独立的粒子池(`particleCount`),按环形缓冲循环复用槽位。`emissionRate`(粒子/秒)结合每帧 `deltaTime` 决定发射数量,寿命到期的粒子自动被新粒子覆盖。

### 加法混合发光 (Additive Glow)
粒子使用 `AdditiveBlending` 渲染到离屏缓冲并累加,再叠加到场景输出,自然形成重叠处更亮的发光效果。粒子为圆形柔光点,中心亮、边缘柔和。

### 相机同步与 DPR
与 `FlowingLight3DPass` 一致,Pass 自动同步相机投影/视图矩阵,并根据 `readBuffer` 尺寸自动调整离屏渲染目标,适配高 DPR 屏幕。

## Configuration

### FireworksLine 配置参数

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| `id` | `string` | | 自动生成 UUID | 唯一标识符,用于 `setLine` 更新单条线。 |
| `points` | `THREE.Vector3[]` | ✓ | - | 世界坐标系路径点数组。至少 2 个点。 |
| `totalLength` | `number` | ✓ | - | 路径总长度(世界单位)。通常为相邻点间距离之和。 |
| `color` | `THREE.Color` | ✓ | - | 粒子颜色(RGB)。 |
| `opacity` | `number` | | `1.0` | 粒子不透明度(0-1)。 |
| `tailLengthRatio` | `number` | | `0.2` | 尾巴长度比例(0-1)。决定粒子喷射点(渐变结束处)相对头部的距离。 |
| `duration` | `number` | | `1000` | 动画周期(毫秒)。尾部完成一个循环所需时长。 |
| `startTime` | `number` | | 当前内部时间 | 线条开始时间(毫秒)。可用于多条线错开起始时间。 |
| `particleCount` | `number` | | `200` | 该线条粒子池大小。越大可同时存在的火花越多。 |
| `emissionRate` | `number` | | `300` | 每秒发射的粒子数量。控制火花密度。 |
| `spread` | `number` | | `0.1` | 扩散尺度(世界单位)。控制火花向四周飞散的范围。 |
| `particleSize` | `number` | | `spread * 0.12` | 粒子尺寸(世界单位)。 |
| `particleLife` | `number` | | `0.5` | 粒子最大寿命(秒)。越长火花拖得越远。 |
| `gravity` | `THREE.Vector3` | | 沿 -Y 缓慢下落 | 粒子加速度(世界单位/秒²)。可制造下坠或上升效果。 |
| `leadRatio` | `number` | | `0.04` | 发射点相对尾部的提前比例(占 `totalLength`)。增大可让火花更早出现,`0` 表示严格在尾部。 |

> **注意**: `spread` / `particleSize` / `gravity` 均为世界坐标单位,需要结合场景尺度调整。默认值适配米级小场景,大场景应相应放大。

## Instance Methods

### `constructor(camera: Camera)`
初始化 Pass,需传入相机以获取投影矩阵和视图矩阵。

### `setLines(lines: FireworksLine[])`
批量设置路径列表,替换所有现有线条。

**自动生成 ID**: 没有 `id` 时自动生成 UUID。
**自动设置 startTime**: 没有 `startTime` 时使用当前内部时间。

```ts
pass.setLines([{
  points: path,
  totalLength: 12,
  color: new THREE.Color(0xff6600),
  duration: 3000,
  tailLengthRatio: 0.2,
}]);
```

### `addLine(...lines: FireworksLine[]): string[]`
追加一条或多条线,返回新增线条的 id 数组,不影响已有线条。

### `removeLine(...ids: string[]): number`
按 id 移除一条或多条线,返回实际移除的数量。id 不存在时输出警告。

### `setLine(params: { id: string } & Partial<Omit<FireworksLine, 'id'>>)`
通过 `id` 部分更新单条线。

**性能说明**:
- 大多数字段(`points`、`color`、`totalLength`、`duration`、`spread`、`gravity` 等)会在下一帧发射时即时生效,**无需重建**。
- 仅当 `particleCount` 改变时才会重建粒子缓冲。

```ts
// 即时生效,无需重建
pass.setLine({ id: 'line-1', color: new THREE.Color(0x00ff00), emissionRate: 500 });

// 改变粒子池大小,会重建缓冲
pass.setLine({ id: 'line-1', particleCount: 400 });
```

### `render(...)`
由 EffectComposer 自动调用,无需手动调用。自动同步相机矩阵与渲染目标尺寸。

### `dispose()`
释放粒子缓冲、着色材质、渲染目标等资源,销毁 Pass 时必须调用。

## Examples

### 与 FlowingLight3DPass 配合(推荐)

烟花跟随流光尾部的标准用法:为两个 Pass 配置**相同**的线条数据。

```typescript
import { Five } from '@realsee/five';
import { FlowingLight3DPass, FlowingFireworks3DPass } from '../../lib/five/renderer/postprocessing';
import { calculatePath3DLength } from '@realsee/five/plugins';
import * as THREE from 'three';

const five = new Five();

const flowingLight = new FlowingLight3DPass(five.camera);
const fireworks = new FlowingFireworks3DPass(five.camera);

const path = [
  new THREE.Vector3(-5, 0, 0),
  new THREE.Vector3(-2, 3, 2),
  new THREE.Vector3(2, 3, -2),
  new THREE.Vector3(5, 0, 0),
];

const lineData = {
  points: path,
  totalLength: calculatePath3DLength(path),
  color: new THREE.Color(0xff00ee),
  duration: 8000,
  tailLengthRatio: 0.2,
};

flowingLight.setLines([lineData]);
fireworks.setLines([lineData]); // 相同数据 => 烟花精准跟随流光尾部

// 注意添加顺序:烟花叠加在流光之上
five.addPass(flowingLight);
five.addPass(fireworks);

function animate() {
  requestAnimationFrame(animate);
  five.render();
}
animate();
```

### 多条 3D 路径烟花

```typescript
const fireworks = new FlowingFireworks3DPass(camera);

fireworks.setLines([
  {
    points: [new THREE.Vector3(0, 0, 0), new THREE.Vector3(10, 5, 0)],
    totalLength: 11,
    color: new THREE.Color(0xff0000),
    duration: 2000,
  },
  {
    points: [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 10, 5)],
    totalLength: 12,
    color: new THREE.Color(0x00ff00),
    duration: 2500,
    startTime: 500,
  },
]);
```

### 参数调优:更密集、飞溅更远的火花

```typescript
fireworks.setLines([{
  points: path,
  totalLength: calculatePath3DLength(path),
  color: new THREE.Color(0xffaa00),
  duration: 6000,
  emissionRate: 600,   // 更密集
  spread: 0.25,        // 飞散更广
  particleLife: 0.9,   // 拖得更远
  particleSize: 0.04,
  gravity: new THREE.Vector3(0, -0.5, 0), // 明显下坠
  leadRatio: 0.08,     // 出现更早
}]);
```

### 调整火花出现时机

```typescript
// 视觉上火花偏晚 => 增大 leadRatio 让其更早出现
pass.setLine({ id: lineId, leadRatio: 0.1 });

// 严格落在流光尾部(渐变结束处)
pass.setLine({ id: lineId, leadRatio: 0 });
```

## Debugging

- **看不到粒子**: 检查路径是否在相机视锥内;确认 `color` / `opacity` 非全透明;确认已调用 `addPass`。
- **火花太稀疏**: 增大 `emissionRate` 或 `particleCount`(注意 `particleCount` 是上限,过小会限制同时存在的粒子数)。
- **火花范围太小/太大**: 调整 `spread`(世界单位),需结合场景尺度。
- **火花出现偏晚/偏早**: 调整 `leadRatio`(增大更早,`0` 严格在尾部)。
- **位置与流光尾部不一致**: 确认两个 Pass 使用了**完全相同**的 `points`/`totalLength`/`duration`/`startTime`/`tailLengthRatio`。
- **粒子不下落或方向不对**: 检查 `gravity` 向量;默认沿 -Y(世界上方向取决于场景坐标系)。

## Performance

- **GPU 积分**: 粒子运动全部在着色器中完成,CPU 每帧只发射少量新粒子,开销低。
- **环形缓冲**: 粒子池大小固定(`particleCount`),不会随时间增长,显存稳定。
- **加法混合**: 重叠粒子会累加亮度,过高的 `emissionRate` × `particleCount` 会增加片元填充率,建议按需调节。

### 优化建议
1. 控制 `particleCount` 与 `emissionRate`,避免不必要的高密度。
2. 大场景适当放大 `spread` / `particleSize`,小场景调小,保持视觉比例。
3. 使用 `setLine` 而非 `setLines` 更新单条线;除 `particleCount` 外的修改无需重建。
4. 低端设备可通过 `pass.enabled = false` 禁用。

## Common Pitfalls

1. **数据不一致**: 烟花与流光必须共享相同的线条参数,否则尾部位置不对齐。
2. **添加顺序**: 应在流光 Pass 之后 `addPass` 烟花 Pass,使火花叠加在流光之上。
3. **单位混淆**: `spread` / `particleSize` / `gravity` 为世界单位,`totalLength` 也是世界单位,不要混用像素。
4. **particleCount 过小**: 它是同时存在粒子数的上限,过小会使高 `emissionRate` 失效。
5. **未调用 dispose()**: 销毁 Pass 时必须调用 `dispose()`,避免 GPU 资源泄漏。

## Related

*   [Flowing Light 3D Pass](./flowing-light-3d-pass.md): 3D 流光效果,本 Pass 的配套来源。
*   [Flowing Light 2D Pass](./flowing-light-2d-pass.md): 屏幕空间的流光效果。
*   [Postprocessing](./postprocessing.md): 后处理通用概念与 EffectComposer 使用指南。

---


tags: [烟花, 火花, 粒子, 流光拖尾, 发散, 爆裂, 特效, postprocessing, effect, fireworks, particle, sparks, flowing, pass, rendering, worldspace, 3d, world-space, additive, gpu-particles]
