สร้างประสบการณ์การใช้งานแผนที่ 3 มิติด้วยมุมมองการวางซ้อนของ WebGL

1. ก่อนเริ่มต้น

Codelab นี้จะสอนวิธีใช้ฟีเจอร์ที่ทำงานด้วย WebGL ของ Maps JavaScript API เพื่อควบคุมและแสดงผลบนแผนที่เวกเตอร์ในรูปแบบ 3 มิติ

เข็มกลัด 3 มิติสุดท้าย

ข้อกำหนดเบื้องต้น

Codelab นี้ถือว่าคุณมีความรู้ระดับกลางเกี่ยวกับ JavaScript และ Maps JavaScript API หากต้องการเรียนรู้พื้นฐานการใช้ Maps JS API ให้ลองใช้ Codelab เพิ่มแผนที่ลงในเว็บไซต์ (JavaScript)

สิ่งที่คุณจะได้เรียนรู้

  • สร้างรหัสแผนที่โดยเปิดใช้แผนที่เวกเตอร์สำหรับ JavaScript
  • ควบคุมแผนที่ด้วยการเอียงและการหมุนแบบเป็นโปรแกรม
  • การแสดงผลออบเจ็กต์ 3 มิติบนแผนที่ด้วย WebGLOverlayView และ Three.js
  • การทำภาพเคลื่อนไหวการเคลื่อนกล้องด้วย moveCamera

สิ่งที่คุณต้องมี

  • บัญชี Google Cloud Platform ที่เปิดใช้การเรียกเก็บเงิน
  • คีย์ API ของ Google Maps Platform ที่เปิดใช้ Maps JavaScript API
  • มีความรู้ระดับกลางเกี่ยวกับ JavaScript, HTML และ CSS
  • โปรแกรมแก้ไขข้อความหรือ IDE ที่คุณเลือก
  • Node.js

2. ตั้งค่า

สำหรับขั้นตอนการเปิดใช้ด้านล่าง คุณจะต้องเปิดใช้ Maps JavaScript API

ตั้งค่า Google Maps Platform

หากยังไม่มีบัญชี Google Cloud Platform และโปรเจ็กต์ที่เปิดใช้การเรียกเก็บเงิน โปรดดูคู่มือเริ่มต้นใช้งาน Google Maps Platform เพื่อสร้างบัญชีสำหรับการเรียกเก็บเงินและโปรเจ็กต์

  1. ใน Cloud Console ให้คลิกเมนูแบบเลื่อนลงของโปรเจ็กต์ แล้วเลือกโปรเจ็กต์ที่ต้องการใช้สำหรับ Codelab นี้

  1. เปิดใช้ Google Maps Platform APIs และ SDK ที่จำเป็นสำหรับ Codelab นี้ใน Google Cloud Marketplace โดยทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้
  2. สร้างคีย์ API ในหน้าข้อมูลเข้าสู่ระบบของ Cloud Console คุณสามารถทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้ คำขอทั้งหมดไปยัง Google Maps Platform ต้องใช้คีย์ API

การตั้งค่า Node.js

หากยังไม่มี ให้ไปที่ https://nodejs.org/ เพื่อดาวน์โหลดและติดตั้งรันไทม์ Node.js ในคอมพิวเตอร์

Node.js มาพร้อมกับตัวจัดการแพ็กเกจ npm ซึ่งคุณต้องติดตั้งการอ้างอิงสำหรับ Codelab นี้

ดาวน์โหลดเทมเพลตเริ่มต้นโปรเจ็กต์

ก่อนที่จะเริ่ม Codelab นี้ ให้ทำดังนี้เพื่อดาวน์โหลดเทมเพลตโปรเจ็กต์เริ่มต้น รวมถึงโค้ดโซลูชันที่สมบูรณ์

  1. ดาวน์โหลดหรือแยกที่เก็บ GitHub สำหรับ Codelab นี้ได้ที่ https://github.com/googlecodelabs/maps-platform-101-webgl/ โปรเจ็กต์เริ่มต้นจะอยู่ในไดเรกทอรี /starter และมีโครงสร้างไฟล์พื้นฐานที่คุณต้องใช้เพื่อทำ Codelab ให้เสร็จสมบูรณ์ ทุกอย่างที่คุณต้องใช้ในการทำงานจะอยู่ในไดเรกทอรี /starter/src
  2. เมื่อดาวน์โหลดโปรเจ็กต์เริ่มต้นแล้ว ให้เรียกใช้ npm install ในไดเรกทอรี /starter ซึ่งจะติดตั้งการอ้างอิงที่จำเป็นทั้งหมดที่ระบุไว้ใน package.json
  3. เมื่อติดตั้งการขึ้นต่อกันแล้ว ให้เรียกใช้ npm start ในไดเรกทอรี

เราได้ตั้งค่าโปรเจ็กต์เริ่มต้นให้คุณใช้ webpack-dev-server ซึ่งจะคอมไพล์และเรียกใช้โค้ดที่คุณเขียนในเครื่อง นอกจากนี้ webpack-dev-server ยังโหลดแอปของคุณในเบราว์เซอร์ซ้ำโดยอัตโนมัติทุกครั้งที่คุณทำการเปลี่ยนแปลงโค้ด

หากต้องการดูโค้ดโซลูชันแบบเต็มที่ทำงานอยู่ ให้ทำตามขั้นตอนการตั้งค่าด้านบนในไดเรกทอรี /solution

เพิ่มคีย์ API

แอปเริ่มต้นมีโค้ดทั้งหมดที่จำเป็นในการโหลดแผนที่ด้วย JS API Loader ดังนั้นสิ่งที่คุณต้องทำคือระบุคีย์ API และรหัสแผนที่ JS API Loader เป็นไลบรารีที่เรียบง่ายซึ่งแยกวิธีการโหลด Maps JS API แบบอินไลน์ในเทมเพลต HTML แบบเดิมด้วยแท็ก script ทำให้คุณจัดการทุกอย่างในโค้ด JavaScript ได้

หากต้องการเพิ่มคีย์ API ให้ทำดังนี้ในโปรเจ็กต์เริ่มต้น

  1. เปิด app.js
  2. ในออบเจ็กต์ apiOptions ให้ตั้งค่าคีย์ API เป็นค่าของ apiOptions.apiKey

3. สร้างและใช้รหัสแผนที่

หากต้องการใช้ฟีเจอร์ที่ใช้ WebGL ของ Maps JavaScript API คุณต้องมีรหัสแผนที่ที่เปิดใช้แผนที่เวกเตอร์

การสร้างรหัสแผนที่

การสร้างรหัสแผนที่

  1. ในคอนโซล Google Cloud ให้ไปที่ "Google Maps Platform" > "การจัดการแผนที่"
  2. คลิก "สร้างรหัสแผนที่ใหม่"
  3. ในช่อง "ชื่อแผนที่" ให้ป้อนชื่อรหัสแผนที่
  4. ในเมนูแบบเลื่อนลง "ประเภทแผนที่" ให้เลือก "JavaScript" "ตัวเลือก JavaScript" จะปรากฏขึ้น
  5. ในส่วน "ตัวเลือก JavaScript" ให้เลือกปุ่มตัวเลือก "เวกเตอร์" ช่องทําเครื่องหมาย "เอียง" และช่องทําเครื่องหมาย "หมุน"
  6. ไม่บังคับ ในช่อง "คำอธิบาย" ให้ป้อนคำอธิบายสำหรับคีย์ API
  7. คลิกปุ่ม "ถัดไป" หน้า "รายละเอียดรหัสแผนที่" จะปรากฏขึ้น

    หน้า "รายละเอียดแผนที่"
  8. คัดลอกรหัสแผนที่ คุณจะต้องใช้ข้อมูลนี้ในขั้นตอนถัดไปเพื่อโหลดแผนที่

การใช้รหัสแผนที่

หากต้องการโหลดแผนที่เวกเตอร์ คุณต้องระบุรหัสแผนที่เป็นพร็อพเพอร์ตี้ในตัวเลือกเมื่อสร้างอินสแตนซ์ของแผนที่ นอกจากนี้ คุณยังระบุรหัสแผนที่เดียวกันเมื่อโหลด Maps JavaScript API ได้ด้วย

หากต้องการโหลดแผนที่ด้วยรหัสแมป ให้ทำดังนี้

  1. ตั้งรหัสแผนที่เป็นค่าของ mapOptions.mapId

    การระบุรหัสแผนที่เมื่อคุณสร้างอินสแตนซ์ของแผนที่จะบอก Google Maps Platform ว่าควรโหลดแผนที่ใดของคุณสำหรับอินสแตนซ์หนึ่งๆ คุณอาจใช้รหัสแผนที่เดียวกันซ้ำในแอปหลายแอปหรือหลายมุมมองภายในแอปเดียวกัน
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

ตรวจสอบแอปที่ทำงานในเบราว์เซอร์ แผนที่เวกเตอร์ที่เปิดใช้การเอียงและการหมุนควรโหลดได้สำเร็จ หากต้องการตรวจสอบว่าเปิดใช้การเอียงและการหมุนหรือไม่ ให้กดแป้น Shift ค้างไว้แล้วลากด้วยเมาส์หรือใช้ปุ่มลูกศรบนแป้นพิมพ์

หากแผนที่ไม่โหลด ให้ตรวจสอบว่าคุณได้ระบุคีย์ API ที่ถูกต้องใน apiOptions หากแผนที่ไม่เอียงและหมุน ให้ตรวจสอบว่าคุณได้ระบุรหัสแผนที่ที่เปิดใช้การเอียงและการหมุนใน apiOptions และ mapOptions

แผนที่เอียง

ตอนนี้ไฟล์ 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 เดียวกันที่ใช้ในการแสดงผลแผนที่ฐานแบบเวกเตอร์ได้โดยตรง ซึ่งหมายความว่าคุณสามารถแสดงผลออบเจ็กต์ 2 มิติและ 3 มิติบนแผนที่ได้โดยตรงโดยใช้ WebGL รวมถึงไลบรารีกราฟิกยอดนิยมที่ใช้ WebGL

WebGLOverlayView แสดงฮุก 5 รายการในวงจรของบริบทการแสดงผล WebGL ของแผนที่ที่คุณใช้ได้ ต่อไปนี้คือคำอธิบายโดยย่อของแต่ละฮุกและสิ่งที่คุณควรใช้

  • onAdd(): เรียกใช้เมื่อเพิ่มการวางซ้อนลงในแผนที่โดยการเรียก setMap ในอินสแตนซ์ WebGLOverlayView คุณควรทำงานที่เกี่ยวข้องกับ WebGL ซึ่งไม่จำเป็นต้องเข้าถึงบริบท WebGL โดยตรงในส่วนนี้
  • onContextRestored(): เรียกใช้เมื่อบริบท WebGL พร้อมใช้งาน แต่ก่อนที่จะมีการแสดงผลใดๆ คุณควรเริ่มต้นออบเจ็กต์ ผูกสถานะ และทำสิ่งอื่นๆ ที่ต้องเข้าถึงบริบท WebGL แต่สามารถทำได้นอกการเรียก onDraw() ซึ่งจะช่วยให้คุณตั้งค่าทุกอย่างที่ต้องการได้โดยไม่ต้องเพิ่มค่าใช้จ่ายที่ไม่จำเป็นในการแสดงผลแผนที่จริง ซึ่งใช้ GPU เป็นจำนวนมากอยู่แล้ว
  • onDraw(): เรียกใช้ 1 ครั้งต่อเฟรมเมื่อ WebGL เริ่มแสดงผลแผนที่และสิ่งอื่นๆ ที่คุณขอ คุณควรทำงานใน onDraw() ให้น้อยที่สุดเพื่อหลีกเลี่ยงปัญหาด้านประสิทธิภาพในการแสดงผลแผนที่
  • onContextLost(): เรียกใช้เมื่อบริบทการแสดงผล WebGL หายไปไม่ว่าด้วยเหตุผลใดก็ตาม
  • onRemove(): เรียกใช้เมื่อนำภาพซ้อนทับออกจากแผนที่โดยการเรียกใช้ setMap(null) ในอินสแตนซ์ WebGLOverlayView

ในขั้นตอนนี้ คุณจะสร้างอินสแตนซ์ของ WebGLOverlayView และใช้ฮุกวงจร 3 รายการ ได้แก่ onAdd, onContextRestored และ onDraw เพื่อให้โค้ดสะอาดและติดตามได้ง่ายขึ้น เราจะจัดการโค้ดทั้งหมดสำหรับการซ้อนทับในฟังก์ชัน initWebGLOverlayView() ที่มีให้ในเทมเพลตเริ่มต้นสำหรับโค้ดแล็บนี้

  1. สร้างอินสแตนซ์ WebGLOverlayView()

    การวางซ้อนนี้มาจาก Maps JS API ใน google.maps.WebGLOverlayView หากต้องการเริ่มต้น ให้สร้างอินสแตนซ์โดยเพิ่มข้อความต่อไปนี้ใน initWebGLOverlayView()
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. ใช้ Hook สำหรับวงจร

    หากต้องการใช้ฮุกวงจร ให้ต่อท้ายข้อความต่อไปนี้ใน initWebGLOverlayView()
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, transformer}) => {};
    
  3. เพิ่มอินสแตนซ์การวางซ้อนลงในแผนที่

    ตอนนี้ให้เรียก setMap() ในอินสแตนซ์การวางซ้อนและส่งแผนที่โดยต่อท้ายข้อความต่อไปนี้กับ initWebGLOverlayView()
    webGLOverlayView.setMap(map)
    
  4. โทรมาที่ initWebGLOverlayView

    ขั้นตอนสุดท้ายคือการเรียกใช้ initWebGLOverlayView() โดยเพิ่มข้อมูลต่อไปนี้ลงในฟังก์ชันที่เรียกใช้ทันทีที่ด้านล่างของ app.js
    initWebGLOverlayView(map);
    

ตอนนี้ initWebGLOverlayView และฟังก์ชันที่เรียกใช้ทันทีควรมีลักษณะดังนี้

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

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

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

ทั้งหมดนี้คือสิ่งที่คุณต้องทำเพื่อใช้งาน WebGLOverlayView จากนั้นคุณจะตั้งค่าทุกอย่างที่จำเป็นในการแสดงผลออบเจ็กต์ 3 มิติบนแผนที่โดยใช้ Three.js

5. ตั้งค่าฉาก three.js

การใช้ WebGL อาจซับซ้อนมากเนื่องจากคุณต้องกำหนดทุกแง่มุมของออบเจ็กต์ทั้งหมดด้วยตนเองและอื่นๆ เพื่อความสะดวกยิ่งขึ้น คุณจะใช้ Three.js ใน Codelab นี้ ซึ่งเป็นไลบรารีกราฟิกยอดนิยมที่มีเลเยอร์การแยกส่วนที่เรียบง่ายอยู่เหนือ WebGL Three.js มาพร้อมฟังก์ชันอำนวยความสะดวกที่หลากหลายซึ่งทำทุกอย่างตั้งแต่การสร้างโปรแกรมแสดงผล WebGL ไปจนถึงการวาดรูปร่างออบเจ็กต์ 2 มิติและ 3 มิติทั่วไป ไปจนถึงการควบคุมกล้อง การเปลี่ยนรูปแบบออบเจ็กต์ และอื่นๆ อีกมากมาย

Three.js มีออบเจ็กต์พื้นฐาน 3 ประเภทที่จำเป็นต่อการแสดงผล

  • ฉาก: "คอนเทนเนอร์" ที่แสดงผลและแสดงออบเจ็กต์ แหล่งกำเนิดแสง พื้นผิว ฯลฯ ทั้งหมด
  • กล้อง: กล้องที่แสดงมุมมองของฉาก มีกล้องหลายประเภทให้เลือกใช้ และคุณอาจเพิ่มกล้องอย่างน้อย 1 ตัวลงในฉากเดียวได้
  • โปรแกรมแสดงผล: โปรแกรมแสดงผลที่จัดการการประมวลผลและการแสดงออบเจ็กต์ทั้งหมดในฉาก ใน Three.js WebGLRenderer เป็นรูปแบบที่ใช้กันมากที่สุด แต่ก็มีรูปแบบอื่นๆ อีก 2-3 รูปแบบที่ใช้เป็นตัวสำรองในกรณีที่ไคลเอ็นต์ไม่รองรับ WebGL

ในขั้นตอนนี้ คุณจะโหลดการอ้างอิงทั้งหมดที่จำเป็นสำหรับ Three.js และตั้งค่าฉากพื้นฐาน

  1. โหลด three.js

    คุณจะต้องมี 2 การอ้างอิงสำหรับโค้ดแล็บนี้ ได้แก่ ไลบรารี Three.js และ GLTF Loader ซึ่งเป็นคลาสที่ช่วยให้คุณโหลดออบเจ็กต์ 3 มิติใน GL Transmission Format (gLTF) ได้ Three.js มีโปรแกรมโหลดเฉพาะสำหรับออบเจ็กต์ 3 มิติในรูปแบบต่างๆ มากมาย แต่เราขอแนะนำให้ใช้ gLTF

    ในโค้ดด้านล่าง ระบบจะนำเข้าไลบรารี Three.js ทั้งหมด ในแอปเวอร์ชันที่ใช้งานจริง คุณอาจต้องการนำเข้าเฉพาะคลาสที่ต้องการ แต่สำหรับ Codelab นี้ ให้นำเข้าทั้งไลบรารีเพื่อให้ง่าย นอกจากนี้ โปรดทราบว่า GLTF Loader ไม่ได้รวมอยู่ในไลบรารีเริ่มต้น และต้องนำเข้าจากเส้นทางแยกต่างหากใน Dependency ซึ่งเป็นเส้นทางที่คุณสามารถเข้าถึงตัวโหลดทั้งหมดที่ 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

    หากต้องการสร้างฉาก ให้สร้างอินสแตนซ์ของคลาส Scene Three.js โดยต่อท้ายโค้ดต่อไปนี้ในฮุก onAdd
    scene = new THREE.Scene();
    
  3. เพิ่มกล้องลงในฉาก

    ดังที่กล่าวไปก่อนหน้านี้ กล้องแสดงถึงมุมมองของฉาก และกำหนดวิธีที่ Three.js จัดการการแสดงผลภาพของออบเจ็กต์ภายในฉาก หากไม่มีกล้อง ระบบจะไม่ "เห็น" ฉาก ซึ่งหมายความว่าวัตถุจะไม่ปรากฏเนื่องจากระบบจะไม่แสดงผล

    Three.js มีกล้องหลายประเภทที่ส่งผลต่อวิธีที่โปรแกรมแสดงผลจัดการออบเจ็กต์ในเรื่องต่างๆ เช่น มุมมองและความลึก ในฉากนี้ คุณจะได้ใช้ PerspectiveCamera ซึ่งเป็นกล้องประเภทที่ใช้กันมากที่สุดใน Three.js ซึ่งออกแบบมาเพื่อจำลองวิธีที่ดวงตาของมนุษย์จะรับรู้ฉาก ซึ่งหมายความว่าวัตถุที่อยู่ไกลจากกล้องจะปรากฏเล็กกว่าวัตถุที่อยู่ใกล้กว่า ฉากจะมีจุดที่เส้นขนานมาบรรจบกัน และอื่นๆ

    หากต้องการเพิ่มกล้องมุมมองลงในฉาก ให้ต่อท้ายฮุก onAdd ด้วยโค้ดต่อไปนี้
    camera = new THREE.PerspectiveCamera();
    
    PerspectiveCamera ยังช่วยให้คุณกําหนดค่าแอตทริบิวต์ที่ประกอบกันเป็นมุมมองได้ด้วย ซึ่งรวมถึงระนาบใกล้และไกล สัดส่วนภาพ และขอบเขตการมองเห็น (fov) คุณลักษณะเหล่านี้รวมกันเป็นสิ่งที่เรียกว่าฟรัสตัมของการดู ซึ่งเป็นแนวคิดสำคัญที่ต้องทำความเข้าใจเมื่อทำงานในระบบ 3 มิติ แต่ไม่อยู่ในขอบเขตของโค้ดแล็บนี้ การกำหนดค่า PerspectiveCamera เริ่มต้นก็เพียงพอแล้ว
  4. เพิ่มแหล่งกำเนิดแสงลงในฉาก

    โดยค่าเริ่มต้น ออบเจ็กต์ที่เรนเดอร์ในฉาก Three.js จะปรากฏเป็นสีดำ ไม่ว่าจะมีพื้นผิวใดก็ตาม เนื่องจากฉาก Three.js จะจำลองวิธีที่ออบเจ็กต์ทำงานในโลกแห่งความจริง ซึ่งการมองเห็นสีจะขึ้นอยู่กับแสงที่สะท้อนออกจากออบเจ็กต์ กล่าวโดยสรุปคือไม่มีแสงก็ไม่มีสี

    Three.js มีแสงประเภทต่างๆ มากมาย ซึ่งคุณจะใช้ 2 ประเภท ได้แก่

  5. AmbientLight: ให้แหล่งกำเนิดแสงแบบกระจายที่ส่องสว่างวัตถุทั้งหมดในฉากอย่างสม่ำเสมอจากทุกมุม ซึ่งจะทำให้ฉากมีปริมาณแสงพื้นฐานเพื่อให้มั่นใจว่าพื้นผิวของออบเจ็กต์ทั้งหมดจะมองเห็นได้
  6. DirectionalLight: ให้แสงที่มาจากทิศทางในฉาก รังสีของแสงที่ออกมาจาก DirectionalLight จะขนานกันทั้งหมดและไม่กระจายตัวเมื่ออยู่ห่างจากแหล่งกำเนิดแสง ซึ่งแตกต่างจากลักษณะการทำงานของแสงที่วางตำแหน่งในโลกแห่งความเป็นจริง

    คุณสามารถกำหนดค่าสีและความเข้มของไฟแต่ละดวงเพื่อสร้างเอฟเฟกต์แสงโดยรวม ตัวอย่างเช่น ในโค้ดด้านล่าง แสงแวดล้อมจะให้แสงสีขาวนวลสำหรับทั้งฉาก ในขณะที่แสงแบบกำหนดทิศทางจะให้แสงรองที่ส่องไปยังออบเจ็กต์ในมุมลง ในกรณีของแสงแบบมีทิศทาง มุมจะตั้งค่าโดยใช้ position.set(x, y ,z) โดยแต่ละค่าจะสัมพันธ์กับแกนที่เกี่ยวข้อง เช่น position.set(0,1,0) จะวางตำแหน่งแสงเหนือฉากโดยตรงบนแกน y ซึ่งชี้ลงมาตรงๆ

    หากต้องการเพิ่มแหล่งกำเนิดแสงลงในฉาก ให้เพิ่มข้อมูลต่อไปนี้ลงใน Hook 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 แสดงผลเนื้อหา 2 มิติและ 3 มิติในเบราว์เซอร์โดยใช้ Canvas API หากเคยใช้ Canvas API มาก่อน คุณอาจคุ้นเคยกับ context ของ Canvas HTML ซึ่งเป็นที่ที่แสดงผลทุกอย่าง สิ่งที่คุณอาจไม่ทราบคืออินเทอร์เฟซนี้จะแสดงบริบทการแสดงผลกราฟิก OpenGL ผ่าน WebGLRenderingContext API ในเบราว์เซอร์

Three.js มี WebGLRenderer ซึ่งเป็น Wrapper ที่ช่วยให้กำหนดค่าบริบทการแสดงผล WebGL ได้ง่ายขึ้น เพื่อให้ Three.js แสดงผลฉากในเบราว์เซอร์ได้ แต่ในกรณีของแผนที่ การแสดงผลฉาก Three.js ในเบราว์เซอร์ควบคู่ไปกับแผนที่เพียงอย่างเดียวนั้นไม่เพียงพอ Three.js ต้องแสดงผลในบริบทการแสดงผลเดียวกันกับแผนที่ เพื่อให้ทั้งแผนที่และออบเจ็กต์จากฉาก Three.js แสดงผลในพื้นที่โลกเดียวกัน ซึ่งช่วยให้โปรแกรมแสดงผลจัดการการโต้ตอบระหว่างออบเจ็กต์บนแผนที่กับออบเจ็กต์ในฉากได้ เช่น การบดบัง ซึ่งเป็นวิธีที่ซับซ้อนในการบอกว่าออบเจ็กต์จะซ่อนออบเจ็กต์ที่อยู่ด้านหลังไม่ให้มองเห็น

ฟังดูซับซ้อนใช่ไหม โชคดีที่ Three.js กลับมาช่วยอีกครั้ง

  1. ตั้งค่าโปรแกรมแสดงผล WebGL

    เมื่อสร้างอินสแตนซ์ใหม่ของ Three.js WebGLRenderer คุณอาจระบุบริบทการแสดงผล WebGL ที่ต้องการให้แสดงผลฉาก จำglอาร์กิวเมนต์ที่ส่งไปยังฮุก onContextRestored ได้ไหม gl ออบเจ็กต์ดังกล่าวคือบริบทการแสดงผล WebGL ของแผนที่ สิ่งที่คุณต้องทำคือระบุบริบท Canvas และแอตทริบิวต์ของบริบทนั้นไปยังอินสแตนซ์ WebGLRenderer ซึ่งทั้งหมดนี้พร้อมใช้งานผ่านออบเจ็กต์ gl ในโค้ดนี้ ระบบจะตั้งค่าพร็อพเพอร์ตี้ autoClear ของโปรแกรมแสดงผลเป็น false ด้วย เพื่อให้โปรแกรมแสดงผลไม่ล้างเอาต์พุตทุกเฟรม

    หากต้องการกำหนดค่าโปรแกรมแสดงผล ให้ต่อท้ายฮุก onContextRestored ด้วยข้อมูลต่อไปนี้
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. แสดงผลฉาก

    เมื่อกำหนดค่าโปรแกรมแสดงผลแล้ว ให้เรียกใช้ requestRedraw ในอินสแตนซ์ WebGLOverlayView เพื่อบอกการซ้อนทับว่าต้องวาดใหม่เมื่อเฟรมถัดไปแสดงผล จากนั้นเรียกใช้ render ในโปรแกรมแสดงผลและส่งฉากและกล้อง Three.js ไปให้เพื่อแสดงผล สุดท้าย ให้ล้างสถานะของบริบทการแสดงผล WebGL นี่เป็นขั้นตอนสำคัญในการหลีกเลี่ยงความขัดแย้งของสถานะ GL เนื่องจากมุมมองซ้อนทับ WebGL ต้องอาศัยสถานะ GL ที่แชร์ หากไม่ได้รีเซ็ตสถานะเมื่อสิ้นสุดการเรียกใช้การวาดทุกครั้ง ความขัดแย้งของสถานะ GL อาจทำให้ตัวเรนเดอร์ทำงานไม่สำเร็จ

    หากต้องการดำเนินการนี้ ให้ต่อท้าย onDraw Hook ด้วยโค้ดต่อไปนี้เพื่อให้โค้ดทำงานในแต่ละเฟรม
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

ตอนนี้ฮุก onContextRestored และ onDraw ควรมีลักษณะดังนี้

    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. แสดงโมเดล 3 มิติบนแผนที่

โอเค ตอนนี้คุณก็มีชิ้นส่วนทั้งหมดพร้อมแล้ว คุณได้ตั้งค่ามุมมองภาพซ้อนทับ WebGL และสร้างฉาก Three.js แล้ว แต่มีปัญหาอย่างหนึ่งคือไม่มีอะไรอยู่ในฉาก ดังนั้นต่อไปก็ถึงเวลาแสดงผลออบเจ็กต์ 3 มิติในฉาก โดยคุณจะต้องใช้ GLTF Loader ที่นำเข้าก่อนหน้านี้

โมเดล 3 มิติมีหลายรูปแบบ แต่สำหรับ Three.js รูปแบบ gLTF เป็นรูปแบบที่แนะนำเนื่องจากมีขนาดและประสิทธิภาพรันไทม์ที่ดี ใน Codelab นี้ เราได้จัดเตรียมโมเดลให้คุณแสดงผลในฉากไว้ใน /src/pin.gltf แล้ว

  1. สร้างอินสแตนซ์โปรแกรมโหลดโมเดล

    เพิ่มข้อความต่อไปนี้ลงใน onAdd
    loader = new GLTFLoader();
    
  2. โหลดโมเดล 3 มิติ

    ตัวโหลดโมเดลเป็นแบบอะซิงโครนัสและจะเรียกใช้ Callback เมื่อโหลดโมเดลเสร็จสมบูรณ์ หากต้องการโหลด pin.gltf ให้เพิ่มข้อความต่อไปนี้ลงใน onAdd
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. เพิ่มโมเดลลงในฉาก

    ตอนนี้คุณเพิ่มโมเดลลงในฉากได้โดยต่อท้ายข้อความต่อไปนี้ในแฮนเดิล loader โปรดทราบว่าระบบจะเพิ่ม gltf.scene ไม่ใช่ gltf
    scene.add(gltf.scene);
    
  4. กำหนดค่าเมทริกซ์การฉายภาพของกล้อง

    สิ่งสุดท้ายที่คุณต้องทำเพื่อให้โมเดลแสดงผลอย่างถูกต้องบนแผนที่คือการตั้งค่าเมทริกซ์การฉายภาพของกล้องในฉาก Three.js เมทริกซ์การฉายภาพระบุเป็นอาร์เรย์ Matrix4 ของ Three.js ซึ่งกำหนดจุดในพื้นที่สามมิติพร้อมกับการแปลง เช่น การหมุน การเฉือน การปรับขนาด และอื่นๆ

    ในกรณีของ WebGLOverlayView ระบบจะใช้เมทริกซ์การฉายภาพเพื่อบอกโปรแกรมแสดงผลว่าจะแสดงผลฉาก Three.js ที่สัมพันธ์กับแผนที่ฐานที่ใดและอย่างไร แต่ก็มีปัญหาเกิดขึ้น ตำแหน่งบนแผนที่จะระบุเป็นคู่พิกัดละติจูดและลองจิจูด ส่วนตำแหน่งในฉาก Three.js จะเป็นพิกัด Vector3 อย่างที่คุณอาจคาดเดาได้ การคำนวณ Conversion ระหว่าง 2 ระบบนี้ไม่ใช่เรื่องง่าย หากต้องการแก้ไขปัญหานี้ WebGLOverlayView จะส่งออบเจ็กต์ coordinateTransformer ไปยังฮุกวงจร OnDraw ที่มีฟังก์ชันชื่อ fromLatLngAltitude fromLatLngAltitude รับออบเจ็กต์ LatLngAltitude หรือ LatLngAltitudeLiteral และอาร์กิวเมนต์ชุดหนึ่ง (ไม่บังคับ) ที่กำหนดการเปลี่ยนรูปสำหรับฉาก จากนั้นจะแปลงออบเจ็กต์เหล่านั้นเป็นเมทริกซ์การฉายภาพมุมมองโมเดล (MVP) ให้คุณ สิ่งที่คุณต้องทำคือระบุตำแหน่งบนแผนที่ที่ต้องการให้แสดงผลฉาก Three.js รวมถึงวิธีที่ต้องการให้แปลง และ WebGLOverlayView จะจัดการส่วนที่เหลือให้ จากนั้นคุณสามารถแปลงเมทริกซ์ MVP เป็นอาร์เรย์ Three.js Matrix4 และตั้งค่าเมทริกซ์การฉายภาพของกล้องเป็นเมทริกซ์ดังกล่าว

    ในโค้ดด้านล่าง อาร์กิวเมนต์ที่ 2 จะบอกให้ WebGl Overlay View ตั้งค่าความสูงของฉาก Three.js ที่ 120 เมตรเหนือพื้นดิน ซึ่งจะทำให้โมเดลดูเหมือนลอยอยู่

    หากต้องการตั้งค่าเมทริกซ์การฉายภาพของกล้อง ให้ต่อท้ายข้อความต่อไปนี้ใน Hook 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. เปลี่ยนรูปแบบโมเดล

    คุณจะเห็นว่าหมุดไม่ได้ตั้งฉากกับแผนที่ ในกราฟิก 3 มิติ นอกเหนือจากพื้นที่ว่างของโลกที่มีแกน x, y และ z ของตัวเองซึ่งกำหนดการวางแนวแล้ว วัตถุแต่ละชิ้นยังมีพื้นที่ว่างของวัตถุของตัวเองที่มีชุดแกนอิสระด้วย

    ในกรณีของโมเดลนี้ โมเดลไม่ได้สร้างขึ้นโดยมีสิ่งที่ปกติเราจะถือว่าเป็น "ด้านบน" ของหมุดหันขึ้นแกน y ดังนั้นคุณจึงต้องเปลี่ยนออบเจ็กต์เพื่อจัดวางในลักษณะที่ต้องการเทียบกับพื้นที่โลกโดยเรียกใช้ rotation.set บนออบเจ็กต์ โปรดทราบว่าใน Three.js การหมุนจะระบุเป็นเรเดียน ไม่ใช่องศา โดยทั่วไปแล้วการคิดเป็นองศาจะง่ายกว่า ดังนั้นจึงต้องทำการแปลงที่เหมาะสมโดยใช้สูตร degrees * Math.PI/180

    นอกจากนี้ โมเดลยังมีขนาดเล็ก คุณจึงต้องปรับขนาดให้เท่ากันในทุกแกนโดยเรียกใช้ scale.set(x, y ,z)

    หากต้องการหมุนและปรับขนาดโมเดล ให้เพิ่มโค้ดต่อไปนี้ในloaderการเรียกกลับของ onAdd before scene.add(gltf.scene) ซึ่งจะเพิ่ม gLTF ลงในฉาก
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

ตอนนี้หมุดจะตั้งตรงเทียบกับแผนที่

เข็มหมุดตั้ง

ตอนนี้ฮุก onAdd และ onDraw ควรมีลักษณะดังนี้

    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. สร้างภาพเคลื่อนไหวของกล้อง

ตอนนี้คุณได้เรนเดอร์โมเดลบนแผนที่และย้ายทุกอย่างแบบ 3 มิติแล้ว สิ่งต่อไปที่คุณน่าจะอยากทำคือควบคุมการเคลื่อนไหวด้วยโปรแกรม ฟังก์ชัน moveCamera ช่วยให้คุณตั้งค่าพร็อพเพอร์ตี้ของศูนย์กลาง การซูม การเอียง และส่วนหัวของแผนที่ได้พร้อมกัน ซึ่งจะช่วยให้คุณควบคุมประสบการณ์ของผู้ใช้ได้อย่างละเอียด นอกจากนี้ คุณยังเรียกใช้ moveCamera ในลูปภาพเคลื่อนไหวเพื่อสร้างการเปลี่ยนภาพที่ราบรื่นระหว่างเฟรมที่อัตราเฟรมเกือบ 60 เฟรมต่อวินาทีได้ด้วย

  1. รอให้โมเดลโหลด

    หากต้องการสร้างประสบการณ์ของผู้ใช้ที่ราบรื่น คุณควรรอจนกว่าระบบจะโหลดโมเดล gLTF เสร็จแล้วจึงเริ่มย้ายกล้อง โดยให้ต่อท้ายonLoadเครื่องจัดการเหตุการณ์ของโปรแกรมโหลดเข้ากับฮุก onContextRestored ดังนี้
    loader.manager.onLoad = () => {}
    
  2. สร้างลูปภาพเคลื่อนไหว

    การสร้างภาพเคลื่อนไหวแบบวนซ้ำทำได้หลายวิธี เช่น ใช้ setInterval หรือ requestAnimationFrame ในกรณีนี้ คุณจะใช้ฟังก์ชัน setAnimationLoop ของตัวแสดงผล Three.js ซึ่งจะเรียกใช้โค้ดที่คุณประกาศใน Callback โดยอัตโนมัติทุกครั้งที่ 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. ขอแสดงความยินดี

หากทุกอย่างเป็นไปตามแผน ตอนนี้คุณควรมีแผนที่ที่มีหมุด 3 มิติขนาดใหญ่ซึ่งมีลักษณะดังนี้

เข็มกลัด 3 มิติสุดท้าย

สิ่งที่คุณได้เรียนรู้

ในโค้ดแล็บนี้ คุณได้เรียนรู้สิ่งต่างๆ มากมายแล้ว ต่อไปนี้คือไฮไลต์

  • การติดตั้งใช้งาน WebGLOverlayView และ Hook สำหรับวงจร
  • การผสานรวม Three.js เข้ากับแผนที่
  • พื้นฐานของการสร้างฉาก Three.js รวมถึงกล้องและแสง
  • การโหลดและจัดการโมเดล 3 มิติโดยใช้ Three.js
  • การควบคุมและสร้างภาพเคลื่อนไหวของกล้องสำหรับแผนที่โดยใช้ moveCamera

ขั้นตอนถัดไปคือ

WebGL และกราฟิกคอมพิวเตอร์โดยทั่วไปเป็นหัวข้อที่ซับซ้อน จึงมีสิ่งต่างๆ ให้เรียนรู้อยู่เสมอ แหล่งข้อมูลที่จะช่วยในการเริ่มต้นใช้งานมีดังนี้