坦克打无人机模拟(three-tile 地形)

本文分享一个用 three-tile 渲染真实地形的 3D 演示:坦克在山地中巡逻并实时锁定最近的无人机, 显示坦克行驶轨迹炮弹弹道预测(抛物线虚线 + 发射命中特效)。坦克、无人机用简单几何体示意。

表面是做一个坦克打无人机的可视化,实际上......你往下看就懂了。

先看效果

先说你打开页面会看到啥: 一座真实的山地,坦克趴在坡上沿着椭圆路线慢悠悠巡逻,身后拖着一条金色轨迹,记着它走过的每一个坑洼;天上几架无人机在绕圈飞;坦克每隔几秒就锁定最近的那个,炮口甩出一道青色虚线------这是弹道预测------紧接着一枚黄色炮弹顺着弧线飞出去,命中的瞬间炸开一团橙色闪光(后期可以加上,之前那个爆炸特效)。

当然了,实际上得先等地图加载出来才能看到。

需要说明的是,坦克和无人机都是几何体凑的,纯示意,别当军事模拟。真正想跟你聊的,是地形那块。

既然都要做可视化了,地形要是还用个平面糊弄,那也太没意思了。所以地形我直接拉真实地形。

地形:本来最头大,结果几行就搞定

说真的,做"真实地形"这四个字,从前最劝退我。你要自己搞高程数据、瓦片切分、LOD 分层、墨卡托反算坐标、法线重算......一套下来热情就没了。

后来我摸到了 three-tile ------一个基于 Three.js 的 3D 瓦片地形引擎。它的思路特别克制:你给它一个影像源 和一个高程源,剩下的瓦片加载、LOD、坐标转换它全包了。

先看最关键的几行:

js 复制代码
import * as plugin from "three-tile/plugin";

// 影像源 + 高程(DEM)源,这里用 ArcGIS
const imgSource = new plugin.ArcGisSource({
  url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/",
  // 瓦片地址模板
});
const demSource = new plugin.ArcGisDemSource({
  url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer/tile/",
});

// 给个中心经度,开个最小层级,地图就出来了
const map = plugin.TileMap.create({
  imgSource,
  demSource,
  lon0: 116.39,   // 场景中心经度(北京附近随便挑的)
  minLevel: 8,
});
map.rotateX(-Math.PI / 2); // 转到 xz 平面,y 朝上,跟 Three.js 习惯一致
scene.add(map);

// 主循环里每帧一句,LOD / 瓦片调度全自动
map.update(camera);

就这些。你没看错,地形起伏、瓦片按需加载、远处降级,库全管了。你只管往 sceneadd,然后安心去摆你的坦克。

坐标互转是它最让我舒服的地方。做贴地、做轨迹、做锁定目标,全靠这俩:

js 复制代码
const world = map.geo2world(geo); // 经纬度 -> 世界坐标
const geo   = map.world2geo(world); // 世界坐标 -> 经纬度

贴地取高度也只要一句话:

js 复制代码
const geo = map.world2geo(worldPos);
const info = map.getLocalInfoFromGeo(geo);
const groundY = info.location.y; // 脚下地形高度,直接拿来贴地

我本来以为贴地得自己采样高程图、插值,结果库一句话就吐出来了。这种"本来以为要写一堆,结果没有"的感觉,后面还会反复出现。

坦克与行驶轨迹

地形有了,那就顺手做个坦克示意。我们的坦克 = 身子(盒子)+ 炮管(圆柱)+ 履带(两排小方块糊弄一下)。示意嘛,不丢人。

重点是让它趴在山坡上巡逻 ,而不是浮在半空。做法其实很朴素:定义一条中心附近的椭圆路径,每帧把路径点从经纬度变成世界坐标,再用 getLocalInfoFromGeo 取地面高度贴上------坦克就老老实实跟着地势起伏了。

js 复制代码
// 巡逻路径:以场景中心为基准的椭圆
const lon = lon0 + rx * Math.cos(t);
const lat = lat0 + ry * Math.sin(t);
const world = map.geo2world(new THREE.Vector3(lon, lat, 0));
const info  = map.getLocalInfoFromGeo(map.world2geo(world));

tank.group.position.set(world.x, info.location.y + tank.alt, world.z);
// 朝向也顺手用前后两点算一下,让炮管朝着前进方向

行驶轨迹更简单,每帧把当前位置喂给一条累加的 Line 就行:

js 复制代码
trailPoints.push(world.x, info.location.y + 0.5, world.z);
trail.geometry.setFromPoints(trailPoints); // 金色线条就出来了

就这么个思路。至于坦克怎么"真的"开起来、履带怎么转(之前有写过,但是在这个大世界尺度上,根本看不清履带),示意版我直接省略了------今天主要是想说地形和贴地,那部分不重要。

无人机

无人机就是个盒子加四个旋翼(细圆柱),飞在「贴地高度 + 巡航高度」上面。多来几架,各自绕不同的圈,供坦克挑最近的锁。代码没啥好讲的,几何体和 position 的事。

炮弹弹道预测

这块才是今天的小高潮(其实也不难)。坦克锁定最近那架无人机,算出炮口到目标的水平距离,按抛物线反推一个初速度 v0,保证顶点高度随距离抬高------这样远的目标弹道高、近的目标弹道平,看着就合理。

js 复制代码
// 已知水平距离 d、重力 g,取一个顶点高度 h,反解 v0
const v0 = Math.sqrt(g * (h + Math.sqrt(h * h + (d * d) / 4)));
// 沿抛物线采样一堆点 -> 青色虚线,就是"弹道预测"

到点就发射一枚黄色炮弹,沿这条弧线飞,命中播放橙色闪光。虚线预测 + 实弹飞行,视觉上就有那味儿了。当然了,就按这么计算的话,那肯定是射不中的,需要提前预判无人机的轨迹才有可能射中。除非是更高级的导弹,发射出去之后还能控制一下方向的。

思路就是这么个思路。

为什么是 three-tile,而不是 Mapbox / Cesium

做这个之前我也纠结过。地形方案翻来翻去就那几个,挨个想过一遍:

  • Mapbox:确实轻,还自带 custom three.js layer,能把 three 场景叠在它上面。但资料真不多,社区也小,真要改点什么、踩个坑,搜半天搜不到答案。而且它的「层」是阉割版的三维能力,想自由放飞其实没那么灵活。
  • Cesium:强大是真强大,地球级地形、时间轴、一堆影像格式都现成。但它太重了,而且它本身是一套独立的渲染管线,跟 three 揉一块儿总有点别扭。要么用双canvas简单同步相机,要么共享 WebGL 上下文 + 自定义 Primitive 那套桥接,心智负担不小。

我就一个朴素诉求:我想用 three,纯纯的 three,地形只是我场景里的一层。 three-tile 正好长在这条需求上------它本身就是 Three.js 的一个扩展,地形就是一堆普通 Mesh,你爱往场景里加啥加啥;坐标互转、贴地取高度都是现成 API,没有第二套渲染体系要你去适配。

所以如果你也用 three、又想搞地形,真心可以考虑 three-tile。

收尾,以及......顺便安利一下

坦克打无人机本身没啥技术含量:几何体拼一拼、轨迹记一记、抛物线解一解,都不难。真正让我省了大劲的,是地形------本来最怕的真实地形那堆瓦片、高程、LOD,three-tile 几行就拉起来了,坐标互转还特别顺手,贴地取高度一句话的事。上面也说了,比起 Mapbox 的资料少、Cesium 的笨重,它刚好卡在「用 three 又想要真实地形」这个舒服的位子上。

所以如果你也在 Three.js 里折腾真实地形、做数字孪生、做地理相关的可视化,真心建议去瞅瞅 three-tile 。它不是那种只给个 demo 就跑路的玩具:瓦片调度、DEM 高程、多数据源(ArcGIS / 天地图 / Mapbox / Bing ......换源基本就是换个 Source 类)都给你管好了,API 也克制,不会一上来就给你一百个配置项劝退。

链接我放评论区了,感兴趣的自取。用熟了你会发现,以前那些"地形好难"的心理负担,其实只是没找对轮子。

希望这篇能给在 Three.js 里折腾地形、折腾可视化的你,一点参考和一点偷懒的灵感。

相关推荐
hunterandroid1 小时前
[鸿蒙从零到一] HarmonyOS 任务调度与并发模型实战:taskpool、Worker 与可取消任务
前端
颜进强1 小时前
前端看后端 14:什么是 CORS?
前端·后端
sugar__salt1 小时前
Vue3 自定义指令与插槽(Slot)技术详解
前端·javascript·vue.js·前端框架·vue
Csvn2 小时前
🎯 Flex 布局的 `min-width: auto` 陷阱:为什么内容总是撑破容器?
前端
果然_2 小时前
纯前端图片压缩怎么做?不用上传服务器,浏览器里跑完所有逻辑
前端
Csvn2 小时前
🖼️ OffscreenCanvas:把 Canvas 绘制搬出主线程,动画卡顿的终极解药
前端
用户69371750013842 小时前
了解一下 Agent Harness
android·前端·后端
swipe2 小时前
16|(前端转全栈)前端人排查后端问题:curl、traceId、日志、MySQL、Redis 怎么用?
前端·后端·面试
anyup2 小时前
uni-app 没有根组件?仅需几行代码实现全局 Toast 和 Modal
前端·架构·uni-app