1. Zanim zaczniesz
Z tego laboratorium dowiesz się, jak używać funkcji interfejsu Maps JavaScript API opartych na WebGL do sterowania mapą wektorową i renderowania jej w 3D.
Wymagania wstępne
W tym samouczku zakłada się, że masz średnio zaawansowaną wiedzę o JavaScript i interfejsie Maps JavaScript API. Aby poznać podstawy korzystania z interfejsu Maps JS API, wypróbuj ćwiczenia z programowania dotyczące dodawania mapy do witryny (JavaScript).
Czego się nauczysz
- Generowanie identyfikatora mapy z włączoną mapą wektorową w JavaScript.
- Sterowanie mapą za pomocą programowego pochylania i obracania.
- Renderowanie obiektów 3D na mapie za pomocą
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;
- średnio zaawansowana znajomość języków JavaScript, HTML i CSS;
- wybrany edytor tekstu lub IDE;
- Node.js
2. Konfiguracja
W kroku włączania poniżej musisz włączyć interfejs Maps JavaScript API.
Konfigurowanie Google Maps Platform
Jeśli nie masz jeszcze konta Google Cloud Platform i projektu z włączonymi płatnościami, zapoznaj się z przewodnikiem Pierwsze kroki z Google Maps Platform, aby utworzyć konto rozliczeniowe i projekt.
- W konsoli Google Cloud kliknij menu projektu i wybierz projekt, którego chcesz użyć w tym samouczku.
- Włącz interfejsy API i pakiety SDK Google Maps Platform wymagane w tym samouczku w Google Cloud Marketplace. Aby to zrobić, wykonaj czynności opisane w tym filmie lub tej dokumentacji.
- Wygeneruj klucz interfejsu API na stronie Dane logowania w konsoli Cloud. Możesz wykonać czynności opisane w tym filmie lub tej dokumentacji. Wszystkie żądania wysyłane do Google Maps Platform wymagają klucza interfejsu API.
Konfiguracja Node.js
Jeśli nie masz jeszcze środowiska wykonawczego Node.js, pobierz je i zainstaluj na komputerze, przechodząc na stronę https://nodejs.org/.
Node.js zawiera menedżera pakietów npm, który jest potrzebny do zainstalowania zależności w tym ćwiczeniu.
Pobieranie szablonu projektu startowego
Zanim zaczniesz to ćwiczenie (w Codelabs), wykonaj te czynności, aby pobrać szablon projektu początkowego oraz pełny kod rozwiązania:
- Pobierz lub sklonuj repozytorium GitHub tego modułu na stronie https://github.com/googlecodelabs/maps-platform-101-webgl/. Projekt początkowy znajduje się w katalogu
/starter
i zawiera podstawową strukturę plików potrzebną do ukończenia ćwiczeń z programowania. Wszystko, czego potrzebujesz do pracy, znajduje się w katalogu/starter/src
. - Po pobraniu projektu początkowego uruchom polecenie
npm install
w katalogu/starter
. Spowoduje to zainstalowanie wszystkich potrzebnych zależności wymienionych w plikupackage.json
. - Po zainstalowaniu zależności uruchom w katalogu polecenie
npm start
.
Projekt startowy został skonfigurowany tak, aby używać webpack-dev-server, który kompiluje i uruchamia pisany przez Ciebie kod lokalnie. webpack-dev-server automatycznie przeładowuje też aplikację w przeglądarce za każdym razem, gdy wprowadzasz zmiany w kodzie.
Jeśli chcesz zobaczyć działający pełny kod rozwiązania, wykonaj powyższe czynności konfiguracyjne w katalogu /solution
.
Dodawanie klucza interfejsu API
Aplikacja startowa zawiera cały kod potrzebny do wczytania mapy za pomocą JS API Loader, więc wystarczy, że podasz klucz interfejsu API i identyfikator mapy. JS API Loader to prosta biblioteka, która abstrahuje tradycyjną metodę wczytywania interfejsu Maps JS API wbudowanego w szablon HTML za pomocą tagu script
, umożliwiając obsługę wszystkiego w kodzie JavaScript.
Aby dodać klucz interfejsu API, wykonaj w projekcie początkowym te czynności:
- Otwórz pokój
app.js
. - W obiekcie
apiOptions
ustaw klucz interfejsu API jako wartośćapiOptions.apiKey
.
3. Generowanie i używanie identyfikatora mapy
Aby korzystać z funkcji interfejsu Maps JavaScript API opartych na WebGL, potrzebujesz identyfikatora mapy z włączoną mapą wektorową.
Generowanie identyfikatora mapy
- W konsoli Google Cloud kliknij „Google Maps Platform” > „Zarządzanie mapami”.
- Kliknij „UTWÓRZ NOWY IDENTYFIKATOR MAPY”.
- W polu „Nazwa mapy” wpisz nazwę identyfikatora mapy.
- W menu „Typ mapy” wybierz „JavaScript”. Pojawi się opcja „Opcje JavaScriptu”.
- W sekcji „Opcje JavaScript” wybierz przycisk „Wektor”, pole wyboru „Pochylenie” i pole wyboru „Obrót”.
- Opcjonalnie. W polu „Opis” wpisz opis klucza interfejsu API.
- Kliknij przycisk „Dalej”. Pojawi się strona „Szczegóły identyfikatora mapy”.
- Skopiuj identyfikator mapy. Użyjesz go w następnym kroku, aby wczytać mapę.
Korzystanie z identyfikatora mapy
Aby wczytać mapę wektorową, musisz podać identyfikator mapy jako właściwość w opcjach podczas tworzenia instancji mapy. Opcjonalnie możesz też podać ten sam identyfikator mapy podczas wczytywania interfejsu Maps JavaScript API.
Aby wczytać mapę za pomocą identyfikatora mapy:
- Ustaw identyfikator mapy jako wartość
mapOptions.mapId
.
Podanie identyfikatora mapy podczas tworzenia instancji mapy informuje Google Maps Platform, którą z Twoich map ma wczytać w przypadku danej instancji. Możesz używać tego samego identyfikatora mapy w kilku aplikacjach lub w kilku widokach w ramach 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 pochyleniem i obrotem powinna się wczytać. Aby sprawdzić, czy pochylenie i obrót są włączone, przytrzymaj klawisz Shift i przeciągnij myszą lub użyj klawiszy strzałek na klawiaturze.
Jeśli mapa się nie wczytuje, sprawdź, czy w apiOptions
podano prawidłowy klucz API. Jeśli mapa nie przechyla się i nie obraca, sprawdź, czy w elementach apiOptions
i mapOptions
podano identyfikator mapy z włączonymi funkcjami przechylania i obracania.
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. Implementowanie WebGLOverlayView
WebGLOverlayView
zapewnia bezpośredni dostęp do tego samego kontekstu renderowania WebGL, który jest używany do renderowania wektorowej mapy bazowej. Oznacza to, że możesz renderować obiekty 2D i 3D bezpośrednio na mapie za pomocą WebGL, a także popularnych bibliotek graficznych opartych na WebGL.
WebGLOverlayView
udostępnia 5 punktów zaczepienia w cyklu życia kontekstu renderowania WebGL mapy, z których możesz korzystać. Oto krótki opis każdego z nich i informacje o tym, do czego służą:
onAdd()
: wywoływana, gdy nakładka jest dodawana do mapy przez wywołanie metodysetMap
na instancjiWebGLOverlayView
. W tym miejscu należy wykonywać wszystkie działania związane z WebGL, które nie wymagają bezpośredniego dostępu do kontekstu WebGL.onContextRestored()
: wywoływana, gdy kontekst WebGL staje się dostępny, ale zanim cokolwiek zostanie wyrenderowane. W tym miejscu należy zainicjować obiekty, powiązać stan i wykonać inne czynności, które wymagają dostępu do kontekstu WebGL, ale można je wykonać poza wywołaniemonDraw()
. Dzięki temu możesz skonfigurować wszystko, czego potrzebujesz, bez dodawania nadmiernego obciążenia do rzeczywistego renderowania mapy, które już jest wymagające dla procesora graficznego.onDraw()
: wywoływana raz na klatkę, gdy WebGL zaczyna renderować mapę i wszystkie inne elementy, o które prosisz. W funkcjionDraw()
należy wykonywać jak najmniej działań, aby uniknąć problemów z wydajnością podczas renderowania mapy.onContextLost()
: wywoływana, gdy kontekst renderowania WebGL zostanie utracony z dowolnego powodu.onRemove()
: wywoływana, gdy nakładka zostanie usunięta z mapy przez wywołanie funkcjisetMap(null)
na instancjiWebGLOverlayView
.
W tym kroku utworzysz instancję WebGLOverlayView
i wdrożysz 3 jej funkcje cyklu życia: onAdd
, onContextRestored
i onDraw
. Aby zachować przejrzystość i ułatwić śledzenie, cały kod nakładki będzie obsługiwany w funkcji initWebGLOverlayView()
podanej w szablonie początkowym tego laboratorium.
- Utwórz instancję
WebGLOverlayView()
.
Nakładka jest udostępniana przez interfejs Maps JS API wgoogle.maps.WebGLOverlayView
. Aby rozpocząć, utwórz instancję, dodając doinitWebGLOverlayView()
ten ciąg znaków:const webGLOverlayView = new google.maps.WebGLOverlayView();
- Wdrażaj punkty zaczepienia cyklu życia.
Aby wdrożyć funkcje cyklu życia, dodaj doinitWebGLOverlayView()
ten kod:webGLOverlayView.onAdd = () => {}; webGLOverlayView.onContextRestored = ({gl}) => {}; webGLOverlayView.onDraw = ({gl, transformer}) => {};
- Dodaj instancję nakładki do mapy.
Teraz wywołajsetMap()
w instancji nakładki i przekaż mapę, dodając doinitWebGLOverlayView()
ten kod:webGLOverlayView.setMap(map)
- Zadzwoń do firmy
initWebGLOverlayView
.
Ostatnim krokiem jest wykonanie funkcjiinitWebGLOverlayView()
przez dodanie do natychmiast wywoływanej funkcji u dołu plikuapp.js
tego kodu:initWebGLOverlayView(map);
Funkcja initWebGLOverlayView
i funkcja wywoływana natychmiast powinny teraz 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, transformer}) => {}
webGLOverlayView.setMap(map);
}
(async () => {
const map = await initMap();
initWebGLOverlayView(map);
})();
To wszystko, co musisz zrobić, aby wdrożyć WebGLOverlayView
. Następnie skonfigurujesz wszystko, co jest potrzebne do renderowania obiektu 3D na mapie za pomocą Three.js.
5. Konfigurowanie sceny three.js
Korzystanie z WebGL może być bardzo skomplikowane, ponieważ wymaga ręcznego zdefiniowania wszystkich aspektów każdego obiektu i nie tylko. Aby ułatwić sobie pracę, w tym ćwiczeniu użyjesz Three.js, popularnej biblioteki graficznej, która zapewnia uproszczoną warstwę abstrakcji na WebGL. Three.js zawiera wiele funkcji ułatwiających pracę, które wykonują różne zadania, od tworzenia renderera WebGL po rysowanie popularnych kształtów obiektów 2D i 3D oraz sterowanie kamerami, przekształceniami obiektów i wieloma innymi elementami.
W Three.js są 3 podstawowe typy obiektów, które są wymagane do wyświetlania czegokolwiek:
- Scena: „kontener”, w którym renderowane i wyświetlane są wszystkie obiekty, źródła światła, tekstury itp.
- Kamera: kamera, która reprezentuje punkt widzenia sceny. Dostępnych jest kilka typów kamer, a do jednej sceny można dodać jedną lub więcej kamer.
- Renderer: renderer, który obsługuje przetwarzanie i wyświetlanie wszystkich obiektów w scenie. W Three.js najczęściej używany jest element
WebGLRenderer
, ale w przypadku, gdy klient nie obsługuje WebGL, dostępne są też inne elementy.
W tym kroku załadujesz wszystkie zależności potrzebne do Three.js i skonfigurujesz podstawową scenę.
- Wczytaj three.js
W tym laboratorium potrzebne będą 2 zależności: biblioteka Three.js i GLTF Loader, czyli klasa, która umożliwia wczytywanie obiektów 3D w formacie GL Transmission Format (gLTF). Three.js oferuje specjalne moduły wczytujące dla wielu różnych formatów obiektów 3D, ale zalecamy używanie formatu gLTF.
W poniższym kodzie importowana jest cała biblioteka Three.js. W aplikacji produkcyjnej prawdopodobnie będziesz importować tylko potrzebne klasy, ale w tym ćwiczeniu zaimportuj całą bibliotekę, aby uprościć zadanie. Zwróć też uwagę, że moduł GLTF Loader nie jest uwzględniony w bibliotece domyślnej i musi zostać zaimportowany z osobnej ścieżki w zależności – to ścieżka, w której możesz uzyskać dostęp do wszystkich modułów wczytujących udostępnianych przez Three.js.
Aby zaimportować Three.js i GLTF Loader, dodaj ten kod na początku plikuapp.js
:import * as THREE from 'three'; import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
- Utwórz scenę three.js.
Aby utworzyć scenę, utwórz instancję klasy Three.jsScene
, dodając ten kod do elementuonAdd
:scene = new THREE.Scene();
- Dodaj kamerę do sceny.
Jak wspomnieliśmy wcześniej, kamera reprezentuje perspektywę oglądania sceny i określa, w jaki sposób Three.js obsługuje wizualne renderowanie obiektów w scenie. Bez kamery scena nie jest „widziana”, co oznacza, że obiekty nie będą się pojawiać, ponieważ nie będą renderowane.
Three.js oferuje różne rodzaje kamer, które wpływają na sposób, w jaki moduł renderujący traktuje obiekty pod względem perspektywy i głębi. W tej scenie użyjeszPerspectiveCamera
, czyli najczęściej używanego typu kamery w Three.js, który został zaprojektowany tak, aby naśladować sposób, w jaki ludzkie oko postrzega scenę. Oznacza to, że obiekty znajdujące się dalej od aparatu będą mniejsze niż te, które są bliżej, scena będzie miała punkt zbiegu i tak dalej.
Aby dodać do sceny kamerę perspektywiczną, dołącz do hakaonAdd
ten kod: Za pomocącamera = new THREE.PerspectiveCamera();
PerspectiveCamera
możesz też skonfigurować atrybuty, które składają się na punkt widzenia, w tym płaszczyzny bliską i daleką, format obrazu oraz pole widzenia (fov). Te atrybuty tworzą tzw. stożek widzenia, czyli ważne pojęcie w pracy z grafiką 3D, ale wykraczające poza zakres tego laboratorium. Domyślna konfiguracjaPerspectiveCamera
będzie w zupełności wystarczająca. - Dodaj do sceny źródła światła.
Domyślnie obiekty renderowane w scenie Three.js będą wyświetlane na czarno, niezależnie od zastosowanych tekstur. Dzieje się tak, ponieważ scena Three.js naśladuje zachowanie obiektów w rzeczywistym świecie, w którym widoczność koloru zależy od światła odbijającego się od obiektu. Krótko mówiąc, bez światła nie ma koloru.
Three.js udostępnia różne typy świateł, z których będziesz używać 2: AmbientLight
: zapewnia rozproszone źródło światła, które równomiernie oświetla wszystkie obiekty na scenie ze wszystkich stron. Dzięki temu scena będzie miała podstawową ilość światła, co zapewni widoczność tekstur na wszystkich obiektach.DirectionalLight
: zapewnia światło pochodzące z określonego kierunku w scenie. W przeciwieństwie do tego, jak działałoby światło w rzeczywistości, promienie świetlne emitowane zDirectionalLight
są równoległe i nie rozpraszają się ani nie rozpraszają w miarę oddalania się od źródła światła.
Możesz skonfigurować kolor i natężenie każdego światła, aby tworzyć złożone efekty świetlne. Na przykład w poniższym kodzie światło otoczenia zapewnia miękkie białe światło dla całej sceny, a światło kierunkowe zapewnia dodatkowe światło padające na obiekty pod kątem skierowanym w dół. W przypadku światła kierunkowego kąt jest ustawiany za pomocą parametruposition.set(x, y ,z)
, gdzie każda wartość jest względna w stosunku do odpowiedniej osi. Na przykładposition.set(0,1,0)
umieści światło bezpośrednio nad sceną na osi Y, skierowane prosto w dół.
Aby dodać źródła światła do sceny, dołącz do hakaonAdd
ten kod: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);
Twój hook onAdd
powinien teraz 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 moduł renderowania WebGL i wyrenderujesz scenę.
6. Renderowanie sceny
Czas wyrenderować scenę. Wszystko, co do tej pory zostało utworzone za pomocą Three.js, jest inicjowane w kodzie, ale w zasadzie nie istnieje, ponieważ nie zostało jeszcze wyrenderowane w kontekście renderowania WebGL. WebGL renderuje treści 2D i 3D w przeglądarce za pomocą interfejsu Canvas API. Jeśli korzystasz już z interfejsu Canvas API, prawdopodobnie znasz context
elementu canvas HTML, w którym wszystko jest renderowane. Może nie wiesz, że jest to interfejs, który udostępnia kontekst renderowania grafiki OpenGL za pomocą interfejsu WebGLRenderingContext
w przeglądarce.
Aby ułatwić korzystanie z renderera WebGL, Three.js udostępnia WebGLRenderer
, czyli otoczkę, która ułatwia konfigurowanie kontekstu renderowania WebGL, dzięki czemu Three.js może renderować sceny w przeglądarce. W przypadku mapy nie wystarczy jednak renderować sceny Three.js w przeglądarce obok mapy. Biblioteka Three.js musi renderować w tym samym kontekście renderowania co mapa, aby zarówno mapa, jak i wszystkie obiekty ze sceny Three.js były renderowane w tej samej przestrzeni świata. Dzięki temu moduł renderujący może obsługiwać interakcje między obiektami na mapie a obiektami w scenie, takie jak zasłanianie, czyli ukrywanie obiektów znajdujących się za nimi.
Brzmi dość skomplikowanie, prawda? Na szczęście z pomocą przychodzi Three.js.
- Skonfiguruj moduł renderowania WebGL.
Podczas tworzenia nowej instancjiWebGLRenderer
Three.js możesz podać konkretny kontekst renderowania WebGL, w którym ma być renderowana scena. Pamiętasz argumentgl
, który jest przekazywany do hookaonContextRestored
? Ten obiektgl
to kontekst renderowania WebGL mapy. Wystarczy, że podasz kontekst, obszar i atrybuty instancjiWebGLRenderer
, które są dostępne w obiekciegl
. W tym kodzie właściwośćautoClear
renderera jest również ustawiona nafalse
, aby renderer nie czyścił danych wyjściowych w każdej klatce.
Aby skonfigurować moduł renderujący, dodaj do hakaonContextRestored
ten kod:renderer = new THREE.WebGLRenderer({ canvas: gl.canvas, context: gl, ...gl.getContextAttributes(), }); renderer.autoClear = false;
- Wyrenderuj scenę.
Po skonfigurowaniu mechanizmu renderowania wywołaj funkcjęrequestRedraw
na instancjiWebGLOverlayView
, aby poinformować nakładkę, że podczas renderowania następnej klatki wymagane jest ponowne rysowanie. Następnie wywołaj funkcjęrender
na mechanizmie renderowania i przekaż jej scenę i kamerę Three.js do renderowania. Na koniec wyczyść stan kontekstu renderowania WebGL. Jest to ważny krok, który pozwala uniknąć konfliktów stanu GL, ponieważ korzystanie z widoku nakładki WebGL zależy od wspólnego stanu GL. Jeśli stan nie zostanie zresetowany na końcu każdego wywołania rysowania, konflikty stanu GL mogą spowodować awarię renderera.
Aby to zrobić, dodaj do hakaonDraw
ten kod, aby był wykonywany w każdej klatce:webGLOverlayView.requestRedraw(); renderer.render(scene, camera); renderer.resetState();
Twoje hooki onContextRestored
i onDraw
powinny teraz 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
OK, wszystkie elementy są na swoim miejscu. Masz skonfigurowany widok nakładki WebGL i utworzoną scenę Three.js, ale jest jeden problem: nic w niej nie ma. Teraz czas na wyrenderowanie obiektu 3D w scenie. Aby to zrobić, użyj zaimportowanego wcześniej narzędzia 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 czasie działania. W tym ćwiczeniu model do renderowania w scenie jest już dostępny w /src/pin.gltf
.
- Utwórz instancję narzędzia do wczytywania modelu.
Dodaj do: listyonAdd
:loader = new GLTFLoader();
- Wczytaj model 3D.
Ładowarki modeli są asynchroniczne i wykonują wywołanie zwrotne po pełnym załadowaniu modelu. Aby wczytaćpin.gltf
, dodaj doonAdd
ten kod:const source = "pin.gltf"; loader.load( source, gltf => {} );
- Dodaj model do sceny.
Teraz możesz dodać model do sceny, dołączając do wywołania zwrotnegoloader
ten kod: Pamiętaj, że dodawany jest znakgltf.scene
, a niegltf
:scene.add(gltf.scene);
- Skonfiguruj macierz projekcji kamery.
Ostatnią rzeczą, którą musisz zrobić, aby model był prawidłowo renderowany na mapie, jest ustawienie macierzy projekcji kamery w scenie Three.js. Macierz projekcji jest określana jako tablica Three.jsMatrix4
, która definiuje punkt w przestrzeni trójwymiarowej wraz z transformacjami, takimi jak obroty, ścinanie, skalowanie i inne.
W przypadkuWebGLOverlayView
macierz projekcji służy do określania, gdzie i jak renderować scenę Three.js względem mapy bazowej. Ale jest pewien problem. Lokalizacje na mapie są określone jako pary współrzędnych szerokości i długości geograficznej, a lokalizacje w scenie Three.js to współrzędneVector3
. Jak zapewne się domyślasz, przeliczenie konwersji między tymi 2 systemami nie jest proste. Aby rozwiązać ten problem,WebGLOverlayView
przekazuje obiektcoordinateTransformer
do haka cyklu życiaOnDraw
, który zawiera funkcję o nazwiefromLatLngAltitude
. FunkcjafromLatLngAltitude
przyjmuje obiektLatLngAltitude
lubLatLngAltitudeLiteral
oraz opcjonalnie zestaw argumentów, które definiują transformację sceny, a następnie przekształca je w macierz projekcji widoku modelu (MVP). Wystarczy, że określisz, w którym miejscu na mapie ma być renderowana scena Three.js, a także jak ma być przekształcana, aWebGLOverlayView
zajmie się resztą. Następnie możesz przekonwertować macierz MVP na tablicęMatrix4
w Three.js i ustawić na nią macierz projekcji kamery.
W poniższym kodzie drugi argument informuje widok nakładki WebGL, aby ustawić wysokość sceny Three.js na 120 metrów nad ziemią, co sprawi, że model będzie wyglądał, jakby unosił się w powietrzu.
Aby ustawić macierz projekcji kamery, dodaj do hakaonDraw
ten kod:const latLngAltitudeLiteral = { lat: mapOptions.center.lat, lng: mapOptions.center.lng, altitude: 120 } const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral); camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
- Przekształć model.
Zauważysz, że pinezka nie jest ustawiona prostopadle do mapy. W grafice 3D oprócz przestrzeni świata, która ma własne osie x, y i z określające orientację, każdy obiekt ma też własną przestrzeń obiektu z niezależnym zestawem osi.
W przypadku tego modelu nie został on utworzony w taki sposób, aby to, co zwykle uważamy za „górę” pinezki, było skierowane wzdłuż osi Y. Musisz więc przekształcić obiekt, aby zorientować go w odpowiedni sposób względem przestrzeni świata, wywołując na nim funkcjęrotation.set
. Pamiętaj, że w Three.js obrót jest określany w radianach, a nie w stopniach. Zwykle łatwiej jest myśleć w stopniach, więc należy dokonać odpowiedniego przeliczenia za pomocą wzorudegrees * Math.PI/180
.
Model jest też trochę mały, więc musisz go równomiernie powiększyć we wszystkich osiach, wywołując funkcjęscale.set(x, y ,z)
.
Aby obrócić i skalować model, dodaj te wiersze wloader
wywołaniu zwrotnymonAdd
przedscene.add(gltf.scene)
dodaniem pliku gLTF do sceny:gltf.scene.scale.set(25,25,25); gltf.scene.rotation.x = 180 * Math.PI/180;
Teraz pinezka jest ustawiona pionowo względem mapy.
Twoje hooki onAdd
i onDraw
powinny teraz 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();
}
Kolejne są animacje aparatu.
8. Animowanie kamery
Po wyrenderowaniu modelu na mapie i możliwości przesuwania wszystkiego w 3D kolejną rzeczą, którą prawdopodobnie zechcesz zrobić, jest programowe sterowanie tym ruchem. Funkcja moveCamera
umożliwia jednoczesne ustawianie właściwości środka, powiększenia, pochylenia i kierunku mapy, co pozwala precyzyjnie kontrolować wrażenia użytkowników. Dodatkowo funkcję moveCamera
można wywoływać w pętli animacji, aby tworzyć płynne przejścia między ramkami z częstotliwością prawie 60 klatek na sekundę.
- Poczekaj, aż model się wczyta.
Aby zapewnić użytkownikom wygodę, zaczekaj z rozpoczęciem przesuwania kamery, aż model gLTF zostanie wczytany. Aby to zrobić, dołącz moduł obsługi zdarzeńonLoad
narzędzia do wczytywania do hakaonContextRestored
:loader.manager.onLoad = () => {}
- Utwórz pętlę animacji.
Istnieje więcej niż jeden sposób tworzenia pętli animacji, np. za pomocąsetInterval
lubrequestAnimationFrame
. W tym przypadku użyjesz funkcjisetAnimationLoop
renderera Three.js, która automatycznie wywoła dowolny kod zadeklarowany w jej wywołaniu zwrotnym za każdym razem, gdy Three.js wyrenderuje nową klatkę. Aby utworzyć pętlę animacji, dodaj do modułu obsługi zdarzeńonLoad
z poprzedniego kroku ten kod:renderer.setAnimationLoop(() => {});
- Ustaw pozycję kamery w pętli animacji.
Następnie wywołajmoveCamera
, aby zaktualizować mapę. W tym przypadku do określenia pozycji kamery używane są właściwości obiektumapOptions
, który został użyty do wczytania mapy:map.moveCamera({ "tilt": mapOptions.tilt, "heading": mapOptions.heading, "zoom": mapOptions.zoom });
- Aktualizuj kamerę w każdej klatce.
Ostatni krok Zaktualizuj obiektmapOptions
na końcu każdej klatki, aby ustawić pozycję kamery dla następnej klatki. W tym kodzie instrukcjaif
służy do zwiększania nachylenia, aż osiągnie ono maksymalną wartość 67,5.Następnie w każdej klatce nieco zmienia się kierunek, aż kamera wykona pełny obrót o 360 stopni. Po zakończeniu animacji do funkcjisetAnimationLoop
przekazywana jest wartośćnull
, aby anulować animację i zapobiec jej ciągłemu działaniu.if (mapOptions.tilt < 67.5) { mapOptions.tilt += 0.5 } else if (mapOptions.heading <= 360) { mapOptions.heading += 0.2; } else { renderer.setAnimationLoop(null) }
Twój hook onContextRestored
powinien teraz 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 się teraz wyświetlać mapa z dużym pinezką 3D, która wygląda tak:
Czego się dowiedziałeś
W tym ćwiczeniu zdobędziesz wiele umiejętności. Oto najważniejsze z nich:
- Wdrażanie interfejsu
WebGLOverlayView
i jego punktów zaczepienia cyklu życia. - Integracja Three.js z mapą.
- Podstawy tworzenia sceny w Three.js, w tym kamer i oświetlenia.
- Wczytywanie modeli 3D i manipulowanie nimi za pomocą Three.js.
- Sterowanie kamerą i animowanie jej na potrzeby mapy za pomocą
moveCamera
.
Co dalej?
WebGL i grafika komputerowa to złożone zagadnienia, więc zawsze jest się czego uczyć. Oto kilka materiałów, które pomogą Ci zacząć:
- Dokumentacja WebGL Overlay View
- Pierwsze kroki z WebGL
- Dokumentacja three.js
- Pomóż nam tworzyć treści, które będą dla Ciebie najbardziej przydatne, i odpowiedz na to pytanie: «codelabs/maps-platform/shared/_next-lab-survey.lab.md» Nie ma tu ćwiczenia z programowania, które Cię interesuje? Zgłoś problem tutaj