You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 22:17:12