Xây dựng trải nghiệm bản đồ 3D bằng Chế độ xem lớp phủ WebGL

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 do WebGL hỗ trợ của API JavaScript của Maps để kiểm soát và hiển thị trên bản đồ vectơ theo ba chiều.

Ghim 3D cuối cùng

Đ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 trung gian về JavaScript và API JavaScript của Maps. Để tìm hiểu cơ bản về việc sử dụng API Maps JS, hãy thử Lớp học lập trình thêm bản đồ vào trang web (JavaScript) của bạn.

Kiến thức bạn sẽ học được

  • Đang tạo mã bản đồ có bản đồ vectơ cho JavaScript đã bật.
  • Kiểm soát bản đồ bằng nghiêng và xoay có lập trình.
  • Hiển thị các đối tượng 3D trên bản đồ bằng WebGLOverlayViewBa.js.
  • Đang chuyển động máy ảnh bằng moveCamera.

Bạn cần có

  • Tài khoản Google Cloud Platform đã bật tính năng thanh toán
  • Khóa API Google Maps Platform có API JavaScript của Maps được bật
  • Kiến thức trung gian về JavaScript, HTML và CSS
  • Trình chỉnh sửa văn bản hoặc IDE mà bạn chọn
  • Nút.js

2. Bắt đầu thiết lập

Với bước bật bên dưới, bạn cần bật API JavaScript của Maps.

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à một dự án đã 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à một dự án.

  1. Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án và chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.

  1. Bật API và SDK của Nền tảng Google Maps bắt buộc 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.
  2. 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 Google Maps Platform đều yêu cầu khóa 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 xuống và cài đặt thời gian chạy Node.js trên máy tính của mình.

Node.js đi kèm với trình quản lý gói npm mà 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 dành cho người mới bắt đầu dự án

Trước khi bạn bắt đầu tham gia lớp học lập trình này, hãy làm như sau để tải mẫu dự án dành cho người mới bắt đầu xuống, cũng như mã giải pháp hoàn chỉnh:

  1. Tải xuống hoặc phân phối 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-ron/. Dự án dành cho người mới bắt đầ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 tất lớp học lập trình. Mọi công việc bạn cần làm đều nằm trong thư mục /starter/src.
  2. Sau khi tải dự án dành cho người mới bắt đầ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 liệt kê trong package.json.
  3. Sau khi cài đặt các phần phụ thuộc, hãy chạy npm start trong thư mục.

Dự án dành cho người mới bắt đầu đã được thiết lập để bạn sử dụng máy chủ webpack-dev. Máy chủ này sẽ biên dịch và chạy 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 kỳ khi nào bạn thực hiện thay đổi mã.

Nếu muốn xem mã giải pháp đầ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 khóa API của bạn

Ứng dụng dành cho người mới bắt đầu bao gồm tất cả mã cần thiết để tải bản đồ bằng Trình tải API JS, để bạn chỉ cần cung cấp khóa API và mã bản đồ của mình. Trình tải API JS là một thư viện đơn giản, trừu tượng hóa phương pháp truyền thống để tải API Maps JS trực tiếp trong mẫu HTML với 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 dành cho người mới bắt đầu:

  1. Mở app.js.
  2. Trong đối tượng apiOptions, hãy đặt khoá API của bạn làm giá trị của apiOptions.apiKey.

3. Tạo và sử dụng ID bản đồ

Để sử dụng các tính năng dựa trên WebGL của API JavaScript của Maps, bạn cần có ID bản đồ với bản đồ vectơ đã được bật.

Đang tạo mã bản đồ

Tạo mã bản đồ

  1. Trong Google Cloud Console, hãy chuyển đến "Google Maps Platform#39; > "Quản lý bản đồ\39".
  2. Nhấp vào "TẠO BẢN ĐỒ MỚI".
  3. Trong trường "Tên bản đồ\39;", hãy nhập tên cho Mã bản đồ của bạn.
  4. Trong trình đơn thả xuống ‘Loại bản đồ#39;, hãy chọn ‘JavaScript#39;. "Lựa chọn JavaScript" sẽ xuất hiện.
  5. Trong phần "Tùy chọn JavaScript#39", hãy chọn nút "Các yêu cầu hỗ trợ" để thao tác, nút chọn, "Nghiêng\39"; và hộp đánh dấu "Xoay vòng#39";
  6. Optional . Trong trường "Mô tả\39"; hãy nhập thông tin mô tả cho khóa API của bạn.
  7. Nhấp vào nút "Tiếp theo\39"; Trang "Thông tin chi tiết về mã bản đồ" sẽ xuất hiện.

    Trang Chi tiết bản đồ
  8. Sao chép mã bản đồ. Bạn sẽ sử dụng công 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 ID bản đồ dưới dạng thuộc tính trong các tùy chọn khi 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 ID bản đồ, hãy làm như sau:

  1. Đặt mã bản đồ làm giá trị của mapOptions.mapId.

    Cung cấp mã bản đồ khi bạn tạo bản đồ cho Google Maps biết nên tải bản đồ nào của bạn cho một phiên bản cụ thể. Bạn có thể dùng lại một Mã bản đồ cho nhiều ứng dụng hoặc nhiều chế độ xem 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 của bạn. Bạn nên tải thành công bản đồ vectơ có bật chế độ nghiêng và xoay. Để kiểm tra xem chế độ nghiêng và xoay có được bật hay không, hãy giữ phím Shift và kéo bằng chuột hoặc sử 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 rằng bạn đã cung cấp khóa API hợp lệ trong apiOptions. Nếu bản đồ không nghiêng và xoay, hãy kiểm tra để chắc chắn rằng bạn đã cung cấp mã bản đồ có nghiêng và xoay trong apiOptionsmapOptions.

Bản đồ được nghiêng

Tệp app.js của bạn hiệ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 bối cảnh kết xuất WebGL được dùng để hiển thị sơ đồ cơ sở vectơ. Điều này có nghĩa là bạn có thể hiển thị các đối tượng 2D và 3D trực tiếp trên bản đồ bằng WebGL, cũng như các thư viện đồ họa dựa trên WebGL phổ biến.

WebGLOverlayView hiển thị 5 nội dung hấp dẫn trong vòng đời của bối cảnh kết xuất WebGL của bản đồ mà bạn có thể dùng. Dưới đây là nội dung mô tả ngắn gọn về từng móc câu và nội dung bạn nên làm theo:

  • onAdd(): Được gọi khi lớp phủ được thêm vào bản đồ bằng cách gọi setMap trên bản sao WebGLOverlayView. Đây là nơi bạn nên thực hiện bất kỳ công việc nào liên quan đến WebGL 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 bất kỳ nội dung nào hiển thị. Đây là nơi bạn sẽ khởi tạo đối tượng, trạng thái liên kết và làm bất cứ việc gì khác cần quyền truy cập vào ngữ cảnh WebGL nhưng có thể thực hiện bên ngoài lệnh gọi onDraw(). Điều này cho phép bạn thiết lập mọi thứ bạn cần mà không phải tốn thêm chi phí vào việc kết xuất thực tế của bản đồ, vốn đã tốn nhiều GPU.
  • onDraw(): Được gọi một lần trên mỗi khung sau khi WebGL bắt đầu hiển thị bản đồ và mọi nội dung khác mà bạn đã yêu cầu. Bạn nên làm ít tác vụ nhất có thể trong onDraw() để tránh gây ra sự cố hiệu suất trong hiển thị bản đồ.
  • onContextLost(): Được gọi khi ngữ cảnh kết xuất WebGL bị mất vì bất kỳ lý do gì.
  • onRemove(): Được gọi khi lớp phủ bị xóa khỏi bản đồ bằng cách gọi setMap(null) trên một phiên bản WebGLOverlayView.

Trong bước này, bạn sẽ tạo một bản sao của WebGLOverlayView và triển khai 3 móc vòng đời: onAdd, onContextRestoredonDraw. Để giữ cho mọi thứ sạch sẽ và dễ theo dõi hơn, tất cả mã cho lớp phủ sẽ được xử lý trong hàm initWebGLOverlayView() được cung cấp trong mẫu dành cho người mới bắt đầu cho lớp học lập trình này.

  1. Tạo một phiên bản WebGLOverlayView().

    Lớp phủ do Maps JS API cung cấp trong google.maps.WebGLOverlayView. Để bắt đầu, hãy tạo một thực thể bằng cách sử dụng những giá trị sau cho initWebGLOverlayView():
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. Triển khai các móc treo trong vòng đời.

    Để triển khai các móc vòng đời, hãy thêm nội dung sau vào initWebGLOverlayView():
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};
    
  3. Thêm thực thể lớp phủ vào bản đồ.

    Bây giờ, hãy gọi setMap() trên thực thể lớp phủ và chuyển vào bản đồ bằng cách thêm nội dung sau vào initWebGLOverlayView():
    webGLOverlayView.setMap(map)
    
  4. Gọi cho initWebGLOverlayView.

    Bước cuối cùng là thực thi initWebGLOverlayView() bằng cách thêm phần sau vào hàm được gọi ngay lập tức ở cuối app.js:
    initWebGLOverlayView(map);
    

Hàm initWebGLOverlayView và hàm được gọi ngay lập tức 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, coordinateTransformer}) => {}
      webGLOverlayView.setMap(map);
    }

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

Đó là tất cả những gì bạn cần để triển khai WebGLOverlayView. Tiếp theo, bạn sẽ thiết lập mọi thứ bạn cần để hiển thị đối tượng 3D trên bản đồ bằng cách sử dụng Triple.js.

5. Thiết lập cảnh ba.js

Việc sử dụng WebGL có thể rất phức tạp vì nó yêu cầu 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 rồi sau đó là một số đối tượng. Để làm cho mọi thứ dễ dàng hơn nhiều, đối với lớp học lập trình này, bạn sẽ sử dụng Triple.js, một thư viện đồ họa phổ biến cung cấp một lớp trừu tượng đơn giản ở trên của WebGL. Triple.js đi kèm với nhiều chức năng tiện lợi giúp làm mọi thứ, từ việc tạo trình kết xuất WebGL cho đến việc vẽ các hình dạng đối tượng 2D và 3D phổ biến, điều khiển máy ảnh, tính năng biến đổi đối tượng và nhiều chức năng khác.

Có ba loại đối tượng cơ bản trong Triple.js cần thiết để hiển thị bất kỳ loại nào:

  • Cảnh: A "container" trong đó tất cả các đối tượng, nguồn sáng, họa tiết, v.v. đều hiển thị và hiển thị.
  • Máy ảnh: Máy ảnh đại diện cho điểm quan sát của cảnh. Bạn có thể sử dụng nhiều loại máy ảnh và có thể thêm một hoặc nhiều máy ảnh vào một cảnh.
  • Trình kết xuất: Trình kết xuất xử lý và xử lý tất cả các đối tượng trong cảnh. TrongBa.js, WebGLRenderer là tham số phổ biến nhất, nhưng một vài tham số khác cũng có sẵn dưới dạng dự phòng trong trường hợp ứng dụng 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 Triple.js và thiết lập một cảnh cơ bản.

  1. Tải ba tệp.js

    Bạn sẽ cần có hai phần phụ thuộc cho lớp học lập trình này: thư viện Triple.js và trình tải GLTF, một lớp cho phép bạn tải các đối tượng 3D trong Định dạng truyền tải GL (gLTF). Triple.js cung cấp các trình tải chuyên dụng cho nhiều định dạng đối tượng 3D nhưng bạn nên dùng gLTF.

    Trong mã bên dưới, toàn bộ thư viện Triple.js đã được nhập. Trong một ứng dụng thực tế, bạn có thể chỉ cần nhập các lớp mình cần, 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 để đơn giản hóa mọi việc. Cũng xin lưu ý rằng Trình tải GLTF không có trong thư viện mặc định và cần được nhập từ một đường dẫn riêng biệt 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 Triple.js cung cấp.

    Để nhập Triple.js và GLTFLoader, hãy thêm phần sau vào đầu app.js:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. Tạo cảnh ba.js.

    Để tạo cảnh, hãy tạo bản sao của lớp Scene.js bằng cách thêm đoạn mã sau vào móc onAdd:
    scene = new THREE.Scene();
    
  3. Thêm camera vào cảnh.

    Như đã đề cập trước đó, máy ảnh thể hiện góc nhìn của cảnh và xác định cách Triple.js xử lý quá trình kết xuất trực quan các đối tượng trong một cảnh. Nếu không có máy ảnh, cảnh quay sẽ không hiển thị & quot;seen" có nghĩa là các đối tượng sẽ không xuất hiện vì không hiển thị lại.

    Triple.js cung cấp nhiều loại máy ảnh khác nhau giúp tác độ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ư phối cảnh và chiều sâu. Trong cảnh này, bạn sẽ sử dụng PerspectiveCamera, loại máy ảnh thường được sử dụng nhất trong Triple.js, loại camera này được thiết kế để mô phỏng cách mọi người nhìn 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 vật thể ở gần hơn, cảnh đó sẽ có điểm biến mất, v.v.

    Để thêm máy quay phối cảnh vào cảnh, hãy ghép nối những điều sau vào móc onAdd:
    camera = new THREE.PerspectiveCamera();
    
    Với 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áy bay gần và xa, tỷ lệ khung hình và trường nhìn (fov). Những thuộc tính này tạo nên những thuộc tính được gọi là niềm tin, một khái niệm quan trọng để thực hiện khi làm việc ở chế độ 3D, nhưng ngoài phạm vi của lớp học lập trình này. Cấu hình PerspectiveCamera mặc định là đủ.
  4. Thêm nguồn sáng vào cảnh.

    Theo mặc định, các đối tượng hiển thị trong cảnh Triple.js sẽ có màu đen, bất kể cấu trúc áp dụng cho đối tượng đó là gì. Điều này là do cảnh Triple.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 phụ thuộc vào ánh sáng phản chiếu một đối tượng. Tóm lại, không sáng, không có màu.

    Triple.js cung cấp những loại đèn khác nhau mà bạn sẽ sử dụng 2 loại:

  5. AmbientLight: Cung cấp một nguồn ánh sáng khuếch tán giúp chiếu sáng đồng đều tất cả các vật thể trong khuôn mặt từ mọi góc độ. Điều này sẽ cung cấp cho cảnh đủ ánh sáng cơ bản để đảm bảo rằng các họa tiết trên tất cả các đối tượng đều hiển thị.
  6. DirectionalLight: Cung cấp ánh sáng bắt nguồn từ một hướng trong cảnh. Không giống như cách ánh sáng định vị hoạt động trong thế giới thực, các tia sáng phát ra từ DirectionalLight đều song song và không lan truyền và khuếch tán khi chúng đến xa nguồn sáng hơn.

    Bạn có thể thiết lập màu sắc và cường độ của mỗi đèn để tạo hiệu ứng ánh sáng tổng hợp. Ví dụ: trong mã dưới đây, ánh sáng xung quanh cung cấp ánh sáng trắng mềm cho toàn bộ cảnh trong khi ánh sáng hướng cung cấp ánh sáng phụ đánh vào đố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ụng position.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 ánh sáng ngay phía trên cảnh trên trục y hướng thẳng xuống dưới.

    Để thêm các nguồn sáng vào cảnh, hãy thêm phần sau vào móc 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);
    

Móc onAdd 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 hiển thị. Tiếp theo, bạn sẽ định cấu hình trình kết xuất WebGL và hiển thị cảnh.

6. Hiển thị 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 Triple.js được khởi tạo trong mã, nhưng về cơ bản không tồn tại vì chưa được hiển thị vào ngữ cảnh kết xuất WebGL. WebGL hiển thị nội dung 2D và 3D trong trình duyệt bằng cách sử dụng API canvas. Nếu trước đây bạn đã sử dụng API Canvas, thì có thể bạn đã quen thuộc với context của canvas canvas. Đây là nơi hiển thị mọi nội dung. Điều mà bạn có thể không biết là đây là giao diện hiển thị ngữ cảnh kết xuất đồ họa OpenGL thông qua API WebGLRenderingContext trong trình duyệt.

Để giúp xử lý trình kết xuất WebGL dễ dàng hơn, Triple.js cung cấp WebGLRenderer, một trình bao bọc giúp việc định cấu hình ngữ cảnh kết xuất WebGL trở nên tương đối dễ dàng để Triple.js có thể hiển thị các cảnh trong trình duyệt. Tuy nhiên, đối với bản đồ, việc chỉ hiển thị cảnh Triple.js trong trình duyệt không đủ để so khớp. Three.js phải hiển thị trong cùng ngữ cảnh hiển thị giống như bản đồ, để cả bản đồ và bất kỳ đối tượng nào từ cảnh Triple.js đều được hiển thị trong cùng một không gian thế giới. Nhờ đó, trình kết xuất có thể 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ư che kín, là cách thức ưu tiên để nói về một đối tượng sẽ ẩn đối tượng phía sau chế độ xem đó.

Nghe có vẻ phức tạp đấy, phải không? Thật may là Ba.js lại giải cứu.

  1. Thiết lập trình kết xuất WebGL.

    Khi tạo một phiên bản mới củaBa.js, bạn có thể cung cấp ngữ cảnh kết xuất WebGL cụ thể mà bạn muốn hiển thị cảnh của mình. Bạn có nhớ đối số gl được chuyển vào móc onContextRestored? Đối tượng gl đó là ngữ cảnh kết xuất WebGL của bản đồ. Tất cả những gì bạn cần làm là cung cấp bối cảnh, canvas và các thuộc tính của đối tượng đó cho phiên bản WebGLRenderer. Tất cả các thuộc tính này đều được cung cấp thông qua đối tượng gl. Trong mã này, thuộc tính autoClear của trình kết xuất cũng được đặt thành false để trình kết xuất không xóa dữ liệu đầu ra ra khỏi mỗi khung.

    Để định cấu hình trình kết xuất, hãy thêm nội dung sau vào móc onContextRestored:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. Hiển thị cảnh.

    Sau khi trình kết xuất được định cấu hình, hãy gọi requestRedraw trên bản sao WebGLOverlayView để thông báo lớp phủ rằng cần có lệnh vẽ lại khi khung hình tiếp theo kết xuất, sau đó gọi render trên trình kết xuất và chuyển lớp kết xuất ba và máy ảnh để kết xuất. Cuối cùng, hãy xóa trạng thái của ngữ cảnh kết xuất WebGL. Đây là bước quan trọng để tránh xung đột trạng thái GL, vì việc sử dụng Chế độ xem lớp phủ WebGL sẽ phụ thuộc vào trạng thái GL được chia sẻ. Nếu trạng thái này không được đặt lại khi kết thúc mỗi lệnh gọi vẽ, thì xung đột trạng thái GL có thể khiến trình kết xuất không thành công.

    Để thực hiện việc này, hãy nối đoạn mã sau vào móc onDraw để thực thi từng khung:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

Các móc onContextRestoredonDraw của bạn giờ đây 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. Hiển thị mô hình 3D trên bản đồ

Được rồi, bạn đã chuẩn bị sẵn mọi thứ. Bạn đã thiết lập Chế độ xem lớp phủ WebGl và tạo một cảnh Ba.js, nhưng có một vấn đề: không có gì trong đó. Vì vậy, tiếp theo, đã đến lúc hiển thị đối tượng 3D trong cảnh. Để thực hiện việc này, bạn sẽ sử dụng Trình tải GLTF mà bạn đã nhập trước đó.

Mô hình 3D có nhiều định dạng, nhưng đối với Triple.js, định dạng gLTF là định dạng được ưu tiên do kích thước và hiệu suất của thời gian chạy. Trong lớp học lập trình này, một mô hình để bạn hiển thị trong cảnh đã được cung cấp cho bạn trong /src/pin.gltf.

  1. Tạo một thực thể trình tải mô hình.

    Thêm thông tin sau vào onAdd:
    loader = new GLTFLoader();
    
  2. Tải mô hình 3D.

    Trình tải mô hình không đồng bộ và thực thi lệnh gọi lại sau khi mô hình đã tải đầy đủ. Để tải pin.gltf, hãy thêm phần sau vào onAdd:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. Thêm mô hình vào cảnh.

    Bây giờ, bạn có thể thêm mô hình vào cảnh bằng cách thêm đoạn mã sau vào lệnh gọi lại loader. Xin lưu ý rằng chúng tôi đang thêm gltf.scene, chứ không phải gltf:
    scene.add(gltf.scene);
    
  4. Định cấu hình ma trận chiếu.

    Điều cuối cùng bạn cần làm để mô hình hiển thị chính xác trên bản đồ là đặt ma trận chiếu của máy ảnh trong cảnh Triple.js. Ma trận chiếu được chỉ định dưới dạng mảng Triple.js Matrix4, mảng này 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ư chuyển động, góc cắt, tỷ lệ và các phép biến đổi khác.

    Trong trường hợp của WebGLOverlayView, ma trận chiếu dùng để thông báo cho trình kết xuất vị trí và cách hiển thị cảnh Triple.js so với sơ đồ cơ sở. Nhưng có một vấn đề. Các vị trí trên bản đồ được chỉ định là cặp tọa độ vĩ độ và kinh độ, trong khi những vị trí trong cảnh Triple.js là tọa độ Vector3. Như bạn có thể đã dự đoán, việc tính toán chuyển đổi giữa hai hệ thống không phải là điều bình thường. Để giải quyết vấn đề này, WebGLOverlayView chuyển một đối tượng coordinateTransformer đến móc móc vòng đời OnDraw có chứa một hàm gọi là fromLatLngAltitude. fromLatLngAltitude lấy đối tượng LatLngAltitude hoặc LatLngAltitudeLiteral và (không bắt buộc) tập hợp các đối số xác định một phép biến đổi cho cảnh, sau đó bao gồm một đối số của ma trận chiếu chế độ xem mô hình (MVP) cho bạn. Tất cả những gì bạn phải làm là chỉ định vị trí trên bản đồ mà bạn muốn cảnh Triple.js hiển thị, cũng như cách bạn muốn biến đổi, 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ảng Matrix4.js và đặt ma trận chiếu máy ảnh thành mảng.

    Trong mã bên dưới, đối số thứ hai yêu cầu Chế độ xem lớp phủ WebGl đặt độ cao của cảnh Triple.js trên mặt đất 120 m, sẽ làm cho mô hình này trông giống như số thực.

    Để đặt ma trận chiếu trên máy ảnh, hãy thêm thông tin sau vào móc 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. Chuyể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 đồ họa 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 của mô hình này, nó không được tạo bằng mô-đun mà chúng ta thường xem là "trên cùng#39"; của ghim hướng lên trục y, vì vậy, bạn cần phải biến đổi đối tượng theo hướng mong muốn so với không gian thế giới bằng cách gọi rotation.set trên đối tượng. Xin lưu ý rằng trong Triple.js, xoay được chỉ định bằng radian, không phải độ. Thường thì người dùng sẽ dễ suy nghĩ hơn về độ, vì vậy, bạn cần thực hiện lượt chuyển đổi phù hợp bằng công thức degrees * Math.PI/180.

    Ngoài ra, mô hình hơi nhỏ nên bạn cũng sẽ mở rộng mô hình này đồng đều trên tất cả các trục bằng cách gọi scale.set(x, y ,z).

    Để xoay và mở rộng quy mô mô hình, hãy thêm nội dung sau vào lệnh gọi lại loader của onAdd trước scene.add(gltf.scene), sau đó, thêm gLTF vào cảnh:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

Lúc này, ghim đã đứng thẳng so với bản đồ.

Ghim thẳng đứng

Các móc onAddonDraw của bạn giờ đây 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. Hoạt hình máy ảnh

Giờ đây, bạn đã kết xuất được một mô hình trên bản đồ và có thể di chuyển mọi thứ ba chiều, điều tiếp theo bạn có thể muốn làm là kiểm soát chuyển động đó theo phương thức lập trình. Chức năng moveCamera cho phép bạn đặt đồng thời thuộc tính tâm, thu phóng, nghiêng và tiêu đề của bản đồ, cho phép bạn kiểm soát chi tiết trải nghiệm người dùng. Ngoài ra, moveCamera có thể được gọi trong vòng lặp ảnh động để tạo chuyển đổi linh hoạt giữa các khung hình ở tốc độ khung hình gần 60 khung hình/giây.

  1. Chờ cho mô hình tải.

    Để mang lại trải nghiệm liền mạch cho người dùng, bạn nên đợi cho đến khi bắt đầu di chuyển máy ảnh cho đến khi mô hình gLTF được tải. Để thực hiện việc này, hãy thêm trình xử lý sự kiện onLoad của trình tải vào lệnh móc onContextRestored:
    loader.manager.onLoad = () => {}
    
  2. Tạo vòng lặp ảnh động.

    Có nhiều cách để tạo vòng lặp ảnh động, chẳng hạn như sử dụng setInterval hoặc requestAnimationFrame. Trong trường hợp này, bạn sẽ dùng hàm setAnimationLoop của trình kết xuất Ba.js. Hàm này sẽ tự động gọi mọi mã bạn khai báo trong lệnh gọi lại mỗi khi nhóm 3.js hiển thị một khung mới. Để tạo vòng lặp ảnh động, hãy thêm đoạn mã sau vào trình xử lý sự kiện onLoad ở bước trước:
    renderer.setAnimationLoop(() => {});
    
  3. Đặt vị trí của máy ảnh trong vòng lặp ảnh động.

    Tiếp theo, hãy gọi moveCamera để cập nhật bản đồ. Tại đây, các thuộc tính của đối tượng mapOptions dùng để tải bản đồ sẽ được dùng để xác định vị trí của máy ảnh:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. Cập nhật camera cho mỗi khung hình.

    Bước cuối cùng! Hãy cập nhật đối tượng mapOptions ở cuối mỗi khung hình để đặt vị trí máy ảnh cho khung tiếp theo. Trong mã này, câu lệnh if được dùng để tăng độ nghiêng cho đến khi đạt đến giá trị nghiêng tối đa là 67,5, sau đó tiêu đề sẽ thay đổi một chút cho đến khi máy ảnh hoàn tất toàn bộ xoay 360 độ. Sau khi ảnh động mong muốn hoàn tất, null sẽ được chuyển đến setAnimationLoop để hủy ảnh động nên sẽ không chạy vĩnh viễn.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

Móc onContextRestored 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ó ghim 3D lớn trông giống như sau:

Ghim 3D cuối cùng

Những điều 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 thứ; dưới đây là các điểm nổi bật:

  • Triển khai WebGLOverlayView và các vòng đời của vòng đời.
  • Tích hợp Triple.js vào bản đồ.
  • Kiến thức cơ bản để tạo cảnh Triple.js, bao gồm cả máy ảnh và ánh sáng.
  • Tải và thao tác với các mô hình 3D bằng Triple.js.
  • Điều khiển và tạo hiệu ứng máy ảnh cho bản đồ bằng cách sử dụng moveCamera.

Nội dung tiếp theo là gì?

Nhìn chung, WebGL và đồ họa máy tính là một chủ đề phức tạp, vì vậy, bạn luôn phải học hỏi rất nhiều. Dưới đây là một số tài nguyên để giúp bạn bắt đầu: