如何优雅地将 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 不再漂浮在抽象空间,而是拥有了家,落在了经纬度上。
你可以在东京摆一尊观音,在纽约放一条龙,或在你家门口放一艘飞船。