🌍 Three.js × 地理坐标:在地球上画一条银河

如何优雅地将 Three.js 与 Mapbox 联姻

"一切3D图形的壮美,如果不落于地理坐标系上,终究只是漂浮在虚空之中。"


🪐 开场白:两个宇宙的语言

在这片数字宇宙中,Three.js 是一个画家,擅长在三维空间里舞动顶点与光影。

而 Mapbox,是一位地图编舞师,它把地球切割成瓦片,铺展开数据与现实的边界。

你想让你的 3D 模型落在北京天安门广场?

或者想在巴黎铁塔上浮现一只飞龙?

抱歉,Three.js 默认的 (0, 0, 0),可不知道这些地方在哪儿。

于是,我们必须做一件事:让地理坐标和 Three.js 的笛卡尔坐标握手言和


🧭 第一课:地理坐标是怎么回事?

地球上的点,通常使用 经纬度表示:

  • 经度(Longitude) :东经/西经多少度
  • 纬度(Latitude) :北纬/南纬多少度
  • 可选的还有:高度(Altitude / Elevation)

看起来像这样:

ini 复制代码
const lng = 116.3913; // 北京
const lat = 39.9075;
const altitude = 50; // 海拔50米

但在 Three.js 中,坐标却是像这样:

arduino 复制代码
const position = new THREE.Vector3(x, y, z);

经度纬度是地球的语言,Three.js 坐标是线性代数的语言,我们需要一个翻译官。


🌐 第二课:Mapbox 如何翻译地理坐标?

Mapbox 的世界是一张Web Mercator 投影地图 ,地球被拉成了一个矩形。每个地理点,都可以被转换为一个世界坐标

ini 复制代码
const mercator = mapboxgl.MercatorCoordinate.fromLngLat(
  { lng: 116.3913, lat: 39.9075 },
  0 // 海拔高度(单位:米)
);

这个函数返回了一个对象:

yaml 复制代码
{
  x: 0.91576,
  y: 0.48563,
  z: 0
}

这个 x, y, z 就是我们可以在 Three.js 中使用的坐标!

不过,慢着......我们要注意一点:Mapbox 的单位是"米",Three.js 的单位是"自己说了算"

所以我们还需要:统一单位比例(scaling factor)


🛠️ 第三课:Three.js 模型落地地球实战

下面是一个 Three.js + Mapbox 联动的完整流程。

🎥 初始化 Mapbox 场景

php 复制代码
mapboxgl.accessToken = '你的 Mapbox Token';

const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v11',
  center: [116.3913, 39.9075],
  zoom: 16,
  pitch: 60,
  bearing: -17,
  antialias: true
});

🎮 添加 Three.js 场景

ini 复制代码
map.on('style.load', () => {
  const customLayer = {
    id: '3d-model-layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function (map, gl) {
      this.camera = new THREE.Camera();
      this.scene = new THREE.Scene();

      const light = new THREE.DirectionalLight(0xffffff);
      light.position.set(0, -70, 100).normalize();
      this.scene.add(light);

      const geometry = new THREE.BoxGeometry(10, 10, 10);
      const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
      const cube = new THREE.Mesh(geometry, material);

      // 经纬度转地图单位坐标
      const mercator = mapboxgl.MercatorCoordinate.fromLngLat(
        { lng: 116.3913, lat: 39.9075 },
        0
      );

      const scale = mercator.meterInMercatorCoordinateUnits(); // 米转单位

      cube.position.set(mercator.x, mercator.y, mercator.z);
      cube.scale.set(scale, scale, scale);

      this.scene.add(cube);

      this.renderer = new THREE.WebGLRenderer({ canvas: map.getCanvas(), context: gl, antialias: true });
      this.renderer.autoClear = false;
    },
    render: function (gl, matrix) {
      const m = new THREE.Matrix4().fromArray(matrix);
      this.camera.projectionMatrix = m;
      this.renderer.state.reset();
      this.renderer.render(this.scene, this.camera);
      map.triggerRepaint();
    }
  };

  map.addLayer(customLayer);
});

🧪 实验小结

"当一座三维城堡落在地图中,它才有了现实世界的归属。"

本例中,我们通过 Mapbox 的 MercatorCoordinate 把经纬度转换为 Three.js 使用的线性坐标,并使用 scale = meterInMercatorCoordinateUnits() 来保证大小看起来正确。


🧠 拓展思考:为什么它行得通?

  • 地图的空间系统和 Three.js 的世界空间,其实都是数学模型;
  • 投影系统(Web Mercator)把地球表面摊平;
  • 只要单位对齐、坐标转换无误,Two Worlds 就可以 Merge。

🧙 Bonus:地球是圆的,但你画的是平的?

是的,这里使用的是 Web Mercator 投影,本质上是 局部平面展开

所以在小范围内非常准确,但如果你试图把 3D 模型放在南极和北极之间飞行......它就开始"变形"了。


🎇 结语:宇宙中最浪漫的交汇

"图形的浪漫在于幻想,地图的浪漫在于现实。而当你让三维图形落地在真实世界,那是浪漫与理性最优雅的交融。"

从此,Three.js 不再漂浮在抽象空间,而是拥有了家,落在了经纬度上。

你可以在东京摆一尊观音,在纽约放一条龙,或在你家门口放一艘飞船。

相关推荐
CDwenhuohuo7 分钟前
滚动提示组件
java·前端·javascript
PineappleCoder14 分钟前
性能优化与状态管理:React的“加速器”与“指挥家”
前端·react.js
_一两风16 分钟前
深入理解React中的虚拟DOM与Diff算法
前端
t_hj21 分钟前
Scrapy
前端·数据库·scrapy
小唐快跑24 分钟前
🚀 2025 VS Code前端开发环境搭建指南:从入门到精通(含插件推荐+配置代码)
前端
bug_kada24 分钟前
全家桶开发之Zustand:轻量级状态管理
前端·react.js
用泥种荷花27 分钟前
【记一忘三二】脚手架学习
前端
庄毕楠1 小时前
【Chrome】下载chromedriver的地址
前端·chrome
大猫会长1 小时前
关闭chrome自带的跨域限制,简化本地开发
前端·chrome
excel1 小时前
JavaScript 中使用 Set 对数组去重并排序的简洁示例
前端