用Three.js打造炫酷3D地图的实战经验与性能优化探索

喧丹🍀 交互 阅读 961
赞 23 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

最近做了一个3D地图的项目,需求是展示一个城市的地标建筑分布。实话说,刚开始我还挺头疼的,毕竟3D地图这玩意儿听起来就复杂,但折腾了几天后发现,其实也没那么难。

用Three.js打造炫酷3D地图的实战经验与性能优化探索

核心思路就是用Three.js加载地图模型,然后结合Mapbox或者Cesium来实现交互。下面我直接上代码,先给大家一个直观的感受:

import * as THREE from 'three';
import { MapboxLayer } from '@deck.gl/mapbox';
import { GeoJsonLayer } from '@deck.gl/layers';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 加载3D模型
const loader = new THREE.GLTFLoader();
loader.load('https://jztheme.com/assets/models/city.gltf', (gltf) => {
    scene.add(gltf.scene);
});

// 设置光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 10, 10).normalize();
scene.add(light);

camera.position.z = 5;

function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

这段代码的核心是用Three.js加载一个GLTF格式的3D城市模型,同时设置光源和摄像机位置。亲测有效,建议直接用这种方式快速搭建基础场景。

这个场景最好用

如果你的需求是展示某个具体区域的3D地图,比如一个城市的地标建筑分布,那么Mapbox + Deck.gl组合绝对是最合适的。为什么?因为它轻量、易扩展,而且支持GeoJSON数据格式,方便你快速加载地标信息。

下面是一个完整的例子:

import mapboxgl from 'mapbox-gl';
import { MapboxLayer } from '@deck.gl/mapbox';
import { GeoJsonLayer } from '@deck.gl/layers';

mapboxgl.accessToken = 'your-mapbox-access-token';

const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [120.1938, 30.2653], // 杭州经纬度
    zoom: 12,
    pitch: 45,
});

const geojsonLayer = new MapboxLayer({
    id: 'geojson-layer',
    type: GeoJsonLayer,
    data: 'https://jztheme.com/data/landmarks.geojson',
    pickable: true,
    stroked: false,
    filled: true,
    extruded: true,
    wireframe: true,
    getElevation: f => f.properties.height || 0,
    getFillColor: [255, 0, 0],
});

map.on('load', () => {
    map.addLayer(geojsonLayer);
});

这里有几个关键点:extruded属性可以让GeoJSON数据以3D形式展示,getElevation可以根据地标高度动态调整建筑物的高度。杭州的地标数据我是从公开API获取的,你们也可以换成自己的数据。

踩坑提醒:这三点一定注意

在开发过程中,我踩了不少坑,这里总结一下,希望能帮你们少走弯路。

  • GLTF模型加载失败:如果你用的是自定义的GLTF模型,记得检查模型路径和跨域问题。我当时用本地文件测试时,浏览器直接报错,后来改成了HTTPS的CDN链接才解决。
  • 性能优化问题:如果地图上有大量地标,渲染会变得很卡。我的解决方案是限制地标数量,只加载当前视口范围内的数据,其他地标延迟加载。
  • 相机视角控制:Three.js默认的相机视角可能不符合实际需求,我花了不少时间调整camera.positioncamera.lookAt参数,最终才找到一个比较舒服的角度。

顺便提一句,调试3D地图的时候,推荐用Chrome的Performance工具查看帧率,能帮你快速定位性能瓶颈。

高级技巧:如何让交互更丝滑

除了展示静态的3D地图,用户交互也是很重要的。比如,点击某个地标后弹出详细信息,或者鼠标悬停时高亮显示。这些功能可以通过Raycaster实现:

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

window.addEventListener('click', (event) => {
    // 将鼠标位置归一化为设备坐标 (-1到+1)
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    // 更新射线
    raycaster.setFromCamera(mouse, camera);

    // 计算与射线相交的对象
    const intersects = raycaster.intersectObjects(scene.children);
    if (intersects.length > 0) {
        console.log('点击到了地标:', intersects[0].object);
    }
});

这段代码的逻辑很简单:监听鼠标点击事件,通过Raycaster检测点击位置是否与3D对象相交。如果相交,就可以拿到对应的对象,进而触发更多操作。

不过这里有个坑要注意:如果你的地图上有多个层级的3D对象,可能会导致误触。我的解决方法是在每个对象上加一个唯一的ID,点击时根据ID判断。

这个技术的拓展用法还有很多

以上是我个人对3D地图的完整讲解,有更优的实现方式欢迎评论区交流。说实话,3D地图这块水还挺深的,比如结合AR/VR技术、实时天气模拟、动态光照等等,这些都可以作为后续的拓展方向。

总之,这个技术的拓展用法还有很多,后续会继续分享这类博客。希望这篇文章能帮到正在折腾3D地图的你!

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论