唯杰地图 logo唯杰地图
VJMAP
EN

fx-overview

vjmapext/references/perf/fx-overview.md 7.4 KB145 行
原文

本文回答:mapcadLayer.fx —— GPU 高性能特效图层:能干什么、和实体层怎么分工。

一、FX 是什么

FX = Visual Effects,是 vjmapext 内置的GPU 端批量特效图层:

  • 入口:mapcadLayer.fx(FxManager 实例,MapCadLayer 自动构造)
  • 原理:instanced rendering,shader 自驱动
  • 特点:1 万个发光/流光实例不掉帧

二、和实体层的分工

实体层(addEntity + AnimationManager) FX 层(mapcadLayer.fx)
驱动 JS 每帧改 bindData GPU 上传一次 buffer,shader 自驱
适合数量 几十~几百 上千~上万
形态 CAD 几何 + 可编辑 发光 / 脉冲 / 流光 / Shadertoy
场景 可编辑图形 / 拖尾 / 变色 告警光环 / 飞线态势 / 粒子
入口 new LineEnt / mapcadLayer.animationManager.add fx.addPoint / fx.addLine

两套系统共存:实体驱动几何形变,FX 铺大量粒子 / 光效。

三、能力清单

能力 API 数量
点预设 fx.addPoint(preset, opts) 12 个(fire / pulseRing / halo / radarSweep / smoke / lightning / meteor / vortex / shield / firework / crosshair / shadertoy)——⚠️ addPoint('shadertoy') 忽略 fragmentShader、实际画的是 fire;自定义 GLSL 只能走 fx.addShadertoy
线预设 fx.addLine(preset, opts) 5 个(flowLine / meteor / lightning / arrowFlow / dashFlow)
Shadertoy 入口 fx.addShadertoy(...) / fx.addShadertoyLine(...) / fx.addShadertoyFullscreen(...) 3 个
CAD 实体绑定 fx.bindToEntity(entity, preset, opts) 联动实体几何
Symbol Shader fx.symbolImage.addPreset / addShadertoy / addVariants 大量同形图标
批量 fx.addPoints / replaceAllPoints / setPointData 万级点用
追加 fx.addPoints(重复调用即追加) ⚠️ FxManager 没有 appendPoints——appendPoints 是 FxLayer 上的方法,manager 层追加就再调 addPoints
一次性触发 { triggerOnce: true, ttl: 2000 } 闪电 / 烟花
三档分桶 bucket: 'below' / 'above' / 'topmost' z-order
画质档位 fx.setQuality('lite' / 'standard' / 'cinematic') 降画质保 FPS
事件 / 指标 fx.on('render / empty / overflow / compact', ...) + fx.getMetrics() 监控

四、Hello FX

const mapcadLayer = new vjmapext.MapCadLayer();
map.addControl(mapcadLayer);

const fx = mapcadLayer.fx;

// 加一个 halo(光晕)
const h = fx.addPoint('halo', {
  cad: [100, 200],         // CAD 坐标
  size: 80,                // 半径(默认 map 单位)
  color: [1, 0.5, 0, 1]    // RGBA
});

// 改色
fx.setPointData(h, { color: [1, 0, 0, 1] });

// 删除
fx.remove(h);

五、坐标与单位

fx.addPoint('halo', {
  cad: [100, 200],     // CAD 坐标(DWG 内部)
  // 或
  lngLat: [116.39, 39.9],   // 经纬度
  // 或
  mercator: [...],      // Web Mercator
  size: 80,
  sizeUnits: 'map'     // 'map' 跟随缩放 / 'pixels' 屏幕恒定
});

六、批量加点(万级)

// 循环 addPoint
for (const p of list) fx.addPoint('fire', { cad: p, size: 30 });

// 一次 addPoints(写法更简洁,直接拿到 handle 数组)
const handles = fx.addPoints('fire', list.map(p => ({ cad: p, size: 30 })));

两种写法性能基本相同:1.0.1 的 addPoints 内部就是逐个调 addPoint;每个点只写 CPU 端影子缓冲并标记脏区间,GPU 上传都在下一帧 draw 时合并成一次 bufferSubData——同步循环 addPoint 并不会每次上传。选 addPoints 是为了代码简洁与按 bucket 分组。

七、CAD 实体绑定

const circle = new vjmapext.CircleEnt([100, 200], 50);
mapcadLayer.addEntity(circle);

fx.bindToEntity(circle, 'pulseRing', { color: [1, 0, 0, 1] });

// 实体动,FX 自动跟随;实体删,FX 自动清理
circle.move(circle.center, [200, 300]);   // center 必须是 Point2D,别直接赋 [x,y] 数组;move 内部已 changed()

bindToEntity(entity, preset, opts) 返回 token:token.handle(当前 FX 句柄)、token.unbind()、token.setEntityVisibleIn(m) / setFxVisibleIn(m) / setFxFollowsEntityVisibility(on)。opts 除点特效字段外还有:anchor(锚点名,如 'center'|'start'|'end'|'midpoint')、removeOnDelete(默认 true)、hideWhileInvalid(默认 true)、entityVisibleIn / fxVisibleIn('both'|'edit'|'browse'|'none',按模式控制实体/特效显隐,默认 'both')、fxFollowsEntityVisibility(实体被隐藏/图层隐藏/超出显示级别时 FX 一起隐藏,默认 false)。

⚠️ 撤销"修改"会杀掉绑定的 FX:undo/redo 恢复修改快照的方式是 store.delete 旧实体再 store.add 快照(id 不变)——entity:deleted 触发绑定的 removeOnDelete 清理,而重新 add 不会自动重新绑定。拖夹点、MOVE 后再 Ctrl+Z,FX 就没了;需要时监听 undo:performed/redo:performed(eventBus 字面量事件)按 id 取回新实体重新 bindToEntity。

八、Symbol Shader 图标

大量同形状的图标(比如 1 万个相同图标的点)→ 走 fx.symbolImage,性能与点数解耦:

// addPreset(id, preset, opts?) — opts 是渲染目标参数:width/height(像素)/color/params/fps/animate
fx.symbolImage.addPreset('warn-halo', 'halo', { width: 64, height: 64, color: [1,0,0,1] });
map.addSymbolLayer('alerts', 'alert-src', {
  iconImage: 'warn-halo',
  iconSize: 1
});

fx.symbolImage 把 FX shader 渲染成 sprite,注入到底座 symbol 图层使用,适合百万级点。

九、典型场景

  • 应急指挥:告警节点 halo / pulseRing,态势飞线 meteor / flowLine
  • 智慧管线:管道 arrowFlow 方向指示,故障点 lightning
  • 战术演示:crosshair 锁定 / shield 范围 / vortex 动态压制
  • 大屏:radarSweep 扫描 / firework 庆祝 / 全屏 Shadertoy 滤镜

十、为什么选 FX 而不是 Marker

  • Marker 每个是 DOM → 500 个就明显卡
  • FX 每个是 GPU instance → 1 万也顺
  • FX 有真正的"发光 / 光晕 / 流光",Marker 要靠 PNG

十一、常见坑

  1. 以为逐个 addPoint 慢:同步循环 addPoint 与 addPoints 一样,都在下一帧合并成一次上传;真正的开销在实例数量与预设 shader 复杂度(看 getMetrics() / 降 setQuality)。
  2. 所有都 cinematic:中端 GPU 撑不住;按重要性给 lite。
  3. 未 remove 的 FX 实例累积:fx.getMetrics() 看 live 数。
  4. FX 绑实体但实体删了 FX 没删:实际应自动删;如果没删,排查 bindToEntity 返回值是否被覆写。
  5. bucket 'topmost' 覆盖全部 UI:确实会覆盖所有图层;慎用。
  6. 自建 new FxLayer() 的默认 id 撞车:FxLayer 缺省 id="vjmapext-fx"——cad.fx 管理器已占用该 id,再 attach 报 Layer with id "vjmapext-fx" already exists,且 detach() 会 map.removeLayer("vjmapext-fx") 把管理器的共享层一并拆掉——之后 fx.addPoint 抛 Cannot read properties of undefined (reading 'toMercator')(coord 已随层移除而清空)。自建层必须传独立 id:new vjmapext.FxLayer({ id: "my-fx" })。

相关