3D-Karten mit WebGL Overlay View erstellen

1. Hinweis

In diesem Codelab erfahren Sie, wie Sie die WebGL-gestützten Funktionen der Maps JavaScript API verwenden, um die Vektorkarte dreidimensional zu steuern und zu rendern.

Final 3D Pin

Vorbereitung

Für dieses Codelab sind fortgeschrittene Kenntnisse von JavaScript und der Maps JavaScript API erforderlich. Die Grundlagen der Verwendung der Maps JS API werden im Codelab zum Hinzufügen einer Karte zu Ihrer Website (JavaScript) erläutert.

Lerninhalte

  • Sie generieren eine Karten-ID, für die die Vektorkarte für JavaScript aktiviert ist.
  • Karte mit programmatischer Neigung und Drehung steuern
  • 3D-Objekte auf der Karte mit WebGLOverlayView und Three.js rendern.
  • Kamerabewegungen mit moveCamera animieren.

Voraussetzungen

  • Ein Google Cloud Platform-Konto mit aktivierter Abrechnung
  • Ein Google Maps Platform API-Schlüssel, für den die Maps JavaScript API aktiviert ist
  • Sie haben mittlere Kenntnisse in JavaScript, HTML und CSS.
  • Ein Texteditor oder eine IDE Ihrer Wahl
  • Node.js

2. Einrichten

Im Aktivierungsschritt unten müssen Sie die Maps JavaScript API aktivieren.

Google Maps Platform einrichten

Wenn Sie noch kein Google Cloud-Konto und kein Projekt mit aktivierter Abrechnung haben, lesen Sie bitte den Leitfaden Erste Schritte mit Google Maps Platform, um ein Rechnungskonto und ein Projekt zu erstellen.

  1. Klicken Sie in der Cloud Console auf das Drop-down-Menü für das Projekt und wählen Sie das Projekt aus, das Sie für dieses Codelab verwenden möchten.

  1. Aktivieren Sie die für dieses Codelab erforderlichen APIs und SDKs der Google Maps Platform im Google Cloud Marketplace. Folgen Sie dazu der Anleitung in diesem Video oder dieser Dokumentation.
  2. Generieren Sie einen API-Schlüssel in der Cloud Console auf der Seite Anmeldedaten. Folgen Sie dazu dieser Anleitung oder dieser Dokumentation. Für alle Anfragen an die Google Maps Platform ist ein API-Schlüssel erforderlich.

Node.js-Einrichtung

Wenn Sie sie noch nicht haben, rufen Sie https://nodejs.org/ auf, um die Node.js-Laufzeit auf Ihrem Computer herunterzuladen und zu installieren.

Node.js wird mit dem npm-Paketmanager geliefert, den Sie zum Installieren von Abhängigkeiten für dieses Codelab benötigen.

Projekt-Startervorlage herunterladen

Bevor Sie mit diesem Codelab beginnen, laden Sie die Starterprojektvorlage und den vollständigen Lösungscode herunter:

  1. Laden Sie das GitHub-Repository für dieses Codelab unter https://github.com/googlecodelabs/maps-platform-101-webgl/ herunter oder forken Sie es. Das Starterprojekt befindet sich im Verzeichnis /starter und enthält die grundlegende Dateistruktur, die Sie für das Codelab benötigen. Alle benötigten Dateien befinden sich im Verzeichnis /starter/src.
  2. Führen Sie nach dem Herunterladen des Startprojekts npm install im Verzeichnis /starter aus. Dadurch werden alle erforderlichen Abhängigkeiten installiert, die in package.json aufgeführt sind.
  3. Führen Sie nach der Installation der Abhängigkeiten npm start im Verzeichnis aus.

Das Starterprojekt ist so eingerichtet, dass Sie webpack-dev-server verwenden können. Damit wird der von Ihnen geschriebene Code lokal kompiliert und ausgeführt. Außerdem wird Ihre App im Browser automatisch neu geladen, wenn Sie Codeänderungen vornehmen.

Wenn Sie den vollständigen Lösungscode ausführen möchten, können Sie die oben beschriebenen Einrichtungsschritte im Verzeichnis /solution ausführen.

Eigenen API-Schlüssel hinzufügen

Die Starter-App enthält den gesamten Code, der zum Laden der Karte mit dem JS-API-Ladeprogramm erforderlich ist. Sie müssen also nur Ihren API-Schlüssel und Ihre Karten-ID angeben. Der JS API Loader ist eine einfache Bibliothek, die die herkömmliche Methode zum Inline-Laden der Maps JS API im HTML-Template mit einem script-Tag abstrahiert. So können Sie alles im JavaScript-Code erledigen.

So fügen Sie Ihren API-Schlüssel im Starterprojekt hinzu:

  1. Öffnen Sie app.js.
  2. Legen Sie im apiOptions-Objekt Ihren API-Schlüssel als Wert von apiOptions.apiKey fest.

3. Karten-ID generieren und verwenden

Wenn Sie die WebGL-basierten Funktionen der Maps JavaScript API verwenden möchten, benötigen Sie eine Karten-ID mit aktivierter Vektorkarte.

Karten-ID erstellen

Map-ID-Generierung

  1. Rufen Sie in der Google Cloud Console „Google Maps Platform“ > „Kartenverwaltung“ auf.
  2. Klicken Sie auf „NEUE KARTEN-ID ERSTELLEN“.
  3. Geben Sie im Feld „Kartenname“ einen Namen für Ihre Karten-ID ein.
  4. Wählen Sie im Drop-down-Menü „Kartentyp“ die Option „JavaScript“ aus. Die Option „JavaScript-Optionen“ wird angezeigt.
  5. Wählen Sie unter „JavaScript-Optionen“ das Optionsfeld „Vektor“, das Kästchen „Neigung“ und das Kästchen „Drehung“ aus.
  6. Optional: Geben Sie im Feld „Beschreibung“ eine Beschreibung für den API-Schlüssel ein.
  7. Klicken Sie auf die Schaltfläche „Weiter“. Die Seite „Karten-ID-Details“ wird angezeigt.

    Seite „Kartendetails“
  8. Kopieren Sie die Karten-ID. Sie benötigen sie im nächsten Schritt, um die Karte zu laden.

Karten-ID verwenden

Wenn Sie die Vektorkarte laden möchten, müssen Sie beim Instanziieren der Karte eine Karten-ID als Eigenschaft in den Optionen angeben. Optional können Sie auch dieselbe Karten-ID angeben, wenn Sie die Maps JavaScript API laden.

So laden Sie die Karte mit Ihrer Karten-ID:

  1. Legen Sie Ihre Karten-ID als Wert von mapOptions.mapId fest.

    Wenn Sie die Karten-ID beim Instanziieren der Karte angeben, wird der Google Maps Platform mitgeteilt, welche Ihrer Karten für eine bestimmte Instanz geladen werden sollen. Sie können dieselbe Karten-ID für mehrere Apps oder mehrere Ansichten innerhalb derselben App verwenden.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

Sehen Sie sich die im Browser ausgeführte App an. Die Vektorkarte mit aktivierter Neigung und Drehung sollte geladen werden. Wenn Sie prüfen möchten, ob Neigung und Drehung aktiviert sind, halten Sie die Umschalttaste gedrückt und ziehen Sie die Karte mit der Maus oder verwenden Sie die Pfeiltasten auf der Tastatur.

Wenn die Karte nicht geladen wird, prüfen Sie, ob Sie in apiOptions einen gültigen API-Schlüssel angegeben haben. Wenn sich die Karte nicht neigt und dreht, prüfen Sie, ob Sie eine Karten-ID mit aktivierter Neigung und Drehung in apiOptions und mapOptions angegeben haben.

Geneigte Karte

Ihre app.js-Datei sollte jetzt so aussehen:

    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 implementieren

WebGLOverlayView bietet direkten Zugriff auf denselben WebGL-Renderingkontext, der zum Rendern der Vektorbasiskarte verwendet wird. So können Sie 2D- und 3D-Objekte direkt auf der Karte rendern. Dazu können Sie WebGL direkt oder gängige WebGL-basierte Grafikbibliotheken verwenden.

WebGLOverlayView bietet fünf Hooks für den Lebenszyklus des WebGL-Renderingkontexts der Karte, die Sie verwenden können. Hier finden Sie eine kurze Beschreibung der einzelnen Hooks und wofür Sie sie verwenden sollten:

  • onAdd(): Wird aufgerufen, wenn das Overlay einer Karte hinzugefügt wird, indem setMap für eine WebGLOverlayView-Instanz aufgerufen wird. Hier sollten Sie alle WebGL-bezogenen Aufgaben ausführen, für die kein direkter Zugriff auf den WebGL-Kontext erforderlich ist.
  • onContextRestored(): Wird aufgerufen, wenn der WebGL-Kontext verfügbar wird, aber bevor etwas gerendert wird. Hier sollten Sie Objekte initialisieren, den Status binden und alle anderen Aktionen ausführen, für die Zugriff auf den WebGL-Kontext erforderlich ist, die aber außerhalb des onDraw()-Aufrufs ausgeführt werden können. So können Sie alles einrichten, was Sie benötigen, ohne die GPU-intensive Darstellung der Karte zusätzlich zu belasten.
  • onDraw(): Wird einmal pro Frame aufgerufen, sobald WebGL mit dem Rendern der Karte und aller anderen von Ihnen angeforderten Elemente beginnt. Sie sollten in onDraw() so wenig Arbeit wie möglich erledigen, um Leistungsprobleme beim Rendern der Karte zu vermeiden.
  • onContextLost(): Wird aufgerufen, wenn der WebGL-Renderingkontext aus irgendeinem Grund verloren geht.
  • onRemove(): Wird aufgerufen, wenn das Overlay von der Karte entfernt wird, indem setMap(null) für eine WebGLOverlayView-Instanz aufgerufen wird.

In diesem Schritt erstellen Sie eine Instanz von WebGLOverlayView und implementieren drei der zugehörigen Lebenszyklus-Hooks: onAdd, onContextRestored und onDraw. Damit alles übersichtlich und leicht nachvollziehbar bleibt, wird der gesamte Code für das Overlay in der Funktion initWebGLOverlayView() verarbeitet, die in der Startervorlage für dieses Codelab enthalten ist.

  1. Erstellen Sie eine WebGLOverlayView()-Instanz.

    Das Overlay wird von der Maps JS API in google.maps.WebGLOverlayView bereitgestellt. Erstellen Sie zuerst eine Instanz, indem Sie initWebGLOverlayView() Folgendes hinzufügen:
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. Lebenszyklus-Hooks implementieren

     Fügen Sie initWebGLOverlayView() Folgendes hinzu, um die Lebenszyklus-Hooks zu implementieren:
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, transformer}) => {};
    
  3. Fügen Sie die Overlay-Instanz der Karte hinzu.

    Rufen Sie nun setMap() für die Overlay-Instanz auf und übergeben Sie die Karte, indem Sie initWebGLOverlayView() Folgendes hinzufügen:
    webGLOverlayView.setMap(map)
    
  4. Rufen Sie einfach initWebGLOverlayView an.

     Im letzten Schritt wird initWebGLOverlayView() ausgeführt. Fügen Sie dazu Folgendes am Ende von app.js in die sofort aufgerufene Funktion ein:
    initWebGLOverlayView(map);
    

Ihre initWebGLOverlayView und die sofort aufgerufene Funktion sollten jetzt so aussehen:

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

Das ist alles, was Sie für die Implementierung von WebGLOverlayView benötigen. Als Nächstes richten Sie alles ein, was Sie zum Rendern eines 3D-Objekts auf der Karte mit Three.js benötigen.

5. three.js-Szene einrichten

Die Verwendung von WebGL kann sehr kompliziert sein, da Sie alle Aspekte jedes Objekts manuell definieren müssen. Um die Dinge zu vereinfachen, verwenden Sie in diesem Codelab Three.js, eine beliebte Grafikbibliothek, die eine vereinfachte Abstraktionsebene über WebGL bietet. Three.js bietet eine Vielzahl von praktischen Funktionen, mit denen sich unter anderem ein WebGL-Renderer erstellen, gängige 2D- und 3D-Objektformen zeichnen und Kameras und Objekttransformationen steuern lassen.

Es gibt drei grundlegende Objekttypen in Three.js, die zum Anzeigen von Inhalten erforderlich sind:

  • Szene: Ein „Container“, in dem alle Objekte, Lichtquellen, Texturen usw. gerendert und angezeigt werden.
  • Kamera: Eine Kamera, die den Blickwinkel der Szene darstellt. Es sind mehrere Kameratypen verfügbar und einer Szene können eine oder mehrere Kameras hinzugefügt werden.
  • Renderer: Ein Renderer, der die Verarbeitung und Darstellung aller Objekte in der Szene übernimmt. In Three.js wird WebGLRenderer am häufigsten verwendet. Es sind aber auch einige andere als Fallback verfügbar, falls der Client WebGL nicht unterstützt.

In diesem Schritt laden Sie alle für Three.js erforderlichen Abhängigkeiten und richten eine einfache Szene ein.

  1. Three.js laden 

    Für dieses Codelab benötigen Sie zwei Abhängigkeiten: die Three.js-Bibliothek und den GLTF-Loader, eine Klasse, mit der Sie 3D-Objekte im GL Transmission Format (gLTF) laden können. Three.js bietet spezielle Loader für viele verschiedene 3D-Objektformate, es wird jedoch empfohlen, gLTF zu verwenden.

     Im folgenden Code wird die gesamte Three.js-Bibliothek importiert. In einer Produktions-App sollten Sie wahrscheinlich nur die benötigten Klassen importieren. In diesem Codelab importieren wir jedoch die gesamte Bibliothek, um die Dinge einfach zu halten. Beachten Sie auch, dass der GLTF-Loader nicht in der Standardbibliothek enthalten ist und aus einem separaten Pfad in der Abhängigkeit importiert werden muss. Über diesen Pfad können Sie auf alle von Three.js bereitgestellten Loader zugreifen.

     Fügen Sie oben in app.js Folgendes ein, um Three.js und den GLTF-Loader zu importieren:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. Erstellen Sie eine three.js-Szene.

     Um eine Szene zu erstellen, instanziieren Sie die Three.js-Klasse Scene, indem Sie Folgendes an den onAdd-Hook anhängen:
    scene = new THREE.Scene();
    
  3. Fügen Sie der Szene eine Kamera hinzu.

    Wie bereits erwähnt, stellt die Kamera die Perspektive der Szene dar und bestimmt, wie Three.js das visuelle Rendern von Objekten in einer Szene handhabt. Ohne Kamera wird die Szene nicht „gesehen“. Das bedeutet, dass Objekte nicht angezeigt werden, weil sie nicht gerendert werden.

    Three.js bietet eine Vielzahl verschiedener Kameras, die sich darauf auswirken, wie der Renderer Objekte in Bezug auf Perspektive und Tiefe behandelt. In dieser Szene verwenden Sie die PerspectiveCamera, den am häufigsten verwendeten Kameratyp in Three.js. Sie ist so konzipiert, dass sie die Szene so wiedergibt, wie das menschliche Auge sie wahrnehmen würde. Das bedeutet, dass Objekte, die weiter von der Kamera entfernt sind, kleiner erscheinen als Objekte, die näher sind. Außerdem hat die Szene einen Fluchtpunkt.

     So fügen Sie der Szene eine Perspektivenkamera hinzu:onAdd
    camera = new THREE.PerspectiveCamera();
    
    Mit PerspectiveCamera können Sie auch die Attribute konfigurieren, aus denen der Blickwinkel besteht, einschließlich der nahen und fernen Ebenen, des Seitenverhältnisses und des Sichtfelds (field of vision, fov). Zusammen bilden diese Attribute den sogenannten Sichtkegel. Das ist ein wichtiges Konzept für die Arbeit in 3D, das in diesem Codelab jedoch nicht behandelt wird. Die Standardkonfiguration von PerspectiveCamera reicht vollkommen aus.
  4. Fügen Sie der Szene Lichtquellen hinzu.

    Standardmäßig werden Objekte, die in einer Three.js-Szene gerendert werden, schwarz dargestellt, unabhängig von den auf sie angewendeten Texturen. Das liegt daran, dass in einer Three.js-Szene das Verhalten von Objekten in der realen Welt nachgeahmt wird. Dort hängt die Sichtbarkeit von Farben davon ab, ob Licht von einem Objekt reflektiert wird. Kurz gesagt: Ohne Licht keine Farbe.

    Three.js bietet eine Vielzahl verschiedener Arten von Lichtquellen, von denen Sie zwei verwenden werden:

  5. AmbientLight: Stellt eine diffuse Lichtquelle bereit, die alle Objekte in der Szene aus allen Blickwinkeln gleichmäßig beleuchtet. So erhält die Szene eine grundlegende Lichtmenge, damit die Texturen aller Objekte sichtbar sind.
  6. DirectionalLight: Stellt ein Licht dar, das aus einer Richtung in der Szene kommt. Anders als in der realen Welt sind die Lichtstrahlen, die von DirectionalLight ausgehen, alle parallel und breiten sich nicht aus, je weiter sie sich von der Lichtquelle entfernen.

     Sie können die Farbe und Intensität jeder Lampe konfigurieren, um kombinierte Lichteffekte zu erzielen. Im folgenden Code sorgt das Umgebungslicht beispielsweise für ein weiches weißes Licht für die gesamte Szene, während das gerichtete Licht eine sekundäre Lichtquelle darstellt, die Objekte in einem abwärts gerichteten Winkel trifft. Beim gerichteten Licht wird der Winkel mit position.set(x, y ,z) festgelegt, wobei jeder Wert relativ zur jeweiligen Achse ist. Mit position.set(0,1,0) wird die Lichtquelle beispielsweise direkt über der Szene auf der y-Achse positioniert und zeigt senkrecht nach unten.

     So fügen Sie der Szene die Lichtquellen hinzu: Hängen Sie Folgendes an den onAdd-Hook an:
    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);
    

Ihr onAdd-Hook sollte jetzt so aussehen:

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

Ihre Szene ist jetzt eingerichtet und kann gerendert werden. Als Nächstes konfigurieren Sie den WebGL-Renderer und rendern die Szene.

6. Szene rendern

Zeit, die Szene zu rendern. Bis zu diesem Punkt wird alles, was Sie mit Three.js erstellt haben, im Code initialisiert, ist aber im Grunde nicht vorhanden, da es noch nicht im WebGL-Renderingkontext gerendert wurde. WebGL rendert 2D- und 3D-Inhalte im Browser mithilfe der Canvas API. Wenn Sie die Canvas API schon einmal verwendet haben, kennen Sie wahrscheinlich das context eines HTML-Canvas, in dem alles gerendert wird. Was Sie vielleicht nicht wissen: Diese Schnittstelle stellt den OpenGL-Grafikrendering-Kontext über die WebGLRenderingContext API im Browser bereit.

Um die Arbeit mit dem WebGL-Renderer zu erleichtern, bietet Three.js WebGLRenderer, einen Wrapper, mit dem sich der WebGL-Renderingkontext relativ einfach konfigurieren lässt, sodass Three.js Szenen im Browser rendern kann. Bei der Karte reicht es jedoch nicht aus, die Three.js-Szene einfach im Browser neben der Karte zu rendern. Three.js muss in denselben Rendering-Kontext wie die Karte gerendert werden, damit sowohl die Karte als auch alle Objekte aus der Three.js-Szene in denselben Weltraum gerendert werden. So kann der Renderer Interaktionen zwischen Objekten auf der Karte und Objekten in der Szene verarbeiten, z. B. Verdeckung. Das bedeutet, dass ein Objekt Objekte dahinter verdeckt.

Das klingt ziemlich kompliziert, oder? Glücklicherweise hilft Three.js hier wieder weiter.

  1. WebGL-Renderer einrichten

    Wenn Sie eine neue Instanz von Three.js WebGLRenderer erstellen, können Sie ihr den spezifischen WebGL-Rendering-Kontext bereitstellen, in den die Szene gerendert werden soll. Erinnern Sie sich an das gl-Argument, das an den onContextRestored-Hook übergeben wird? Das gl-Objekt ist der WebGL-Renderingkontext der Karte. Sie müssen nur den Kontext, den Canvas und die Attribute für die WebGLRenderer-Instanz angeben. Alle sind über das gl-Objekt verfügbar. In diesem Code wird die autoClear-Property des Renderers auch auf false gesetzt, damit der Renderer seine Ausgabe nicht bei jedem Frame löscht.

     Hängen Sie Folgendes an den onContextRestored-Hook an, um den Renderer zu konfigurieren:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. Rendern Sie die Szene.

     Nachdem der Renderer konfiguriert wurde, rufen Sie requestRedraw für die WebGLOverlayView-Instanz auf, um dem Overlay mitzuteilen, dass beim Rendern des nächsten Frames ein erneutes Zeichnen erforderlich ist. Rufen Sie dann render für den Renderer auf und übergeben Sie ihm die Three.js-Szene und -Kamera, die gerendert werden sollen. Löschen Sie zum Schluss den Status des WebGL-Renderingkontexts. Dies ist ein wichtiger Schritt, um GL-Statuskonflikte zu vermeiden, da die Verwendung von WebGL Overlay View auf einem gemeinsamen GL-Status beruht. Wenn der Zustand am Ende jedes Zeichenaufrufs nicht zurückgesetzt wird, können GL-Zustandskonflikte dazu führen, dass der Renderer fehlschlägt.

     Hängen Sie dazu Folgendes an den onDraw-Hook an, damit er bei jedem Frame ausgeführt wird:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

Ihre onContextRestored- und onDraw-Hooks sollten jetzt so aussehen:

    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-Modell auf der Karte rendern

Ok, du hast alle Voraussetzungen erfüllt. Sie haben die WebGL-Overlay-Ansicht eingerichtet und eine Three.js-Szene erstellt, aber es gibt ein Problem: Die Szene ist leer. Als Nächstes rendern wir ein 3D-Objekt in der Szene. Dazu verwenden Sie den GLTF-Loader, den Sie zuvor importiert haben.

 3D‑Modelle sind in vielen verschiedenen Formaten verfügbar. Für Three.js ist das gLTF-Format aufgrund seiner Größe und Laufzeitleistung das bevorzugte Format. In diesem Codelab wird ein Modell, das Sie in der Szene rendern können, bereits in /src/pin.gltf bereitgestellt.

  1. Erstellen Sie eine Instanz des Modell-Loaders.

    Hängen Sie Folgendes an onAdd an:
    loader = new GLTFLoader();
    
  2. Laden Sie ein 3D-Modell.

    Modell-Loader sind asynchron und führen einen Callback aus, sobald das Modell vollständig geladen wurde. Hängen Sie Folgendes an onAdd an, um pin.gltf zu laden:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. Fügen Sie das Modell der Szene hinzu.

     Sie können das Modell jetzt der Szene hinzufügen, indem Sie den folgenden Code an den loader-Callback anhängen. Beachten Sie, dass gltf.scene und nicht gltf hinzugefügt wird:
    scene.add(gltf.scene);
    
  4. Konfigurieren Sie die Kameraprojektionsmatrix.

    Damit das Modell auf der Karte richtig gerendert wird, müssen Sie als Letztes die Projektionsmatrix der Kamera in der Three.js-Szene festlegen. Die Projektionsmatrix wird als Three.js-Matrix4-Array angegeben, das einen Punkt im dreidimensionalen Raum zusammen mit Transformationen wie Rotationen, Scherung und Skalierung definiert.

     Bei WebGLOverlayView wird die Projektionsmatrix verwendet, um dem Renderer mitzuteilen, wo und wie die Three.js-Szene relativ zur Basiskarte gerendert werden soll. Es gibt jedoch ein Problem. Positionen auf der Karte werden als Koordinatenpaare für Breiten- und Längengrad angegeben, während Positionen in der Three.js-Szene Vector3-Koordinaten sind. Wie Sie sich vielleicht schon gedacht haben, ist die Berechnung der Conversion zwischen den beiden Systemen nicht trivial. Um dieses Problem zu beheben, übergibt WebGLOverlayView ein coordinateTransformer-Objekt an den OnDraw-Lifecycle-Hook, der eine Funktion namens fromLatLngAltitude enthält. fromLatLngAltitude akzeptiert ein LatLngAltitude- oder LatLngAltitudeLiteral-Objekt und optional eine Reihe von Argumenten, die eine Transformation für die Szene definieren, und konvertiert sie dann in eine MVP-Matrix (Model View Projection). Sie müssen nur angeben, wo auf der Karte die Three.js-Szene gerendert werden soll und wie sie transformiert werden soll. WebGLOverlayView übernimmt den Rest. Anschließend können Sie die MVP-Matrix in ein Three.js-Matrix4-Array umwandeln und die Kameraprojektionsmatrix darauf festlegen.

    Im folgenden Code wird mit dem zweiten Argument festgelegt, dass die Höhe der Three.js-Szene in WebGl Overlay View 120 Meter über dem Boden liegen soll. Dadurch scheint das Modell zu schweben.

     So legen Sie die Kameraprojektionsmatrix fest: Hängen Sie Folgendes an den onDraw-Hook an:
    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. Modell transformieren

     Die Markierung ist nicht senkrecht zur Karte ausgerichtet. In der 3D-Grafik hat die 3D-Welt nicht nur eigene X-, Y- und Z-Achsen, die die Ausrichtung bestimmen, sondern jedes Objekt hat auch einen eigenen Objektraum mit einem unabhängigen Satz von Achsen.

    In diesem Fall wurde das Modell nicht so erstellt, dass die Oberseite des Pins nach oben in Richtung der Y-Achse zeigt. Sie müssen das Objekt also transformieren, um es wie gewünscht im Weltraum auszurichten. Rufen Sie dazu rotation.set auf. In Three.js wird die Drehung in Radianten und nicht in Grad angegeben. Es ist in der Regel einfacher, in Grad zu denken. Daher muss die entsprechende Umrechnung mit der Formel degrees * Math.PI/180 erfolgen.

     Außerdem ist das Modell etwas klein. Daher wird es durch Aufrufen von scale.set(x, y ,z) gleichmäßig auf allen Achsen skaliert.

     Wenn Sie das Modell drehen und skalieren möchten, fügen Sie Folgendes im loader-Callback von onAdd vor scene.add(gltf.scene) hinzu, wodurch das gLTF der Szene hinzugefügt wird:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

Der Pin steht jetzt aufrecht auf der Karte.

Aufrechter Pin

Ihre onAdd- und onDraw-Hooks sollten jetzt so aussehen:

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

Als Nächstes kommen Kameraanimationen.

8. Kamera animieren

Nachdem Sie ein Modell auf der Karte gerendert haben und alles dreidimensional bewegen können, möchten Sie diese Bewegung wahrscheinlich programmatisch steuern. Mit der Funktion moveCamera können Sie die Eigenschaften „Mitte“, „Zoom“, „Neigung“ und „Ausrichtung“ der Karte gleichzeitig festlegen und so die Nutzerfreundlichkeit optimieren. Außerdem kann moveCamera in einer Animationsschleife aufgerufen werden, um flüssige Übergänge zwischen Frames bei einer Framerate von fast 60 Bildern pro Sekunde zu erstellen.

  1. Warten Sie, bis das Modell geladen ist.

    Um eine nahtlose Nutzererfahrung zu schaffen, sollten Sie mit dem Bewegen der Kamera warten, bis das gLTF-Modell geladen ist. Hängen Sie dazu den onLoad-Event-Handler des Loaders an den onContextRestored-Hook an:
    loader.manager.onLoad = () => {}
    
  2. Erstellen Sie eine Animationsschleife.

    Es gibt mehrere Möglichkeiten, eine Animationsschleife zu erstellen, z. B. mit setInterval oder requestAnimationFrame. In diesem Fall verwenden Sie die setAnimationLoop-Funktion des Three.js-Renderers. Damit wird automatisch jeder Code aufgerufen, den Sie in seinem Callback deklarieren, wenn Three.js einen neuen Frame rendert. Fügen Sie dem onLoad-Event-Handler aus dem vorherigen Schritt Folgendes hinzu, um die Animationsschleife zu erstellen:
    renderer.setAnimationLoop(() => {});
    
  3. Legen Sie die Kameraposition im Animationszyklus fest.

    Rufen Sie als Nächstes moveCamera auf, um die Karte zu aktualisieren. Hier werden Eigenschaften des mapOptions-Objekts verwendet, mit dem die Karte geladen wurde, um die Kameraposition zu definieren:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. Aktualisieren Sie die Kamera für jeden Frame.

    Letzter Schritt: Aktualisieren Sie das mapOptions-Objekt am Ende jedes Frames, um die Kameraposition für den nächsten Frame festzulegen. In diesem Code wird mit einer if-Anweisung die Neigung erhöht, bis der maximale Neigungswert von 67,5 erreicht ist.Anschließend wird die Ausrichtung in jedem Frame etwas geändert, bis die Kamera eine vollständige 360‑Grad-Drehung abgeschlossen hat. Sobald die gewünschte Animation abgeschlossen ist, wird null an setAnimationLoop übergeben, um die Animation abzubrechen, damit sie nicht unendlich lange ausgeführt wird.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

Ihr onContextRestored-Hook sollte jetzt so aussehen:

    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. Glückwunsch

Wenn alles nach Plan gelaufen ist, sollte jetzt eine Karte mit einer großen 3D-Markierung angezeigt werden, die so aussieht:

Final 3D Pin

Das haben Sie gelernt

In diesem Codelab haben Sie viel gelernt. Hier sind die wichtigsten Punkte:

  • Implementieren von WebGLOverlayView und seinen Lifecycle-Hooks.
  • Three.js in die Karte einbinden
  • Grundlagen zum Erstellen einer Three.js-Szene, einschließlich Kameras und Beleuchtung.
  • 3D-Modelle mit Three.js laden und bearbeiten.
  • Steuern und Animieren der Kamera für die Karte mit moveCamera.

Nächste Schritte

WebGL und Computergrafiken im Allgemeinen sind komplexe Themen, bei denen es immer viel zu lernen gibt. Hier sind einige Ressourcen, die Ihnen den Einstieg erleichtern: