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

1. ข้อควรทราบก่อนที่จะเริ่มต้น

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

หมุด 3 มิติขั้นสุดท้าย

สิ่งที่ต้องมีก่อน

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

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

  • กําลังสร้างรหัสแผนที่ด้วยแผนที่เวกเตอร์สําหรับ 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 API และ SDK ที่จําเป็นสําหรับ Codelab นี้ใน Google Cloud Marketplace โดยทําตามขั้นตอนในวิดีโอนี้หรือเอกสารนี้
  2. สร้างคีย์ API ในหน้าข้อมูลเข้าสู่ระบบของ Cloud Console คุณสามารถทําตามขั้นตอนในวิดีโอนี้หรือเอกสารนี้ คําขอทั้งหมดสําหรับ Google Maps Platform ต้องใช้คีย์ API

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

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

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

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

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

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

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

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

เพิ่มคีย์ API

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

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

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

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

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

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

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

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

    หน้ารายละเอียดแผนที่
  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() ที่ให้ไว้ในเทมเพลตเริ่มต้นสําหรับ Codelab นี้ เพื่อให้ทุกอย่างดูง่ายขึ้นและติดตามได้ง่าย

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

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

    หากต้องการนําตะขอแบบวงจรการใช้งานต่อท้าย ให้ต่อท้าย initWebGLOverlayView() ด้วย
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};
    
  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, coordinateTransformer}) => {}
      webGLOverlayView.setMap(map);
    }

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

ทั้งหมดนี้คือขั้นตอนการติดตั้ง WebGLOverlayView จากนั้นให้ตั้งค่าทุกอย่างที่จําเป็นเพื่อแสดงวัตถุ 3 มิติบนแผนที่โดยใช้ 3.js

5. ตั้งค่าโหมด 3.js

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

ประเภทออบเจ็กต์พื้นฐานใน 3.js ที่ต้องใช้แสดงทุกสิ่งมี 3 ประเภท ดังนี้

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

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

  1. โหลด 3.js

    คุณต้องใช้ทรัพยากร Dependency สําหรับ Codelab นี้ 2 รายการ ได้แก่ ไลบรารี Three.js และ LoadTF Loader ซึ่งเป็นคลาสที่ให้คุณโหลดออบเจ็กต์ 3 มิติในรูปแบบ GL Trasmission Format (gLTF) 3.js มีตัวโหลดพิเศษสําหรับรูปแบบวัตถุ 3 มิติมากมาย แต่แนะนําให้ใช้ gLTF

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

    หากต้องการนําเข้า 3.js และตัวโหลด GLTF ให้เพิ่มสิ่งต่อไปนี้ที่ด้านบนของ app.js
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. สร้างโหมด 3.js

    ในการสร้างฉาก ให้เรียกใช้คลาส 3.js Scene โดยต่อท้ายสิ่งต่อไปนี้ด้วย onAdd Hood:
    scene = new THREE.Scene();
    
  3. เพิ่มกล้องไปยังฉาก

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

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

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

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

    Three.js มีหลอดไฟประเภทต่างๆ ที่คุณจะใช้ 2 ประเภท ได้แก่

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

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

    หากต้องการเพิ่มแหล่งที่มาของแสงในฉาก ให้เพิ่มค่าต่อไปนี้ลงในตะขอของ onAdd:
    const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);
    

ตะขอของ onAdd ควรมีลักษณะดังนี้

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
      scene.add(ambientLight);
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);
    }

ฉากของคุณได้รับการตั้งค่าและพร้อมแสดงผลแล้ว จากนั้นให้กําหนดค่าตัวแสดงผล WebGL และแสดงผลฉาก

6. แสดงผลฉาก

เวลาที่ใช้ในการแสดงผลฉาก จนถึงตอนนี้ ทุกอย่างที่คุณสร้างด้วย 3.js จะมีการเริ่มต้นเป็นโค้ด แต่ที่จริงแล้วไม่มีอยู่จริงเนื่องจากยังไม่ได้แสดงผลในบริบทการแสดงผล WebGL WebGL แสดงเนื้อหา 2 มิติและ 3 มิติในเบราว์เซอร์โดยใช้ canvas API หากคุณเคยใช้ Canvas API มาก่อน คุณอาจคุ้นเคยกับ context ของผืนผ้าใบ HTML ซึ่งเป็นที่ที่แสดงทุกอย่าง สิ่งที่คุณอาจไม่รู้ว่าคืออินเทอร์เฟซที่จะแสดงบริบทการแสดงภาพกราฟิกของ OpenGL ผ่าน WebGLRenderingContext API ในเบราว์เซอร์

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

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

  1. ตั้งค่าตัวแสดงผล WebGL

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

    หากต้องการกําหนดค่าตัวแสดงผล ให้เพิ่มค่าต่อไปนี้ลงในตะขอของ onContextRestored:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. แสดงผลของฉาก

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

    ในการดําเนินการดังกล่าว ให้เพิ่มข้อมูลต่อไปนี้ลงในตะขอ onDraw เพื่อให้ดําเนินการแต่ละเฟรมได้
    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 และสร้างฉาก 3.js แล้ว แต่ปัญหาหนึ่งคือไม่มีสิ่งใดเลย จากนั้นก็ได้เวลาแสดงผลวัตถุ 3 มิติในฉาก ในการทําเช่นนี้ คุณต้องใช้ตัวโหลด GLTF ที่นําเข้าก่อนหน้านี้

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

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

    ต่อท้าย onAdd:
    loader = new GLTFLoader();
    
  2. โหลดโมเดล 3 มิติ

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

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

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

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

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

    หากต้องการตั้งค่าเมทริกซ์การฉายภาพของกล้อง ให้เพิ่มค่าต่อไปนี้ลงในเว็บฮุค onDraw
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 120
    }
    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
  5. เปลี่ยนรูปแบบ

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

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

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

    หากต้องการหมุนและปรับขนาดโมเดล ให้เพิ่มค่าต่อไปนี้ในการเรียกกลับ loader ของ onAdd ก่อน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. สร้างภาพเคลื่อนไหวแบบวนซ้ํา

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

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

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

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

มีอะไรอีกบ้าง

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