用Three.js打造炫酷3D地图的实战经验与性能优化探索
先看效果,再看代码
最近做了一个3D地图的项目,需求是展示一个城市的地标建筑分布。实话说,刚开始我还挺头疼的,毕竟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.position和camera.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地图的你!

暂无评论