Files
planet/docs/plans/earth-vessel-rendering-performance-plan.md
linkong 8c204717cd
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled
release: bump version to 0.70.0
2026-06-04 17:16:23 +08:00

251 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Earth Vessel Rendering Performance Plan
## 当前状态
该计划的前端核心部分已经在 `0.44.1` 落地,但最终实现不是原文设想的 `InstancedBufferGeometry` quad而是更稳的分桶 `THREE.Points` 方案:
- 普通船只按 moving / anchored 和 `VESSEL_COURSE_BINS` 航向分桶,使用 `PointsMaterial` 批量绘制。
- 航行船只仍是带方向的三角形,停泊或低速船只仍是圆点。
- hover / locked 不再放大成世界尺寸 Sprite而是在原点位叠加同尺寸单点 glow overlay。
- picking 改为屏幕空间命中,拖动和惯性期间跳过 hover picking。
- 普通态关闭 glow交互态才显示 glow降低 overdraw 并让默认地图更干净。
- `0.70.0` 起船只继续走全球当前状态快照:`vessel_current_state` + 全球 bbox + `limit=3000`。上一轮“按当前镜头 bbox 刷新/订阅”的方向已撤回,性能优化集中在批量渲染、关闭动态聚类和减少 picking / rebuild 开销。
后续如果需要更高船只密度,再评估是否从分桶 `Points` 升级到真正 instanced quad 或服务端 LOD不要把普通旋转/缩放重新接回视口驱动请求。
## 背景
Earth 船只图层已经形成了一套较好的视觉语言:
- 航行船只使用三角形标记
- 标记按航向旋转
- 停泊或低速船只使用圆点
- 不同船型使用不同颜色
- hover / locked 状态有 glow、透明度和聚焦反馈
- 标记带有轻微 glow / soft edge和 Earth HUD 的观感一致
当前性能问题不应通过降级成无方向、无船型语义的普通小点来解决。目标是在保留现有观赏性的前提下,把底层从“每艘船一个 Sprite 对象”优化为批量绘制和轻量交互。
## 当前问题判断
卫星图层能承载几万个对象,是因为它主要走 `THREE.Points` / `BufferGeometry` / instanced trail 路径。船只图层目前每艘船创建一个 `THREE.Sprite` 和独立 `SpriteMaterial`,这会带来:
- draw call 随船只数量增长
- 透明 sprite 排序和 overdraw 成本上升
- 每帧遍历所有船只更新 opacity / scale / visible
- pointer move 时对船只 sprite 做对象级 raycast
- hover reset 时全量遍历 marker
因此,即使免费 BarentsWatch AIS 只开放挪威周边数据,前端仍可能因为对象级 sprite、raycast 和每帧全量更新出现地球拖动卡顿。
## 目标
1. 保留当前船只标记的视觉质量。
2. 保留 hover tooltip、点击详情、lock、轨迹等交互。
3. 显著降低 draw call、每帧 JS 遍历和 pointer picking 成本。
4. 为后续全球 AIS 或更高船只数量预留扩展空间。
## 非目标
- 不把船只降级为普通无方向 `Points`
- 不取消船型颜色、航向三角和停泊圆点。
- 不为了短期性能直接删除 hover / click 交互。
## Phase 1交互路径止血
这一阶段不改视觉,只减少 pointer move 和 hover 状态开销。
### 1. 拖动和惯性期间跳过船只 picking
地球拖动时用户主要关注视角变化,不需要每个 pointer move 都命中船只。
处理方式:
- `isDragging === true` 时跳过船只 hover picking。
- 惯性旋转期间也跳过船只 hover picking。
- 拖动结束后再恢复 hover 检测。
### 2. vessel hover picking 节流
对船只 hover 命中增加节流,例如 `80ms ~ 120ms` 一次。鼠标高速移动时复用上一次 hover 状态,不在每个 pointer event 上都做 raycast。
### 3. hover reset 从全量遍历改为增量更新
当前 `resetTransientVesselStates()` 会遍历所有船只。改为记录:
- `hoveredVessel`
- `lockedObject`
当 hover 目标变化时,只更新旧 hover 和新 hover。
### 4. 点击路径只在 click 时做一次精确 picking
点击仍保留精确命中,但只在 click 事件里执行,不参与拖动和高频 pointer move。
## Phase 2每帧更新减负
这一阶段仍保留 `Sprite` 外观,但减少每帧对全部 marker 的写操作。
### 1. `updateVesselVisualState()` 增量化
当前每帧都会遍历船只并写:
- `marker.material.opacity`
- `marker.scale`
- `marker.visible`
优化方向:
- 图层关闭时直接 return。
- 没有船只时直接 return。
- 只有以下状态变化时才更新 marker
- show/hide 变化
- hover 变化
- locked 变化
- camera zoom / distance scale 变化超过阈值
- focus dim 状态变化
### 2. 缓存 distance scale
`getDistanceScale(camera)` 可以按 camera distance 或 zoom 阈值缓存。缩放没有明显变化时,不必每帧重设所有船只 scale。
### 3. 降低透明 overdraw
在不破坏视觉的前提下微调:
- marker 基础尺寸
- glow blur 半径
- 最大 size stabilization
目标是减少屏幕空间重叠面积,而不是改变符号设计。
## Phase 3保留视觉的批量渲染已落地为分桶 Points
原设想是把每艘船的视觉从 `THREE.Sprite` 迁移为 instanced sprite batch。实际落地时选择了更稳的分桶 `THREE.Points`
- 不依赖自定义 shader。
- 不依赖 `Points` 自带 raycaster。
- 用 canvas 纹理保留三角、圆点、船型颜色和航向。
- 用 hover / locked 单点 overlay 保留交互 glow。
如果未来全球 AIS 导致分桶 `Points` 仍不够,再升级到 instanced quad。
### 1. 原候选方案instanced quad
每艘船仍然显示为带贴图/软边的 billboard但底层使用
- `THREE.InstancedBufferGeometry`
- 每类船只一个或少量 material
- per-instance attributes
可按形状和船型拆 batch
- moving cargo
- moving tanker
- moving passenger
- moving fishing
- moving military
- moving other
- anchored / slow dot
这样 draw call 从“每艘船一个”变为“每类船只一个”。
### 2. 原候选方案per-instance attributes
每个 instance 存:
- position
- color
- rotation
- scale
- opacity
- state
- mmsi / data index
hover、locked、dimmed 可通过更新少量 instance attribute 实现,不再逐个修改 material。
### 3. 当前落地方案:分桶 `THREE.Points`
当前实现按以下方式复刻视觉:
- moving 船只按 `VESSEL_COURSE_BINS` 做航向分桶。
- anchored / slow 船只使用圆点分桶。
- 每个分桶生成一组 `THREE.Points`,共享 `PointsMaterial` 和 canvas 点纹理。
- `VESSEL_CONFIG.colors` 仍通过 vertex colors 表示船型颜色。
- hover / locked 在原位置叠加同尺寸单点 overlay普通态不带 glow交互态才带 glow。
这样 draw call 从“每艘船一个”变为“每个形状 / 航向分桶一组”,同时避免自定义 shader 的兼容风险。
### 4. 复刻当前视觉
视觉上继续使用当前 canvas texture 或等效 shader
- moving 使用三角形纹理
- anchored 使用圆点纹理
- 保留 soft glow
- 保留航向 rotation
- 保留 hover / locked 放大
因此用户看到的效果应与当前船只图层基本一致。
## Phase 4picking 改造
批量渲染后不再适合对所有 sprite object 做 `raycaster.intersectObjects()`
### 1. 屏幕空间 picking
参考卫星 picking
1. 过滤背面船只。
2. 将候选船只世界坐标投影到屏幕。
3. 用鼠标位置计算距离。
4. 取距离最近且小于半径阈值的船只。
### 2. 可选空间索引
如果后续船只数量明显上升,可增加轻量空间索引:
- 经纬度网格 bucket
- 屏幕空间 bucket
- viewport bbox 过滤
第一阶段不必引入复杂索引。
## Phase 5数据层和 LOD
当接入全球 AIS 或船只数量显著增加时,再做数据层优化。
### 1. 当前状态快照
前端请求 `/api/v1/vessels/snapshot` 时仍必须带 `bbox``zoom` 和受控 `limit`,但 Earth 运行时传全球 bbox 和 `limit=3000`,不使用当前镜头视口驱动刷新。旧 `/api/v1/visualization/geo/vessels` 路由已移除。
### 2. 后端排序策略
从单纯 `received_at desc` 改为综合排序:
- 数据新鲜度
- 船型优先级
- 全球当前状态可读性
- 是否正在航行
### 3. 远景聚合
远景可显示聚合或 top N近景展开单船。
## 验收指标
1. 船只视觉效果保持当前质量三角、圆点、颜色、航向、hover、lock 都保留。
2. 开启船只图层后拖动地球不应明显掉帧。
3. pointer move 不应因为船只 hover 导致卡顿。
4. 船只数量达到 `1000` 级别时仍可顺畅旋转地球。
5. `renderer.info.render.calls` 相比 Sprite 版本显著下降。
6. hover / click 命中体验不低于当前版本。
## 建议落地顺序
1. 先做 Phase 1快速恢复地球拖动手感。
2. 再做 Phase 2减少每帧 JS 写操作。
3. Phase 3 和 Phase 4 已按分桶 `THREE.Points` + 屏幕空间 picking 落地。
4. Phase 5 等全球船只数据或数量压力出现后再推进。
5. 如果分桶 `THREE.Points` 达到瓶颈,再评估 instanced quad。