对于大规模城市场景,在cesium端流行的方法是,使用工具将GIS数据shp建筑白模或geojson白模直接转换成3dtiles格式。然后使用着色器代码对白模贴图,常见的就是渐变色贴图效果。

但是这种贴图效果不能很好的表达真实建筑纹理。这种网格mesh也不能很好表达建筑属性,比如屋顶属性。因为shp直接转换3dtiles并没有屋顶结构。现在面临的问题,客户要求 1、没有无人机倾斜摄影数据,直接基于白模贴图 2、能部分独立贴图,部分批量贴图,贴图要能反应建筑纹理。 3、实现L3级别的mesh结构,包含屋顶结构。 4、能导出3dtiles格式,和地理对齐 5、能在3dtiles中拾取任意楼栋 buffer被拉满了。我们来尝试解决。 下图是上海市60w+建筑数据

其中部分建筑使用了独立的材质贴图,和设置了屋顶结构。如图白色贴图建筑 和 旁边的低矮建筑。

第一步 导出 gltf贴图模型序列文件,延续原有功能

设置导出参数。选择白模批量贴图方案 《实景风城市》。设置draco压缩,模型切片会更小。

第二步,在导出后生成倾斜摄影的索引文件tileset.json文件. 3dtiles是cesium推出的数据标准,支持直接将gltf模型作为切片加载。

同时导出了基于cesium的3dtiles加载和单体化拾取建筑代码 。代码中详细注释了cesium点击事件,楼栋单体化高亮代码。
Cesium.Cesium3DTileset.fromUrl(
'tileset.json',
{
maximumScreenSpaceError: 1.0, // 降低屏幕空间误差,强制高 LOD
skipLevelOfDetail: false, // 禁用 LOD 跳跃
}
).then(tileset => {
_this.viewer.scene.primitives.add(tileset);
//模型简单提亮
tileset.style = new Cesium.Cesium3DTileStyle({
color: "color() * 1.1"
});
//鼠标点击事件处理
const handler = new Cesium.ScreenSpaceEventHandler(_this.viewer.scene.canvas);
handler.setInputAction(async function(click) {
const pickedObject = _this.viewer.scene.pick(click.position);
if(Cesium.defined(pickedObject) && pickedObject['id']&&pickedObject['id']['geojson']){
//点击了单体化围墙
alert(JSON.stringify(pickedObject['id']['geojson']))
}else if (pickedObject && pickedObject.primitive==tileset && pickedObject.content) {
const content = pickedObject.content;
const uri = content.url || content._url || content.uri;
if (uri) {
// 提取 b3dm 文件名
const b3dmName = uri.split('/').pop().split('?')[0]; // 防缓存参数
var modelinfo = await (await fetch('modelinfo.json')).json();
const worldPoint = _this.viewer.scene.pickPosition(click.position);
pickedObject.primitive.userData = modelinfo[b3dmName];
if (Cesium.defined(worldPoint)) {
//1、获取模型点击的经纬度和高度。
var modellnglat = _this.gltfInstance.GetClickLnglat(_this.viewer, worldPoint, pickedObject)
/*
2、查询点所在的轮廓。
!!!演示文件通过前端来查询点在轮廓内。!!!实际应用中需通过后端查询,避免GIS数据泄漏!
内置属性解释
https://i1.hdslb.com/bfs/article/55509bc62ca58bb6a8463819342258cd98081c25.png@1192w.avif
如果一个建筑体包含多个gis轮廓数据。在Geobuilding软件内对建筑体的gis数据【选择框】-打组。打组后这些gis数据有相同的属性值groupid
根据groupid可找到关联数据
*/
let geojson = await (await fetch('geojson/' + pickedObject.primitive.userData.geojson)).text();
let result = geojson.split("\n").map(function(r) {return JSON.parse(r);});
var hitgeo,pointCircle = turf.circle(turf.point([modellnglat.lng, modellnglat.lat]), 1, {units: "meters"});
for (let i = 0; i < result.length; i++) {
//检查点是否在轮廓内。点扩大成圆判断相交,防止边缘点计算误差
if (turf.booleanIntersects(pointCircle, result[i])){
var demheight = result[i].properties.demheight;
var clickheight = modellnglat.height - demheight;
if(clickheight>= result[i].properties.pfh*result[i].properties.minfloor && clickheight<= result[i].properties.roofHeight+result[i].properties.pfh*result[i].properties.minfloor + (result[i].properties.wfh*result[i].properties.floor)){
hitgeo = result[i]
break;
}
}
}
if(!hitgeo) {
console.log("没有找到轮廓")
return;
}
//原始geojson数据
//alert(JSON.stringify(hitgeo))
//3、对hitgeo并进行偏移转换(相对于模型)。
hitgeo = _this.gltfInstance.TransFeature(hitgeo, pickedObject);
//4、将geojson转换成cesium世界坐标,添加单体化高亮围墙。
var wallpos = turf.buffer(hitgeo, 1, {
units: "meters"
}).geometry.coordinates[0].map(function(z) {
return Cesium.Cartesian3.fromDegrees(z[0], z[1], hitgeo.properties.gltfbheight)
})
if (wall) wall.remove();
wall = new WallObject.FlowWall(_this.viewer,wallpos,{
copyright:'geobuilding',
wallHeight: hitgeo.properties.wfh*hitgeo.properties.floor,
wallColor: Cesium.Color.fromCssColorString('rgba(0, 255, 26, 0.3)'),
duration: 1000,
materialType: 3,
}
);
wall.flowWallEntity.geojson = hitgeo;
}else {
console.log('无法获取点击点的世界坐标');
}
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
});

打开demo4查看效果是否符合预期。白模有了更丰富的贴图。

拉近查看自定义贴图部分。可以看到材质和屋顶都准确复现。贴图包含了底商+楼面+楼面顶端+屋顶。

当点击场景中的楼栋时,建筑高亮。

和地理引擎完美对齐

基于高性能mesh结构,经过测试加载60w+贴图建筑也能丝滑加载浏览,游刃有余。 现在我们成功实现了L3级别的数字城市效果,并且支持单体化操作,可应用于城市精细化治理,比如网格化,一标三实,以房找人等场景。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。