经纬志
首页 / Cesium / 教程

拾取与场景交互

先看结论

三维场景里的「点到了什么」不是一次命中判断。同一个像素上可能叠着模型、实体、地形三层内容, 拾取 API 的选择取决于想拿到哪一层。

最后验证 2026-11-03 · CesiumJS 1.121 · 难度:进阶 · 核验 2026-10-29 · 引用本文

四个拾取入口

方法返回穿透遮挡适用
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)。