fx-overview
本文回答:
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
十一、常见坑
- 以为逐个 addPoint 慢:同步循环
addPoint与addPoints一样,都在下一帧合并成一次上传;真正的开销在实例数量与预设 shader 复杂度(看getMetrics()/ 降setQuality)。 - 所有都
cinematic:中端 GPU 撑不住;按重要性给lite。 - 未 remove 的 FX 实例累积:
fx.getMetrics()看 live 数。 - FX 绑实体但实体删了 FX 没删:实际应自动删;如果没删,排查
bindToEntity返回值是否被覆写。 - bucket 'topmost' 覆盖全部 UI:确实会覆盖所有图层;慎用。
- 自建
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" })。
相关
- 调优 → fx-quality
- 预设 → fx-presets
- 大量点替代 → large-markers