יצירת חוויות מפה בתלת-ממד עם תצוגת שכבת-על WebGL

1. לפני שמתחילים

שיעור Lab זה מלמד איך להשתמש בתכונות המופעלות על ידי WebGL של ה-API של מפות Google JavaScript כדי לשלוט ולעבד במפה הווקטורית בשלושה מאפיינים.

סיכת תלת-ממד סופית

דרישות מוקדמות

ערכת ה-Codelab הזו מבוססת על הידע שלכם ב-JavaScript וב-API של מפות Google. לקבלת מידע בסיסי על השימוש ב-API של מפות JS, נסו את הוספת מפה לאתר שלכם (קוד JavaScript).

מה תלמדו

  • יצירת מזהה מפה עם מפה וקטורית עבור JavaScript מופעלת.
  • שליטה במפה באמצעות הטיה פרוגרמטית וסיבוב.
  • רינדור אובייקטים בתלת-ממד במפה באמצעות WebGLOverlayView ו-Three.js.
  • אנימציית תנועות המצלמה באמצעות moveCamera.

מה תצטרך להכין

  • חשבון ב-Google Cloud Platform עם חיוב מופעל
  • מפתח API של פלטפורמת מפות Google עם ממשק API של JavaScript במפות Google מופעל
  • ידע בינוני ב-JavaScript, ב-HTML וב-CSS
  • עורך טקסט או IDE לבחירתך
  • Node.js

2. להגדרה

בשלב ההפעלה הבא, יש להפעיל את API של מפות Google.

הגדרת מפות Google

אם עדיין אין לכם חשבון Google Cloud Platform ופרויקט שבו מופעל חיוב, כדאי לעיין במדריך תחילת העבודה עם הפלטפורמה של מפות Google ליצירת חשבון לחיוב ופרויקט.

  1. ב-Cloud Console, לוחצים על התפריט הנפתח של הפרויקט ובוחרים את הפרויקט שבו רוצים להשתמש ב-Codelab הזה.

  1. מפעילים את ממשקי ה-API ואת ערכות ה-SDK של מפות Google הנדרשים למעבדת קוד זו ב-Google Cloud Marketplace. כדי לעשות זאת, יש לבצע את השלבים המפורטים בסרטון הזה או בתיעוד הזה.
  2. יוצרים מפתח API בדף פרטי הכניסה ב-Cloud Console. ניתן לבצע את השלבים המפורטים בסרטון הזה או בתיעוד הזה. לכל הבקשות שנשלחות לפלטפורמה של מפות Google נדרש מפתח API.

הגדרת Node.js

אם עדיין לא עשיתם זאת, נכנסים לכתובת https://nodejs.org/ כדי להוריד ולהתקין את זמן הריצה של Node.js במחשב.

Node.js מגיע עם מנהל חבילת npm, שצריך להתקין יחסי תלות עבור מעבדת קוד זו.

הורדת התבנית של פרויקט למתחילים

לפני שמתחילים את ה-Codelab הזה, צריך לבצע את הפעולות הבאות כדי להוריד את התבנית של הפרויקט למתחילים, וגם את קוד הפתרון המלא:

  1. אפשר להוריד את ה-Repo RepoGveb או לממש אותו במעבדה זו בכתובת https://github.com/googlecodelabs/maps-platform-101-webgl/. הפרויקט למתחילים נמצא בספרייה של /starter וכולל את מבנה הקבצים הבסיסי הנדרש כדי להשלים את מעבדת הקוד. כל מה שצריך לעבוד נמצא בספרייה של /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 הוא ספרייה פשוטה שמפשטת את השיטה המסורתית של טעינת ממשק API של JS API בתוך תבנית ה-HTML עם תג script, ומאפשרת לך לטפל בהכול בקוד JavaScript.

כדי להוסיף את מפתח ה-API, יש לבצע את הפעולות הבאות בפרויקט למתחילים:

  1. פתיחת app.js
  2. באובייקט apiOptions, מגדירים את מפתח ה-API כערך של apiOptions.apiKey.

3. יצירת מזהה מפה ושימוש בו

כדי להשתמש בתכונות המבוססות על WebGL של ה-API של JavaScript במפות, נדרש מזהה מפה שבו מופעלת מפת וקטור.

יצירת מזהה מפה

הפקת מזהה מפה

  1. במסוף Google Cloud, עוברים אל 'מפות Google מפות' >'ניהול מפות'.
  2. לוחצים על 'יצירת מזהה מפה חדש'.
  3. בשדה 'שם המפה&#39', מזינים שם למזהה המפה.
  4. בתפריט הנפתח 'סוג מפה&339; בוחרים באפשרות 'JavaScript'. ‘אפשרויות JavaScript' יופיעו.
  5. בקטע 'אפשרויות JavaScript', בוחרים בתיבת הסימון 'וקטור', תיבת הסימון 'הטיה' ותיבת הסימון 'סיבוב''
  6. Optional. בשדה ‘תיאור&33’, מזינים תיאור של מפתח ה-API שלכם.
  7. לוחצים על הלחצן 'הבא'. הדף 'פרטי מזהה מפה' יופיע.

    דף פרטי המפה
  8. מעתיקים את מזהה המפה. הקוד ישמש אתכם בשלב הבא לטעינת המפה.

שימוש במזהה מפה

כדי לטעון את המפה הווקטורית, יש לספק מזהה מפה כנכס באפשרויות של יצירת המפה. אפשר גם לספק את אותו מזהה מפה כשטוענים את API של JavaScript.

כדי לטעון את המפה באמצעות מזהה המפה, מבצעים את הפעולות הבאות:

  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 המשמש לעיבוד מפת הבסיס הווקטורית. כלומר, תוכלו לעבד אובייקטים דו-ממדיים ותלת-ממדיים ישירות במפה באמצעות WebGL, וכן בספריות גרפיות פופולריות המבוססות על WebGL.

WebGLOverlayView מדגישה חמישה ווים במחזור החיים של הקשר ברינדור WebGL של המפה שבה אפשר להשתמש. הנה תיאור קצר של כל קרס ולמה הוא אמור:

  • onAdd(): מופעלת כשמוסיפים שכבת-על למפה על ידי קריאה ל-setMap במופע WebGLOverlayView. במקרה כזה, עליכם לבצע כל פעולה הקשורה ל-WebGL, ללא צורך בגישה ישירה להקשר של WebGL.
  • onContextRestored(): מתבצעת התקשרות כשההקשר של WebGL זמין, אבל לפני עיבוד של תוכן כלשהו. לכאן צריך להפעיל אובייקטים, לחבר את המכשירים ולבצע כל פעולה אחרת שצריך גישה להקשר WebGL, אבל אפשר לבצע אותם מחוץ לשיחה ב-onDraw(). כך תוכלו להגדיר את כל מה שאתם צריכים בלי להוסיף תקורה מיותרת לעיבוד בפועל של המפה, שכבר נעשה בה שימוש ב-GPU.
  • onDraw(): מתבצעת קריאה פעם אחת לכל מסגרת אחרי ש-WebGL מתחיל לעבד את המפה וכל דבר אחר שביקשת. עליך לעבוד מעט ככל האפשר בonDraw() כדי למנוע בעיית ביצועים בעיבוד המפה.
  • onContextLost(): מתבצעת כשהקשר של WebGL אבד מסיבה כלשהי.
  • onRemove(): מתבצעת קריאה כששכבת-העל תוסר מהמפה על ידי קריאה ל-setMap(null) במופע WebGLOverlayView.

בשלב זה, עליכם ליצור מופע של WebGLOverlayView ולהטמיע שלושה ווים במחזור החיים: onAdd, onContextRestored ו-onDraw. כדי לשמור על תוכן נקי ונוח למעקב, כל הקוד של שכבת-העל יעובד בפונקציה initWebGLOverlayView() שסופקה בתבנית למתחילים של מעבדת הקוד הזו.

  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. בשלב הבא, עליך להגדיר את כל מה שדרוש לך כדי לעבד אובייקט תלת-ממדי במפה באמצעות Three.js.

5. הגדרה של סצנת Tri.js

השימוש ב-WebGL יכול להיות מסובך מאוד, כי כדי להשתמש בו צריך להגדיר את כל ההיבטים של כל אובייקט באופן ידני, ואז את חלקם. כדי להקל על התהליך, אתם יכולים להשתמש ב-Three.js, ספריית גרפיקה פופולרית המציעה שכבת הפשטה פשוטה בחלק העליון של WebGL. Three.js מגיע עם מגוון רחב של פונקציות נוחות שמבצעות כל מיני פעולות, מיצירת רינדור של WebGL ועד ציור צורות אובייקט דו-ממדיות ותלת-ממדיות ועד שליטה במצלמות, טרנספורמציות אובייקטים ועוד הרבה יותר.

ב-Three.js יש שלושה סוגי אובייקטים בסיסיים הנדרשים להצגת כל תוכן:

  • סצנה: "קונטיינר" כאשר כל האובייקטים, מקורות התאורה, המרקמים וכו' מעובדים ומוצגים.
  • מצלמה: מצלמה שמייצגת את נקודת המבט של הסצנה. קיימים מספר סוגי מצלמות זמינים, וייתכן שמצלמה אחת או יותר יתווספו לסצנה יחידה.
  • רינדור: כלי עיבוד שמטפל בעיבוד ובהצגה של כל האובייקטים בסצינה. ב-Three.js, WebGLRenderer הוא הנפוץ ביותר, אך יש עוד כמה כלים שמאפשרים גיבוי במקרה שהלקוח לא תומך ב-WebGL.

בשלב זה, טוענים את כל התלות הנדרשות ב-Three.js ומגדירים סצנה בסיסית.

  1. טעינת שלוש.js

    אתה זקוק ל-2 יחסי תלות ב-codelab זה: הספרייה Three.js ו-GLTF Loader, מחלקה שמאפשרת לטעון אובייקטים תלת-ממדיים בפורמט העברה של GL (gLTF). Three.js מציע מטענים מיוחדים בפורמטים רבים של אובייקט תלת-ממדי, אבל מומלץ להשתמש ב-gLTF.

    בקוד הבא מתבצע ייבוא של כל ספריית Three.js. באפליקציית ייצור, סביר להניח שתרצו לייבא רק את הכיתות הדרושות לכם, אבל ב-Codelab הזה אפשר לייבא את כל הספרייה כדי שהתהליך יהיה פשוט. כמו כן, יש לשים לב שכלי הטעינה של GLTF לא נכלל בספריית ברירת המחדל, וצריך לייבא אותו מנתיב נפרד מהתלויות - זהו הנתיב שממנו אפשר לגשת לכל המטענים שסופקו על ידי Three.js.

    כדי לייבא את האפליקציה Three.js ל-GLTF Loader, יש להוסיף את הפרטים הבאים לראש הדף app.js:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. ליצור סצינה 3.js.

    כדי ליצור סצנה, יש להחפיץ את המחלקה Three.js Scene על ידי הוספת הפריט הבא ב-onAdd:webhook
    scene = new THREE.Scene();
    
  3. מוסיפים מצלמה לסצנה.

    כפי שצוין קודם לכן, המצלמה מייצגת את נקודת המבט של הסצנה והיא קובעת את אופן הטיפול של Three.js בהדמיה של אובייקטים בסצנה. ללא מצלמה, למעשה התמונה לא מוחזרת; כלומר אובייקטים לא יופיעו כי הם לא יעובדו.

    לאתר Three.js יש מגוון מצלמות שמשפיעות על האופן שבו אפשר לעבד את האובייקטים בהקשרים כמו פרספקטיבה ועומק. בסצנה הזו אתם משתמשים ב-PerspectiveCamera, סוג המצלמה הנפוץ ביותר ב-Three.js, שנועד לדמות את הדרך שבה העין האנושית תזהה את התמונה. כלומר, אובייקטים רחוקים יותר מהמצלמה נראים קטנים יותר מאובייקטים קרובים יותר, סצנת הצילום תיעלם ועוד.

    כדי להוסיף מצלמת פרספקטיבה לסצנה, צריך להוסיף את המאפיינים הבאים לווקינג onAdd:
    camera = new THREE.PerspectiveCamera();
    
    באמצעות PerspectiveCamera, אפשר גם להגדיר את המאפיינים שמהם מורכבת נקודת המבט, כולל המטוסים הקרובים והרחוקים, שדה הראייה ושדה הראייה (פוב). יחד, המאפיינים האלה מהווים את מה שמכונה תפיסה: תפיסה חשובה שצריך להבין בעת עבודה בתלת-ממד, אבל מחוץ להיקף של שיעור Lab זה. הגדרת ברירת המחדל של PerspectiveCamera מספיק בשביל זה.
  4. מוסיפים סביבות תאורה לסצנה.

    כברירת מחדל, אובייקטים שעברו עיבוד בסצנת Three.js יופיעו בשחור, ללא קשר למרקמים שהוחלו עליהם. הסיבה לכך היא שסצנה עם Three.js נועדה לחקות את פעולת האובייקטים בעולם האמיתי, כאשר חשיפת הצבע תלויה באור שמשקף את האובייקט. בקצרה, ללא אור, ללא צבע.

    פלטפורמת Three.js מספקת מגוון סוגים שונים של תאורה
  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. רינדור הסצנה

הגיע הזמן לעבד את הסצנה. עד עכשיו, כל מה שיצרת באמצעות Three.js מופעל בקוד, אבל למעשה לא קיים כי הוא עדיין לא עבר רינדור בהקשר של WebGL. WebGL מעבד תוכן דו-ממדי ותלת-ממדי בדפדפן באמצעות Canvas API. אם השתמשתם בעבר ב-Canvas API, אתם בוודאי מכירים את context של הקנבס של HTML, שבו הכול מעובד. ייתכן שלא ידוע לכם שזה ממשק שחושף את ההקשר של עיבוד הגרפיקה ב-OpenGL דרך ה-API של WebGLRenderingContext בדפדפן.

כדי להקל על הטיפול ברינדור WebGL, 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 במפה. צריך רק לספק את ההקשר, אזור העריכה והמאפיינים שלו למופע WebGLRenderer, שכולם זמינים דרך האובייקט gl. בקוד הזה, מאפיין autoClear של המעבד מוגדר גם ל-false כדי שהמעבד לא ינקה את הפלט שלו בכל מסגרת.

    כדי להגדיר את כלי הרינדור, יש לצרף את הפריטים הבאים לוואק של onContextRestored:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. רינדור הסצנה.

    לאחר הגדרת הרינדור, יש להתקשר אל requestRedraw במופע WebGLOverlayView כדי לציין את שכבת-העל הנדרשת לצביעה מחדש כשהמסגרת הבאה תוצג, ואז להתקשר אל render ברינדור ולעבור אותו לסצנת ה-שלוש.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. עיבוד מודל תלת-ממדי במפה

בסדר, הכל מוכן. הגדרת שכבת-על ב-WebGL ויצרת סצנת Three.js, אבל יש בעיה אחת: אין בה כלום. אז הגיע הזמן לעבד אובייקט תלת-ממדי בסצנה. לשם כך, עליך להשתמש ב-GLTF Loader שייבאת בעבר.

דגמים תלת-ממדיים זמינים במגוון פורמטים, אך בפורמט Three.js הפורמט gLTF הוא הפורמט המועדף בשל הגודל שלו וביצועי זמן הריצה. במעבדה זו, כבר קיים ב-/src/pin.gltf מודל שמתאים להצגה בסצנה.

  1. יצירת מופע של הכלי לטעינת דגם.

    יש להוסיף את הטקסט הבא אל onAdd:
    loader = new GLTFLoader();
    
  2. טעינת מודל תלת-ממדי.

    טוענים את הדגמים הם אסינכרוניים ומבצעים קריאה חוזרת (callback) לאחר שהמודל נטען במלואו. כדי לטעון את pin.gltf, יש לצרף את הטקסט הבא אל onAdd:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. מוסיפים את המודל לסצנה.

    עכשיו אפשר להוסיף את המודל לסצנה על ידי צירוף הפרמטר הבא לקריאה חוזרת (callback) ב-loader. חשוב להוסיף את gltf.scene, ולא את gltf:
    scene.add(gltf.scene);
    
  4. הגדרת המטריצה של המצלמה.

    הדבר האחרון שצריך לעשות כדי שהדגם יעובד כראוי במפה הוא להגדיר את מטריצת ההקרנה של המצלמה בסצנת Three.js. מטריצת התחזית מוגדרת כמערך של שלוש.js Matrix4, שמגדיר נקודה בשלושה מימדים בשילוב עם טרנספורמציות, כמו סיבובים, חיתוך, קנה מידה ועוד.

    במקרה של WebGLOverlayView, אנחנו משתמשים במטריצת ההקרנה כדי לציין לגורם הרינדור איפה ואיך לעבד את סצנת Three.js ביחס למפת הבסיס. אבל יש בעיה. המיקומים במפה מסומנים כצמדים של קו אורך וקו רוחב, ואילו המיקומים בסצנת Three.js הם קואורדינטות Vector3. כפי שוודאי ניחשת, חישוב ההמרה בין שתי המערכות אינו טריוויאלי. כדי לפתור את הבעיה הזו, WebGLOverlayView מעביר אובייקט coordinateTransformer אל הובלה במחזור OnDraw שמכילה פונקציה שנקראת fromLatLngAltitude. fromLatLngAltitude משתמש באובייקט LatLngAltitude או LatLngAltitudeLiteral, ובאופן אופציונלי גם קבוצת ארגומנטים שמגדירה טרנספורמציה לסצנה, ולאחר מכן מכסה אותם במטריצה של תצוגת מודל (MVP) עבורך. כל מה שאתם צריכים לעשות הוא לציין איפה אתם רוצים שסביבת ה-Three.js תופיע במפה, ואיך אתם רוצים שהיא תשתנה. WebGLOverlayView נעשה את כל השאר. לאחר מכן אפשר להמיר את מטריצת ה-MVP למערך Matrix4 של Three.js ולהגדיר בו את מטריצת ההקרנה של המצלמה.

    בקוד שלמטה, הארגומנט השני מורה לתצוגת שכבת-על באינטרנט של WebGL להגדיר את הגובה של סצנת Three.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. משנים את המודל.

    ייתכן שהסיכה לא תמוקם לרוחב המפה. בגרפיקה בתלת-ממד, בנוסף למרחב משותף, יש ציר X, y ו-z שקובעים את הכיוון, לכל אובייקט יש מרחב אובייקטים משלו עם צירים עצמאיים.

    במקרה של המודל הזה, הוא לא נוצר עם מה שנחשב בדרך כלל כ'למעלה ו-#39'; של הסיכה שפונה כלפי ציר ה-y. לכן, צריך לסובב את האובייקט כדי לכוון אותו לכיוון הרצוי ביחס למרחב המשותף. לשם כך, יש להפעיל את הערך rotation.set. הערה: ב-Three.js, הסיבוב מצוין ברדיאנים, ולא במעלות. בדרך כלל קל יותר לחשוב על מעלות. לכן צריך לבצע את ההמרה המתאימה באמצעות הנוסחה degrees * Math.PI/180.

    כמו כן, המודל קטן יחסית, כך שגם אם תתקשרו ל-scale.set(x, y ,z) תוכלו להרחיב אותו באופן שווה בכל הצירים.

    כדי לסובב את המודל ולשנות את קנה המידה שלו, יש להוסיף את האפשרויות הבאות בקריאה החוזרת (callback) של 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. אנימציה של המצלמה

עכשיו, לאחר שעיינתם במודל במפה ואתם יכולים להעביר כל דבר באופן תלת-ממדי, הדבר הבא שסביר להניח שתרצו לעשות הוא לשלוט בתנועה הזו באופן פרוגרמטי. הפונקציה moveCamera מאפשרת להגדיר בו-זמנית את המרחק מהמרכז, את המרחק מהתצוגה, את ההטיה ואת מאפייני הכותרת, וכך לקבל שליטה טובה על חוויית המשתמש. בנוסף, ניתן להפעיל את moveCamera בלולאה של אנימציה כדי ליצור מעברים נוזלים בין פריימים בקצב של כמעט 60 פריימים לשנייה.

  1. ממתינים שהמודל ייטען.

    כדי ליצור חוויית משתמש חלקה, מומלץ להמתין עד שתתחילו להעביר את המצלמה עד לטעינת מודל gLTF. כדי לעשות זאת, יש להוסיף את ה-handler של האירוע של onLoad לכלי הטעינה onContextRestored:
    loader.manager.onLoad = () => {}
    
  2. יצירת לולאת אנימציה.

    יש יותר מדרך אחת ליצור לולאת אנימציה, כמו שימוש ב-setInterval או ב-requestAnimationFrame. במקרה כזה, תשתמש בפונקציה setAnimationLoop של הרינדור Three.js, שתתקשר באופן אוטומטי לכל קוד שתכריש בקריאה החוזרת שלו בכל פעם ש-Three.js יבצע עיבוד של מסגרת חדשה. כדי ליצור את לולאת האנימציה, יש להוסיף את הדברים הבאים ל-handler של האירוע 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. מזל טוב

אם הכל בסדר, אמורה להיות לכם מפה עם סיכה תלת-ממדית גדולה שנראית כך:

סיכת תלת-ממד סופית

מה למדת

במעבדה זו למדתם כמה דברים, והכנו לכם כמה נקודות חשובות:

  • הטמעה של WebGLOverlayView וקרסים במחזור החיים שלהם.
  • שילוב של Three.js במפה.
  • העקרונות הבסיסיים ליצירת סצנת Three.js, כולל מצלמות ותאורה.
  • טעינה ומניפולציה של דגמים תלת-ממדיים באמצעות Three.js.
  • השליטה במצלמה ובאנימציה של המפה באמצעות moveCamera.

מה עכשיו?

WebGL, וגרפיקה ממוחשבת באופן כללי, הם נושא מורכב, כך שתמיד יש הרבה מה ללמוד. ריכזנו כאן הפניות לכמה מקורות מידע שיעזרו לך להתחיל בעבודה: