251 lines
8.7 KiB
Markdown
251 lines
8.7 KiB
Markdown
# 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 4:picking 改造
|
||
|
||
批量渲染后不再适合对所有 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。
|