使用 WebGL 叠加视图打造 3D 地图体验

1. 准备工作

此 Codelab 会教您如何使用由 WebGL 提供支持的 Maps JavaScript API 功能控制三维矢量地图并在地图上渲染对象。

最终的 3D 图钉

前提条件

此 Codelab 假定您熟悉 JavaScript 和 Maps JavaScript API 方面的知识。如需了解使用 Maps JS API 的基础知识,不妨试着学习向网站添加地图 (JavaScript) Codelab

学习内容

  • 生成地图 ID,并启用支持 JavaScript 的矢量地图。
  • 通过程序化的倾斜和旋转控制地图。
  • 使用 WebGLOverlayViewThree.js 在地图上渲染 3D 对象。
  • 使用 moveCamera 呈现相机移动动画。

所需条件

  • 已启用结算功能的 Google Cloud Platform 帐号
  • 已启用 Maps JavaScript API 的 Google Maps Platform API 密钥
  • 熟悉 JavaScript、HTML 和 CSS 方面的知识
  • 您自己喜欢用的文本编辑器或 IDE
  • Node.js

2. 进行设置

若要完成下面的启用步骤,您需要启用 Maps JavaScript API

设置 Google Maps Platform

如果您还没有已启用结算功能的 Google Cloud Platform 帐号和项目,请参阅 Google Maps Platform 使用入门指南,创建结算帐号和项目。

  1. Cloud Console 中,点击项目下拉菜单,选择要用于此 Codelab 的项目。

  1. Google Cloud Marketplace 中启用此 Codelab 所需的 Google Maps Platform API 和 SDK。为此,请按照此视频此文档中的步骤操作。
  2. 在 Cloud Console 的凭据页面中生成 API 密钥。您可以按照此视频此文档中的步骤操作。必须要有 API 密钥才能向 Google Maps Platform 发送请求。

Node.js 设置

如果您还没有 Node.js,请转到 https://nodejs.org/,在计算机上下载并安装 Node.js 运行时。

Node.js 附带 npm 软件包管理器,您可以借此安装此 Codelab 的依赖项。

下载项目入门模板

在开始此 Codelab 之前,请按照下列步骤下载入门项目模板以及完整的解决方案代码:

  1. 请访问 https://github.com/googlecodelabs/maps-platform-101-webgl/,下载此 Codelab 的 GitHub 代码库或创建分支。入门项目位于 /starter 目录,其中包含完成此 Codelab 所需的基本文件结构。您需要的所有内容均位于 /starter/src 目录中。
  2. 下载入门项目后,请运行 /starter 目录中的 npm install。此操作会安装 package.json 中列出的所有必需依赖项。
  3. 安装依赖项后,请运行目录中的 npm start

入门项目已设置完毕,您可以使用 webpack-dev-server 编译并运行您在本地编写的代码。webpack-dev-server 还会在您更改代码时自动在浏览器中重新加载您的应用。

如果您希望运行完整的解决方案代码,请在 /solution 目录中完成上述设置步骤。

添加您的 API 密钥

入门应用包含通过 JS API 加载器加载地图所需的所有代码,因此您只需提供 API 密钥和地图 ID 即可。JS API 加载器是一个简单的库,它将在 HTML 模板中使用 script 标记内联加载 Maps JS API 的传统方式抽象化,让您可以通过 JavaScript 代码处理所有内容。

若要添加 API 密钥,请在入门项目中执行以下操作:

  1. 打开 app.js
  2. apiOptions 对象中,将您的 API 密钥设为 apiOptions.apiKey 的值。

3. 生成和使用地图 ID

若要使用基于 WebGL 的 Maps JavaScript API 功能,您需要一个地图 ID,还要启用矢量地图。

生成地图 ID

地图 ID 生成

  1. 在 Google Cloud Console 中,转到“Google Maps Platform”,然后进入“地图管理”。
  2. 点击“创建新的地图 ID”。
  3. 在“地图名称”字段中,输入地图 ID 的名称。
  4. 在“地图类型”下拉菜单中,选择“JavaScript”。此时将显示“JavaScript Options”(JavaScript 选项)。
  5. 在“JavaScript Options”(JavaScript 选项)下,选中“矢量”单选按钮、“倾斜”复选框和“旋转”复选框。
  6. 可选项。在“说明”字段中,输入 API 密钥的说明。
  7. 点击“下一步”按钮。此时会显示“地图 ID 详情”页面。

    “地图详情”页面
  8. 复制地图 ID。您将在下一步中使用该 ID 来加载地图。

使用地图 ID

若要加载矢量地图,您必须在实例化地图时在相应选项中提供地图 ID 作为属性。或者,您也可以在加载 Maps JavaScript API 时提供相同的地图 ID。

若要使用地图 ID 加载地图,请执行以下操作:

  1. 将您的地图 ID 设为 mapOptions.mapId 的值。

    在实例化地图时提供地图 ID 可告知 Google Maps Platform 针对特定实例加载哪些地图。您可以跨多个应用或同一应用内的多个视图重复使用同一个地图 ID。
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

检查在浏览器中运行的应用。启用了倾斜和旋转的矢量地图应该可以成功加载。若要检查倾斜和旋转是否已启用,请按住 Shift 键,并使用鼠标拖动或使用键盘上的箭头键。

如果地图未加载,请检查您是否已在 apiOptions 中提供有效的 API 密钥。如果地图未倾斜和旋转,请检查您是否已在 apiOptionsmapOptions 中提供地图 ID 并启用倾斜和旋转。

倾斜的地图

您的 app.js 文件现在应如下所示:

    import { Loader } from '@googlemaps/js-api-loader';

    const apiOptions = {
      "apiKey": 'YOUR_API_KEY',
      "version": "beta"
    };

    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_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;
      // WebGLOverlayView code goes here
    }

    (async () => {
      const map = await initMap();
    })();

4. 实现 WebGLOverlayView

借助 WebGLOverlayView,您可以直接访问用于渲染矢量基本地图的相同 WebGL 渲染上下文。也就是说,您可以使用 WebGL 以及基于 WebGL 的热门图形库直接在地图上渲染 2D 和 3D 对象。

WebGLOverlayView 提供五个钩子,您可以在地图的 WebGL 渲染上下文的生命周期中使用它们。以下是每个钩子的简要说明及其用途:

  • onAdd():在对 WebGLOverlayView 实例调用 setMap 以将叠加层添加到地图时调用。您应在此处执行与 WebGL 相关但不需要直接访问 WebGL 上下文的任何操作。
  • onContextRestored():在 WebGL 上下文可用且未渲染任何内容之前调用。您应在此处初始化对象、绑定状态,并执行需要访问 WebGL 上下文但无需调用 onDraw() 的其他任何操作。这样一来,您就可以完成所需的全部设置,而不用增加地图的实际渲染开销,地图本身已经占用大量 GPU 资源。
  • onDraw():在 WebGL 开始渲染地图以及您请求的其他内容后,每帧调用一次。您应尽可能减少在 onDraw() 中的操作,以免地图渲染出现性能问题。
  • onContextLost():在 WebGL 渲染上下文因任何原因丢失时调用。
  • onRemove():在 WebGLOverlayView 实例上调用 setMap(null) 以从地图中移除叠加层时调用。

在此步骤中,您将创建 WebGLOverlayView 实例,并实现三个生命周期钩子:onAddonContextRestoredonDraw。为了确保内容简洁且易于理解,叠加层的所有代码都将通过此 Codelab 入门模板中提供的 initWebGLOverlayView() 函数进行处理。

  1. 创建 WebGLOverlayView() 实例。

    叠加层由 Maps JS API 在 google.maps.WebGLOverlayView 中提供。首先,通过将以下代码附加到 initWebGLOverlayView() 来创建一个实例:
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. 实现生命周期钩子。

    若要实现生命周期钩子,请将以下代码附加到 initWebGLOverlayView()
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};
    
  3. 向地图添加叠加层实例。

    现在,在叠加层实例上调用 setMap(),并将以下代码附加到 initWebGLOverlayView() 以传入地图:
    webGLOverlayView.setMap(map)
    
  4. 调用 initWebGLOverlayView

    最后一步是执行 initWebGLOverlayView(),将以下代码添加到 app.js 底部立即调用的函数中即可:
    initWebGLOverlayView(map);
    

您的 initWebGLOverlayView 和立即调用的函数现在应如下所示:

    async function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      const webGLOverlayView = new google.maps.WebGLOverlayView();

      webGLOverlayView.onAdd = () => {}
      webGLOverlayView.onContextRestored = ({gl}) => {}
      webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {}
      webGLOverlayView.setMap(map);
    }

    (async () => {
      const map = await initMap();
      initWebGLOverlayView(map);
    })();

以上就是实现 WebGLOverlayView 的所有步骤。接下来,您将使用 Three.js 完成在地图上渲染 3D 对象所需的全部设置。

5. 设置 Three.js 场景

使用 WebGL 可能会非常复杂,因为您需要手动定义每个对象的各个方面,还要注意其他事项。为了简化操作,此 Codelab 中将使用 Three.js,这个一个热门的图形库,可在 WebGL 上提供简化的抽象层。Three.js 附带多种便捷函数,可用于处理各种任务,例如创建 WebGL 渲染程序、绘制常见的 2D 和 3D 对象形状、控制相机和对象转换等。

Three.js 中有以下三种基本的对象类型,它们是显示内容的必要条件:

  • 场景:“容器”,所有对象、光源及纹理等内容的渲染和显示都在其中完成。
  • 相机:代表场景视角的相机。有多种相机类型可供选择,可以为一个场景添加一个或多个相机。
  • 渲染程序:用于处理和显示场景中所有对象的渲染程序。WebGLRenderer 是 Three.js 中最常用的渲染程序,但在客户端不支持 WebGL 的情况下,可以将其他一些渲染程序作为备用。

在此步骤中,您将加载 Three.js 所需的所有依赖项,并设置基本场景。

  1. 加载 Three.js

    在此 Codelab 中,您需要两个依赖项:Three.js 库和 GLTF 加载器,后者是一个可让您以 GL 传输格式 (gLTF) 加载 3D 对象的类。Three.js 为许多不同的 3D 对象格式提供了专用加载器,但建议使用 gLTF。

    在下面的代码中,导入了整个 Three.js 库。在正式版应用中,您可能只需要导入所需的类,但对于此 Codelab,导入整个库是为了简化操作。另请注意,GLTF 加载器未包含在默认库中,需要从依赖项中的单独路径导入,您可以通过此路径访问 Three.js 提供的所有加载器。

    若要导入 Three.js 和 GLTF 加载器,请将以下代码添加到 app.js 的顶部:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. 创建一个 Three.js 场景。

    若要创建场景,请将以下代码附加到 onAdd 钩子,以实例化 Three.js Scene 类:
    scene = new THREE.Scene();
    
  3. 向场景添加相机。

    如前所述,相机代表场景的视角,并决定 Three.js 如何处理场景中对象的视觉渲染。如果没有相机,场景实际上就不会被“看到”,这意味着对象不会显示,因为它们没有被渲染。

    Three.js 提供了各种不同的相机,这些相机会影响渲染程序处理对象的方式,比如视角和深度。在此场景中,您将使用 PerspectiveCamera,这是 Three.js 中最常用的相机类型,旨在模拟人眼感知场景的方式。也就是说,离相机较远的对象看起来比离得较近的要小,场景中会有一个消失点,类似特点还有很多。

    若要向场景添加透视相机,请将以下代码附加到 onAdd 钩子:
    camera = new THREE.PerspectiveCamera();
    
    借助 PerspectiveCamera,您还能够配置构成视角的属性,包括近平面和远平面、宽高比和视野 (fov)。这些属性共同构成了所谓的视锥体,这是处理 3D 对象时务必要理解的一个概念,但不在此 Codelab 的范围内。默认的 PerspectiveCamera 配置足以满足需求。
  4. 向场景添加光源。

    默认情况下,在 Three.js 场景中渲染的对象将显示为黑色,无论它们应用何种纹理都是如此。这是因为 Three.js 场景会模仿对象在现实世界中的呈现方式,其中颜色取决于对象反射的光线。简而言之,没有光,就没有颜色。

    Three.js 提供了各种不同类型的光源,您将使用其中两种:

  5. AmbientLight:提供漫射光源,可从各个角度均匀地照射场景中的所有对象。这将为场景提供基础亮度,确保所有对象的纹理都可呈现出来。
  6. DirectionalLight:沿着场景中的某个方向发出光线。与现实世界中定位光的原理不同,DirectionalLight 发出的光线都是平行的,不会随着远离光源而扩散和漫射。

    您可以配置每个光线的颜色和强度,以创造整体的光线效果。例如,在下面的代码中,环境光为整个场景提供柔和的白光,而平行光作为辅助光向下照射对象。对于平行光,应使用 position.set(x, y ,z) 设置角度,其中每个值都对应各自的轴。例如,设为 position.set(0,1,0) 时,光线会从场景正上方沿着 y 轴直射下来。

    若要向场景添加光源,请将以下代码附加到 onAdd 钩子:
    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);
    

您的 onAdd 钩子现在应如下所示:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      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);
    }

您的场景现已设置完毕,可以开始渲染了。接下来,您将配置 WebGL 渲染程序,并渲染场景。

6. 渲染场景

我们现在开始渲染场景。到目前为止,您使用 Three.js 创建的所有内容都已在代码中进行初始化,但实际上并不存在,因为它尚未渲染到 WebGL 渲染上下文中。WebGL 使用 Canvas API 在浏览器中渲染 2D 和 3D 内容。如果您之前使用过 Canvas API,应该熟悉 HTML 画布的 context,这是渲染所有内容的地方。您可能不知道,它是一个通过 WebGLRenderingContext API 在浏览器中公开 OpenGL 图形渲染上下文的接口。

为了更轻松地处理 WebGL 渲染程序,Three.js 提供了 WebGLRenderer,这是一个封装容器,可相对轻松地配置 WebGL 渲染上下文,让 Three.js 在浏览器中渲染场景。但对于地图而言,仅在浏览器中与地图一起渲染 Three.js 场景是不够的。Three.js 场景必须渲染到与地图完全相同的渲染上下文中,以便地图和 Three.js 场景中的任何对象都渲染到相同的世界空间中。这样一来,渲染程序就可以处理地图上对象与场景中对象之间的互动(例如遮挡),换一种说法就是某个对象会遮住它后面的对象,让人看不到它们。

听起来很复杂,对吧?幸运的是,仍然可以通过 Three.js 简化操作。

  1. 设置 WebGL 渲染程序。

    当您创建 Three.js WebGLRenderer 新实例时,可以为其提供您希望用于渲染场景的特定 WebGL 渲染上下文。还记得传递到 onContextRestored 钩子的 gl 参数吗?该 gl 对象就是地图的 WebGL 渲染上下文。您只需向 WebGLRenderer 实例提供上下文、画布及属性,所有这些内容均可通过 gl 对象获得。在下面的代码中,渲染程序的 autoClear 属性也设为 false,这样渲染程序就不会在每一帧都清除其输出。

    若要配置渲染程序,请将以下代码附加到 onContextRestored 钩子:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. 渲染场景。

    渲染程序配置完毕后,在 WebGLOverlayView 实例上调用 requestRedraw,以告知叠加层在渲染下一帧时需要重新绘制,然后在渲染程序上调用 render 并向其传递 Three.js 场景和相机,以进行渲染。最后,清除 WebGL 渲染上下文的状态。这一步对于避免 GL 状态冲突至关重要,因为 WebGL 叠加视图的使用依赖于共享的 GL 状态。如果每次绘制调用结束时都未重置状态,GL 状态冲突可能会导致渲染程序出现问题。

    为此,请将以下代码附加到 onDraw 钩子,以便在每一帧渲染结束时重置状态:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

您的 onContextRestoredonDraw 钩子现在应如下所示:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

7. 在地图上渲染 3D 模型

好了,您已经完成所有准备工作。您设置了 WebGL 叠加视图,创建了 Three.js 场景,但有一个问题:场景中没有任何内容。接下来,我们开始在场景中渲染 3D 对象。为此,您将使用之前导入的 GLTF 加载器。

3D 模型具有多种不同格式,但考虑到 Three.js 场景的大小和运行时性能,gLTF 是首选格式。在此 Codelab 中,/src/pin.gltf 中已经提供一个可用于在场景中进行渲染的模型。

  1. 创建模型加载器实例。

    将以下代码附加到 onAdd
    loader = new GLTFLoader();
    
  2. 加载 3D 模型。

    模型加载器是异步的,并会在模型完全加载后执行回调。若要加载 pin.gltf,请将以下代码附加到 onAdd
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. 向场景添加模型。

    现在,您可以将以下代码附加到 loader 回调,以向场景添加模型。请注意,要添加的是 gltf.scene,而不是 gltf
    scene.add(gltf.scene);
    
  4. 配置相机投影矩阵。

    为了在地图上正确渲染模型,最后一步是在 Three.js 场景中设置相机的投影矩阵。投影矩阵通过 Three.js Matrix4 数组的形式指定,数组可以定义三维空间中的一个点以及转换(例如旋转、剪切和缩放)。

    对于 WebGLOverlayView,投影矩阵可用于告知渲染程序相对于基本地图的位置以及如何渲染 Three.js 场景。但有一个问题,地图上的位置通过纬度和经度坐标对的形式指定,而 Three.js 场景中的位置则采用 Vector3 坐标。您可能已经猜到,计算两个坐标系统之间的转换并非易事。为了解决此问题,WebGLOverlayView 会将 coordinateTransformer 对象传递到 OnDraw 生命周期钩子(其中包含名为 fromLatLngAltitude 的函数)。fromLatLngAltitude 采用 LatLngAltitudeLatLngAltitudeLiteral 对象,以及一组可为场景定义转换的参数(视情况而定),然后将它们转换为模型视图投影 (MVP) 矩阵。您只需指定 Three.js 场景在地图上的渲染位置以及转换方式,WebGLOverlayView 将完成剩余的工作。然后,您可以将 MVP 矩阵转换为 Three.js Matrix4 数组,并为其设置相机投影矩阵。

    在下面的代码中,第二个参数告知 WebGL 叠加视图将 Three.js 场景的海拔高度设为离地面 120 米,这会让模型看起来像是悬浮在空中。

    若要设置相机投影矩阵,请将以下代码附加到 onDraw 钩子:
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 120
    }
    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
  5. 转换模型。

    您会发现图钉并没有垂直于地图。在 3D 图形中,除了世界空间拥有自己的 x 轴、y 轴和 z 轴(用于确定方向)之外,每个对象也有自己的对象空间和一组独立的轴。

    对此模型而言,图钉“顶部”通常不会沿着 y 轴朝上,因此您需要通过对其调用 rotation.set 来转换对象,以便获得相对于世界空间的正确方向。请注意,在 Three.js 中,旋转的单位是弧度,而不是角度。通常情况下,使用角度会更方便,因此需要使用公式 degrees * Math.PI/180 进行适当的转换。

    此外,这个模型有点小,因此您还可以调用 scale.set(x, y ,z),以便在所有轴上均匀缩放该模型。

    若要旋转和缩放模型,请将以下代码添加到 onAddloader 回调,并将其放在用于将向场景添加 gLTF 的 scene.add(gltf.scene) 之前
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

现在,图钉垂直于地图。

垂直式图钉

您的 onAddonDraw 钩子现在应如下所示:

    webGLOverlayView.onAdd = () => {
      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);

      loader = new GLTFLoader();
      const source = 'pin.gltf';
      loader.load(
        source,
        gltf => {
          gltf.scene.scale.set(25,25,25);
          gltf.scene.rotation.x = 180 * Math.PI/180;
          scene.add(gltf.scene);
        }
      );
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 100
      }

      const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
      camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);

      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

接下来是相机动画!

8. 呈现相机移动动画

现在,您已经在地图上渲染了一个模型,可以通过三个维度移动任何对象,接下来可能需要以编程方式控制这种移动。借助 moveCamera 函数,您可以同时设置地图的中心、缩放、倾斜和方向属性,从而精细控制用户体验。此外,可在动画循环中调用 moveCamera,以接近每秒 60 帧的帧速率在帧之间打造流畅的过渡。

  1. 等待模型加载。

    为了打造顺畅的用户体验,您需要在 gLTF 模型加载完毕后再开始移动相机。为此,请将加载器的 onLoad 事件处理脚本附加到 onContextRestored 钩子:
    loader.manager.onLoad = () => {}
    
  2. 创建动画循环。

    动画循环创建方式有很多,例如使用 setIntervalrequestAnimationFrame。在本示例中,您将使用 Three.js 渲染程序的 setAnimationLoop 函数,该函数会在 Three.js 每次渲染新帧时自动调用您在其回调中声明的任何代码。若要创建动画循环,请将以下代码添加到上一步的 onLoad 事件处理脚本:
    renderer.setAnimationLoop(() => {});
    
  3. 设置相机在动画循环中的位置。

    接下来,调用 moveCamera 以更新地图。在这里,用于加载地图的 mapOptions 对象中的属性用于定义相机位置:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. 逐帧更新相机。

    最后一步!在每一帧结束时更新 mapOptions 对象,以设置下一帧的相机位置。在下面的代码中,if 语句用于增加倾斜度,直至达到最大倾斜值 67.5,每帧的方向都略有变动,直至相机完成完整的 360 度旋转。达到理想动画效果后,系统会将 null 传递给 setAnimationLoop 以取消动画,使其不会永久运行。
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

您的 onContextRestored 钩子现在应如下所示:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;

      loader.manager.onLoad = () => {
        renderer.setAnimationLoop(() => {
           map.moveCamera({
            "tilt": mapOptions.tilt,
            "heading": mapOptions.heading,
            "zoom": mapOptions.zoom
          });

          if (mapOptions.tilt < 67.5) {
            mapOptions.tilt += 0.5
          } else if (mapOptions.heading <= 360) {
            mapOptions.heading += 0.2;
          } else {
            renderer.setAnimationLoop(null)
          }
        });
      }
    }

9. 恭喜

如果一切按计划进行,您现在应该可以看到一个带有较大 3D 图钉的地图,如下所示:

最终的 3D 图钉

所学内容

您在此 Codelab 中学习了许多知识;重点内容如下:

  • 实现 WebGLOverlayView 及其生命周期钩子。
  • 将 Three.js 集成到地图中。
  • 创建 Three.js 场景的基础知识,包括相机和光线。
  • 使用 Three.js 加载和操控 3D 模型。
  • 使用 moveCamera 控制地图的相机,并呈现相机移动动画效果。

后续内容

一般来说,WebGL 和计算机图形是比较复杂的主题,因此总有许多内容需要学习。以下是一些可帮助您开始学习的资源: