1. לפני שמתחילים
ב-codelab הזה נסביר איך להשתמש בתכונות שמבוססות על WebGL של Maps JavaScript API כדי לשלוט במפה הווקטורית ולעבד אותה בתלת-ממד.
דרישות מוקדמות
ההנחה ב-codelab הזה היא שיש לכם ידע בינוני ב-JavaScript וב-Maps JavaScript API. כדי ללמוד את היסודות של השימוש בממשק Maps JS API, אפשר לנסות את הסדנה בנושא הוספת מפה לאתר (JavaScript).
מה תלמדו
- יצירת מזהה מפה עם המפה הווקטורית ל-JavaScript מופעלת.
- שליטה במפה באמצעות הטיה וסיבוב מתוכנתים.
- Rendering של אובייקטים בתלת-ממד במפה באמצעות
WebGLOverlayView
ו-Three.js. - יצירת אנימציה של תנועות המצלמה באמצעות
moveCamera
.
מה נדרש
- חשבון ב-Google Cloud Platform עם חיוב מופעל
- מפתח API של הפלטפורמה של מפות Google עם ממשק API של JavaScript במפות Google מופעל
- ידע בינוני ב-JavaScript, ב-HTML וב-CSS
- עורך טקסט או סביבת פיתוח משולבת (IDE) לפי בחירתכם
- Node.js
2. להגדרה
בשלב ההפעלה שבהמשך, תצטרכו להפעיל את Maps JavaScript API.
הגדרת הפלטפורמה של מפות Google
אם עדיין אין לכם חשבון ב-Google Cloud Platform ופרויקט עם חיוב מופעל, תוכלו לעיין במדריך תחילת העבודה עם הפלטפורמה של מפות Google כדי ליצור חשבון לחיוב ופרויקט.
- בCloud Console, לוחצים על התפריט הנפתח של הפרויקט ובוחרים את הפרויקט שבו רוצים להשתמש ב-codelab הזה.
- מפעילים ב-Google Cloud Marketplace את ממשקי ה-API וערכות ה-SDK של הפלטפורמה של מפות Google שנדרשים ל-codelab הזה. כדי לעשות זאת, פועלים לפי השלבים בסרטון הזה או בתיעוד הזה.
- יוצרים מפתח API בדף Credentials במסוף Cloud. אפשר לפעול לפי השלבים שמפורטים בסרטון הזה או בתיעוד הזה. כל הבקשות אל הפלטפורמה של מפות Google מחייבות מפתח API.
הגדרת Node.js
אם עדיין לא התקנתם אותו, עוברים אל https://nodejs.org/ כדי להוריד ולהתקין את זמן הריצה של Node.js במחשב.
Node.js מגיע עם מנהל החבילות npm, שנדרש להתקנת יחסי תלות בשביל ה-codelab הזה.
הורדת תבנית של פרויקט
לפני שמתחילים את ה-codelab הזה, צריך לבצע את הפעולות הבאות כדי להוריד את תבנית פרויקט המתחילים, וגם את קוד הפתרון המלא:
- מורידים או יוצרים עותק (fork) של מאגר GitHub של ה-codelab הזה בכתובת https://github.com/googlecodelabs/maps-platform-101-webgl/. פרויקט המתחילים נמצא בספרייה
/starter
והוא כולל את מבנה הקבצים הבסיסי שדרוש לכם כדי להשלים את ה-codelab. כל מה שצריך לעבודה נמצא בספרייה/starter/src
. - אחרי שמורידים את פרויקט המתחילים, מריצים את הפקודה
npm install
בספרייה/starter
. כל הרכיבים התלויים שנדרשים מפורטים ב-package.json
. - אחרי שמתקינים את יחסי התלות, מריצים את הפקודה
npm start
בספרייה.
פרויקט המתחילים הוגדר לשימוש ב-webpack-dev-server, שמקמפל ומריץ את הקוד שאתם כותבים באופן מקומי. בנוסף, webpack-dev-server טוען מחדש את האפליקציה בדפדפן באופן אוטומטי בכל פעם שאתם מבצעים שינויים בקוד.
כדי לראות את קוד הפתרון המלא בפעולה, אפשר להשלים את שלבי ההגדרה שלמעלה בספרייה /solution
.
הוספת מפתח API
אפליקציית המתחילים כוללת את כל הקוד שנדרש לטעינת המפה באמצעות JS API Loader, כך שכל מה שצריך לעשות הוא לספק את מפתח ה-API ואת מזהה המפה. ה-JS API Loader היא ספרייה פשוטה שמבצעת הפשטה של השיטה המסורתית לטעינת Maps JS API בשורה בתבנית HTML באמצעות תג script
, ומאפשרת לכם לטפל בכל הפעולות בקוד JavaScript.
כדי להוסיף את מפתח ה-API, מבצעים את הפעולות הבאות בפרויקט המתחיל:
- פתיחת
app.js
. - באובייקט
apiOptions
, מגדירים את מפתח ה-API כערך שלapiOptions.apiKey
.
3. יצירה ושימוש במזהה מפה
כדי להשתמש בתכונות שמבוססות על WebGL בממשק API של JavaScript במפות Google, צריך מזהה מפה עם מפת וקטור מופעלת.
יצירת מזהה מפה
- במסוף Google Cloud, עוברים אל Google Maps Platform > Map Management (פלטפורמת מפות Google > ניהול מפות).
- לוחצים על 'יצירת מזהה מפה חדש'.
- בשדה 'שם המפה', מזינים שם למזהה המפה.
- בתפריט הנפתח 'סוג המפה', בוחרים באפשרות 'JavaScript'. יופיעו 'אפשרויות JavaScript'.
- בקטע 'אפשרויות JavaScript', בוחרים בלחצן הבחירה 'וקטור', בתיבת הסימון 'הטיה' ובתיבת הסימון 'סיבוב'.
- אופציונלי. בשדה 'תיאור', מזינים תיאור למפתח ה-API.
- לוחצים על הלחצן 'הבא'. יופיע הדף 'פרטים של מזהה המפה'.
- מעתיקים את מזהה המפה. תשתמשו בזה בשלב הבא כדי לטעון את המפה.
שימוש במזהה מפה
כדי לטעון את מפת הווקטור, צריך לספק מזהה מפה כמאפיין באפשרויות כשיוצרים מופע של Map. אפשר גם לציין את אותו מזהה מפה כשמטעינים את ממשק API של JavaScript במפות Google.
כדי לטעון את המפה עם מזהה המפה, מבצעים את הפעולות הבאות:
- מגדירים את מזהה המפה כערך של
mapOptions.mapId
.
כשמספקים את מזהה המפה כשיוצרים מופע של המפה, הפלטפורמה של מפות Google יודעת איזו מפה לטעון עבור מופע מסוים. אפשר להשתמש באותו מזהה מפה בכמה אפליקציות או בכמה תצוגות באותה אפליקציה.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
חושף חמישה ווים (hooks) למחזור החיים של הקשר של עיבוד WebGL של המפה שבהם אפשר להשתמש. הנה תיאור קצר של כל וו ומה השימוש בו:
-
onAdd()
: מופעל כשמוסיפים את שכבת העל למפה על ידי קריאה ל-setMap
במופעWebGLOverlayView
. כאן צריך לבצע את כל הפעולות שקשורות ל-WebGL שלא דורשות גישה ישירה להקשר של WebGL. -
onContextRestored()
: נקראת כשההקשר של WebGL הופך לזמין, אבל לפני שמתבצע עיבוד כלשהו. כאן צריך לאתחל אובייקטים, לקשור מצב ולבצע כל פעולה אחרת שדורשת גישה להקשר WebGL אבל אפשר לבצע אותה מחוץ לקריאהonDraw()
. כך תוכלו להגדיר את כל מה שצריך בלי להוסיף עומס מיותר על הרינדור בפועל של המפה, שכבר צורך הרבה משאבים של ה-GPU. -
onDraw()
: נקראת פעם אחת לכל פריים אחרי ש-WebGL מתחיל לעבד את המפה וכל דבר אחר שביקשתם. כדי למנוע בעיות בביצועים של עיבוד המפה, מומלץ לבצע כמה שפחות פעולות ב-onDraw()
. -
onContextLost()
: מופעלת כשהקשר העיבוד של WebGL אבד מסיבה כלשהי. -
onRemove()
: מופעלת כשמסירים את שכבת העל מהמפה על ידי הפעלתsetMap(null)
במופעWebGLOverlayView
.
בשלב הזה, תיצרו מופע של WebGLOverlayView
ותטמיעו שלושה מתוך ה-lifecycle hooks שלו: onAdd
, onContextRestored
ו-onDraw
. כדי שהקוד יהיה מסודר וקל להבנה, כל הקוד של שכבת העל יטופל בפונקציה initWebGLOverlayView()
שמופיעה בתבנית למתחילים של ה-codelab הזה.
- יוצרים מכונה
WebGLOverlayView()
.
שכבת העל מסופקת על ידי Maps JS API ב-google.maps.WebGLOverlayView
. כדי להתחיל, יוצרים מופע על ידי הוספת המחרוזת הבאה ל-initWebGLOverlayView()
:const webGLOverlayView = new google.maps.WebGLOverlayView();
- הטמעה של הוקים (hooks) של מחזור החיים.
כדי להטמיע את ה-lifecycle hooks, מוסיפים את הקוד הבא ל-initWebGLOverlayView()
:webGLOverlayView.onAdd = () => {}; webGLOverlayView.onContextRestored = ({gl}) => {}; webGLOverlayView.onDraw = ({gl, transformer}) => {};
- מוסיפים את מופע שכבת העל למפה.
עכשיו מתקשרים אלsetMap()
במופע של שכבת העל ומעבירים את המפה על ידי הוספת המחרוזת הבאה אלinitWebGLOverlayView()
:webGLOverlayView.setMap(map)
- התקשרו אל
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
. בשלב הבא, תגדירו את כל מה שצריך כדי לעבד אובייקט תלת-ממדי במפה באמצעות Three.js.
5. הגדרת סצנה ב-three.js
השימוש ב-WebGL יכול להיות מסובך מאוד כי צריך להגדיר ידנית את כל ההיבטים של כל אובייקט, ועוד כמה דברים. כדי להקל על התהליך, במעבדת ה-codelab הזו תשתמשו ב-Three.js, ספריית גרפיקה פופולרית שמספקת שכבת הפשטה פשוטה מעל WebGL. Three.js מגיעה עם מגוון רחב של פונקציות נוחות שעושות הכול, החל מיצירת רכיב WebGL renderer ועד לציור צורות נפוצות של אובייקטים דו-ממדיים ותלת-ממדיים, שליטה במצלמות, המרות של אובייקטים ועוד הרבה יותר.
יש שלושה סוגים בסיסיים של אובייקטים ב-Three.js שנדרשים כדי להציג משהו:
- סצנה: 'מאגר' שבו כל האובייקטים, מקורות האור, הטקסטורות וכו' עוברים עיבוד ומוצגים.
- מצלמה: מצלמה שמייצגת את נקודת המבט של הסצנה. יש כמה סוגים של מצלמות, ואפשר להוסיף מצלמה אחת או יותר לסצנה אחת.
- רכיב עיבוד: רכיב עיבוד שמטפל בעיבוד ובהצגה של כל האובייקטים בסצנה. ב-Three.js,
WebGLRenderer
הוא הנפוץ ביותר, אבל יש עוד כמה שזמינים כגיבוי למקרה שהלקוח לא תומך ב-WebGL.
בשלב הזה, טוענים את כל הרכיבים התלויים שנדרשים ל-Three.js ומגדירים סצנה בסיסית.
- טוענים את three.js
תצטרכו שני יחסי תלות בשביל ה-codelab הזה: ספריית Three.js ו-GLTF Loader, מחלקה שמאפשרת לכם לטעון אובייקטים תלת-ממדיים בפורמט GL Trasmission (gLTF). Three.js מציעה טוענים ייעודיים לפורמטים רבים ושונים של אובייקטים תלת-ממדיים, אבל מומלץ להשתמש ב-gLTF.
בקוד שלמטה, ספריית Three.js כולה מיובאת. באפליקציה לייצור, סביר להניח שתרצו לייבא רק את המחלקות שאתם צריכים, אבל כדי לפשט את התהליך בסדנת הקוד הזו, מייבאים את כל הספרייה. שימו לב גם ש-GLTF Loader לא נכלל בספריית ברירת המחדל, וצריך לייבא אותו מנתיב נפרד בתלות – זה הנתיב שדרכו אפשר לגשת לכל הטוענים שסופקו על ידי Three.js.
כדי לייבא את Three.js ואת GLTF Loader, מוסיפים את השורה הבאה לחלק העליון שלapp.js
:import * as THREE from 'three'; import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
- יוצרים סצנה ב-three.js.
כדי ליצור סצנה, יוצרים מופע של המחלקהScene
Three.js על ידי הוספת הקוד הבא ל-hookonAdd
:scene = new THREE.Scene();
- מוסיפים מצלמה לסצנה.
כמו שצוין קודם, המצלמה מייצגת את נקודת המבט של הצפייה בסצנה, וקובעת איך Three.js מטפל ברינדור החזותי של אובייקטים בסצנה. בלי מצלמה, הסצנה לא 'נראית', כלומר האובייקטים לא יופיעו כי הם לא יעברו רינדור.
Three.js מציעה מגוון מצלמות שונות שמשפיעות על האופן שבו כלי העיבוד מתייחס לאובייקטים, למשל מבחינת פרספקטיבה ועומק. בסצנה הזו נשתמש ב-PerspectiveCamera
, סוג המצלמה הנפוץ ביותר ב-Three.js, שנועד לחקות את האופן שבו העין האנושית תופסת את הסצנה. כלומר, אובייקטים רחוקים יותר מהמצלמה ייראו קטנים יותר מאובייקטים קרובים יותר, בסצנה תהיה נקודת מפגש של קווים מקבילים ועוד.
כדי להוסיף מצלמה פרספקטיבית לסצנה, מוסיפים את הקוד הבא ל-hookonAdd
: ב-camera = new THREE.PerspectiveCamera();
PerspectiveCamera
אפשר גם להגדיר את המאפיינים שמרכיבים את נקודת המבט, כולל המישורים הקרובים והרחוקים, יחס הגובה-רוחב ושדה הראייה (fov). כל המאפיינים האלה ביחד יוצרים את מה שנקרא פירמידת הראייה, מושג חשוב להבנה כשעובדים בתלת-ממד, אבל הוא לא נכלל בהיקף של ה-codelab הזה. ההגדרהPerspectiveCamera
שמוגדרת כברירת מחדל תספיק. - מוסיפים מקורות אור לסצנה.
כברירת מחדל, אובייקטים שעברו רינדור בסצנת Three.js יופיעו בשחור, ללא קשר לטקסטורות שהוחלו עליהם. הסיבה לכך היא שסצנת Three.js מחקה את האופן שבו אובייקטים מתנהגים בעולם האמיתי, שבו הנראות של הצבע תלויה באור שמוחזר מאובייקט. בקיצור, בלי אור, בלי צבע.
Three.js מספקת מגוון סוגים שונים של מקורות אור, מתוכם נשתמש בשניים: -
AmbientLight
: מספק מקור אור מפוזר שמאיר באופן שווה את כל האובייקטים בסצנה מכל הזוויות. כך תהיה בסצנה כמות בסיסית של אור כדי להבטיח שהמרקמים של כל האובייקטים יהיו גלויים. -
DirectionalLight
: מספק אור שמגיע מכיוון מסוים בסצנה. בניגוד לאופן שבו אור ממוקם פועל בעולם האמיתי, קרני האור שיוצאות מ-DirectionalLight
הן מקבילות ולא מתפשטות ומתפזרות ככל שהן מתרחקות ממקור האור.
אתם יכולים להגדיר את הצבע והעוצמה של כל אור כדי ליצור אפקטים משולבים של תאורה. לדוגמה, בקוד שלמטה, תאורת האווירה מספקת אור לבן רך לכל הסצנה, בעוד שהאור הכיווני מספק אור משני שפוגע באובייקטים בזווית כלפי מטה. במקרה של אור כיווני, הזווית מוגדרת באמצעותposition.set(x, y ,z)
, כאשר כל ערך הוא יחסי לציר המתאים. לדוגמה,position.set(0,1,0)
ימקם את האור ישירות מעל הסצנה בציר ה-y, כשהוא מכוון כלפי מטה.
כדי להוסיף את מקורות האור לסצנה, מוסיפים את הקוד הבא ל-onAdd
hook: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);
ה-hook 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 renderer ותעבדו את הסצנה.
6. עיבוד הסצנה
הגיע הזמן לעבד את הסצנה. עד עכשיו, כל מה שיצרתם באמצעות Three.js אותחל בקוד, אבל הוא בעצם לא קיים כי הוא עדיין לא עבר רינדור בהקשר של רינדור WebGL. WebGL מעבד תוכן דו-ממדי ותלת-ממדי בדפדפן באמצעות canvas API. אם השתמשתם בעבר ב-Canvas API, סביר להניח שאתם מכירים את context
של Canvas HTML, שבו מתבצע הרינדור של הכול. מה שאולי לא ידעתם הוא שמדובר בממשק שחושף את הקשר של עיבוד הגרפיקה של OpenGL דרך WebGLRenderingContext
API בדפדפן.
כדי להקל על השימוש במעבד WebGL, Three.js מספקת WebGLRenderer
, עטיפה שמקלה יחסית על הגדרת הקשר של עיבוד WebGL, כך ש-Three.js יכולה לעבד סצנות בדפדפן. אבל במקרה של המפה, לא מספיק רק לעבד את סצנת Three.js בדפדפן לצד המפה. הספרייה Three.js צריכה לבצע רינדור לאותו הקשר רינדור כמו המפה, כדי שהמפה וכל האובייקטים מסצנת Three.js ירונדרו לאותו מרחב עולמי. כך המעבד יכול לטפל באינטראקציות בין אובייקטים במפה לבין אובייקטים בסצנה, כמו הסתרה. הסתרה היא דרך מתוחכמת לומר שאובייקט יסתיר אובייקטים שמאחוריו.
נשמע מסובך, נכון? למזלנו, Three.js שוב עוזרת לנו.
- מגדירים את רכיב ה-WebGL לרינדור.
כשיוצרים מופע חדש של Three.jsWebGLRenderer
, אפשר לספק לו את הקשר הספציפי של עיבוד WebGL שרוצים שהוא יעבד את הסצנה. זוכרים את הארגומנטgl
שמועבר אל ה-hookonContextRestored
? האובייקטgl
הוא הקשר העיבוד של WebGL במפה. כל מה שצריך לעשות הוא לספק את ההקשר, את אזור התצוגה שלו ואת המאפיינים שלו למופעWebGLRenderer
, וכל אלה זמינים דרך האובייקטgl
. בקוד הזה, המאפייןautoClear
של רכיב ה-renderer מוגדר גם הוא ל-autoClear
, כדי שרכיב ה-renderer לא ינקה את הפלט שלו בכל פריים.false
כדי להגדיר את רכיב ה-renderer, מוסיפים את הקוד הבא ל-hookonContextRestored
:renderer = new THREE.WebGLRenderer({ canvas: gl.canvas, context: gl, ...gl.getContextAttributes(), }); renderer.autoClear = false;
- מעבדים את הסצנה.
אחרי שמגדירים את מעבד ה-HTML, קוראים ל-requestRedraw
במופעWebGLOverlayView
כדי לציין לשכבת העל שצריך לבצע שרטוט מחדש כשמסגרת הווידאו הבאה מוצגת, ואז קוראים ל-render
במעבד ה-HTML ומעבירים לו את הסצנה והמצלמה של Three.js כדי לבצע עיבוד. לבסוף, מנקים את המצב של הקשר העיבוד של WebGL. זהו שלב חשוב כדי למנוע התנגשויות במצב GL, כי השימוש בתצוגת שכבת-על של WebGL מסתמך על מצב GL משותף. אם המצב לא מאופס בסוף כל קריאה לציור, יכול להיות שיתרחשו התנגשויות במצב GL שיגרמו לכשל בעיבוד.
כדי לעשות את זה, מוסיפים את הקוד הבא ל-hookonDraw
כדי שהוא יופעל בכל פריים:webGLOverlayView.requestRedraw(); renderer.render(scene, camera); renderer.resetState();
ה-hooks 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 Overlay View ויצרתם סצנה ב-Three.js, אבל יש בעיה אחת: אין בה כלום. עכשיו הגיע הזמן לעבד אובייקט תלת-ממדי בסצנה. כדי לעשות את זה, תשתמשו ב-GLTF Loader שייבאתם קודם.
מודלים תלת-ממדיים זמינים בפורמטים שונים, אבל לשימוש ב-Three.js, הפורמט המועדף הוא gLTF בגלל הגודל וביצועי זמן הריצה שלו. ב-codelab הזה, מודל שתוכלו לעבד בסצנה כבר מסופק לכם ב-/src/pin.gltf
.
- יוצרים מופע של טוען מודלים.
מוסיפים את הטקסט הבא ל-onAdd
:loader = new GLTFLoader();
- טוענים מודל תלת-ממדי.
טועני המודלים הם אסינכרוניים ומבצעים קריאה חוזרת אחרי שהמודל נטען במלואו. כדי לטעון אתpin.gltf
, מוסיפים את הטקסט הבא ל-onAdd
:const source = "pin.gltf"; loader.load( source, gltf => {} );
- מוסיפים את המודל לסצנה.
עכשיו אפשר להוסיף את המודל לסצנה על ידי הוספת הקריאה החוזרתloader
הבאה. שימו לב שמוסיפים אתgltf.scene
ולא אתgltf
:scene.add(gltf.scene);
- מגדירים את מטריצת ההטלה של המצלמה.
הדבר האחרון שצריך לעשות כדי שהמודל יוצג בצורה תקינה במפה הוא להגדיר את מטריצת ההטלה של המצלמה בסצנת Three.js. מטריצת ההטלה מוגדרת כמערךMatrix4
של Three.js, שמגדיר נקודה במרחב תלת-ממדי יחד עם טרנספורמציות, כמו סיבובים, גזירה, שינוי גודל ועוד.
במקרה שלWebGLOverlayView
, מטריצת ההטלה משמשת כדי להגדיר לכלי העיבוד איפה ואיך לעבד את סצנת Three.js ביחס למפת הבסיס. אבל יש בעיה. המיקומים במפה מצוינים כזוגות של קואורדינטות של קווי רוחב ואורך, ואילו המיקומים בסצנת Three.js הם קואורדינטותVector3
. כפי שניתן לנחש, חישוב ההמרה בין שתי המערכות הוא לא פשוט. כדי לפתור את הבעיה,WebGLOverlayView
מעביר אובייקטcoordinateTransformer
ל-lifecycle hookOnDraw
שמכיל פונקציה בשםfromLatLngAltitude
. fromLatLngAltitude
מקבלת אובייקטLatLngAltitude
אוLatLngAltitudeLiteral
, ובאופן אופציונלי קבוצת ארגומנטים שמגדירים טרנספורמציה של הסצנה, ואז ממירה אותם למטריצת הקרנה של תצוגת מודל (MVP) בשבילכם. כל מה שצריך לעשות הוא לציין איפה במפה רוצים שהסצנה של Three.js תוצג, וגם איך רוצים שהיא תעבור טרנספורמציה, ו-WebGLOverlayView
יעשה את כל השאר. לאחר מכן אפשר להמיר את מטריצת ה-MVP למערךMatrix4
Three.js ולהגדיר אותה כמטריצת ההטלה של המצלמה.
בקוד שלמטה, הארגומנט השני אומר ל-WebGl Overlay View להגדיר את הגובה של סצנת Three.js ל-120 מטרים מעל פני הקרקע, כך שהמודל ייראה כאילו הוא צף.
כדי להגדיר את מטריצת ההיטל של המצלמה, מוסיפים את הקוד הבא ל-hookonDraw
:const latLngAltitudeLiteral = { lat: mapOptions.center.lat, lng: mapOptions.center.lng, altitude: 120 } const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral); camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
- משנים את המודל.
תשימו לב שהסיכה לא ניצבת בניצב למפה. בגרפיקה תלת-ממדית, בנוסף למרחב העולם שיש לו צירים משלו x, y ו-z שקובעים את הכיוון, לכל אובייקט יש גם מרחב אובייקט משלו עם קבוצת צירים עצמאית.
במקרה של המודל הזה, הוא לא נוצר עם מה שאנחנו בדרך כלל מחשיבים כ'חלק העליון' של הסיכה שפונה כלפי מעלה בציר ה-Y, ולכן צריך להפעיל עליו את הפונקציהrotation.set
כדי לשנות את האובייקט כך שיהיה בכיוון הרצוי ביחס למרחב העולם. שימו לב שב-Three.js, הסיבוב מצוין ברדיאנים ולא במעלות. בדרך כלל קל יותר לחשוב במעלות, ולכן צריך לבצע את ההמרה המתאימה באמצעות הנוסחהdegrees * Math.PI/180
.
בנוסף, המודל קטן מדי, ולכן צריך גם לשנות את קנה המידה שלו באופן שווה בכל הצירים באמצעות קריאה ל-scale.set(x, y ,z)
.
כדי לסובב את המודל ולשנות את הגודל שלו, מוסיפים את הקוד הבא ל-callbackloader
שלonAdd
לפניscene.add(gltf.scene)
שמוסיף את ה-gLTF לסצנה:gltf.scene.scale.set(25,25,25); gltf.scene.rotation.x = 180 * Math.PI/180;
עכשיו הסיכה מוצבת זקופה ביחס למפה.
ה-hooks 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 פריימים לשנייה.
- מחכים שהמודל ייטען.
כדי ליצור חוויית משתמש חלקה, כדאי להמתין עד שטעינת מודל ה-gLTF תסתיים לפני שתתחילו להזיז את המצלמה. כדי לעשות זאת, מוסיפים את הגורם שמטפל באירועים של הכלי לטעינת נתוניםonLoad
ל-hookonContextRestored
:loader.manager.onLoad = () => {}
- ליצור לופ של אנימציה.
יש כמה דרכים ליצור לולאת אנימציה, למשל באמצעותsetInterval
אוrequestAnimationFrame
. במקרה כזה, תשתמשו בפונקציהsetAnimationLoop
של רכיב ה-renderer של Three.js, שתפעיל באופן אוטומטי כל קוד שתצהירו בפונקציית הקריאה החוזרת שלה בכל פעם ש-Three.js מעבד פריים חדש. כדי ליצור את לולאת האנימציה, מוסיפים את הקוד הבא ל-onLoad
event handler בשלב הקודם:renderer.setAnimationLoop(() => {});
- מגדירים את מיקום המצלמה בלופ האנימציה.
לאחר מכן, מתקשרים אלmoveCamera
כדי לעדכן את המפה. בדוגמה הזו, מאפיינים מאובייקטmapOptions
ששימש לטעינת המפה משמשים להגדרת מיקום המצלמה:map.moveCamera({ "tilt": mapOptions.tilt, "heading": mapOptions.heading, "zoom": mapOptions.zoom });
- עדכון המצלמה בכל פריים.
שלב אחרון! מעדכנים את אובייקט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) }
ה-hook 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. מזל טוב
אם הכול התנהל לפי התוכנית, עכשיו אמורה להופיע מפה עם סיכה גדולה בתלת-ממד שנראית כך:
מה למדתם
ב-codelab הזה למדתם הרבה דברים. הנה כמה מהנקודות העיקריות:
- הטמעה של
WebGLOverlayView
וה-lifecycle hooks שלו. - שילוב של Three.js במפה.
- היסודות של יצירת סצנה ב-Three.js, כולל מצלמות ותאורה.
- טעינה של מודלים תלת-ממדיים וביצוע שינויים בהם באמצעות Three.js.
- שליטה במצלמה והנפשתה במפה באמצעות
moveCamera
.
מה השלב הבא?
WebGL וגרפיקה ממוחשבת באופן כללי הם נושאים מורכבים, ולכן תמיד יש מה ללמוד. ריכזנו כאן כמה מקורות מידע שיעזרו לכם להתחיל:
- מסמכי תיעוד בנושא שכבת על מבוססת-WebGL
- איך מתחילים להשתמש ב-WebGL
- התיעוד של three.js
- כדי לעזור לנו ליצור את התוכן שהכי יעניין אותך, נשמח אם תענה על השאלה הבאה: «codelabs/maps-platform/shared/_next-lab-survey.lab.md» לא מצאת את ה-codelab שרצית ברשימה שלמעלה? כאן אפשר לשלוח בקשה בנושא בעיה חדשה.