Giới thiệu về quả địa cầu 3D World Wonders
Nếu đã xem trang web Google World Wonders (Kỳ quan thế giới của Google) mới ra mắt trên một trình duyệt có hỗ trợ WebGL, bạn có thể đã thấy một quả địa cầu xoay rực rỡ ở cuối màn hình. Bài viết này sẽ giới thiệu cho bạn cách hoạt động của quả địa cầu và những công cụ mà chúng tôi sử dụng để tạo ra quả địa cầu này.
Để cung cấp cho bạn thông tin tổng quan nhanh, quả địa cầu World Wonders là phiên bản được điều chỉnh rất nhiều của Quả địa cầu WebGL do Nhóm nghệ thuật dữ liệu của Google tạo ra. 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 các điểm đánh dấu HTML có thể nhấp vào và hình học lục địa Natural Earth từ bản minh hoạ GlobeTweeter của Mozilla (cảm ơn Cedric Pinson!) Tất cả đều nhằm tạo ra một quả địa cầu động đẹp mắt, phù hợp với bảng phối màu của trang web và tăng thêm sự tinh tế cho trang web.
Yêu cầu thiết kế cho quả địa cầu 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 được đặt trên đầu các Di sản Thế giới. Với suy nghĩ đó, tôi bắt đầu tìm kiếm một công cụ phù hợp. Điều đầu tiên tôi nghĩ đến là Quả địa cầu WebGL do Nhóm nghệ thuật dữ liệu của Google tạo ra. Đó là một quả địa cầu và trông rất tuyệt. Bạn cần gì nữa không?
Thiết lập WebGL Globe
Bước đầu tiên để tạo tiện ích địa cầu là tải WebGL Globe xuống rồi khởi động và chạy tiện ích đó. Quả địa cầu WebGL có trên mạng tại Google Code và bạn có thể dễ dàng 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ột máy chủ web cơ bản: python -m SimpleHTTPServer
. (Xin lưu ý rằng theo mặc định, tính năng này không bật UTF-8; bạn có thể sử dụng tính năng này.) Bây giờ, nếu chuyển đến http://localhost:8000/globe/globe.html
, bạn sẽ thấy Quả địa cầu WebGL.
Khi quả địa cầu WebGL đã khởi động và chạy, đã đến lúc cắt bỏ tất cả các phần không cần thiết. Tôi đã chỉnh sửa HTML để loại bỏ các phần giao diện người dùng và xoá nội dung thiết lập biểu đồ thanh địa cầu khỏi hàm khởi chạy địa cầu. Khi kết thúc quá trình đó, tôi có một quả địa cầu WebGL rất đơn giản trên màn hình. Bạn có thể xoay nó và nó trông rất ngầu, nhưng đó là tất cả.
Để cắt bỏ những nội dung không cần thiết, tôi đã xoá tất cả các thành phần 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 để có dạng 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 máy ảnh ở gần bề mặt quả địa cầu, nhưng khi thử nghiệm quả địa cầu được thu phóng, chúng tôi nhận thấy rõ sự thiếu độ phân giải kết cấu. Khi phóng to, hoạ tiết của Quả địa cầu WebGL sẽ trở nên bị mờ và bị khối. Chúng tôi có thể sử dụng hình ảnh lớn hơn, nhưng điều đó sẽ làm cho quá trình tải xuống và chạy quả địa cầu chậm hơn. Vì vậy, chúng tôi đã chọn sử dụng vectơ đại diện cho các lục địa và đường biên giới.
Đối với hình học của lục địa, tôi đã chuyển sang bản minh hoạ GlobeTweeter nguồn mở và tải mô hình 3D trong đó vào Three.js. Khi mô hình đã tải và kết xuất, đã đến lúc bắt đầu hoàn thiện giao diện cho quả địa cầu. Vấn đề đầu tiên là mô hình lục địa trên quả địa cầu không đủ hình cầu để khớp với Quả địa cầu WebGL, vì vậy, tôi đã viết một thuật toán phân tách lưới nhanh để làm cho mô hình lục địa có hình cầu hơn.
Với mô hình lục địa hình cầu, tôi có thể đặt mô hình này chỉ hơi lệch so với bề mặt quả địa cầu, tạo ra các lục địa nổi được viền bằng một đường màu đen 2px bên dưới để tạo bóng. Tôi cũng thử nghiệm với các đường viền màu neon để tạo ra một kiểu dáng giống như Tron.
Với hình ảnh địa cầu và các lục địa được kết xuất, tôi bắt đầu thử nghiệm nhiều kiểu dáng cho địa cầu. Vì chúng tôi muốn sử dụng giao diện đơn sắc tinh tế, nên tôi đã sử dụng quả địa cầu và các lục địa theo thang màu xám. Ngoài các đường viền neon nêu trên, tôi đã thử một quả địa cầu tối với các vùng đất tối trên nền sáng, trông khá thú vị. Nhưng nó có độ tương phản quá thấp để dễ đọc và không phù hợp với cảm giác của dự án nên tôi đã loại bỏ nó.
Một ý tưởng khác mà tôi có cho giao diện quả địa cầu là làm cho nó trông giống như đồ sứ tráng men. Tôi không thể thử nghiệm hiệu ứng này vì không thể viết chương trình đổ bóng để tạo hiệu ứng sứ (trình chỉnh sửa tài liệu hình ảnh sẽ rất hữu ích). Điều gần nhất tôi thử là quả địa cầu phát sáng màu trắng với các lục địa màu đen. Giao diện này khá gọn gàng nhưng có độ tương phản quá cao. Và nó trông không đẹp lắm. Vậy là lại một ứng dụng khác bị loại bỏ.
Chương trình đổ bóng trong quả địa cầu đen trắng đang sử dụng một loại ánh sáng phản chiếu giả mạo. Độ sáng của quả địa cầu phụ thuộc vào khoảng cách của pháp tuyến bề mặt với mặt phẳng màn hình. Vì vậy, các pixel ở giữa quả địa cầu đang hướng vào màn hình sẽ có màu tối và các pixel ở các cạnh của quả địa cầu sẽ có màu sáng. Khi kết hợp với nền sáng, bạn sẽ thấy quả địa cầu phản chiếu nền sáng rực rỡ, tạo nên một hình ảnh sang trọng cho phòng trưng bày. Quả địa cầu màu đen cũng sử dụng hoạ tiết WebGL Globe làm bản đồ độ bóng để các thềm lục địa (khu vực nước nông) trông sáng bóng so với các phần khác của quả đị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 rất cơ bản và chương trình đổ bóng mảnh "oh that looks kinda neat tweak tweak" (ôi, trông có vẻ khá gọn gàng chỉnh sửa chỉnh sửa).
'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ọn một quả địa cầu tối với các lục địa màu xám nhạt được chiếu sáng từ trên cao. Mẫu này gần nhất với bản thiết kế ngắn gọn, trông đẹp mắt và dễ đọc. Ngoài ra, việc tạo quả địa cầu có độ tương phản thấp một chút sẽ giúp các điểm đánh dấu và nội dung còn lại nổi bật hơn so với quả địa cầu. Phiên bản dưới đây sử dụng đại dương hoàn toàn màu đen, trong khi phiên bản phát hành chính thức có đại dương màu xám đậm và các đ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 lục địa đã hoạt động, tôi bắt đầu làm việc trên điểm đánh dấu vị trí. Tôi quyết định sử dụng các phần tử HTML kiểu CSS cho điểm đánh dấu để dễ dàng tạo và tạo kiểu cho điểm đánh dấu, đồng thời có thể sử dụng lại các điểm đánh dấu trong bản đồ 2D mà nhóm đang làm việc. Vào thời điểm đó, tôi cũng không biết cách dễ dàng để tạo các điểm đánh dấu WebGL có thể nhấp vào và không muốn viết thêm mã để tải / tạo mô hình điểm đánh dấu. Nhìn lại, 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 kết hợp và trình kết xuất của 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à lựa chọn tốt hơn. Tuy nhiên, các điểm đánh dấu CSS đã tiết kiệm được rất nhiều thời gian phát triển.
Các điểm đánh dấu CSS bao gồm một vài div được định vị tuyệt đối bằng thuộc tính biến đổi CSS. Nền của điểm đánh dấu là một hiệu ứng chuyển màu CSS và phần tam giác của điểm đánh dấu là một div được xoay. Các điểm đánh dấu có một bóng đổ nhỏ để nổi bật trên nền. Vấn đề lớn nhất với các điểm đánh dấu là làm sao để chúng hoạt động đủ tốt. Nghe có vẻ buồn, nhưng việc vẽ vài chục div di chuyển xung quanh và thay đổi chỉ mục z trên mọi khung hình là một cách khá hiệu quả để kích hoạt mọi loại lỗi hiển thị trình duyệt.
Cách đồng bộ hoá đ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. Để lấy toạ độ không gian màn hình, tôi lấy ma trận Three.js cho quả địa cầu và điểm đánh dấu, rồi nhân một vectơ bằng 0 với các ma trận đó. Từ đó, tôi có được vị trí cảnh của điểm đánh dấu. Để lấy vị trí điểm đánh dấu trên màn hình, tôi chiếu vị trí cảnh thông qua máy ảnh. Vectơ chiếu 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 nhanh nhất là sử dụng các phép biến đổi CSS để di chuyển các điểm đánh dấu, không sử dụng độ 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á các điểm đánh dấu đó khi chúng nằm phía sau quả địa cầu. Chúng tôi cũng thử nghiệm việc sử dụng phép biến đổi 3D thay vì chỉ số z, nhưng vì lý do nào đó, phương thức này không hoạt động đúng cách trong ứng dụng (nhưng lại hoạt động trong một trường hợp kiểm thử rút gọn, hãy tìm hiểu), và chúng tôi chỉ còn vài ngày nữa là đến ngày ra mắt nên phải để phần đó cho việc 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 danh sách các địa danh có thể nhấp vào. Đây đều là những nội dung DOM HTML thông thường, vì vậy, việc viết mã rất dễ dàng. Tất cả các đường liên kết và kết xuất văn bản đều hoạt động mà chúng ta không cần làm gì thêm.
Rút gọn kích thước tệp
Khi bản minh hoạ hoạt động và được kết nối với phần còn lại của trang web World Wonders, vẫn còn một vấn đề lớn cần giải quyết. Lưới ở định dạng JSON cho các lục địa trên thế giới có kích thước khoảng 3 MB. Không phù hợp với trang chủ của trang web giới thiệu. Điều đáng mừng là việc nén lưới bằng gzip đã giảm kích thước xuống còn 350 kB. Nhưng 350 kB vẫn hơi lớn. Sau vài email, chúng tôi đã thu nạp được Won Chun – người đã làm việc trong việc nén các lưới Google Body khổng lồ – để giúp chúng tôi nén lưới. Anh nén 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 toạ độ 11 bit nén với tam giác được lập chỉ mục và giảm kích thước tệp xuống còn 95 kB được nén bằng gzip.
Việc sử dụng lưới nén không chỉ tiết kiệm băng thông mà còn giúp phân tích cú pháp lưới nhanh hơn. Việc chuyển đổi 3 MB số được chuyển đổi thành chuỗi thành số gốc sẽ mất nhiều công sức hơn so với việc phân tích cú pháp 100 kB dữ liệu nhị phân. Việc giảm kích thước 250 kB cho trang là rất hiệu quả, đồng thời giúp giảm thời gian tải ban đầu xuống dưới một giây trên kết nối 2 Mb/giây. Nhanh hơn và nhỏ hơn, thật tuyệt vời!
Đồng thời, tôi cũng đang vẽ vời bằng cách tải các tệp Shapefile ban đầu của Natural Earth, từ đó lưới GlobeTweeter được lấy ra. Tôi đã tải được tệp Shapefile nhưng để hiển thị chúng dưới dạng các vùng đất phẳng, bạn cần phải tạo tam giác cho các tệp đó (với các lỗ cho hồ, tất nhiên). Tôi đã tạo hình tam giác cho các hình dạng bằng các tiện ích THREE.js nhưng không tạo được lỗ. Và lưới thu được có các cạnh rất dài, đò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 thể làm cho nó hoạt động kịp thời, nhưng điều thú vị là định dạng tệp Shapefile được nén thêm sẽ cung cấp cho bạn một mô hình lục địa có kích thước 8 kB. Thôi, có lẽ lần sau.
Công việc trong tương lai
Một việc cần làm thêm 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 sẽ hơi khó chịu. Ngoài ra, bạn nên có một ảnh động thú vị cho phần mở đầu của điểm đánh dấu.
Về hiệu suất, hai điều còn thiếu là tối ưu hoá thuật toán phân tách lưới và làm cho điểm đánh dấu nhanh hơn. Ngoài ra, mọi thứ đều ổn. Hoan hô!
Tóm tắt
Trong bài viết này, tôi đã mô tả cách chúng tôi tạo quả địa cầu 3D cho dự án Google World Wonders. Tôi hy vọng bạn đã thích các ví dụ và sẽ thử tạo tiện ích bản đồ thế giới tuỳ chỉnh của riêng mình.