如何在启用3D地形的react-map-gl实例中添加自定义3D物体?
在启用3D地形的react-map-gl中添加自定义3D物体的方案
方案一:Mapbox GL JS v2 自定义图层 + Three.js
react-map-gl本质是Mapbox GL JS的React封装,直接用Mapbox v2的自定义图层能力结合Three.js就能实现需求,完全支持3D地形:
- 自定义图层需实现Mapbox的
CustomLayerInterface接口,核心是复用Mapbox的WebGL上下文,避免重复创建渲染环境 - 通过Mapbox的
queryTerrainElevation方法获取指定经纬度的地形海拔,将经纬度坐标转换为Three.js世界坐标,让3D物体贴合地形 - 在react-map-gl的
<Map>组件中注册这个自定义图层
简单代码示例:
import { useRef, useEffect } from 'react'; import Map from 'react-map-gl'; import * as THREE from 'three'; class ThreeCustomLayer { constructor(id, map, options) { this.id = id; this.type = 'custom'; this.renderingMode = '3d'; this.map = map; this.options = options; this.scene = null; this.camera = null; this.renderer = null; this.mesh = null; } onAdd(map, gl) { this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(); this.renderer = new THREE.WebGLRenderer({ canvas: gl.canvas, context: gl, antialias: true }); this.renderer.autoClear = false; // 创建3D立方体 const geometry = new THREE.BoxGeometry(50, 50, 50); const material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); this.mesh = new THREE.Mesh(geometry, material); // 获取地形海拔并设置物体位置(让立方体顶部和地形齐平) const { lng, lat } = this.options.position; const elevation = map.queryTerrainElevation([lng, lat]); const mercatorCoord = map.project([lng, lat]); this.mesh.position.set(mercatorCoord.x, mercatorCoord.y, elevation + 25); this.scene.add(this.mesh); } render(gl, matrix) { const cameraMatrix = new THREE.Matrix4().fromArray(matrix); this.camera.projectionMatrix = cameraMatrix; this.renderer.state.reset(); this.renderer.render(this.scene, this.camera); this.map.triggerRepaint(); } } function MapWith3DObject() { const mapRef = useRef(null); useEffect(() => { if (mapRef.current) { const map = mapRef.current.getMap(); const customLayer = new ThreeCustomLayer('three-layer', map, { position: { lng: -122.4194, lat: 37.7749 } // 示例坐标:旧金山 }); map.addLayer(customLayer); return () => { map.removeLayer('three-layer'); }; } }, []); return ( <Map ref={mapRef} initialViewState={{ longitude: -122.4194, latitude: 37.7749, zoom: 14, pitch: 60, bearing: 0 }} style={{ width: '100vw', height: '100vh' }} mapStyle="mapbox://styles/mapbox/outdoors-v12" mapboxAccessToken={process.env.REACT_APP_MAPBOX_TOKEN} terrain={{ source: 'mapbox-dem', exaggeration: 1.5 }} /> ); } export default MapWith3DObject;
方案二:使用deck.gl的3D图层
deck.gl和react-map-gl深度兼容,支持直接加载3D模型并贴合Mapbox v2地形,无需手动处理WebGL上下文:
- 用
ScenegraphLayer加载glTF/GLB格式的3D模型,自动适配地形高度 - 通过
elevationScale参数控制模型相对于地形的高度,getPosition指定模型的经纬度坐标
示例代码片段:
import { Map } from 'react-map-gl'; import { ScenegraphLayer } from '@deck.gl/mapbox'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; // 预加载3D模型 let model; const loader = new GLTFLoader(); loader.load('/path/to/your/model.glb', (gltf) => { model = gltf.scene; }); function MapWithDeckGL3DObject() { const layers = [ model && new ScenegraphLayer({ id: 'scenegraph-layer', data: [{ position: [-122.4194, 37.7749] }], scenegraph: model, getPosition: d => d.position, getScale: [10, 10, 10], // 缩放模型大小 elevationScale: 1, // 让模型贴合地形 pickable: true }) ].filter(Boolean); return ( <Map initialViewState={{ longitude: -122.4194, latitude: 37.7749, zoom: 14, pitch: 60, bearing: 0 }} style={{ width: '100vw', height: '100vh' }} mapStyle="mapbox://styles/mapbox/outdoors-v12" mapboxAccessToken={process.env.REACT_APP_MAPBOX_TOKEN} terrain={{ source: 'mapbox-dem', exaggeration: 1.5 }} layers={layers} /> ); } export default MapWithDeckGL3DObject;
关键注意事项
- 坐标转换:Mapbox使用墨卡托投影,需将经纬度转换为Mapbox的屏幕/世界坐标后再映射到Three.js空间
- 地形高度更新:当地形加载完成或相机移动时,需重新查询地形海拔并更新3D物体的Z轴位置
- 性能优化:复用WebGL上下文、合并3D模型几何体、避免在每一帧重复创建对象
内容的提问来源于stack exchange,提问作者bitTorin
相关产品推荐
相关产品推荐

