拾取与场景交互
三维场景里的「点到了什么」不是一次命中判断。同一个像素上可能叠着模型、实体、地形三层内容, 拾取 API 的选择取决于想拿到哪一层。
四个拾取入口
| 方法 | 返回 | 穿透遮挡 | 适用 |
|---|---|---|---|
scene.pick(position) | 最上层对象或 undefined | 否 | 常规点击、悬停 |
scene.drillPick(position, limit) | 同一像素上的对象数组 | 是 | 叠加要素的逐层选择 |
scene.pickPosition(position) | Cartesian3 世界坐标 | 否 | 在模型或地形上取点 |
camera.pickEllipsoid(position) | Cartesian3 椭球面坐标 | 忽略全部几何 | 快速取经纬度、地图定位 |
scene.pick 返回的包装对象有两个关键字段:
primitive(实际绘制的对象)和 id(添加时挂的业务对象)。
通过 entities.add 创建的内容,id 就是那个 Entity。
var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
var picked = viewer.scene.pick(movement.position);
if (!Cesium.defined(picked)) {
console.log('点到了空白处');
return;
}
if (picked.id instanceof Cesium.Entity) {
console.log('实体', picked.id.name, picked.id.properties);
} else if (picked instanceof Cesium.Cesium3DTileFeature) {
console.log('瓦片要素', picked.getProperty('name'));
} else {
console.log('图元', picked.primitive);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
叠加要素用 drillPick
地面多边形上压着一个图标、图标上压着一个标注,普通 pick
只会返回最上面那一个。drillPick 按深度顺序返回数组,
可以做成「点击循环切换选中对象」。
var cycle = 0;
var lastPixel = null;
handler.setInputAction(function (movement) {
var pos = movement.position;
if (!lastPixel || lastPixel.x !== pos.x || lastPixel.y !== pos.y) {
cycle = 0;
lastPixel = pos;
}
var stack = viewer.scene.drillPick(pos, 6);
if (stack.length === 0) return;
cycle = (cycle + 1) % stack.length;
var target = stack[cycle];
console.log('第', cycle + 1, '层', target.id && target.id.name);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
limit 参数控制最多返回几个对象,默认 10。它不限制遍历深度,
只是截断结果数组,数值越大单次拾取越慢。
悬停高亮要节流
每次 scene.pick 都会触发一次额外渲染来生成拾取缓冲。
鼠标移动事件每秒几十次,直接绑定 MOUSE_MOVE 会让帧率掉一半。
常见做法是记录上一次的像素,只有位置变化超过阈值才拾取。
var lastPos = { x: 0, y: 0 };
var hovered = null;
handler.setInputAction(function (movement) {
var p = movement.endPosition;
if (Math.abs(p.x - lastPos.x) + Math.abs(p.y - lastPos.y) < 6) return;
lastPos = p;
var picked = viewer.scene.pick(p);
var next = picked && picked.id instanceof Cesium.Entity ? picked.id : null;
if (next === hovered) return;
if (hovered) {
hovered.billboard.scale = 1.0;
hovered.label.fillColor = Cesium.Color.WHITE;
}
hovered = next;
if (hovered) {
hovered.billboard.scale = 1.25;
viewer.scene.canvas.style.cursor = 'pointer';
} else {
viewer.scene.canvas.style.cursor = 'default';
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
把「点到地面」的坐标取准
pickEllipsoid 得到的是椭球面交点,山上的点击会落到山脚。
pickPosition 使用深度缓冲,能取到地形与模型表面,
但要求地形已经渲染过,并受 depthTestAgainstTerrain 影响。
handler.setInputAction(function (movement) {
var scene = viewer.scene;
var pos = movement.position;
var world = scene.pickPosition(pos);
if (!Cesium.defined(world)) {
world = viewer.camera.pickEllipsoid(pos, scene.globe.ellipsoid);
}
if (!Cesium.defined(world)) return;
var carto = Cesium.Cartographic.fromCartesian(world);
viewer.entities.add({
position: world,
point: { pixelSize: 8, color: Cesium.Color.ORANGE }
});
console.log(
Cesium.Math.toDegrees(carto.longitude).toFixed(6),
Cesium.Math.toDegrees(carto.latitude).toFixed(6),
carto.height.toFixed(1)
);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
顺序很重要:先 pickPosition,失败再退回
pickEllipsoid。反过来会让所有点击都落在椭球面,
在山区表现为点位明显滑到山脚。
注意事项
- 拾取结果依赖上一帧的渲染内容。刚添加实体就立刻
pick,可能返回undefined,等一帧再试。 - 设置了
allowPicking: false的图元不会被拾取,这是性能优化项,排查时先确认没关。 - 半透明物体默认不参与深度拾取,需要开启
scene.pickTranslucentDepth = true。 - 瓦片要素拾取要求瓦片集
allowPicking为 true,点云还需要开启对应能力。 requestRenderMode开启时,鼠标移动不会自动重绘,高亮变化需要调用scene.requestRender()。- 事件处理器在页面销毁时要调用
handler.destroy(),否则切换页面后仍占用画布事件。
经纬志(2026)。《拾取与场景交互》。https://www.jingweizhi.com/cesium/tutorial/picking.html(访问日期 2026-11-03)。