Cesium:如何在Primitive上同时实现贴地与地形深度检测?
解决Cesium中Primitive箭头在depthTestAgainstTerrain开启时clampToGround失效的问题
问题描述
我需要绘制大量箭头,Cesium中只能通过Primitive实现箭头绘制。但开启depthTestAgainstTerrain = true后,即使设置了clampToGround: true,箭头仍会在地面上方消失。
原实现代码:
var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; scene.globe.depthTestAgainstTerrain = true; // Create sample polyline primitive. var polylines = scene.primitives.add(new Cesium.PolylineCollection()); var centerLon = -42.25; var centerLat = -22.25; var angle = 47; var distance = 50000; var nPos = project( centerLat, centerLon, distance, angle ); console.log( JSON.stringify( nPos ) ); var polyline = polylines.add({ positions : Cesium.Cartesian3.fromDegreesArray([centerLon, centerLat, nPos.lon, nPos.lat]), material: Cesium.Material.fromType('PolylineArrow', { color: new Cesium.Color.fromBytes(255, 255, 0, 185) }), width : 15.0, clampToGround : true }); function project( lat, lon, distance, angle ){ var radian = Cesium.Math.toRadians(angle); var ellipsoid = Cesium.Ellipsoid.WGS84; var firstPos = Cesium.Cartesian3.fromDegrees(lon,lat, 0); var ENU = Cesium.Transforms.eastNorthUpToFixedFrame(firstPos, ellipsoid); var offsetX = distance * Math.sin(radian); var offsetY = distance * Math.cos(radian); var offset = new Cesium.Cartesian3(offsetX, offsetY, 0); var thePosition = Cesium.Matrix4.multiplyByPoint(ENU, offset, new Cesium.Cartesian3()); const cartographic = Cesium.Cartographic.fromCartesian( thePosition ); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); return { lat: latitude, lon: longitude }; }
问题原因
当开启depthTestAgainstTerrain时,Cesium会对比Primitive的深度和地形的深度。而PolylineCollection的clampToGround只是将线贴在椭球表面,而非真实地形表面。如果真实地形比椭球高,箭头就会被地形的深度测试遮挡,看起来像是在地面上方消失。
解决步骤
- 获取真实地形高度:使用Cesium的
sampleTerrainMostDetailedAPI获取每个点的实际地形高度,替换原来的0高度。 - 异步处理地形数据:地形采样是异步操作,需在获取到高度后再创建或更新Polyline的positions。
- 保留Primitive性能优势:继续使用
PolylineCollection批量绘制箭头,保证大量箭头场景下的性能。
修改后的代码
var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; scene.globe.depthTestAgainstTerrain = true; var polylines = scene.primitives.add(new Cesium.PolylineCollection()); var centerLon = -42.25; var centerLat = -22.25; var angle = 47; var distance = 50000; var nPos = project(centerLat, centerLon, distance, angle); // 收集需要采样地形的点 var pointsToSample = [ Cesium.Cartographic.fromDegrees(centerLon, centerLat), Cesium.Cartographic.fromDegrees(nPos.lon, nPos.lat) ]; // 采样最详细的地形高度 Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, pointsToSample).then(function(sampledPoints) { // 将采样后的点转换为Cartesian3坐标 var positions = sampledPoints.map(function(point) { return Cesium.Cartesian3.fromRadians(point.longitude, point.latitude, point.height); }); // 添加箭头线 polylines.add({ positions: positions, material: Cesium.Material.fromType('PolylineArrow', { color: new Cesium.Color.fromBytes(255, 255, 0, 185) }), width: 15.0, // 手动贴地形后可关闭clampToGround clampToGround: false }); }); function project(lat, lon, distance, angle) { var radian = Cesium.Math.toRadians(angle); var ellipsoid = Cesium.Ellipsoid.WGS84; var firstPos = Cesium.Cartesian3.fromDegrees(lon, lat, 0); var ENU = Cesium.Transforms.eastNorthUpToFixedFrame(firstPos, ellipsoid); var offsetX = distance * Math.sin(radian); var offsetY = distance * Math.cos(radian); var offset = new Cesium.Cartesian3(offsetX, offsetY, 0); var thePosition = Cesium.Matrix4.multiplyByPoint(ENU, offset, new Cesium.Cartesian3()); const cartographic = Cesium.Cartographic.fromCartesian(thePosition); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); return { lat: latitude, lon: longitude }; }
额外说明
- 绘制大量箭头时,建议批量处理地形采样请求,避免频繁调用异步API影响性能。
- 动态更新箭头位置时,需重新采样地形高度,确保线始终贴合真实地形。
内容的提问来源于stack exchange,提问作者Magno C
相关产品推荐
相关产品推荐

