# 调研 —— 设备状态特效在 Cesium 的可用手段与开销预算 Type: research Status: resolved Labels: wayfinder:research Blocked by: None ## 进度:100% 已完成:Cesium 1.145.0 上设备状态特效的五项事实全部核实完毕(可用原语的能力边界、批量做法与 10/50/200 的开销量级、与限距雾后处理的同集合共存与执行顺序、色与形的单一来源在引擎侧的可行边界、拾取与层级),产物落在 [`research/device-status-effect-mechanisms.md`](../research/device-status-effect-mechanisms.md),每条结论带随包 d.ts/源码路径与行号或官方文档链接;查不到的项已在产物里显式列出。只读调研:未改 `src/`、未装依赖、未跑 build/dev/截图。 ## Question 需求⑤要求「定位到设备时,设备点位周围产生对应的特效(对应设备状态:报警、预警、工单、正常、离线等)」。本票只查**事实**:Cesium 1.145.0 上这类特效怎么做、代价多大、与既定的雾气后处理是否打架。 1. **可用原语与能力边界**:逐项给出「能否按实体定位、能否只影响一个点、能否被拾取、是否每帧重算」—— - `Entity` + `CallbackProperty` 驱动的动画 billboard / `ellipse` / `cylinder`; - `CustomDataSource` 与 `Primitive` + `GeometryInstance` + `Appearance`; - `PostProcessStage` / `PostProcessStageCollection`; - 粒子系统(`ParticleSystem`,注意它挂在 `Scene.primitives` 上,是**全局**而非按实体)。 2. **批量做法与开销**:有没有「一次 draw call 画 N 个脉冲」的路子(自定义 `Primitive` + `Appearance` 的 instancing、`BillboardCollection` 的 scale 动画、shader 里按 `attribute` 驱动)?给同屏 10 / 50 / 200 个特效点的量级判断(有实测数据最好,没有就写清是推断并说明依据)。 3. **与限距雾气的共存**(关键):`.scratch/cockpit-primary-area-guardrails` 的 07 已定「自建 Cesium depth 后处理级视距雾(路线 B)」,产物在本地图 `research/three-fog-shader-reuse.md`。请核对:同一 `PostProcessStageCollection` 里再挂设备特效级是否可行?`PostProcessStage` 的 `execute` 顺序语义是什么?该图 06 已更正「`Context.depthTexture` 是**布尔能力标志**而非纹理对象」——设备特效若也要读深度,路径是什么? 4. **色与形的单一来源**:特效的颜色/形状怎么仍然只从 `styles/tokens.css` 的语义色(`--lv1..--lv4/--neutral`)与 `domain/pointStatus.js` 那张表来(票 06/09 的纪律)。引擎侧有哪些地方**读不到 CSS 变量**、必须像 `markerSvg()` 那样由服务层烘焙(列出具体 API)。 5. **拾取与层级**:半透明面片/粒子/扩散圈会不会挡住监测点与设备的拾取?`Scene.pick` 与 `Scene.drillPick` 在多个 primitive 叠加时的行为,以及「特效不该吃点击」有哪些做法。 产物写到 `research/device-status-effect-mechanisms.md`,每条结论带来源(随包 d.ts 路径 + 行号、官方文档 URL、或仓库内文件)。 ## Answer **结论:状态特效走「`BillboardCollection` 逐点 `scale`/`color` 动画」为主、「单个自定义 `Primitive`(N 个 `GeometryInstance` + 同一 `Appearance` + batch table)一次 draw call 画 N 个」为升级路径;`Entity` + `CallbackProperty` 只留给 1–3 个聚焦设备;与限距雾同处一个 `PostProcessStageCollection` 可行,但顺序必须显式定为「先特效、后雾」。** 全文与逐条证据:[`research/device-status-effect-mechanisms.md`](../research/device-status-effect-mechanisms.md)。 1. **原语边界(问题 1)**:`Entity` + `CallbackProperty` 能做「按实体定位、只影响一点、可拾取」,但**每帧重算且代价极高**——非常量属性让 `EllipseGeometryUpdater._isDynamic` 判真(`EllipseGeometryUpdater.js:222-239`),走「每实体一个 updater」的 `DynamicGeometryBatch`(`DynamicGeometryBatch.js:14-22`),而 `DynamicGeometryUpdater.update` 每帧 `removeAndDestroy` 再 `new Primitive({asynchronous:false})` 重建(`DynamicGeometryUpdater.js:76-85, 139-153`);社区实测 25 个 CallbackProperty 圆柱 ≈8 FPS、200 个 ≈0 FPS。`CustomDataSource` 只是容器,**不产生渲染原语**。`Primitive` + `GeometryInstance` + `Appearance` 是「一次 draw call 画 N 个」的正统路径(N 个实例合并成一个几何,draw command 数 = `_va.length`,逐实例数据走 batch table 纹理按 `batchId` 查,`Primitive.js:538-543, 1868, 887`)。`PostProcessStage` 是**全屏**后处理,**无法只影响一个点**(除非用只对 3D Tiles feature 有效的 `selected` + `czm_selected`,对点位图标无效)。`ParticleSystem` 挂在 `Scene.primitives` 上、内部自建一个 `BillboardCollection`(`ParticleSystem.js:710, 807`),靠 `modelMatrix`/`emitterModelMatrix` 定位、**没有位置参数、没有 pick 路径**,N 个点就是 N 份粒子模拟。 2. **批量与开销(问题 2)**:「一次 draw call 画 N 个」**成立**,两条实证路径:① 自定义 `Primitive`(逐实例数据走 batch table 纹理,`Primitive.js:817-963`);② 1.145 的 `BufferPointCollection` 显式一次 `DrawCommand` 画完(`renderBufferPointCollection.js:278-294`),但它 `@experimental`(`BufferPointCollection.js:56-57`)且只能改 size/color,**不建议押注**。10/50/200 的量级判断:`BillboardCollection`(整集合一次 drawElements,逐点脏更新 `BillboardCollection.js:1466-1507`)在 **200 个点仍属安全区**——官方 10,000 个静态 billboard 266 FPS、动画 138 FPS([Billboard Instancing](https://cesium.com/blog/2015/10/28/billboard-instancing/));`CallbackProperty` 椭圆路线 200 个**不可行**。**本票未在本机实测,10/50/200 是依据绘制结构 + 上述可引实测的推断**,真数需在实现票实测。 3. **与雾气共存(问题 3)**:同一 `scene.postProcessStages` **可以**再挂一个设备特效级(`add()` 无上限、只有名字唯一约束,`PostProcessStageCollection.js:436-466`)。`execute` 顺序 = **加入顺序**(无插入/排序 API,`_index = stages.length`),骨架固定为「AO → Bloom → 色调映射 → 自建 stage 按序链式传色 → FXAA」(`:799-834`);**颜色链式、深度不链式**——每级拿到的 `depthTexture` 都是场景那一张(`:801-827`),所以两级读同一份深度,但**谁后跑谁在最上层**。→ 顺序应定为**先特效、后雾气**,否则侧视时会出现「雾盖住了县城、设备特效却亮在雾上面」,违反该图 07 的门控诉求(`07-fog-implementation-spec.md:20-24`);两级开关要一次原子对齐(`enabled` 是逐级独立的,`PostProcessStage.js:178-182`)。读深度的正确路径 = `PostProcessStage` 默认提供的 `sampler2D depthTexture`(`PostProcessStage.js:32-33, 526-531`)+ `czm_readDepth`;**但点位特效通常不需要读深度**,正常深度测试即可。注意能力探测是**文本正则**(`PostProcessStage.js:397-404`),着色器里必须写成 `uniform sampler2D depthTexture`。 4. **色与形的单一来源(问题 4)**:**CSS 变量在 WebGL 路径一律读不到**,所以任何引擎侧颜色都必须像 `markerSvg()` 一样由服务层烘焙(现成入口 `mapService.js:631-651` 的 `toneColors()` → `:670-676`)。必须烘焙的具体 API:`billboard.image`(SVG 作为图片加载时读不到页面 CSS,`pointStatus.js:218`)、`Billboard#color`、`Material` 的 uniform(`Material.js:1255-1278` 要求 number/Color,字符串会被当作图片或 stage 输出名)、`ColorGeometryInstanceAttribute`、`BufferPointMaterial.color/outlineColor`、`PostProcessStage.uniforms`、以及自建 GLSL 里的颜色常量(只能走 `uniforms`)。形状仍只来自 `pointStatus.js` 的 `SHAPES`(`:42-59`),引擎层不新增第二张业务映射表(`syncPointMarkers` 只认 `shape`/`color` 是同一条纪律,`cesiumEngine.js:303-319`)。 5. **拾取与层级(问题 5)**:特效吃不吃点击**只取决于有没有 pickId,与「半透明」无关**。拾取通道只在命令有 `derivedCommands.picking`(由 `command.pickId` 派生,`Scene.js:1810-1817`)或 `depth` 时才画(`executeIdCommand`,`Scene.js:2379-2404`),**没有 pickId 的实体命令被整条跳过**。做法:① 首选 `Primitive({ allowPicking: false })`(`Primitive.js:76, 2055-2059`)或 `BufferPointCollection` 默认 `allowPicking=false`(`BufferPointCollection.js:159`),`ParticleSystem` / `PostProcessStage` 本就无拾取路径;② 特效放独立 collection 且**不带 `pointCode` 属性**(现有拾取口径就是「有 `pointCode` 才算点位」,`cesiumEngine.js:417-431`);③ 做成**空心环**(billboard 拾取看非透明像素)。**最易踩的坑**:不透明特效即使没有 pickId,也有 `depthOnly` 派生命令(`Scene.js:2400-2403`),会在拾取通道写深度挡住背后的设备点——**特效面片必须走半透明 pass**(`Appearance.js:152-153`)。`Scene.pick` 只回最靠近相机的一个;`Scene.drillPick` 是「拾一个藏一个再拾」的多次拾取(`Picking.js:895-941`),可穿透但代价是 N 次 pick pass,且靠改 `show` 实现,与特效独立开关会互相干扰,不推荐。