基于 SuperMap iClient3D for WebGL 实现区域掩膜(裁剪遮罩)效果
基于 SuperMap iClient3D for WebGL 实现区域掩膜裁剪遮罩效果一、前言在 WebGIS 可视化开发中经常遇到这样一个需求高亮显示某个区域同时淡化或遮挡区域外的内容形成一种聚光灯式的视觉引导效果。这种效果通常称为掩膜Mask / Clip Layer。在二维 GIS 中实现掩膜相对简单——使用一个覆盖全图的大矩形中间挖掉目标区域即可。但在三维场景SuperMap iClient3D for WebGL中由于涉及地形起伏、相机视角、地球曲率等因素实现起来有不少需要注意的细节。本文基于实际项目经验详细介绍如何基于SuperMap iClient3D for WebGL实现一个高性能、效果正确的 3D 掩膜功能。二、效果预览实现后的效果掩膜区域内地图正常显示所有要素完整可见掩膜区域外被深色半透明遮罩覆盖形成视觉遮挡区域边界带有青色立体墙增强视觉效果和空间感三、实现原理3.1 核心思路掩膜的核心思路可以概括为画一个大面中间掏个洞。具体来说以目标区域为中心构建一个覆盖周边的大矩形在矩形中间挖掉目标区域使用 PolygonHierarchy 的 holes 机制给这个大矩形填充深色半透明材质沿着目标区域的边界添加一道立体墙作为视觉边界3.2 PolygonHierarchy 的层次结构SuperMap iClient3D for WebGL 使用PolygonHierarchy来表示带孔洞的多边形。其结构如下PolygonHierarchy { positions: Cartesian3[], // 外环大矩形的四个角必须闭合 holes: [ // 孔洞数组 { positions: Cartesian3[] }, // 内环目标区域边界必须闭合 { positions: Cartesian3[] } // 如果有多个面每个面作为一个孔洞 ] }关键要点外环必须是顺时针或逆时针的闭合环首尾坐标相同每个孔洞也必须是闭合环孔洞的方向通常与外环相反不过会自动处理支持多层嵌套孔洞里还可以继续嵌套子孔洞四、完整代码实现以下是完整的loadClipLayer函数实现分为 6 个步骤/** * 加载 clipLayer 数据并生成 3D 裁剪遮罩效果 * param {string} datasetUrl - 数据集 URL * param {string} alias - 裁剪区域名称可选 */asyncfunctionloadClipLayer(datasetUrl,alias){constviewerwindow.MapManager.getViewer();if(!viewer)return;try{// 步骤 1获取并解析 GeoJSON 数据 constgeoJsonDataawaitfetchGeoJsonSmart(datasetUrl);if(!geoJsonData||!geoJsonData.features||geoJsonData.features.length0){console.warn(裁剪区域数据为空);return;}// 提取所有面的外环坐标constallRings[];for(constfeatureofgeoJsonData.features){constgeometryfeature.geometry;if(!geometry||!geometry.coordinates)continue;if(geometry.typePolygon){allRings.push(geometry.coordinates[0]);}elseif(geometry.typeMultiPolygon){for(constpolygonofgeometry.coordinates){allRings.push(polygon[0]);}}}if(allRings.length0)return;constboundscalculateBounds(geoJsonData);constnamealias||裁剪区域;// 步骤 2配置地球/场景参数 // 为了让遮罩在高于地面的视角下也能正确显示// 需要关闭地形的深度检测并开启地表半透明度constsceneviewer.scene;constglobescene.globe;globe.depthTestAgainstTerrainfalse;// 关闭地形深度检测scene.skyAtmosphere.showfalse;// 关闭大气阴影globe.translucency.enabledtrue;// 开启地表半透globe.translucency.frontFaceAlphaByDistancenewSuperMap3D.NearFarScalar(400.0,0.0,800.0,1.0);globe.translucency.frontFaceAlphaByDistance.nearValueSuperMap3D.Math.clamp(0.3,0.0,0.1);// 近处地表透明globe.translucency.frontFaceAlphaByDistance.farValue1.0;// 限制相机缩放范围避免拉太远或太近scene.screenSpaceCameraController.minimumZoomDistance10000;scene.screenSpaceCameraController.maximumZoomDistance50000;// 步骤 3计算外覆矩形范围 // 以目标区域为中心向外扩展约 30%最少 0.3 度const[minLon,minLat,maxLon,maxLat]bounds;constlonRangemaxLon-minLon;constlatRangemaxLat-minLat;constextendMath.max(lonRange*0.3,latRange*0.3,0.3);constouterWestminLon-extend;constouterSouthminLat-extend;constouterEastmaxLonextend;constouterNorthmaxLatextend;// 步骤 4构建带孔洞的遮罩多边形 // 外环大矩形constouterPositionsSuperMap3D.Cartesian3.fromDegreesArray([outerWest,outerSouth,outerEast,outerSouth,outerEast,outerNorth,outerWest,outerNorth,outerWest,outerSouth// 闭合环]);// 孔洞所有裁剪面constholes[];for(constringofallRings){constflatRing[];for(constcoordofring){flatRing.push(coord[0],coord[1]);}if(flatRing.length6){holes.push({positions:SuperMap3D.Cartesian3.fromDegreesArray(flatRing)});}}// 创建遮罩实体constmaskEntitynewSuperMap3D.Entity({name:name - 遮罩,polygon:{hierarchy:{positions:outerPositions,holes:holes},material:SuperMap3D.Color.fromCssColorString(#0C1F34).withAlpha(0.85),outline:false,arcType:SuperMap3D.ArcType.RHUMB// 等角航线性能更优}});viewer.entities.add(maskEntity);// 步骤 5构建边界立体墙 // 沿着裁剪边界添加一道竖向墙增强视觉效果if(holes.length0){constwallPositionsholes[0].positions;constwallHeightswallPositions.map(()600);// 墙顶高度constwallMinHeightswallPositions.map(()-600);// 墙底高度constwallEntitynewSuperMap3D.Entity({name:name - 边界墙,wall:{positions:wallPositions,maximumHeights:wallHeights,minimumHeights:wallMinHeights,material:SuperMap3D.Color.fromCssColorString(#6dcdeb).withAlpha(0.8)}});viewer.entities.add(wallEntity);}// 步骤 6飞行定位到裁剪区域 const[west,south,east,north]bounds;viewer.camera.flyTo({destination:SuperMap3D.Rectangle.fromDegrees(west,south,east,north),orientation:{heading:SuperMap3D.Math.toRadians(0),pitch:SuperMap3D.Math.toRadians(-90),roll:0.0},duration:2.0});console.log(裁剪遮罩已加载);}catch(error){console.error(加载裁剪遮罩失败:,error);}}五、关键参数详解5.1 地表透明度设置globe.translucency.frontFaceAlphaByDistancenewSuperMap3D.NearFarScalar(400.0,0.0,800.0,1.0);NearFarScalar定义了基于相机距离的渐变参数参数含义nearDistance: 400.0近端距离米nearValue: 0.0近端透明度0 完全透明farDistance: 800.0远端距离米farValue: 1.0远端透明度1 完全不透明效果在距离地面 400 米内地表逐渐透明800 米外完全恢复不透明。这样在进入地下视角观察时能看到遮罩。5.2 外覆矩形的大小控制constextendMath.max(lonRange*0.3,latRange*0.3,0.3);扩展量没有统一标准可以参考以下原则场景建议扩展量说明小范围1°0.3° ~ 0.5°覆盖周边即可太大影响性能中等范围1°~5°范围的 20%~30%平衡覆盖面积和性能大范围5°范围的 10%~20%大范围本身视野大无需太多扩展5.3 arcType 的选择arcType:SuperMap3D.ArcType.RHUMBGeodesic大圆航线两点之间的最短路径在地球曲率下呈曲线。精度高但计算量大。Rhumb等角航线以恒定方位角连接两点在地图上呈直线。计算量小适合小范围。对于小范围几度以内两者视觉效果几乎无差别建议使用 RHUMB 以获得更好的性能。六、常见问题与解决方案6.1 遮罩没有显示或显示不正确原因最常见的错误是PolygonHierarchy的 holes 参数格式不对。正确写法// ✅ 正确holes 使用 { positions } 对象数组hierarchy:{positions:outerPositions,holes:[{positions:holePositions1},{positions:holePositions2}]}错误写法// ❌ 错误直接传 Cartesian3 数组holes:[holePositions1,holePositions2]// ❌ 错误传 PolygonHierarchy 对象数组hierarchy:newSuperMap3D.PolygonHierarchy(outerPositions,[newSuperMap3D.PolygonHierarchy(holePositions1)])6.2 性能差、卡顿原因外覆矩形范围过大导致需要对数万平方公里的区域进行多边形三角剖分和地形裁剪计算。解决方案缩小外覆范围按目标范围大小的 20%~30% 扩展而不是固定扩展 5° 或 10°使用 RHUMB arcType减少曲线插值计算量避免 height: 0不设置 height 可以让框架自动优化渲染策略减少孔洞顶点数量如果原始数据顶点过密可以适当抽稀6.3 遮罩与地形之间有缝隙原因globe.depthTestAgainstTerrain开启时遮罩与地形会产生深度冲突。解决方案关闭地形深度检测即可globe.depthTestAgainstTerrainfalse;6.4 加载多个裁剪区域如果同时需要多个裁剪区域只需在 holes 数组中添加多个孔洞即可holes:[{positions:area1},// 第一个裁剪面{positions:area2},// 第二个裁剪面{positions:area3}// 第三个裁剪面]七、总结本文介绍了基于 SuperMap iClient3D for WebGL 实现 3D 掩膜效果的方法。核心思路是利用PolygonHierarchy的 holes 机制用一个带孔洞的大多边形来覆盖非目标区域。几个关键要点格式要对holes 必须使用{ positions }对象格式范围要小外覆矩形只需小幅扩展避免性能问题地表要透配合depthTestAgainstTerrain和translucency设置才能看到效果边界要显配合 Wall 实体可以增强视觉引导效果这套方案已经在我们基于 SuperMap iClient3D 的 WebGIS 产品中实际应用在千万级要素的场景下仍能保持流畅的交互体验。