Draw GLTF with mapbox-gl
结合 mapbox-gl 的自定义图层渲染 GLTF.
tip
主要因为有地形需要操作一下地图,猜测是为了重新更新矩阵。
示例
Result
Loading...
Live Editor
function render(props) { const refDom = useRef(null); const store = leva.useCreateStore(); class MeshLayer { constructor (id) { this.id = id; this.type = 'custom'; this.renderingMode = '3d'; } get camera () { return this.sync.camera; } updateCamera() { this.sync.update(); } projectToWorld (coord) { const mc = mbve.fromLngLat({ lng: coord[0], lat: coord[1], }, coord[2]); return [mc.x, mc.y, mc.z]; } async loadGLTF ( file = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF-Binary/DamagedHelmet.glb', ) { const data = await GLTFLoader.load(this.renderer, file); return data; } onAdd (map, gl) { this.renderer = new Renderer(gl, { autoClear: false, }); this.scene = new Scene(); this.sync = new mbve.CameraSync(map, 'perspective', this.scene); this.updateCamera = this.updateCamera.bind(this); map.on('move', this.updateCamera); map.on('resize', this.updateCamera); this.loadGLTF().then((gltfObject) => { this.scene.children.forEach((child) => child.setParent(null)); const s = gltfObject.scene || gltfObject.scenes[0]; const coords = [6.58968, 45.39701, 1913.2236908406628]; // const coords = [6.58968, 45.39701, 0]; const center = this.projectToWorld(coords); s[0].position.set( center[0], center[1], center[2] + mbve.mercatorZfromAltitude(100, 45.39701), ); s[0].quaternion.fromAxisAngle( new Vector3(0, 1, 0), (180 * Math.PI) / 180, ); const scale = mbve.meterInMercatorCoordinateUnits(center[1]) * 100; s[0].scale.set(scale, scale, scale); // 为什么在自定义图层中FrontFace不一样,猜测可能与顶点数据组织有关系 s[0].children[0].program.setStates({ frontFace: this.renderer.gl.CW, }); s.forEach((root) => { this.scene.add(root); }); console.log(this.scene, scale); }); this.updateCamera(); } onRemove () { } prerender () { } render () { this.scene.worldMatrixNeedsUpdate = true; this.renderer.render({ scene: this.scene, camera: this.camera, }); this.renderer.resetState(); } } const init = () => { mapboxgl.accessToken = 'pk.eyJ1IjoidTEwaW50IiwiYSI6InQtMnZvTkEifQ.c8mhXquPE7_xoB3P4Ag8cA'; if (mapboxgl.Map.prototype._setupPainter.toString().indexOf('webgl2') === -1) { const setupPainterPrev = mapboxgl.Map.prototype._setupPainter; mapboxgl.Map.prototype._setupPainter = function () { const getContextPrev = this._canvas.getContext; this._canvas.getContext = function (name, attrib) { return getContextPrev.apply(this, ['webgl2', attrib]) || getContextPrev.apply(this, ['webgl', attrib]) || getContextPrev.apply(this, ['experimental-webgl', attrib]); }; setupPainterPrev.apply(this); this._canvas.getContext = getContextPrev; }; } const map = new mapboxgl.Map({ container: refDom.current, zoom: 13.954629345329566, center: [6.58968, 45.39701], pitch: 77.00000000000013, bearing: 0, style: 'mapbox://styles/mapbox/satellite-streets-v12', antialias: true, projection: 'mercator', // projection: 'globe', }); map.on('load', () => { map.addSource('mapbox-dem', { 'type': 'raster-dem', 'url': 'mapbox://mapbox.mapbox-terrain-dem-v1', 'tileSize': 512, 'maxzoom': 14 }); // add the DEM source as a terrain layer with exaggerated height map.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1 }); const layer = new MeshLayer('mesh'); map.addLayer(layer); window.layer = layer; }); window.map = map; return map; } useEffect(() => { const map = init(); return () => { console.log(map); }; }, []); return ( <div className="live-wrap"> <div className="leva-wrap"> <Leva collapsed fill ></Leva> <LevaPanel collapsed store={store} fill></LevaPanel> </div> <div className="scene-canvas" ref={refDom}></div> </div> ); }