以 3D 地球儀製作世界奇觀

Ilmari Heikkinen

World Wonders 3D 地球簡介

如果您最近在支援 WebGL 的瀏覽器上檢視最近啟動的 Google World Wonders 網站,就可能在畫面底部看到旋轉的地球儀。這篇文章將帶您瞭解地球上的運作方式,以及我們用於建構這些內容的方式。

為了讓您快速瞭解一下,World Wonders 地球是 Google Data Arts 團隊精心調整的 WebGL Globe 版本。我們利用原始地球來消除長條圖、改變著色器、從 Mozilla GlobeTweeter 示範中添加了精美的可點擊 HTML 標記和 Natural Earth 陸地幾何圖形 (多虧了 Cedric Pinson!)這個平台除了製作一個符合網站色彩配置的精美地球外,也為網站增添更精緻的細節。

全球設計簡報最簡單,就是將一張美觀的動畫地圖,放置於「世界遺產」的地點上方,並帶有可點擊的標記。因此,我開始尋找合適的選擇。第一個想到的就是 Google Data Arts 團隊打造的 WebGL Globe。是顆地球,看起來很酷。您還需要哪些東西?

設定 WebGL Globe

製作地球小工具的第一步,就是下載 WebGL Globe 並加以執行。WebGL Globe 是在 Google 程式碼線上提供,下載和執行都很簡單。下載並解壓縮 ZIP 檔,接著執行 cd 並執行基本網路伺服器:python -m SimpleHTTPServer。(請注意,根據預設,這個項目沒有 UTF-8 ;您可以使用。)現在當你前往 http://localhost:8000/globe/globe.html 時,應該會看到 WebGL Globe。

隨著 WebGL Globe 啟動,可以裁掉所有不需要的部分。我編輯了 HTML 來修改 UI 文字,並從地球初始化函式中移除地球長條圖設定內容。最後,我的螢幕畫面顯示了 WebGL Globe。您可以轉動手腕,看起來很酷,但大概就是這樣。

為了剪下不需要的資料,我從地球的 index.html 刪除所有 UI 元素,並編輯了初始化指令碼,如下所示:

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

新增大陸幾何圖形

我們希望相機能接近地球表面,但在測試地球時,地球上缺乏紋理的成因顯而易見。放大後,WebGL Globe 的紋理會變得塊狀並模糊。我們可以使用較大圖片,但這會降低地球下載和運行速度,因此選擇使用向量來表示地域和邊界。

於是,我改採開放原始碼的 Globe 訊息工具示範,並將 3D 模型載入至 Three.js。模型載入並轉譯後,是時候開始潤飾地球的外觀了。第一個問題是,地球的陸地模型的球面不夠流暢,無法隨 WebGL Globe 清除。因此,我最後寫出一個快速的網狀分割演算法,讓大陸地模型變得更加球體模型。

利用球體陸地模型,我就能把它放置在與地球表面稍微偏移的地方,創造出漂浮的大陸外圍以黑色 2 px 的外框描繪出漂浮的大陸,形成陰影。我還嘗試使用霓虹色輪廓,製作看起來像 Tron 一樣的樣子。

於是地球和陸地算繪,我開始嘗試各種視覺效果來呈現地球。我們想設計出低調的單色外觀,因此困在灰階的地球儀和陸地上。除了上述霓虹燈外框外,我也試過在淺色背景上搭配深色陸塊的深色地球,實際上看起來很酷。但該元素對比度不足,難以閱讀,而且不符合專案風格,因此遭到扭曲。

我很早以為地球儀式就是像玻璃陶瓷米。我沒有打算試用這種顏色,因為我沒辦法編寫著色器來做陶瓷米 (視覺化質感編輯器還很不錯)。我最近試想了,這塊發亮的白色星球有黑色的陸地。大概有點整齊,但對比度過高。而且看起來不太美觀。另一個是抓取食物

黑白地球中的著色器使用某種假象,讓背光變色。地球的亮度取決於表面正常與螢幕平面的距離。因此,地球中間指向畫面的像素會是深色,地球邊緣的像素則為淺色。結合淺色背景和淺色背景,你可以看見地球反映擴散對比度較高的背景,營造出經典的展示空間風格。黑色的地球也會使用 WebGL Globe 紋理做為亮面地圖,因此相較於地球的其他地區,大陸書架 (淺水區域) 看起來更閃亮。

這是黑色地球的海洋著色器畫面。相當基本的頂點著色器,以及一個駭人「看起來有利於調整細部」片段著色器。

    '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')
    }

最後我們看到了一片深色地球儀,上面有淺灰色的陸地上。其最靠近設計簡介,外觀精美且易於閱讀。此外,地球上採用某個低對比度的設計,也使得標記和其他內容更加搶眼。下方版本使用的是全黑海洋,而生產版本則使用深灰色的海洋,標記則稍有不同。

使用 CSS 建立標記

說到標記,在地球和陸地上運作,我開始著手設計地標。我決定使用 CSS 樣式的 HTML 元素來處理標記、更輕鬆地製作標記及設定標記樣式,還可以在團隊處理的 2D 地圖中重複使用標記。我不知道該如何將 WebGL 標記設為可點擊,也不想撰寫額外的程式碼來載入 / 建立標記模型。隱形時,CSS 標記雖然運作良好,但在瀏覽器合成器和轉譯器的流通期間有時也會碰到效能問題。從效能的角度來看,在 WebGL 中執行標記會是更好的選擇。同樣的,CSS 標記也省下了許多開發時間。

CSS 標記包含兩個 div 絕對位置,帶有 CSS 轉換屬性。標記的背景是 CSS 漸層,而標記的三角形部分是旋轉的 div。標記會有小型的投射陰影,讓它們從背景彈出。此標記的最大問題在於,它們的執行效能必須夠高。聽起來很難過,請繪製幾十個 div 並在每個頁框中移動並變更其 Z-index,這個方式能有效觸發各種瀏覽器轉譯錯誤。

標記與 3D 場景的同步處理方式並不複雜。每個標記的 Three.js 情境中都有對應的 Object3D,可用來追蹤標記。為了取得螢幕空間座標,我取出地球的 Three.js 矩陣和標記,然後乘以零向量。然後,我就會取得標記的場景位置。如要取得標記的螢幕位置,我會透過相機投影場景位置。產生的投影向量會包含標記的螢幕空間座標,可供在 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;
}

總而言之,最快的方法是使用 CSS 轉換來移動標記,不使用不透明度淡出的效果,因為它會在 Firefox 中觸發緩慢的路徑,並確保所有標記在 DOM 中保留,而不是在標記位於地球後方時移除。我們也嘗試使用 3D 轉換而非 Z-index,但基於某些原因,該應用程式無法在應用程式中正常運作 (但確實在測試案例減少,但執行時有幾天),因此我們必須在推出後幾天才執行維護作業。

按一下標記,該標記會展開為一份可點擊的地點清單。這全都是一般的 HTML DOM 內容,因此非常容易撰寫。所有連結和文字轉譯方式都不需要額外處理。

壓制檔案大小

因此,由於示範確實有效,但與 World Wonders 網站的其餘部分相連,仍有一項重大問題需要解決。地球地形的 JSON 格式網格大小約為 3 MB。不適用於展示網站的首頁。好消息是,使用 gzip 將網格壓縮成 350 KB。但有 350 KB 依然是有點大的。我們後來邀請 Won Chun 將這座巨大的 Google Body 網格壓縮成 Google 的網紅,派出一隻手幫忙壓縮網格。他將網格從以 JSON 座標形式提供的大型三角形清單向下排擠,到將含有索引三角形的 11 位元座標壓縮後,檔案大小降至 95 kB 的 gzip 格式。

使用壓縮的網格不僅可節省頻寬,而且網格的剖析速度也更快。將 3 MEG 的數字轉換為原生數字,比剖析數百 KB 的二進位資料更加費心。結果網頁的大小減少了 250 KB,而且在 2 Mbps 連線的情況下,初始載入時間不到一秒。更快、更小,太棒了!

同時,我也在載入 Globe 高音網網格的來源 Natural Earth Shapefile,我慢慢載入 Shapefile,但算繪成平坦的地勢時,需要將 Shapefile 進行三角化,比如針對湖水坑洞我利用 THREE.js 公用程式進行形狀三角化,但是沒有這些洞。而產生的網格則具有非常長的邊緣,因此必須將網格分割為較小的三角形。很短的故事很短,我沒有辦法及時工作,但最精巧的 Shapefile 格式確實可以達到 8 KB 的陸地模型。喔,下次見。

未來工作

需要多付出一些心力,就能讓標記動畫更美觀。現在,當這些人穿越地平線時,效果就不太好了。此外,如果可以提供很酷的標記開啟動畫,也更棒。

對效能而言,缺少兩項缺少的要素是最佳化網格分割演算法,並加快標記速度。除此之外,事物也很危險。呵呵!

摘要

針對 Google World Wonders 專案,我說明瞭我們如何打造 3D 地球。希望您喜歡範例,試著建立自己的自訂地球小工具。

參考資料