1. Trước khi bắt đầu
Lớp học lập trình này hướng dẫn bạn cách sử dụng các tính năng dựa trên WebGL của Maps JavaScript API để kiểm soát và kết xuất trên bản đồ vectơ ở chế độ 3D.
Điều kiện tiên quyết
Lớp học lập trình này giả định rằng bạn có kiến thức cơ bản về JavaScript và Maps JavaScript API. Để tìm hiểu những kiến thức cơ bản về cách sử dụng Maps JS API, hãy thử tham gia lớp học lập trình Thêm bản đồ vào trang web (JavaScript).
Kiến thức bạn sẽ học được
- Tạo mã bản đồ khi đã bật bản đồ vectơ cho JavaScript.
- Kiểm soát bản đồ bằng độ nghiêng và hướng xoay có lập trình.
- Kết xuất các đối tượng 3D trên bản đồ bằng
WebGLOverlayView
và Three.js. - Tạo ảnh động cho chuyển động của camera bằng
moveCamera
.
Bạn cần có
- Tài khoản Google Cloud Platform có bật tính năng thanh toán
- Khoá API Google Maps Platform có API JavaScript của Maps được bật
- Kiến thức trung cấp về JavaScript, HTML và CSS
- Trình chỉnh sửa văn bản hoặc IDE mà bạn chọn
- Node.js
2. Bắt đầu thiết lập
Đối với bước bật bên dưới, bạn cần bật Maps JavaScript API.
Thiết lập Nền tảng Google Maps
Nếu bạn chưa có tài khoản Google Cloud Platform và dự án có bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và dự án.
- Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án rồi chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.
- Bật các API và SDK của Google Maps Platform cần thiết cho lớp học lập trình này trong Google Cloud Marketplace. Để làm như vậy, hãy làm theo các bước trong video này hoặc tài liệu này.
- Tạo khoá API trong trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả các yêu cầu gửi đến Nền tảng Google Maps đều cần có khoá API.
Thiết lập Node.js
Nếu bạn chưa có, hãy truy cập vào https://nodejs.org/ để tải và cài đặt thời gian chạy Node.js trên máy tính.
Node.js đi kèm với trình quản lý gói npm. Bạn cần cài đặt các phần phụ thuộc cho lớp học lập trình này.
Tải mẫu khởi đầu dự án xuống
Trước khi bắt đầu lớp học lập trình này, hãy làm như sau để tải mẫu dự án khởi đầu cũng như mã giải pháp hoàn chỉnh xuống:
- Tải xuống hoặc phân nhánh kho lưu trữ GitHub cho lớp học lập trình này tại https://github.com/googlecodelabs/maps-platform-101-webgl/. Dự án khởi đầu nằm trong thư mục
/starter
và bao gồm cấu trúc tệp cơ bản mà bạn cần để hoàn thành lớp học lập trình. Mọi thứ bạn cần để làm việc đều nằm trong thư mục/starter/src
. - Sau khi tải dự án khởi đầu xuống, hãy chạy
npm install
trong thư mục/starter
. Thao tác này sẽ cài đặt tất cả các phần phụ thuộc cần thiết có trongpackage.json
. - Sau khi bạn cài đặt các phần phụ thuộc, hãy chạy
npm start
trong thư mục.
Dự án khởi đầu đã được thiết lập để bạn sử dụng webpack-dev-server, trình này sẽ biên dịch và chạy mã mà bạn viết cục bộ. webpack-dev-server cũng tự động tải lại ứng dụng của bạn trong trình duyệt bất cứ khi nào bạn thực hiện thay đổi mã.
Nếu muốn xem đoạn mã giải pháp đầy đủ đang chạy, bạn có thể hoàn tất các bước thiết lập ở trên trong thư mục /solution
.
Thêm khoá API
Ứng dụng khởi động bao gồm tất cả mã cần thiết để tải bản đồ bằng JS API Loader, vì vậy, bạn chỉ cần cung cấp khoá API và mã bản đồ. JS API Loader là một thư viện đơn giản giúp trừu tượng hoá phương thức truyền thống để tải Maps JS API nội tuyến trong mẫu HTML bằng thẻ script
, cho phép bạn xử lý mọi thứ trong mã JavaScript.
Để thêm khoá API, hãy làm như sau trong dự án khởi đầu:
- Mở
app.js
. - Trong đối tượng
apiOptions
, hãy đặt khoá API làm giá trị củaapiOptions.apiKey
.
3. Tạo và sử dụng Mã bản đồ
Để sử dụng các tính năng dựa trên WebGL của Maps JavaScript API, bạn cần có một mã bản đồ đã bật bản đồ vectơ.
Tạo mã bản đồ
- Trong Google Cloud Console, hãy chuyển đến "Google Maps Platform" > "Quản lý bản đồ".
- Nhấp vào "TẠO MAP ID MỚI".
- Trong trường "Tên bản đồ", hãy nhập tên cho Mã bản đồ của bạn.
- Trong trình đơn thả xuống "Loại bản đồ", hãy chọn "JavaScript". "JavaScript Options" (Lựa chọn JavaScript) sẽ xuất hiện.
- Trong phần "Lựa chọn JavaScript", hãy chọn nút chọn "Vector", hộp đánh dấu "Tilt" và hộp đánh dấu "Rotation".
- Không bắt buộc. Trong trường "Nội dung mô tả", hãy nhập nội dung mô tả cho khoá API của bạn.
- Nhấp vào nút "Tiếp theo". Trang "Chi tiết về mã bản đồ" sẽ xuất hiện.
- Sao chép Mã bản đồ. Bạn sẽ dùng chỉ mục này trong bước tiếp theo để tải Bản đồ.
Sử dụng mã bản đồ
Để tải bản đồ vectơ, bạn phải cung cấp một Mã bản đồ làm thuộc tính trong các lựa chọn khi khởi tạo Bản đồ. Nếu muốn, bạn cũng có thể cung cấp cùng một mã bản đồ khi tải API JavaScript của Maps.
Để tải bản đồ bằng mã bản đồ, hãy làm như sau:
- Đặt Mã bản đồ làm giá trị của
mapOptions.mapId
.
Khi bạn khởi tạo bản đồ, việc cung cấp mã bản đồ sẽ cho Nền tảng Google Maps biết bản đồ nào của bạn cần tải cho một phiên bản cụ thể. Bạn có thể sử dụng lại cùng một mã bản đồ cho nhiều ứng dụng hoặc nhiều khung hiển thị trong cùng một ứng dụng.const mapOptions = { "tilt": 0, "heading": 0, "zoom": 18, "center": { lat: 35.6594945, lng: 139.6999859 }, "mapId": "YOUR_MAP_ID" };
Kiểm tra ứng dụng đang chạy trong trình duyệt. Bản đồ vectơ có chế độ nghiêng và xoay được bật sẽ tải thành công. Để kiểm tra xem chế độ nghiêng và xoay có được bật hay không, hãy giữ phím Shift rồi kéo bằng chuột hoặc dùng các phím mũi tên trên bàn phím.
Nếu bản đồ không tải, hãy kiểm tra để đảm bảo bạn đã cung cấp khoá API hợp lệ trong apiOptions
. Nếu bản đồ không nghiêng và xoay, hãy kiểm tra để đảm bảo rằng bạn đã cung cấp một Mã bản đồ có chế độ nghiêng và xoay được bật trong apiOptions
và mapOptions
.
Bây giờ, tệp app.js
của bạn sẽ có dạng như sau:
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. Triển khai WebGLOverlayView
WebGLOverlayView
cho phép bạn truy cập trực tiếp vào cùng một ngữ cảnh kết xuất WebGL được dùng để kết xuất bản đồ cơ sở dạng vectơ. Điều này có nghĩa là bạn có thể kết xuất các đối tượng 2D và 3D ngay trên bản đồ bằng WebGL, cũng như các thư viện đồ hoạ phổ biến dựa trên WebGL.
WebGLOverlayView
cung cấp 5 lệnh gọi vào vòng đời của ngữ cảnh kết xuất WebGL của bản đồ mà bạn có thể sử dụng. Sau đây là nội dung mô tả ngắn gọn về từng hook và mục đích sử dụng của chúng:
onAdd()
: Được gọi khi lớp phủ được thêm vào bản đồ bằng cách gọisetMap
trên một thực thểWebGLOverlayView
. Đây là nơi bạn nên thực hiện mọi thao tác liên quan đến WebGL mà không yêu cầu quyền truy cập trực tiếp vào ngữ cảnh WebGL.onContextRestored()
: Được gọi khi ngữ cảnh WebGL có sẵn nhưng trước khi kết xuất bất kỳ nội dung nào. Đây là nơi bạn nên khởi tạo các đối tượng, liên kết trạng thái và làm mọi việc khác cần có quyền truy cập vào ngữ cảnh WebGL nhưng có thể được thực hiện bên ngoài lệnh gọionDraw()
. Điều này cho phép bạn thiết lập mọi thứ cần thiết mà không cần thêm chi phí phát sinh cho quá trình kết xuất thực tế của bản đồ (vốn đã sử dụng nhiều GPU).onDraw()
: Được gọi một lần cho mỗi khung hình khi WebGL bắt đầu kết xuất bản đồ và mọi thứ khác mà bạn đã yêu cầu. Bạn nên thực hiện ít thao tác nhất có thể trongonDraw()
để tránh gây ra vấn đề về hiệu suất khi kết xuất bản đồ.onContextLost()
: Được gọi khi ngữ cảnh kết xuất WebGL bị mất vì bất kỳ lý do nào.onRemove()
: Được gọi khi lớp phủ bị xoá khỏi bản đồ bằng cách gọisetMap(null)
trên một thực thểWebGLOverlayView
.
Trong bước này, bạn sẽ tạo một thực thể của WebGLOverlayView
và triển khai 3 trong số các lệnh gọi lại vòng đời của thực thể đó: onAdd
, onContextRestored
và onDraw
. Để giữ cho mọi thứ gọn gàng và dễ theo dõi, tất cả mã cho lớp phủ sẽ được xử lý trong hàm initWebGLOverlayView()
có trong mẫu khởi đầu của lớp học lập trình này.
- Tạo một thực thể
WebGLOverlayView()
.
Lớp phủ do Maps JS API cung cấp tronggoogle.maps.WebGLOverlayView
. Để bắt đầu, hãy tạo một phiên bản bằng cách thêm nội dung sau vàoinitWebGLOverlayView()
:const webGLOverlayView = new google.maps.WebGLOverlayView();
- Triển khai các lệnh gọi vòng đời.
Để triển khai các lệnh gọi lại vòng đời, hãy thêm nội dung sau vàoinitWebGLOverlayView()
:webGLOverlayView.onAdd = () => {}; webGLOverlayView.onContextRestored = ({gl}) => {}; webGLOverlayView.onDraw = ({gl, transformer}) => {};
- Thêm thực thể lớp phủ vào bản đồ.
Bây giờ, hãy gọisetMap()
trên thực thể lớp phủ và truyền bản đồ bằng cách thêm nội dung sau vàoinitWebGLOverlayView()
:webGLOverlayView.setMap(map)
- Gọi cho
initWebGLOverlayView
.
Bước cuối cùng là thực thiinitWebGLOverlayView()
bằng cách thêm nội dung sau vào hàm được gọi ngay ở cuốiapp.js
:initWebGLOverlayView(map);
Hàm initWebGLOverlayView
và hàm được gọi ngay lập tức của bạn giờ đây sẽ có dạng như sau:
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);
})();
Đó là tất cả những gì bạn cần làm để triển khai WebGLOverlayView
. Tiếp theo, bạn sẽ thiết lập mọi thứ cần thiết để kết xuất một đối tượng 3D trên bản đồ bằng Three.js.
5. Thiết lập cảnh three.js
Việc sử dụng WebGL có thể rất phức tạp vì bạn phải xác định tất cả các khía cạnh của mọi đối tượng theo cách thủ công và hơn thế nữa. Để mọi thứ trở nên dễ dàng hơn, trong lớp học lập trình này, bạn sẽ sử dụng Three.js, một thư viện đồ hoạ phổ biến cung cấp lớp trừu tượng đơn giản hoá trên WebGL. Three.js có nhiều hàm tiện lợi, thực hiện mọi thao tác từ tạo trình kết xuất WebGL đến vẽ các hình dạng đối tượng 2D và 3D phổ biến, kiểm soát camera, biến đổi đối tượng và nhiều thao tác khác.
Có 3 loại đối tượng cơ bản trong Three.js cần thiết để hiển thị mọi thứ:
- Cảnh: "Vùng chứa" nơi tất cả các đối tượng, nguồn sáng, hoạ tiết, v.v. được kết xuất và hiển thị.
- Camera: Camera đại diện cho điểm nhìn của cảnh. Có nhiều loại camera và bạn có thể thêm một hoặc nhiều camera vào một cảnh.
- Trình kết xuất: Trình kết xuất xử lý và hiển thị tất cả các đối tượng trong cảnh. Trong Three.js,
WebGLRenderer
là loại được dùng phổ biến nhất, nhưng một số loại khác có sẵn dưới dạng dự phòng trong trường hợp máy khách không hỗ trợ WebGL.
Trong bước này, bạn sẽ tải tất cả các phần phụ thuộc cần thiết cho Three.js và thiết lập một cảnh cơ bản.
- Tải three.js
Bạn sẽ cần 2 phần phụ thuộc cho lớp học lập trình này: thư viện Three.js và GLTF Loader, một lớp cho phép bạn tải các đối tượng 3D ở Định dạng truyền GL (gLTF). Three.js cung cấp các trình tải chuyên dụng cho nhiều định dạng đối tượng 3D khác nhau, nhưng bạn nên sử dụng gLTF.
Trong đoạn mã bên dưới, toàn bộ thư viện Three.js sẽ được nhập. Trong một ứng dụng phát hành công khai, có thể bạn chỉ muốn nhập những lớp cần thiết, nhưng đối với lớp học lập trình này, hãy nhập toàn bộ thư viện để mọi thứ đơn giản. Cũng xin lưu ý rằng GLTF Loader không có trong thư viện mặc định và cần được nhập từ một đường dẫn riêng trong phần phụ thuộc – đây là đường dẫn mà bạn có thể truy cập vào tất cả các trình tải do Three.js cung cấp.
Để nhập Three.js và GLTF Loader, hãy thêm nội dung sau vào đầuapp.js
:import * as THREE from 'three'; import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
- Tạo một cảnh three.js.
Để tạo một cảnh, hãy khởi tạo lớpScene
Three.js bằng cách thêm nội dung sau vào hookonAdd
:scene = new THREE.Scene();
- Thêm camera vào cảnh.
Như đã đề cập trước đó, camera thể hiện góc nhìn của cảnh và xác định cách Three.js xử lý việc kết xuất hình ảnh của các đối tượng trong một cảnh. Nếu không có camera, cảnh sẽ không được "nhìn thấy", tức là các đối tượng sẽ không xuất hiện vì chúng sẽ không được kết xuất.
Three.js cung cấp nhiều loại camera khác nhau, ảnh hưởng đến cách trình kết xuất xử lý các đối tượng liên quan đến những yếu tố như góc nhìn và độ sâu. Trong cảnh này, bạn sẽ sử dụngPerspectiveCamera
, loại camera thường dùng nhất trong Three.js, được thiết kế để mô phỏng cách mắt người cảm nhận cảnh. Điều này có nghĩa là các đối tượng ở xa máy ảnh sẽ xuất hiện nhỏ hơn các đối tượng ở gần, cảnh sẽ có một điểm tụ và nhiều điểm khác.
Để thêm camera phối cảnh vào cảnh, hãy thêm nội dung sau vào hookonAdd
: Vớicamera = new THREE.PerspectiveCamera();
PerspectiveCamera
, bạn cũng có thể định cấu hình các thuộc tính tạo nên điểm nhìn, bao gồm cả mặt phẳng gần và mặt phẳng xa, tỷ lệ khung hình và trường nhìn (fov). Nhìn chung, các thuộc tính này tạo nên cái gọi là hình nón cụt khi xem, đây là một khái niệm quan trọng cần hiểu khi làm việc trong không gian 3D, nhưng nằm ngoài phạm vi của lớp học lập trình này. Cấu hìnhPerspectiveCamera
mặc định sẽ đủ. - Thêm nguồn sáng vào cảnh.
Theo mặc định, các đối tượng được kết xuất trong cảnh Three.js sẽ có màu đen, bất kể bạn áp dụng hoạ tiết nào cho các đối tượng đó. Điều này là do cảnh Three.js bắt chước cách các đối tượng hoạt động trong thế giới thực, trong đó khả năng hiển thị màu sắc phụ thuộc vào ánh sáng phản chiếu từ một đối tượng. Tóm lại, không có ánh sáng thì không có màu sắc.
Three.js cung cấp nhiều loại đèn khác nhau, trong đó bạn sẽ sử dụng 2 loại: AmbientLight
: Cung cấp một nguồn sáng khuếch tán chiếu sáng đều tất cả các đối tượng trong cảnh từ mọi góc độ. Điều này sẽ cung cấp cho cảnh một lượng ánh sáng cơ bản để đảm bảo các hoạ tiết trên tất cả các đối tượng đều có thể nhìn thấy.DirectionalLight
: Cung cấp ánh sáng phát ra từ một hướng trong cảnh. Không giống như cách hoạt động của ánh sáng được định vị trong thế giới thực, các tia sáng phát ra từDirectionalLight
đều song song và không lan rộng cũng như khuếch tán khi chúng ở xa nguồn sáng hơn.
Bạn có thể định cấu hình màu sắc và cường độ của từng đèn để tạo hiệu ứng ánh sáng tổng hợp. Ví dụ: trong đoạn mã dưới đây, ánh sáng xung quanh cung cấp ánh sáng trắng dịu cho toàn bộ cảnh, trong khi ánh sáng định hướng cung cấp ánh sáng thứ cấp chiếu vào các đối tượng ở góc xuống. Trong trường hợp đèn định hướng, góc được đặt bằng cách sử dụngposition.set(x, y ,z)
, trong đó mỗi giá trị tương ứng với trục tương ứng. Ví dụ:position.set(0,1,0)
sẽ đặt đèn ngay phía trên cảnh trên trục y, hướng thẳng xuống.
Để thêm nguồn sáng vào khung cảnh, hãy thêm nội dung sau vào hookonAdd
: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);
Bây giờ, hook onAdd
của bạn sẽ có dạng như sau:
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);
}
Cảnh của bạn hiện đã được thiết lập và sẵn sàng kết xuất. Tiếp theo, bạn sẽ định cấu hình trình kết xuất WebGL và kết xuất cảnh.
6. Kết xuất cảnh
Đã đến lúc kết xuất cảnh của bạn. Cho đến thời điểm này, mọi thứ bạn đã tạo bằng Three.js đều được khởi tạo trong mã, nhưng về cơ bản là không tồn tại vì chưa được kết xuất vào ngữ cảnh kết xuất WebGL. WebGL kết xuất nội dung 2D và 3D trong trình duyệt bằng canvas API. Nếu đã từng sử dụng Canvas API, có lẽ bạn đã quen thuộc với context
của canvas HTML. Đây là nơi mọi thứ được kết xuất. Có thể bạn chưa biết rằng đây là một giao diện hiển thị ngữ cảnh kết xuất đồ hoạ OpenGL thông qua API WebGLRenderingContext
trong trình duyệt.
Để giúp bạn dễ dàng xử lý trình kết xuất WebGL, Three.js cung cấp WebGLRenderer
, một trình bao bọc giúp bạn tương đối dễ dàng định cấu hình ngữ cảnh kết xuất WebGL để Three.js có thể kết xuất các cảnh trong trình duyệt. Tuy nhiên, đối với bản đồ, chỉ kết xuất cảnh Three.js trong trình duyệt cùng với bản đồ là chưa đủ. Three.js phải kết xuất vào chính xác cùng một ngữ cảnh kết xuất như bản đồ, để cả bản đồ và mọi đối tượng trong cảnh Three.js đều được kết xuất vào cùng một không gian thế giới. Điều này giúp trình kết xuất xử lý các hoạt động tương tác giữa các đối tượng trên bản đồ và các đối tượng trong cảnh, chẳng hạn như sự che khuất. Đây là một cách nói hoa mỹ để chỉ việc một đối tượng sẽ che khuất các đối tượng phía sau khỏi tầm nhìn.
Nghe có vẻ khá phức tạp phải không? Thật may mắn là Three.js lại xuất hiện để giải cứu.
- Thiết lập trình kết xuất WebGL.
Khi tạo một thực thể mới của Three.jsWebGLRenderer
, bạn có thể cung cấp cho thực thể đó ngữ cảnh kết xuất WebGL cụ thể mà bạn muốn thực thể kết xuất cảnh của mình vào. Bạn có nhớ đối sốgl
được truyền vào lệnh gọionContextRestored
không? Đối tượnggl
đó là ngữ cảnh kết xuất WebGL của bản đồ. Bạn chỉ cần cung cấp ngữ cảnh, canvas và các thuộc tính của ngữ cảnh đó cho thực thểWebGLRenderer
. Tất cả các thuộc tính này đều có sẵn thông qua đối tượnggl
. Trong mã này, thuộc tínhautoClear
của trình kết xuất cũng được đặt thànhfalse
để trình kết xuất không xoá đầu ra của mình ở mỗi khung hình.
Để định cấu hình trình kết xuất, hãy thêm đoạn mã sau vào lệnh gọionContextRestored
:renderer = new THREE.WebGLRenderer({ canvas: gl.canvas, context: gl, ...gl.getContextAttributes(), }); renderer.autoClear = false;
- Kết xuất cảnh.
Sau khi bạn định cấu hình trình kết xuất, hãy gọirequestRedraw
trên thực thểWebGLOverlayView
để cho lớp phủ biết rằng cần vẽ lại khi khung hình tiếp theo kết xuất, sau đó gọirender
trên trình kết xuất và truyền cho trình kết xuất cảnh và camera Three.js để kết xuất. Cuối cùng, hãy xoá trạng thái của ngữ cảnh kết xuất WebGL. Đây là một bước quan trọng để tránh xung đột trạng thái GL, vì việc sử dụng WebGL Overlay View dựa trên trạng thái GL dùng chung. Nếu trạng thái không được đặt lại vào cuối mỗi lệnh gọi vẽ, thì các xung đột trạng thái GL có thể khiến trình kết xuất không hoạt động.
Để thực hiện việc này, hãy thêm nội dung sau vào lệnh gọionDraw
để lệnh gọi này được thực thi mỗi khung hình:webGLOverlayView.requestRedraw(); renderer.render(scene, camera); renderer.resetState();
Giờ đây, các hook onContextRestored
và onDraw
của bạn sẽ có dạng như sau:
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. Kết xuất mô hình 3D trên bản đồ
Được rồi, bạn đã có tất cả các phần cần thiết. Bạn đã thiết lập WebGl Overlay View và tạo một cảnh Three.js, nhưng có một vấn đề là không có gì trong cảnh đó. Vì vậy, tiếp theo, đã đến lúc kết xuất một đối tượng 3D trong cảnh. Để làm việc này, bạn sẽ sử dụng GLTF Loader mà bạn đã nhập trước đó.
Mô hình 3D có nhiều định dạng khác nhau, nhưng đối với Three.js, định dạng gLTF là định dạng được ưu tiên do kích thước và hiệu suất thời gian chạy của định dạng này. Trong lớp học lập trình này, một mô hình để bạn kết xuất trong cảnh đã được cung cấp cho bạn trong /src/pin.gltf
.
- Tạo một thực thể trình tải mô hình.
Thêm nội dung sau vàoonAdd
:loader = new GLTFLoader();
- Tải một mô hình 3D.
Trình tải mô hình là không đồng bộ và thực thi một lệnh gọi lại sau khi mô hình đã tải xong. Để tảipin.gltf
, hãy thêm nội dung sau vàoonAdd
:const source = "pin.gltf"; loader.load( source, gltf => {} );
- Thêm mô hình vào cảnh.
Giờ đây, bạn có thể thêm mô hình vào cảnh bằng cách thêm nội dung sau vào lệnh gọi lạiloader
. Xin lưu ý rằnggltf.scene
đang được thêm chứ không phảigltf
:scene.add(gltf.scene);
- Định cấu hình ma trận chiếu camera.
Điều cuối cùng bạn cần làm để mô hình hiển thị đúng cách trên bản đồ là đặt ma trận phép chiếu của camera trong cảnh Three.js. Ma trận phép chiếu được chỉ định dưới dạng mảngMatrix4
của Three.js, xác định một điểm trong không gian ba chiều cùng với các phép biến đổi, chẳng hạn như xoay, cắt, thu phóng, v.v.
Trong trường hợpWebGLOverlayView
, ma trận phép chiếu được dùng để cho trình kết xuất biết vị trí và cách kết xuất cảnh Three.js so với bản đồ cơ sở. Nhưng đã xảy ra một vấn đề. Vị trí trên bản đồ được chỉ định dưới dạng cặp toạ độ vĩ độ và kinh độ, trong khi vị trí trong cảnh Three.js là toạ độVector3
. Như bạn có thể đoán, việc tính toán lượt chuyển đổi giữa hai hệ thống này không hề đơn giản. Để giải quyết vấn đề này,WebGLOverlayView
sẽ truyền một đối tượngcoordinateTransformer
đến lệnh gọi lại vòng đờiOnDraw
chứa một hàm có tên làfromLatLngAltitude
.fromLatLngAltitude
lấy một đối tượngLatLngAltitude
hoặcLatLngAltitudeLiteral
và tuỳ ý lấy một tập hợp các đối số xác định một phép biến đổi cho cảnh, sau đó chuyển đổi các đối số đó thành ma trận phép chiếu chế độ xem mô hình (MVP) cho bạn. Bạn chỉ cần chỉ định vị trí trên bản đồ mà bạn muốn hiển thị cảnh Three.js, cũng như cách bạn muốn chuyển đổi cảnh đó, vàWebGLOverlayView
sẽ làm phần còn lại. Sau đó, bạn có thể chuyển đổi ma trận MVP thành một mảngMatrix4
Three.js và đặt ma trận chiếu camera thành mảng đó.
Trong mã bên dưới, đối số thứ hai yêu cầu WebGl Overlay View đặt độ cao của cảnh Three.js ở độ cao 120 mét so với mặt đất, điều này sẽ khiến mô hình xuất hiện ở trạng thái lơ lửng.
Để đặt ma trận chiếu camera, hãy thêm nội dung sau vào 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);
- Biến đổi mô hình.
Bạn sẽ nhận thấy rằng ghim không nằm vuông góc với bản đồ. Trong đồ hoạ 3D, ngoài không gian thế giới có các trục x, y và z riêng để xác định hướng, mỗi đối tượng cũng có không gian đối tượng riêng với một bộ trục độc lập.
Trong trường hợp mô hình này, mô hình không được tạo ra với những gì chúng ta thường coi là "đỉnh" của ghim hướng lên trục y, vì vậy, bạn cần biến đổi đối tượng để định hướng đối tượng theo cách mong muốn so với không gian thế giới bằng cách gọirotation.set
trên đối tượng đó. Xin lưu ý rằng trong Three.js, hướng xoay được chỉ định bằng radian chứ không phải độ. Thông thường, bạn sẽ dễ dàng suy nghĩ theo độ hơn, vì vậy, bạn cần chuyển đổi thích hợp bằng công thứcdegrees * Math.PI/180
.
Ngoài ra, mô hình này hơi nhỏ, vì vậy, bạn cũng sẽ chia tỷ lệ mô hình này một cách đồng đều trên tất cả các trục bằng cách gọiscale.set(x, y ,z)
.
Để xoay và điều chỉnh tỷ lệ mô hình, hãy thêm nội dung sau vào lệnh gọi lạiloader
củaonAdd
trướcscene.add(gltf.scene)
để thêm gLTF vào cảnh:gltf.scene.scale.set(25,25,25); gltf.scene.rotation.x = 180 * Math.PI/180;
Giờ đây, ghim sẽ nằm thẳng đứng so với bản đồ.
Giờ đây, các hook onAdd
và onDraw
của bạn sẽ có dạng như sau:
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();
}
Tiếp theo là ảnh động của camera!
8. Tạo ảnh động cho camera
Giờ đây, bạn đã kết xuất một mô hình trên bản đồ và có thể di chuyển mọi thứ theo không gian ba chiều. Việc tiếp theo mà bạn có thể muốn làm là kiểm soát chuyển động đó theo cách lập trình. Hàm moveCamera
cho phép bạn đặt đồng thời các thuộc tính tâm, mức thu phóng, độ nghiêng và hướng của bản đồ, giúp bạn kiểm soát chi tiết trải nghiệm người dùng. Ngoài ra, bạn có thể gọi moveCamera
trong một vòng lặp ảnh động để tạo hiệu ứng chuyển đổi mượt mà giữa các khung hình với tốc độ khung hình gần 60 khung hình/giây.
- Chờ mô hình tải xong.
Để tạo trải nghiệm liền mạch cho người dùng, bạn nên đợi cho đến khi mô hình gLTF được tải xong rồi mới bắt đầu di chuyển camera. Để thực hiện việc này, hãy thêm trình xử lý sự kiệnonLoad
của trình tải vào lệnh gọionContextRestored
:loader.manager.onLoad = () => {}
- Tạo một vòng lặp ảnh động.
Có nhiều cách để tạo một vòng lặp ảnh động, chẳng hạn như sử dụngsetInterval
hoặcrequestAnimationFrame
. Trong trường hợp này, bạn sẽ dùng hàmsetAnimationLoop
của trình kết xuất Three.js. Hàm này sẽ tự động gọi mọi mã mà bạn khai báo trong lệnh gọi lại mỗi khi Three.js kết xuất một khung hình mới. Để tạo vòng lặp ảnh động, hãy thêm nội dung sau vào trình xử lý sự kiệnonLoad
ở bước trước:renderer.setAnimationLoop(() => {});
- Đặt vị trí camera trong vòng lặp ảnh động.
Tiếp theo, hãy gọimoveCamera
để cập nhật bản đồ. Ở đây, các thuộc tính từ đối tượngmapOptions
được dùng để tải bản đồ sẽ được dùng để xác định vị trí của camera:map.moveCamera({ "tilt": mapOptions.tilt, "heading": mapOptions.heading, "zoom": mapOptions.zoom });
- Cập nhật camera ở mỗi khung hình.
Bước cuối cùng! Cập nhật đối tượngmapOptions
ở cuối mỗi khung hình để đặt vị trí camera cho khung hình tiếp theo. Trong đoạn mã này, câu lệnhif
được dùng để tăng độ nghiêng cho đến khi đạt được giá trị nghiêng tối đa là 67,5, sau đó tiêu đề sẽ thay đổi một chút ở mỗi khung hình cho đến khi camera hoàn thành vòng xoay 360 độ. Sau khi ảnh động mong muốn hoàn tất,null
sẽ được truyền đếnsetAnimationLoop
để huỷ ảnh động, nhờ đó ảnh động sẽ không chạy mãi mãi.if (mapOptions.tilt < 67.5) { mapOptions.tilt += 0.5 } else if (mapOptions.heading <= 360) { mapOptions.heading += 0.2; } else { renderer.setAnimationLoop(null) }
Bây giờ, hook onContextRestored
của bạn sẽ có dạng như sau:
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. Xin chúc mừng
Nếu mọi thứ diễn ra theo đúng kế hoạch, thì giờ đây bạn sẽ có một bản đồ có một ghim 3D lớn trông như thế này:
Kiến thức bạn học được
Trong lớp học lập trình này, bạn đã học được rất nhiều điều; sau đây là những điểm nổi bật:
- Triển khai
WebGLOverlayView
và các phương thức móc vòng đời của nó. - Tích hợp Three.js vào bản đồ.
- Kiến thức cơ bản về cách tạo cảnh Three.js, bao gồm cả camera và ánh sáng.
- Tải và thao tác với các mô hình 3D bằng Three.js.
- Điều khiển và tạo ảnh động cho camera trên bản đồ bằng cách sử dụng
moveCamera
.
Tiếp theo là gì?
WebGL và đồ hoạ máy tính nói chung là một chủ đề phức tạp, vì vậy bạn luôn có nhiều điều để học. Dưới đây là một số tài nguyên giúp bạn bắt đầu:
- Tài liệu về Chế độ xem lớp phủ WebGL
- Bắt đầu sử dụng WebGL.
- Tài liệu về Three.js
- Hãy giúp chúng tôi tạo ra nội dung hữu ích nhất cho bạn bằng cách trả lời câu hỏi bên dưới: "codelabs/maps-platform/shared/_next-lab-survey.lab.md" Lớp học lập trình bạn muốn không có trong danh sách trên? Yêu cầu cấp lại bằng cách báo lỗi mới tại đây.