使用 WebGL 疊加層檢視打造 3D 地圖體驗

1. 事前準備

本程式碼研究室將說明如何使用 Maps JavaScript API 的 WebGL 技術功能,在三維向量地圖上進行控制和算繪。

最終 3D 釘選

必要條件

本程式碼研究室假設您已具備 JavaScript 和 Maps JavaScript API 的中階知識。如要瞭解使用 Maps JS API 的基本概念,請試試在網站中新增地圖 (JavaScript) 程式碼研究室

課程內容

  • 產生已啟用 JavaScript 專用向量地圖的地圖 ID。
  • 透過程式輔助方式傾斜和旋轉地圖。
  • 使用 WebGLOverlayViewThree.js 在地圖上算繪 3D 物件。
  • 使用 moveCamera 為攝影機動作加入動畫效果。

軟硬體需求

  • 已啟用計費功能的 Google Cloud Platform 帳戶
  • 已啟用 Maps JavaScript API 的 Google 地圖平台 API 金鑰
  • 具備 JavaScript、HTML 和 CSS 中級知識
  • 您選擇的文字編輯器或 IDE
  • Node.js

2. 做好準備

在下方的啟用步驟中,您需要啟用 Maps JavaScript API

設定 Google 地圖平台

如果您尚未建立 Google Cloud Platform 帳戶,以及啟用計費功能的專案,請參閱「開始使用 Google 地圖平台」指南,建立帳單帳戶和專案。

  1. Cloud 控制台中,按一下專案下拉式選單,然後選取要用於本程式碼研究室的專案。

  1. Google Cloud Marketplace 中,啟用本程式碼研究室所需的 Google 地圖平台 API 和 SDK。如要瞭解如何操作,請觀看這部影片或參閱這份說明文件
  2. 在 Cloud Console 的「憑證」頁面中產生 API 金鑰。你可以按照這部影片這份文件中的步驟操作。所有 Google 地圖平台要求都需要 API 金鑰。

Node.js 設定

如果尚未安裝,請前往 https://nodejs.org/,在電腦上安裝 Node.js 執行階段。

Node.js 隨附 npm 套件管理員,您需要安裝本程式碼研究室的依附元件。

下載專案範本

開始本程式碼研究室之前,請先下載範例專案範本和完整解決方案程式碼,方法如下:

  1. 前往 https://github.com/googlecodelabs/maps-platform-101-webgl/,下載或建立這個程式碼研究室的 GitHub 存放區分支。範例專案位於 /starter 目錄,內含完成本程式碼研究室所需的檔案結構。所有工作所需項目都位於 /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 載入器是簡單的程式庫,可將傳統的 Maps JS API 載入方法 (使用 script 標記在 HTML 範本中內嵌載入) 抽象化,讓您在 JavaScript 程式碼中處理所有事項。

如要新增 API 金鑰,請在入門專案中執行下列操作:

  1. 開啟 app.js
  2. apiOptions 物件中,將 API 金鑰設為 apiOptions.apiKey 的值。

3. 產生及使用地圖 ID

如要使用 Maps JavaScript API 的 WebGL 功能,您需要啟用向量地圖的地圖 ID。

產生地圖 ID

產生地圖 ID

  1. 在 Google Cloud 控制台中,依序前往「Google 地圖平台」>「地圖管理」。
  2. 按一下「建立新地圖 ID」。
  3. 在「地圖名稱」欄位中,輸入地圖 ID 的名稱。
  4. 在「地圖類型」下拉式選單中,選取「JavaScript」。畫面上會顯示「JavaScript 選項」。
  5. 在「JavaScript 選項」下方,選取「向量」單選按鈕、「傾斜」核取方塊和「旋轉」核取方塊。
  6. (選用) 在「說明」欄位中輸入 API 金鑰的說明。
  7. 按一下「下一步」按鈕。系統隨即會顯示「地圖 ID 詳細資料」頁面。

    地圖詳細資料頁面
  8. 複製地圖 ID。您將在下一個步驟中使用此項目載入地圖。

使用地圖 ID

如要載入向量地圖,您必須在例項化地圖時,將地圖 ID 做為選項中的屬性提供。載入 Maps JavaScript API 時,您也可以選擇提供相同的地圖 ID。

如要載入含地圖 ID 的地圖,請按照下列步驟操作:

  1. 將地圖 ID 設為 mapOptions.mapId 的值。

    執行地圖例項化時提供地圖 ID,可讓 Google 地圖平台知道要為特定例項載入哪個地圖。您可以在多個應用程式或同一個應用程式中的多個檢視畫面,重複使用相同的地圖 ID。
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

檢查瀏覽器中執行的應用程式。啟用傾斜和旋轉功能的向量地圖應會順利載入。如要確認是否已啟用傾斜和旋轉功能,請按住 Shift 鍵,然後使用滑鼠拖曳或使用鍵盤上的方向鍵。

如果地圖未載入,請檢查您是否已在 apiOptions 中提供有效的 API 金鑰。如果地圖無法傾斜和旋轉,請確認您提供的地圖 ID 已在 apiOptionsmapOptions 中啟用傾斜和旋轉功能。

傾斜地圖

您的 app.js 檔案現在看起來應像這樣:

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

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

    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 算繪環境生命週期,供您使用。以下簡要說明各個 Hook,以及適用的用途:

  • onAdd():在 WebGLOverlayView 執行個體上呼叫 setMap,將疊加層加到地圖時呼叫。您應在此處執行任何與 WebGL 相關的工作,不需要直接存取 WebGL 環境。
  • onContextRestored():在 WebGL 環境可用時呼叫,但會在任何內容算繪前呼叫。您應在此初始化物件、繫結狀態,以及執行任何需要存取 WebGL 環境,但可在 onDraw() 呼叫之外執行的作業。這樣一來,您就能設定所需的一切,而不會為地圖的實際算繪作業增加過多負擔,因為這項作業本來就很耗用 GPU 資源。
  • onDraw():WebGL 開始算繪地圖和您要求的任何其他內容時,每個影格都會呼叫一次這個函式。您應盡量減少 onDraw() 中的工作,避免地圖算繪發生效能問題。
  • onContextLost():如有任何因素造成 WebGL 算繪環境遺失,系統會呼叫這個方法。
  • onRemove():在 WebGLOverlayView 執行個體上呼叫 setMap(null),從地圖中移除疊加層時,系統會呼叫這個方法。

在這個步驟中,您將建立 WebGLOverlayView 的執行個體,並實作三個生命週期掛鉤:onAddonContextRestoredonDraw。為確保程式碼乾淨整潔且易於追蹤,所有疊加層的程式碼都會在 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, transformer}) => {};
    
  3. 將疊加層例項加到地圖。

    現在請在疊加層例項上呼叫 setMap(),並將地圖傳遞至 initWebGLOverlayView(),方法是在 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, transformer}) => {}
      webGLOverlayView.setMap(map);
    }

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

這樣就完成 WebGLOverlayView 的實作作業。接著,您將設定使用 Three.js 在地圖上算繪 3D 物件所需的一切。

5. 設定 three.js 場景

使用 WebGL 可能非常複雜,因為您必須手動定義每個物件的所有層面,以及其他一些項目。為了簡化操作,本程式碼研究室將使用 Three.js,這是熱門的圖形程式庫,可在 WebGL 上提供簡化的抽象層。Three.js 隨附各種便利函式,可執行從建立 WebGL 轉譯器到繪製常見 2D 和 3D 物件形狀,以及控制攝影機、物件轉換等各種作業。

Three.js 中有三種基本物件類型,顯示任何內容都必須使用這些類型:

  • 場景:所有物件、光源、紋理等項目都會在這個「容器」中算繪及顯示。
  • 攝影機:代表場景視角的攝影機。你可以使用多種攝影機類型,並在單一場景中新增一或多部攝影機。
  • 算繪器:處理及顯示場景中所有物件的算繪器。在 Three.js 中,WebGLRenderer 是最常用的,但如果用戶端不支援 WebGL,您也可以使用其他幾種做為備援。

在這個步驟中,您會載入 Three.js 所需的所有依附元件,並設定基本場景。

  1. 載入 three.js

    本程式碼研究室需要兩個依附元件:Three.js 程式庫和 GLTF Loader,這個類別可讓您載入 GL 傳輸格式 (gLTF) 的 3D 物件。Three.js 提供許多不同 3D 物件格式的專用載入器,但建議使用 gLTF。

    在下列程式碼中,系統會匯入整個 Three.js 程式庫。在正式版應用程式中,您可能只想匯入所需的類別,但在本程式碼研究室中,請匯入整個程式庫,讓操作更簡單。另請注意,GLTF Loader 不會納入預設程式庫,必須從依附元件中的個別路徑匯入,您可透過這個路徑存取 Three.js 提供的所有載入器。

    如要匯入 Three.js 和 GLTF Loader,請在 app.js 頂端新增下列內容:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. 建立 three.js 場景。

    如要建立場景,請將下列內容附加至 onAdd hook,例項化 Three.js Scene 類別:
    scene = new THREE.Scene();
    
  3. 在場景中新增攝影機。

    如先前所述,相機代表場景的觀看角度,並決定 Three.js 如何處理場景中物件的視覺化算繪作業。如果沒有攝影機,系統就無法「看到」場景,因此不會轉譯物件,物件也不會顯示。

    Three.js 提供各種不同的攝影機,可影響算繪器處理物件的方式,例如透視和深度。在這個場景中,您將使用 PerspectiveCamera,這是 Three.js 中最常用的攝影機類型,旨在模擬人眼感知場景的方式。也就是說,離相機越遠的物體會越小,場景會有消失點等等。

    如要將透視攝影機新增至場景,請在 onAdd 勾點後方加上以下內容:
    camera = new THREE.PerspectiveCamera();
    
    您也可以使用 PerspectiveCamera 設定構成視點的屬性,包括近平面和遠平面、長寬比和視野 (fov)。這些屬性共同構成所謂的檢視截錐體,這是處理 3D 時的重要概念,但超出本程式碼研究室的範圍。預設的 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 Canvas 的 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 Hook:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. 算繪場景。

    設定好算繪器後,請在 WebGLOverlayView 執行個體上呼叫 requestRedraw,告知疊加層在算繪下一個影格時需要重新繪製,然後在算繪器上呼叫 render,並傳遞要算繪的 Three.js 場景和攝影機。最後,請清除 WebGL 算繪環境的狀態。這是避免 GL 狀態衝突的重要步驟,因為使用 WebGL Overlay View 時,會用到共用的 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 Loader。

3D 模型有許多不同格式,但就 Three.js 而言,gLTF 格式是首選格式,因為其大小和執行階段效能都相當出色。在本程式碼研究室中,/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 回呼中,加入以下內容 scene.add(gltf.scene),將 gLTF 新增至場景:
    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 釘選

您學到的內容

在本程式碼研究室中,您學到許多內容,以下是重點:

  • 實作 WebGLOverlayView 及其生命週期掛鉤。
  • 將 Three.js 整合到地圖中。
  • 建立 Three.js 場景的基本知識,包括攝影機和照明。
  • 使用 Three.js 載入及操控 3D 模型。
  • 使用 moveCamera 控制地圖的攝影機並製作動畫。

後續步驟

WebGL 和一般電腦繪圖都是複雜的主題,因此總有許多內容可供學習。以下資源可協助您快速上手: