Xây dựng những kỳ quan thế giới – mô hình 3D

Ilmari Heikkinen

Giới thiệu về hình ảnh địa cầu 3D của các Kỳ quan thế giới

Nếu đã xem trang web Google World Wonders mới ra mắt gần đây trên một trình duyệt hỗ trợ WebGL, bạn có thể đã bắt gặp một quả cầu xoay lạ mắt ở phía dưới cùng màn hình. Bài viết này cho bạn biết cách hoạt động của hình ảnh địa cầu và những gì chúng tôi sử dụng để xây dựng hình ảnh địa cầu.

Để giúp bạn có cái nhìn tổng quan nhanh chóng, World Wonders là một phiên bản được chỉnh sửa kỹ lưỡng của WebGL Globe của Nhóm Google Data Arts. Chúng tôi đã lấy quả địa cầu ban đầu, loại bỏ các bit biểu đồ thanh, thay đổi chương trình đổ bóng, thêm điểm đánh dấu HTML có thể nhấp vào và hình học lục địa Tự nhiên của Trái đất tự nhiên từ bản minh hoạ GlobeTweeter của Mozilla (công cụ lớn nhờ có Cedric Pinson!) Tất cả để tạo ra một hình ảnh động đẹp mắt phù hợp với bảng phối màu của trang web và thêm một lớp tinh vi hơn cho trang web.

Mục đích thiết kế của thế giới là phải có một bản đồ động trông đẹp mắt với các điểm đánh dấu có thể nhấp vào được đặt trên các Khu di sản thế giới. Vì vậy, tôi bắt đầu tìm một sản phẩm phù hợp. Điều đầu tiên nghĩ đến là WebGL Globe do Nhóm Google Data Arts xây dựng. Đó là một quả địa cầu trông thật ngầu. Bạn còn cần gì khác không?

Thiết lập WebGL Globe

Bước đầu tiên khi tạo tiện ích hình ảnh địa cầu là tải WebGL Globe xuống và thiết lập và chạy. WebGL Globe có trực tuyến tại Google Code, và rất đơn giản để tải xuống và chạy. Tải xuống và giải nén tệp zip, cd vào tệp đó và chạy máy chủ web cơ bản: python -m SimpleHTTPServer. (Xin lưu ý rằng theo mặc định, mã này không bật UTF-8; bạn có thể sử dụng cách này.) Bây giờ, nếu bạn di chuyển đến http://localhost:8000/globe/globe.html, bạn sẽ thấy WebGL Globe.

Với WebGL Globe được thiết lập và chạy, đã đến lúc bạn cắt bỏ tất cả những phần không cần thiết. Tôi đã chỉnh sửa HTML để tách các bit giao diện người dùng và xóa nội dung thiết lập biểu đồ thanh địa cầu khỏi hàm khởi chạy hình ảnh địa cầu. Vào cuối quá trình đó, tôi đã có một WebGL Globe rất đơn giản trên màn hình của mình. Bạn có thể xoay xung quanh và trông thật ngầu, nhưng chỉ vậy thôi.

Để cắt bỏ những nội dung không cần thiết, tôi đã xoá tất cả các phần tử trên giao diện người dùng khỏi index.html của quả địa cầu và chỉnh sửa tập lệnh khởi chạy thành như sau:

if(!Detector.webgl){
  Detector.addGetWebGLMessage();
} else {
  var container = document.getElementById('container');
  var globe = new DAT.Globe(container);
  globe.animate();
}

Thêm hình học lục địa

Chúng tôi muốn đặt máy ảnh ở gần bề mặt hình ảnh địa cầu, nhưng khi chúng tôi thử nghiệm hình ảnh địa cầu, việc thiếu độ phân giải của hoạ tiết trở nên rõ ràng. Khi phóng to, hoạ tiết của WebGL Globe trở nên hình khối và mờ. Chúng tôi có thể sử dụng một hình ảnh lớn hơn, nhưng điều đó sẽ làm cho việc tải xuống và chạy hình ảnh địa cầu chậm hơn, vì vậy chúng tôi đã chọn sử dụng biểu diễn vectơ về các vùng đất và đường viền.

Đối với hình học của vùng đất, tôi đã chuyển sang bản minh hoạ GlobeTweeter nguồn mở và tải mô hình 3D vào Three.js. Sau khi mô hình được tải và kết xuất, đã đến lúc bắt đầu hoàn thiện diện mạo của hình ảnh địa cầu. Vấn đề đầu tiên là mô hình vùng đất trên địa cầu không đủ hình cầu để lấp đầy với WebGL Globe, vì vậy tôi đã quyết định viết một thuật toán chia lưới nhanh giúp mô hình vùng đất hình cầu hơn.

Với mô hình vùng đất hình cầu, tôi có thể đặt nó cách bề mặt địa cầu một chút, tạo ra các lục địa trôi nổi được vẽ đường viền 2px màu đen bên dưới chúng để tạo ra bóng đổ. Tôi cũng thử nghiệm với các đường viền màu neon để tạo ra chúng trông giống như Tron.

Với hình ảnh địa cầu và các địa danh, tôi bắt đầu thử nghiệm các kiểu dáng khác nhau cho địa cầu. Khi muốn tạo ra giao diện đơn sắc tinh tế, tôi cảm thấy khó tin với một quả địa cầu thang màu xám và các khối đất. Ngoài các đường viền màu neon nêu trên, tôi đã thử chụp một quả địa cầu tối với các khối đất tối trên nền sáng, trông khá thú vị. Nhưng độ tương phản quá thấp nên không thể đọc được, và nó không phù hợp với cảm nhận của dự án, nên tôi đã loại bỏ nội dung đó.

Một suy nghĩ ban đầu khác của tôi về diện mạo địa cầu là làm cho nó trông giống như sứ tráng men. Tôi không thể thử dùng chương trình này vì tôi không quản lý được việc viết chương trình đổ bóng để có vẻ ngoài sứ (trình chỉnh sửa chất liệu trực quan sẽ rất tuyệt). Điều gần nhất tôi thử là quả địa cầu phát sáng trắng này với những hòn đất màu đen. Hình ảnh khá đẹp mắt nhưng độ tương phản quá cao. Và trông không đẹp lắm. Vậy là lại thêm một câu đố nữa nhé.

Các chương trình đổ bóng trong hình ảnh địa cầu đen trắng đang sử dụng một loại ánh sáng đèn nền khuếch tán giả. Độ sáng của hình ảnh địa cầu phụ thuộc vào khoảng cách của bề mặt bình thường so với mặt phẳng màn hình. Vì vậy, các pixel ở giữa hình ảnh địa cầu trỏ vào màn hình sẽ có màu tối và các pixel ở các cạnh của hình ảnh địa cầu sẽ sáng. Khi kết hợp với nền sáng, bạn sẽ thấy được hình ảnh địa cầu đang phản chiếu trên nền sáng khuếch tán, tạo nên vẻ sang trọng cho phòng trưng bày. Quả địa cầu màu đen cũng đang sử dụng hoạ tiết WebGL Globe làm bản đồ bóng, để các kệ lục địa (vùng nước nông) trông sáng bóng so với các phần khác của địa cầu.

Dưới đây là hình ảnh của chương trình đổ bóng đại dương cho quả địa cầu màu đen. Chương trình đổ bóng đỉnh (vertex) rất cơ bản và chương trình đổ bóng mảnh "trời ơi, trông hơi tợn chỉnh".

    'ocean' : {
      uniforms: {
        'texture': { type: 't', value: 0, texture: null }
      },
      vertexShader: [
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
          'vNormal = normalize( normalMatrix * normal );',
          'vUv = uv;',
        '}'
      ].join('\n'),
      fragmentShader: [
        'uniform sampler2D texture;',
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'vec3 diffuse = texture2D( texture, vUv ).xyz;',
          'float intensity = pow(1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0);',
          'float i = 0.8-pow(clamp(dot( vNormal, vec3( 0, 0, 1.0 )), 0.0, 1.0), 1.5);',
          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * intensity;',
          'float d = clamp(pow(max(0.0,(diffuse.r-0.062)*10.0), 2.0)*5.0, 0.0, 1.0);',
          'gl_FragColor = vec4( (d*vec3(i)) + ((1.0-d)*diffuse) + atmosphere, 1.0 );',
        '}'
      ].join('\n')
    }

Cuối cùng, chúng tôi đã chụp được một quả địa cầu tối với các khối đất màu xám nhạt chiếu sáng từ trên cao. Bản tóm tắt này gần giống nhất với bản tóm tắt thiết kế, trông đẹp và dễ đọc. Ngoài ra, việc hình ảnh địa cầu có độ tương phản thấp một chút sẽ làm cho điểm đánh dấu và phần còn lại của nội dung nổi bật hơn so với. Phiên bản dưới đây sử dụng các đại dương hoàn toàn màu đen, trong khi phiên bản sản xuất có các đại dương màu xám đậm và điểm đánh dấu hơi khác.

Tạo điểm đánh dấu bằng CSS

Nói về điểm đánh dấu, khi hình ảnh địa cầu và các khối địa cầu đang hoạt động, tôi đã bắt đầu công việc trên các dấu vị trí. Tôi quyết định sử dụng phần tử HTML kiểu CSS cho các điểm đánh dấu, để giúp dễ dàng tạo và tạo kiểu cho các điểm đánh dấu cũng như có thể sử dụng lại các điểm đánh dấu trong bản đồ 2D mà nhóm đang xử lý. Tại thời điểm này, tôi cũng không biết cách dễ dàng để tạo điểm đánh dấu WebGL có thể nhấp và không muốn viết thêm mã để tải / tạo mô hình điểm đánh dấu. Nhìn chung, các điểm đánh dấu CSS hoạt động tốt nhưng đôi khi có xu hướng gặp phải các vấn đề về hiệu suất khi trình tổng hợp và trình kết xuất trình duyệt đang trong giai đoạn thay đổi. Từ quan điểm hiệu suất, việc tạo điểm đánh dấu trong WebGL sẽ là một lựa chọn tốt hơn. Sau đó, một lần nữa, các điểm đánh dấu CSS đã giúp nhà phát triển tiết kiệm đáng kể thời gian.

Các điểm đánh dấu CSS bao gồm một vài div được đặt ở vị trí tuyệt đối với thuộc tính biến đổi CSS. Nền của điểm đánh dấu là độ dốc CSS và phần tam giác của điểm đánh dấu là một div xoay. Các điểm đánh dấu có bóng đổ nhỏ để làm nổi bật chúng khỏi nền. Vấn đề lớn nhất với điểm đánh dấu là làm cho chúng hoạt động đủ tốt. Nghe có vẻ buồn, việc vẽ vài chục div di chuyển xung quanh và thay đổi chỉ mục z của chúng trên mọi khung hình là một cách khá hay để gây ra tất cả các loại cạm bẫy kết xuất của trình duyệt.

Cách đồng bộ hoá các điểm đánh dấu với cảnh 3D không quá phức tạp. Mỗi điểm đánh dấu có một Object3D tương ứng trong cảnh Three.js, được dùng để theo dõi các điểm đánh dấu. Để có toạ độ không gian màn hình, tôi lấy ma trận Three.js cho hình ảnh địa cầu và điểm đánh dấu, rồi nhân một vectơ 0 với các ma trận đó. Từ đó, tôi tìm được vị trí cảnh của điểm đánh dấu. Để biết vị trí trên màn hình của điểm đánh dấu, tôi chiếu vị trí cảnh qua máy ảnh. Vectơ chiếu được thu được có toạ độ không gian màn hình cho điểm đánh dấu, sẵn sàng để sử dụng trong CSS.

var mat = new THREE.Matrix4();
var v = new THREE.Vector3();

for (var i=0; i<locations.length; i++) {
  mat.copy(scene.matrix);
  mat.multiplySelf(locations[i].point.matrix);
  v.set(0,0,0);
  mat.multiplyVector3(v);
  projector.projectVector(v, camera);
  var x = w * (v.x + 1) / 2; // Screen coords are between -1 .. 1, so we transform them to pixels.
  var y = h - h * (v.y + 1) / 2; // The y coordinate is flipped in WebGL.
  var z = v.z;
}

Cuối cùng, cách tiếp cận nhanh nhất là sử dụng các biến đổi CSS để di chuyển các điểm đánh dấu, không sử dụng tính năng làm mờ độ mờ vì nó đang kích hoạt một đường dẫn chậm trên Firefox và giữ tất cả các điểm đánh dấu trong DOM, không xoá chúng khi chúng đi ra sau hình ảnh địa cầu. Chúng tôi cũng đã thử nghiệm việc sử dụng biến đổi 3D thay vì chỉ mục z, nhưng vì lý do nào đó, nó không hoạt động ngay trong ứng dụng (nhưng nó hoạt động trong một trường hợp kiểm thử giảm, hình ảnh) và chúng tôi đã vài ngày sau khi ra mắt vào thời điểm đó nên phải rời khỏi phần đó để bảo trì sau khi ra mắt.

Khi bạn nhấp vào một điểm đánh dấu, điểm đánh dấu đó sẽ mở rộng thành một danh sách các địa điểm có thể nhấp vào. Đây là tất cả nội dung DOM HTML thông thường, vì vậy rất dễ viết. Tất cả đường liên kết và kết xuất văn bản đều hoạt động mà chúng tôi không cần phải làm gì thêm.

Bóp kích thước tệp

Khi bản minh hoạ hoạt động và kết nối với phần còn lại của trang web Những kỳ quan thế giới, vẫn còn một vấn đề lớn cần giải quyết. Lưới định dạng JSON dành cho các vùng đất trên địa cầu có kích thước khoảng 3 meg. Không phù hợp cho trang chủ của một trang web quảng cáo. Ưu điểm là việc nén lưới bằng gzip đã giảm tốc độ xuống còn 350 kB. Nhưng 350 kB vẫn hơi lớn. Một vài email sau đó, chúng tôi đã tuyển dụng được Won Chun – người phụ trách việc nén các lưới cơ thể khổng lồ của Google – để giúp chúng tôi nén lưới. Anh nét lưới từ một danh sách lớn các tam giác phẳng được cung cấp dưới dạng toạ độ JSON thành các toạ độ 11 bit được nén với các tam giác được lập chỉ mục và giảm kích thước tệp xuống còn 95 kB khi nén.

Việc sử dụng lưới nén không chỉ tiết kiệm băng thông mà còn phân tích cú pháp lưới nhanh hơn. Việc chuyển 3 meg của các số có chuỗi thành số bản địa sẽ mất nhiều công sức hơn việc phân tích cú pháp một trăm kB dữ liệu nhị phân. Kết quả là giảm được kích thước 250 kB cho trang cũng như nhận được thời gian tải ban đầu dưới một giây trên kết nối 2 Mbps. Nhanh hơn, nhỏ gọn hơn, tuyệt vời hơn!

Đồng thời, tôi vẽ các tác phẩm của mình bằng việc tải các Shapefile của Trái đất tự nhiên ban đầu mà từ đó bắt nguồn của lưới GlobeTweeter. Tôi đã cố tải được Shapefile nhưng hiển thị chúng dưới dạng đất phẳng đòi hỏi chúng phải chia thành các tam giác (có lỗ để hồ, v.v.) Tôi đã tạo các hình dạng được chia theo tam giác bằng cách sử dụng các tiện ích THREE.js nhưng không có các lỗ. Kết quả là các lưới có các cạnh rất dài, điều này đòi hỏi phải chia lưới thành các tam giác nhỏ hơn. Tóm lại, tôi đã không quản lý để hoạt động kịp thời, nhưng điều thú vị là định dạng Shapefile được nén thêm sẽ giúp bạn có được mô hình diện tích 8 kB. À, có thể lần sau nhé.

Công việc trong tương lai

Bạn có thể thực hiện thêm một thao tác là làm cho ảnh động của điểm đánh dấu đẹp hơn. Giờ đây, khi chúng đi qua đường chân trời, hiệu ứng hơi khó hiểu. Ngoài ra, việc có một hoạt ảnh thú vị cho phần mở đầu điểm đánh dấu sẽ rất tuyệt.

Về hiệu suất, còn thiếu hai yếu tố là tối ưu hoá thuật toán chia lưới và giúp các điểm đánh dấu nhanh hơn. Ngoài ra, mọi thứ còn rất hào nhoáng. Tuyệt vời!

Tóm tắt

Trong bài viết này, tôi đã mô tả cách chúng tôi xây dựng quả địa cầu 3D cho dự án Google World Wonders. Tôi hy vọng bạn thích các ví dụ này và sẽ thử xây dựng tiện ích địa cầu tuỳ chỉnh của riêng bạn.

Tài liệu tham khảo