使用Three.js在Google Map叠加层精准定位GLTF模型失败求助
问题描述
尝试使用Three.js在Google Map的Overlay View上通过经纬度放置3D模型标记公交站点,使用@googlemaps/three库的latLngAltitudeToVector3方法,但无法将模型渲染到指定的精准经纬度位置。以下是基于Vanilla JS的实现代码(项目按照Google官方WebGL教程启动),求解决方向:
import { Loader } from '@googlemaps/js-api-loader'; import { ThreeJSOverlayView, latLngToVector3 } from "@googlemaps/three"; import * as THREE from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; var coords = [ { lng: 126.9108135, lat: 37.61399168 }, { lng: 126.9126067, lat: 37.61336021 }, { lng: 126.9143072, lat: 37.61274979 }, { lng: 126.9178395, lat: 37.61086107 }, { lng: 126.9201833, lat: 37.60934572 }, { lng: 126.922542, lat: 37.60665167 }, ]; var stops = []; const apiOptions = { apiKey: 'API_KEY', version: "beta" }; const mapOptions = { "tilt": 0, "heading": 0, "zoom": 18, "center": { lat: 37.5234, lng: 126.9234 }, "mapId": "MAP_ID" } async function initMap() { const mapDiv = document.getElementById("map"); const apiLoader = new Loader(apiOptions); await apiLoader.load(); return new google.maps.Map(mapDiv, mapOptions); } function initWebGLOverlayView(map) { let scene, renderer, camera, loader; const webGLOverlayView = new google.maps.WebGLOverlayView(); const overlay = new ThreeJSOverlayView(); webGLOverlayView.onAdd = () => { // set up the scene scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(); const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25); directionalLight.position.set(0.5, -1, 0.5); scene.add(directionalLight); // load the model loader = new GLTFLoader(); const source = "original.glb"; loader.load( source, gltf => { for (const [i, coord] of coords.entries()) { gltf.scene.scale.set(8,8,8); gltf.scene.rotation.x = 90 * Math.PI/180; stops.push(gltf.scene.clone()); } for (const [i, coord] of coords.entries()) { stops[i].rotation.x = 90 * Math.PI/180; stops[i].position.copy(overlay.latLngAltitudeToVector3({lat: coord.lat, lng: coord.lng})) scene.add(stops[i]); } }); } webGLOverlayView.onContextRestored = ({gl}) => { // create the three.js renderer, using the // maps's WebGL rendering context. renderer = new THREE.WebGLRenderer({ canvas: gl.canvas, context: gl, ...gl.getContextAttributes(), }); renderer.autoClear = false; // wait to move the camera until the 3D model loads loader.manager.onLoad = () => { renderer.setAnimationLoop(() => { for (const stop of stops) { stop.rotation.y += 0.02 } }); } } webGLOverlayView.onDraw = ({gl, transformer}) => { // update camera matrix to ensure the model is georeferenced correctly on the map const latLngAltitudeLiteral = { lat: mapOptions.center.lat, lng: mapOptions.center.lng, altitude: 50 } const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral); camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix); webGLOverlayView.requestRedraw(); renderer.render(scene, camera); // always reset the GL state renderer.resetState(); } webGLOverlayView.setMap(map); } (async () => { const map = await initMap(); initWebGLOverlayView(map); })();
解决方向与修改建议
1. 核心问题分析
- 同时初始化原生
google.maps.WebGLOverlayView和@googlemaps/three的ThreeJSOverlayView,但未正确关联两者,导致坐标转换依赖的上下文失效 - 在
onAdd阶段调用latLngAltitudeToVector3,此时地图WebGL上下文未初始化,坐标转换无有效结果 - 相机矩阵固定基于地图中心计算,未跟随地图视角动态更新,引发位置偏移
2. 修正后的代码示例
import { Loader } from '@googlemaps/js-api-loader'; import { ThreeJSOverlayView } from "@googlemaps/three"; import * as THREE from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; var coords = [ { lng: 126.9108135, lat: 37.61399168 }, { lng: 126.9126067, lat: 37.61336021 }, { lng: 126.9143072, lat: 37.61274979 }, { lng: 126.9178395, lat: 37.61086107 }, { lng: 126.9201833, lat: 37.60934572 }, { lng: 126.922542, lat: 37.60665167 }, ]; var stops = []; const apiOptions = { apiKey: 'API_KEY', version: "beta" }; const mapOptions = { "tilt": 0, "heading": 0, "zoom": 18, "center": { lat: 37.61399168, lng: 126.9108135 }, // 改为第一个站点坐标便于测试 "mapId": "MAP_ID" } async function initMap() { const mapDiv = document.getElementById("map"); const apiLoader = new Loader(apiOptions); await apiLoader.load(); return new google.maps.Map(mapDiv, mapOptions); } function initWebGLOverlayView(map) { let loader; // 直接使用ThreeJSOverlayView封装类,替代原生OverlayView const threeOverlay = new ThreeJSOverlayView({ map, anchor: { lat: mapOptions.center.lat, lng: mapOptions.center.lng, altitude: 0 } }); // 初始化场景与灯光 const scene = threeOverlay.scene; const ambientLight = new THREE.AmbientLight(0xffffff, 0.75); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25); directionalLight.position.set(0.5, -1, 0.5); scene.add(directionalLight); // 加载模型并设置位置 loader = new GLTFLoader(); const source = "original.glb"; loader.load( source, gltf => { gltf.scene.scale.set(8,8,8); gltf.scene.rotation.x = 90 * Math.PI/180; // 等待ThreeJSOverlayView初始化完成后执行坐标转换 threeOverlay.onAdd = () => { for (const coord of coords) { const stop = gltf.scene.clone(); // 使用threeOverlay自带的转换方法,此时transformer已就绪 const position = threeOverlay.latLngAltitudeToVector3({ lat: coord.lat, lng: coord.lng, altitude: 0 // 可根据需求设置海拔 }); stop.position.copy(position); scene.add(stop); stops.push(stop); } }; // 设置动画循环 threeOverlay.setAnimationLoop(() => { for (const stop of stops) { stop.rotation.y += 0.02; } }); } ); threeOverlay.setMap(map); } (async () => { const map = await initMap(); initWebGLOverlayView(map); })();
3. 关键修改说明
- 移除原生
WebGLOverlayView:ThreeJSOverlayView已封装场景、相机、渲染器的初始化逻辑,无需手动维护原生OverlayView - 延迟坐标转换:将模型位置计算放在
threeOverlay.onAdd回调中,此时地图的transformer上下文已初始化,转换结果有效 - 使用封装的转换方法:
ThreeJSOverlayView实例自带的latLngAltitudeToVector3会自动关联内部transformer,确保经纬度转换精度 - 简化渲染逻辑:
ThreeJSOverlayView自动处理相机矩阵更新和渲染循环,无需手动在onDraw中设置投影矩阵 - 调整测试中心:将地图中心设为其中一个站点坐标,便于快速验证模型位置是否正确
内容的提问来源于stack exchange,提问作者pHoon
相关产品推荐
相关产品推荐

