3dtiles加载时被地形遮挡问题研究及处理思路

倾斜摄影「穿模」了?Cesium 里做一次 3D Tiles × 地形交叉检测与压平

面向 CesiumJS / 实景三维开发者的一篇实战分享。


效果展示

原始3dtiles加载会被地形遮挡
在这里插入图片描述
处理后效果
在这里插入图片描述
只压平突出区域
在这里插入图片描述
动态效果
在这里插入图片描述

写在前面

做三维地球时,几乎都会遇到同一类画面:

  • 倾斜摄影(3D Tiles)叠在地形上;
  • 有的地方建筑「沉」进山里;
  • 有的地方山体从屋顶「冒」出来;
  • 相机一转,穿模更明显。
    很多人第一反应是:把模型整体抬高/压低几米
    这在小范围、高程误差均匀时够用;一旦地形起伏大、模型底面也不平,整体平移就治不好局部穿模。
    更稳的思路是:
  1. 先找出「地形相对模型底部突出」的那些区域
  2. 只把这些区域的地形,压到模型底附近
  3. 边缘做过渡,避免出现刀切一样的台阶。
    本文分享的就是这条链路:
    交叉检测 → 生成压平面 → GPU 地形压平

一句话理解问题

把地形想成一张弹性橡胶垫,把 3D Tiles 想成放在垫子上的积木。
穿模 = 垫子某处鼓得太高,把积木「顶穿」了
我们要做的不是把整张垫子压扁,而是:

找到鼓包位置,只把鼓包按下去,按到积木底面附近,边缘再缓缓过渡回原地形。


整体流程(先看地图)

加载 3D Tiles + 地形
        │
        ▼
① 得到模型水平覆盖(脚印 / footprint)
        │
        ▼
② 在覆盖范围内,逐格比对:
   地形高程  vs  模型表面/底部高程
        │
        ▼
③ 地形高出模型超过阈值 → 记为「突出区」
        │
        ▼
④ 把突出区轮廓交给地形着色器
        │
        ▼
⑤ GPU 把对应地形高度压到目标高程(带过渡)

可以记成两步 API:

await flatten.detect();       // 找出要挖的地方
await flatten.apply();        // 真正压平
// 或一键
await flatten.detectAndApply();

为什么不能只靠「模型整体抬高」

做法优点缺点
modelMatrix 整体抬高实现简单全局一刀切,起伏地形仍会局部穿模
改地形源数据(离线重切)一劳永逸周期长、多模型难复用
运行时检测 + GPU 压平按区域处理、可调参、可撤销要算网格、吃一点性能
如果产品里经常换模型、换区域,运行时方案更适合调试和交付

检测:怎么知道「哪里地形太高」

核心不是拍脑袋选一块多边形,而是用数据说话。

1)先拿到模型覆盖范围

对每个水平采样点,判断它是否落在 3D Tiles 覆盖下。常见两条路径:
快速路径(优先)
解析 tileset 根/子节点的 b3dm / glb 网格,在 Worker 里做水平投影栅格化。
优点:不依赖主场景把所有瓦片刷完,速度快。
兜底路径
快速路径失败时(复杂结构、压缩格式等),用离屏正交相机渲染模型,从像素/深度里读覆盖与高程。

工程经验:很多 CesiumLab 导出的模型,根节点没有 mesh,真正内容在子 NoLod_*.glb 里。
只解析 root content 会误判「没模型」——要递归收集子 tile。

2)再和地形比高低

在覆盖网格上:

  • 读该格的模型高程(更贴近「底面/表面」);
  • sampleTerrainMostDetailed 读该格的地形高程
  • 地形高 − 模型高 > 阈值,判定为突出。
    阈值(heightThresholdMeters)可以理解为「允许的误差带」。
    设成 0 更严格,设成 0.1~0.5 可以过滤噪声。

3)内缩一下,少误伤边缘

模型外轮廓附近,采样噪声更大,也更容易把「正常坡地」算进去。
所以通常会做 boundaryInset(检测内缩):从覆盖边缘往里缩几米再比对。
直观效果:
少挖一点边缘,结果更稳。

4)输出什么?

检测结果不是一张截图,而是一组可用于压平的几何:

  • 突出区多边形(footprints);
  • 每块对应的目标高程(可以不同块不同高度);
  • 挖深、面积等统计信息(方便 GUI / 日志)。
    有了这些,压平阶段就不用再猜「挖哪里」。

压平:为什么放在 GPU 做

如果用 Entity 挖洞、或者每帧 CPU 改地形 mesh,成本和闪烁都难控。
更合适的做法是:

  1. 把 footprint 做成一张/一组 mask(带边缘过渡);
  2. 在地形材质/着色器里注入:
    最终高度 = mix(原地形高, 目标高, mask)
  3. 过渡带用距离场渐变,避免硬边。
    用户看到的是:
    模型底下的山被「按」下去了,周围还连着原来的地形。
    并且可以:
  • 多块同时压;
  • 每块目标高不同;
  • remove() 一键撤销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值