Tworzenie trójwymiarowych map za pomocą widoku nakładki WebGL

1. Zanim zaczniesz

Dzięki nim dowiesz się, jak korzystać z funkcji WebGL API interfejsu Maps JavaScript do wyświetlania i renderowania mapy wektorowej na 3 wymiarach.

Końcowy kod 3D

Wymagania wstępne

W tym ćwiczeniu zakładamy, że znasz język JavaScript i interfejs API Maps JavaScript. Aby poznać podstawy korzystania z interfejsu Maps JS API, skorzystaj z ćwiczenia z programowania dotyczącego dodawania kodu JavaScript do witryny.

Czego się nauczysz

  • Generowanie identyfikatora mapy z mapą wektorową dla JavaScriptu.
  • Kontrolowanie mapy za pomocą automatycznego pochylenia i obrotu.
  • Renderowanie obiektów 3D na mapie przy użyciu znaczników WebGLOverlayView i Three.js.
  • Animowanie ruchów kamery za pomocą moveCamera.

Czego potrzebujesz

  • konto Google Cloud Platform z włączonymi płatnościami,
  • Klucz interfejsu API Google Maps Platform z włączonym interfejsem Maps JavaScript API
  • biegła znajomość JavaScriptu, HTML i CSS
  • Twój wybrany edytor tekstu lub IDE
  • Node.js

2. Konfiguracja

Aby włączyć tę funkcję, musisz włączyć interfejs Maps JavaScript API.

Konfigurowanie Google Maps Platform

Jeśli nie masz jeszcze konta Google Cloud Platform ani projektu z włączonymi płatnościami, przeczytaj przewodnik Pierwsze kroki z Google Maps Platform, by utworzyć konto rozliczeniowe i projekt.

  1. W Cloud Console kliknij menu projektu i wybierz projekt, którego chcesz użyć w tym ćwiczeniu z programowania.

  1. Włącz interfejsy API i pakiety SDK Google Maps Platform wymagane w ramach tego ćwiczenia z ćwiczeń w Google Cloud Marketplace. W tym celu wykonaj czynności opisane w tym filmie lub w tej dokumentacji.
  2. Wygeneruj klucz interfejsu API na stronie Dane logowania w Cloud Console. Odpowiednie instrukcje znajdziesz w tym filmie lub w tej dokumentacji. Wszystkie żądania wysyłane do Google Maps Platform wymagają klucza interfejsu API.

Konfiguracja Node.js

Jeśli nie masz jeszcze środowiska Node.js, otwórz stronę https://nodejs.org/, aby je pobrać i zainstalować na komputerze.

Do środowiska Node.js dołączona jest menedżer pakietów npm, który musisz zainstalować w zależności od tego ćwiczenia z programowania.

Pobierz szablon startowy projektu

Zanim rozpoczniesz to ćwiczenie programowania, wykonaj te czynności, by pobrać szablon projektu startowego oraz pełny kod rozwiązania:

  1. Pobierz lub utwórz rozwidlenie repozytorium GitHub dla tego ćwiczenia z programowania na https://github.com/googlecodelabs/maps-platform-101-webgl/. Projekt początkowy znajduje się w katalogu /starter i obejmuje podstawową strukturę pliku potrzebną do ukończenia ćwiczeń z programowania. Wszystkie potrzebne informacje znajdziesz w katalogu /starter/src.
  2. Po pobraniu projektu początkowego uruchom npm install w katalogu /starter. Spowoduje to zainstalowanie wszystkich wymaganych zależności wymienionych w narzędziu package.json.
  3. Po zainstalowaniu zależności uruchom npm start w katalogu.

Projekt startowy został skonfigurowany tak, aby użytkownicy mogli korzystać z Webpack-dev-server, który kompiluje kod i uruchamia go lokalnie. Komponent webpack-dev-server automatycznie ładuje też Twoją aplikację w przeglądarce za każdym razem, gdy zmieniasz kod.

Jeśli chcesz zobaczyć pełny kod rozwiązania, możesz wykonać opisane powyżej czynności konfiguracyjne w katalogu /solution.

Dodaj klucz interfejsu API

Aplikacja startowa zawiera cały kod potrzebny do wczytania mapy za pomocą biblioteki JS API, więc wystarczy, że podasz klucz interfejsu API i identyfikator mapy. Biblioteka JS API to prosta biblioteka, która abstrakcyjnie tradycyjną metodę wczytywania interfejsu API JS Map Google w szablonie HTML za pomocą tagu script umożliwia obsługę wszystkich elementów w kodzie JavaScript.

Aby dodać klucz interfejsu API, wykonaj te czynności w projekcie początkowym:

  1. Otwórz aplikację app.js.
  2. W obiekcie apiOptions ustaw klucz interfejsu API na wartość apiOptions.apiKey.

3. Generowanie i używanie identyfikatora mapy

Aby korzystać z funkcji Maps JavaScript API opartych na WebGL, potrzebujesz identyfikatora mapy z włączoną mapą wektorową.

Generowanie identyfikatora mapy

Generowanie identyfikatora mapy

  1. W Google Cloud Console otwórz „Google Maps Platform” i „Zarządzanie mapami”.
  2. Kliknij „UTWÓRZ NOWY IDENTYFIKATOR MAPY”.
  3. W polu „Nazwa mapy” wpisz nazwę dla identyfikatora mapy.
  4. Z menu „Typ mapy” wybierz „JavaScript'. Pojawią się opcje JavaScript.
  5. W sekcji „Opcje JavaScript” zaznacz pole „Wektor i 39; zaznacz pole wyboru „Przechylanie” i „Obrót&#39.”.
  6. Opcjonalnie. W polu „Opis” wpisz opis klucza interfejsu API.
  7. Kliknij przycisk „Dalej”. Pojawi się strona „Szczegóły identyfikatora mapy”.

    Strona szczegółów mapy
  8. Skopiuj identyfikator mapy. Użyjesz go w następnym kroku, aby wczytać mapę.

Używanie identyfikatora mapy

Aby wczytać mapę wektorową, musisz podać identyfikator mapy jako właściwość w opcjach tworzenia instancji tej mapy. Opcjonalnie możesz też podać ten sam identyfikator mapy podczas wczytywania interfejsu Maps JavaScript API.

Aby wczytać mapę za pomocą identyfikatora mapy, wykonaj te czynności:

  1. Ustaw identyfikator mapy jako wartość mapOptions.mapId.

    Jeśli podasz identyfikator mapy podczas tworzenia instancji mapy, Google Maps Platform wskaże, które z nich mają zostać wczytane w danej instancji. Możesz wykorzystać ten sam identyfikator mapy w wielu aplikacjach lub różnych widokach w tej samej aplikacji.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

Sprawdź aplikację działającą w przeglądarce. Mapa wektorowa z włączonym przechyleniem i obrotem powinna się wczytywać. Aby sprawdzić, czy pochylenie i obrót są włączone, przytrzymaj klawisz Shift i przeciągnij kursorem myszy lub użyj klawiszy strzałek na klawiaturze.

Jeśli mapa się nie wczytuje, sprawdź, czy w kluczu apiOptions podano prawidłowy klucz interfejsu API. Jeśli mapa nie przechyla się i obraca, sprawdź, czy w apiOptions i mapOptions masz podany identyfikator mapy z włączonym pochyleniem i obrotem.

Przechylona mapa

Twój plik app.js powinien teraz wyglądać tak:

    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. Wdrażanie WebGLOverlayView

WebGLOverlayView zapewnia bezpośredni dostęp do tego samego kontekstu renderowania WebGL, który jest używany do renderowania mapy podstawowej wektorowej. Oznacza to, że obiekty 2D i 3D możesz renderować bezpośrednio na mapie przy użyciu WebGL, a także w popularnych bibliotekach graficznych opartych na WebGL.

WebGLOverlayView udostępnia 5 haków na cykl życia renderowania WebGL mapy, którego możesz użyć. Oto krótki opis każdego przywiązania i jego celu:

  • onAdd(): wywoływane, gdy nakładka zostanie dodana do mapy, wywołując setMap w instancji WebGLOverlayView. W tym miejscu wykonaj zadania związane z WebGL, które nie wymagają bezpośredniego dostępu do kontekstu WebGL.
  • onContextRestored(): element jest wywoływany, gdy kontekst WebGL będzie dostępny, ale przed wyrenderowaniem czegokolwiek. W tym miejscu należy zainicjować obiekty, powiązać stan i wykonać inne czynności, które wymagają dostępu do kontekstu WebGL, ale mogą być wykonywane poza wywołaniem onDraw(). Dzięki temu możesz skonfigurować wszystko, czego potrzebujesz, bez nadmiernego nakładu pracy związanego z renderowaniem mapy, które jest już bardzo zaawansowane z użyciem GPU.
  • onDraw(): wywoływana raz na klatkę, gdy WebGL zaczyna renderować mapę i wszelkie inne żądane elementy. Postaraj się jak najszybciej wykonać zadanie w wersji onDraw(), aby uniknąć problemów z wydajnością renderowania mapy.
  • onContextLost(): wywoływane, gdy kontekst renderowania WebGL zostanie utracony z dowolnego powodu.
  • onRemove(): wywołana, gdy nakładka została usunięta z mapy, wywołując setMap(null) w instancji WebGLOverlayView.

W tym kroku utworzysz instancję WebGLOverlayView i zaimplementujesz 3 haczyki cyklu życia: onAdd, onContextRestored i onDraw. W celu zachowania czytelności i łatwości śledzenia cały kod nakładki będzie obsługiwany przez funkcję initWebGLOverlayView() podaną w szablonie początkowym tego ćwiczenia z ćwiczenia.

  1. Utwórz instancję WebGLOverlayView().

    Nakładka jest dostarczana przez interfejs Maps JS API w google.maps.WebGLOverlayView. Na początek utwórz instancję, oczekując na initWebGLOverlayView():
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. Zaimplementuj haki cyklu życia.

    Aby zaimplementować cykle cyklu życia, dodaj ten ciąg do elementu initWebGLOverlayView():
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};
    
  3. Dodaj wystąpienie mapy do mapy.

    Teraz wywołaj setMap() w instancji nakładki i przekaż mapę, dodając następujący fragment do initWebGLOverlayView():
    webGLOverlayView.setMap(map)
    
  4. Zadzwoń do firmy initWebGLOverlayView.

    Ostatnim krokiem jest wykonanie initWebGLOverlayView(), dodając poniższy kod do funkcji wywoływanej u dołu elementu app.js:
    initWebGLOverlayView(map);
    

Funkcja initWebGLOverlayView i natychmiastowo wywołana funkcja powinny wyglądać tak:

    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);
    })();

To wszystko, czego potrzebujesz, aby wdrożyć WebGLOverlayView. Następnie skonfiguruj wszystko, czego potrzebujesz do renderowania obiektu 3D na mapie, używając Three.js.

5. Skonfiguruj scenę 3.js

Korzystanie z WebGL może być bardzo skomplikowane, ponieważ wymaga ręcznego określenia wszystkich aspektów każdego obiektu, a potem niektórych. Dla ułatwienia do tego ćwiczenia wykorzystamy 3.js – popularną bibliotekę grafiki – udostępnia ona uproszczoną warstwę abstrakcyjną oprócz interfejsu WebGL. Three.js oferuje szeroką gamę wygodnych funkcji, które pozwalają korzystać z wielu elementów – od renderowania renderowania WebGL po rysowanie popularnych kształtów obiektów 2D i 3D, a także sterowanie kamerami i przekształcaniem obiektów.

W pliku Three.js istnieją 3 podstawowe typy obiektów, które są wymagane do wyświetlania czegokolwiek:

  • Scena: &kontener" gdzie wszystkie obiekty, źródła światła, tekstury itd. są renderowane i wyświetlane.
  • Aparat: kamera reprezentująca punkt widokowy. Dostępnych jest wiele rodzajów kamer. Do jednej sceny można dodać co najmniej jedną kamerę.
  • Mechanizm renderowania: mechanizm renderowania, który służy do przetwarzania i wyświetlania wszystkich obiektów w scenie. W aplikacji Three.js najczęściej używany jest WebGLRenderer, ale kilka innych jest dostępnych jako działanie awaryjne na wypadek, gdyby klient nie obsługiwał WebGL.

W tym kroku wczytasz wszystkie zależności wymagane przez kod Three.js i skonfigurujesz podstawową scenę.

  1. Wczytaj trzy.js

    Te ćwiczenia z programowania będą wymagały 2 zależności: biblioteki Three.js i modułu wczytywania GLTF, który umożliwia ładowanie obiektów 3D w formacie GL Trasmission (gLTF). Three.js oferuje specjalistyczne narzędzia do wczytywania różnych formatów obiektów 3D, ale zalecamy używanie gLTF.

    W poniższym kodzie zaimportowana jest cała biblioteka Three.js. W aplikacji produkcyjnej możesz chcieć zaimportować tylko te zajęcia, z którymi chcesz korzystać. Aby ułatwić sobie pracę z programowaniem, zaimportuj całą bibliotekę. Zauważ też, że komponent GLTF Loader nie znajduje się w bibliotece domyślnej i trzeba go zaimportować z osobnej ścieżki w zależności – jest to ścieżka, na której masz dostęp do wszystkich modułów wczytywania dostępnych w Three.js.

    Aby zaimportować 3.js i ładowanie GLTF, u góry strony app.js dodaj:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. Utwórz scenę 3.js.

    Aby utworzyć scenę, wymień klasę 3.js Scene, dołączając do haczyka onAdd:
    scene = new THREE.Scene();
    
  3. Dodaj kamerę do sceny.

    Jak wspomnieliśmy wcześniej, kamera reprezentuje perspektywę sceny, a także określa, jak Three.js obsługuje wizualne renderowanie obiektów w ramach sceny. Bez kamery scena nie będzie „widoczna”, co oznacza, że obiekty nie pojawią się, ponieważ nie zostaną wyrenderowane.

    Strona 3.js udostępnia różne aparaty, które wpływają na to, jak mechanizm renderowania traktuje obiekty względem określonej perspektywy i głębi. W tej scenie użyjesz PerspectiveCamera (najczęściej stosowanego typu kamery w Three.js), który ma emulować sposób postrzegania obrazu przez ludzkie oko. Oznacza to, że obiekty w większej odległości od kamery będą mniejsze od obiektów, które są bliżej, scena ma znikać i nie tylko.

    Aby dodać do punktu widzenia kamerę z perspektywy, dołącz do uchwytu onAdd te elementy:
    camera = new THREE.PerspectiveCamera();
    
    W usłudze PerspectiveCamera możesz też skonfigurować atrybuty składające się na punkt widokowy, w tym zbliżenia i odległe obszary, format obrazu oraz pole widzenia (fov). Łącznie tworzą one tzw. frustrację oglądania, co jest niezbędną podstawą przy pracy w 3D, ale wykracza poza zakres tych ćwiczeń z programowania. Wystarczy Ci domyślna konfiguracja PerspectiveCamera.
  4. Dodaj do sceny źródła światła.

    Domyślnie obiekty renderowane w widoku 3.js są wyświetlane jako czarne, bez względu na zastosowane tekstury. Dzieje się tak, ponieważ scena Three.js naśladuje sposób działania obiektów w świecie rzeczywistym, gdzie widoczność jest zależna od oświetlenia obiektu. Krótko mówiąc, bez światła, bez koloru.

    W aplikacji Three.js znajdziesz różnego rodzaju światła, których możesz użyć:

  5. AmbientLight: źródło światła rozproszonego, które równomiernie rozmieszcza wszystkie obiekty w trójkońcu, ze wszystkich kątów. W ten sposób obiekt uzyska podstawową ilość światła, co zapewni, że tekstury na wszystkich obiektach będą widoczne.
  6. DirectionalLight: światło nadaje się do kierunku. W przeciwieństwie do tego, jak działało światło w rzeczywistym świecie, promienie emitujące światło, które emitują DirectionalLight, są równoległe i nie rozkładają się ani nie rozpraszają, gdy oddalają się od źródła światła.

    Możesz skonfigurować kolor i intensywność każdego światła, aby uzyskać efekt zagregowanego oświetlenia. Na przykład w poniższym kodzie światło otoczenia jest dostarczane przez miękkie, białe światło przez całą scenę, a światło kierunkowe zapewnia światło dodatkowe, które pada na kąt obrotu. W przypadku światła kierunkowego kąt jest ustawiany za pomocą wartości position.set(x, y ,z), gdzie każda wartość jest określana względem odpowiedniej osi. Na przykład position.set(0,1,0) może umieścić światło bezpośrednio nad sceną na osi Y, skierowanej prosto w dół.

    Aby dodać do sceny źródła światła, do haka typu onAdd dodaj te informacje:
    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);
    

Twoja reguła onAdd powinna wyglądać tak:

    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);
    }

Scena jest teraz skonfigurowana i gotowa do renderowania. Następnie skonfigurujesz mechanizm renderowania WebGL i renderujesz scenę.

6. Renderuj scenę

Czas wyrenderowania sceny. Do tej pory wszystko, co zostało utworzone przez Ciebie w języku Three.js, jest inicjowane w kodzie, ale zasadniczo nie istnieje, ponieważ nie zostało jeszcze wyrenderowane w kontekście renderowania WebGL. Interfejs WebGL umożliwia renderowanie treści 2D i 3D w przeglądarce przy użyciu interfejsu canvas API. Jeśli zdarzyło Ci się już korzystać z interfejsu Canvas API, prawdopodobnie znasz interfejs context kanwy HTML, w której wszystko jest renderowane. Możesz nie wiedzieć, że to jest interfejs, który ujawnia kontekst renderowania grafiki przez OpenGL za pomocą interfejsu API WebGLRenderingContext w przeglądarce.

Aby ułatwić obsługę mechanizmu renderowania WebGL, Three.js udostępnia opakowanie WebGLRenderer, które pozwala stosunkowo łatwo skonfigurować kontekst renderowania WebGL, co pozwoli przeglądarce Three.js renderować sceny w przeglądarce. W przypadku mapy nie wystarczy jednak po prostu wyrenderowanie sceny Three.js w przeglądarce obok mapy. Kod 3.js musi renderować się w tym samym kontekście co mapa, tak aby mapa i wszystkie obiekty ze sceny Three.js były renderowane w tym samym miejscu w świecie. Dzięki temu mechanizm renderowania może obsługiwać interakcje między obiektami na mapie i innymi obiektami w scenie, np. przesłanianie – to świetna metoda, dzięki której obiekt ukrywa się za obiektem.

Brzmi skomplikowanie, prawda? Na szczęście Three.js przychodzi z pomocą.

  1. Skonfiguruj mechanizm renderowania WebGL.

    Podczas tworzenia nowego wystąpienia pliku Three.js WebGLRenderer możesz podać specyficzny kontekst renderowania WebGL, w którym chcesz renderować swoją scenę. Pamiętasz argument gl przekazywany do haka onContextRestored? Ten obiekt gl to kontekst renderowania mapy za pomocą WebGL. Wystarczy, że podasz kontekst, jego obszar roboczy i jego atrybuty do instancji WebGLRenderer. Wszystkie one będą dostępne w obiekcie gl. W tym kodzie właściwość autoClear jest też ustawiona na false, dzięki czemu mechanizm renderowania nie czyści danych wyjściowych każdej klatki.

    Aby skonfigurować mechanizm renderowania, dodaj te informacje do haczyka onContextRestored:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. Renderuj scenę.

    Po skonfigurowaniu mechanizmu renderowania wywołaj metodę requestRedraw w instancji WebGLOverlayView, aby poinformować nakładkę, że przy renderowaniu następnej klatki potrzebna jest edycja, a następnie wywołaj render w mechanizmie renderowania i prześlij mu scenę i kamerę Three.js, które mają być renderowane. Na koniec wyczyść stan renderowania WebGL. To ważny krok w celu uniknięcia konfliktów stanu GL, ponieważ korzystanie z widoku nakładki WebGL wykorzystuje stan współużytkowany. Jeśli stan nie zostanie zresetowany na końcu każdego wywołania rysowania, konflikty stanu GL mogą spowodować błędy mechanizmu renderowania.

    Aby to zrobić, dołącz do haka typu onDraw ten fragment, tak by został on uruchomiony w każdej klatce:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

Haki onContextRestored i onDraw powinny wyglądać tak:

    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. Renderowanie modelu 3D na mapie

Dobrze, masz już wszystko. Udało Ci się skonfigurować widok nakładki WebGL i utworzyć scenę w formacie Three.js, ale wystąpił jeden problem: nie ma w tym nic. Teraz czas wyrenderować obiekt 3D w scenie. Aby to zrobić, użyj zaimportowanego wcześniej GLTF Loader.

Modele 3D są dostępne w wielu różnych formatach, ale w przypadku Three.js preferowany jest format gLTF ze względu na rozmiar i wydajność. W tym ćwiczeniu z programowania masz już model do renderowania na scenie w /src/pin.gltf.

  1. Utwórz instancję modelu wczytującego model.

    Dołącz do użytkownika onAdd te informacje:
    loader = new GLTFLoader();
    
  2. Wczytaj model 3D.

    Ładowarki modeli są asynchroniczne i wykonują wywołanie zwrotne po całkowitym wczytaniu modelu. Aby wczytać pin.gltf, dołącz do onAdd ten element:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. Dodaj model do sceny.

    Teraz możesz dodać model do sceny, dołączając do wywołania zwrotnego loader poniższy kod. Dodajemy kolumnę gltf.scene, a nie gltf:
    scene.add(gltf.scene);
    
  4. Skonfiguruj macierz projekcji.

    Ostatnią rzeczą, jakiej potrzebujesz do prawidłowego wyrenderowania modelu na mapie, jest ustawienie macierzy projekcji w scenie 3.js. Macierz projekcji jest określana jako tablica Three.js Matrix4, która definiuje punkt w przestrzeni trójwymiarowej razem z przekształceniami, takimi jak obrót, przecięcie, skala i inne.

    W przypadku WebGLOverlayView tablica projekcji służy do renderowania mechanizmu renderowania, aby pokazać, gdzie i jak ma renderować scenę 3.js w odniesieniu do mapy bazowej. Wystąpił jednak problem. Lokalizacje na mapie są określane jako współrzędne szerokości i długości geograficznej, a w widoku Three.js – lokalizacje Vector3. Jak można się domyślić, obliczenie konwersji między dwoma systemami nie jest proste. Aby rozwiązać ten problem, WebGLOverlayView przekazuje obiekt coordinateTransformer do webhooka cyklu życia OnDraw, który zawiera funkcję o nazwie fromLatLngAltitude. fromLatLngAltitude przyjmuje obiekt LatLngAltitude lub LatLngAltitudeLiteral oraz opcjonalnie zbiór argumentów, które definiują przekształcenie sceny, a następnie obejmują je za matrycę wyświetlania modelu (MVP). Wystarczy, że określisz miejsce na mapie, w którym ma być renderowana scena Three.js, oraz sposób, w jaki ma ona zostać przekształcona, a WebGLOverlayView zajmie się resztą. Potem możesz przekonwertować matrycę MVP na tablicę Matrix4 z danymi Three.js i ustawić na nią macierz projekcji.

    W drugim kodzie drugi argument informuje widok nakładki Webgl o ustawieniu wysokości sceny 3.js nad 120 metrów nad ziemią. Dzięki temu model będzie się unosił.

    Aby ustawić macierz wyświetlania obrazu z kamery onDraw, zamieść ten fragment:
    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. Przekształć model.

    Zobaczysz, że pinezka nie znajduje się prostopadle do mapy. Oprócz grafiki świata w przestrzeni 3D z osobnymi osiami x, y i z, która określa orientację, każdy obiekt ma też własną przestrzeń obiektu z niezależnym osiem.

    W tym modelu nie został on utworzony z zastosowaniem parametru „góra&#39”, czyli pinezki składającej się z górą osi Y, dlatego musisz przekształcić obiekt, aby ustawić go w odpowiedni sposób względem powierzchni świata, wywołując go rotation.set. Pamiętaj, że w aplikacji Three.js obrót jest określany w radianach, a nie w stopniach. Ogólnie rzecz biorąc, łatwiej jest myśleć w stopniach, dlatego odpowiednią konwersję należy zrealizować za pomocą formuły degrees * Math.PI/180.

    Ponadto model jest trochę za mały, więc możesz ustawić jego skalowanie równomiernie na wszystkich osiach, wywołując właściwość scale.set(x, y ,z).

    Aby obrócić i skalować model, dodaj te elementy w wywołaniu zwrotnym loader (onAdd) przed scene.add(gltf.scene), które dodaje gLTF do sceny:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

Od tej pory pinezka stoi pionowo na mapie.

Kręgle

Haki onAdd i onDraw powinny wyglądać tak:

    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();
    }

Dalej mamy animacje animacji.

8. Animowanie kamery

Po wyrenderowaniu modelu na mapie i przeniesieniu wszystkiego w sposób trójwymiarowy możesz chcieć kontrolować swój ruch. Funkcja moveCamera umożliwia jednoczesne ustawianie środka, powiększenia, pochylenia i właściwości nagłówka mapy, dzięki czemu masz większą kontrolę nad wrażeniami użytkowników. Dodatkowo można wywołać funkcję moveCamera w pętli animacji, aby utworzyć płynne przejścia między klatkami o prawie 60 klatek na sekundę.

  1. Poczekaj na załadowanie modelu.

    Aby zadbać o wygodę użytkowników, poczekaj, aż rozpocznie się przenoszenie kamery aż do załadowania modelu gLTF. Aby to zrobić, dołącz moduł obsługi zdarzeń onLoad programu wczytującego onContextRestored haczyk:
    loader.manager.onLoad = () => {}
    
  2. Utwórz pętlę animacji.

    Istnieje więcej niż 1 sposób tworzenia pętli animacji, np. setInterval lub requestAnimationFrame. W takim przypadku użyjesz funkcji setAnimationLoop mechanizmu renderowania Three.js, która będzie automatycznie wywoływać kod zadeklarowany w wywołaniu zwrotnym za każdym razem, gdy wyrenderuje ona nową klatkę. Aby utworzyć pętlę animacji, w poprzednim kroku dodaj do modułu obsługi zdarzeń onLoad ten element:
    renderer.setAnimationLoop(() => {});
    
  3. Ustaw pozycję kamery w pętli animacji.

    Następnie zadzwoń do: moveCamera, aby zaktualizować mapę. Do określenia pozycji kamery służą właściwości z obiektu mapOptions, które zostały użyte do załadowania mapy:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. Aparat aktualizuje każdą klatkę.

    To już ostatni krok: Zaktualizuj obiekt mapOptions na końcu każdej klatki, aby w następnej klatce ustawić jej położenie. W tym kodzie instrukcja if zwiększa nachylenie do osiągnięcia maksymalnej wartości nachylenia, która wynosi 67,5, a następnie zmienia się nieco w każdej klatce, dopóki kamera nie przewróci w pełni obrotu o 360 stopni. Po zakończeniu wybranej animacji null jest przekazywana do setAnimationLoop w celu anulowania animacji, dzięki czemu nie będzie ona działać na zawsze.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

Twoja reguła onContextRestored powinna wyglądać tak:

    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. Gratulacje

Jeśli wszystko poszło zgodnie z planem, powinna być mapa z dużą pinezką 3D, która wygląda tak:

Końcowy kod 3D

Czego się nauczysz

Dzięki tym ćwiczeniom nauczysz się wielu rzeczy:

  • Implementacja znaczników WebGLOverlayView i haków cyklu życia.
  • Integracja Three.js z mapą.
  • Podstawy tworzenia sceny Three.js, w tym kamery i oświetlenie.
  • Wczytywanie modeli 3D i manipulowanie nimi za pomocą Three.js.
  • Sterowanie kamerą i animowanie jej na mapie za pomocą funkcji moveCamera.

Co dalej?

WebGL i grafika komputerowa to zagadnienie skomplikowane, dlatego zawsze warto się uczyć. Na początek zapoznaj się z tymi materiałami: