世界の名所旧跡の 3D グローブの概要
WebGL 対応のブラウザで最近リリースされた Google World Wonders サイトをご覧になったことがある方は、画面の下部に回転する地球儀が表示されているのをご覧になったかもしれません。この記事では、地球儀の仕組みと、作成に使用したツールについて説明します。
世界七不思議の地球儀は、Google データ アート チームが WebGL 地球儀を大幅に改良したものです。元の地球儀をベースに、棒グラフの部分を削除し、シェーダーを変更し、クリック可能な HTML マーカーと、Mozilla の GlobeTweeter デモから取得した Natural Earth 大陸ジオメトリを追加しました(Cedric Pinson さんに感謝!)サイトの配色に合わせてアニメーション化された美しい地球儀を作成して、サイトの洗練度を高めることができます。
地球儀のデザインに関する要件は、世界遺産の上にクリック可能なマーカーを配置した、美しいアニメーション マップを作るというものでした。それを念頭に、適切なものを探し始めました。最初に思い浮かんだのは、Google データ アート チームが作成した WebGL グローブでした。地球儀で、かっこいいです。他に必要なことはありますか?
WebGL グローブの設定
地球儀ウィジェットを作成する最初のステップは、WebGL 地球儀をダウンロードして稼働させることでした。WebGL グローブは Google Code でオンラインで公開されており、簡単にダウンロードして実行できます。zip をダウンロードして解凍し、cd して基本的なウェブサーバー python -m SimpleHTTPServer
を実行します。(デフォルトでは UTF-8 はオンになっていません。こちらを使用できます)。これで、http://localhost:8000/globe/globe.html
に移動すると WebGL グローブが表示されます。
WebGL グローブが稼働したので、不要な部分をすべて削除しました。HTML を編集して UI ビットを削除し、地球儀の初期化関数から地球儀の棒グラフの設定を削除しました。このプロセスの最後に、非常にシンプルな WebGL グローブが画面に表示されました。回転させて見栄えを良くすることはできますが、それ以上はできません。
不要なものを削除するため、地球儀の index.html からすべての UI 要素を削除し、初期化スクリプトを次のように編集しました。
if(!Detector.webgl){
Detector.addGetWebGLMessage();
} else {
var container = document.getElementById('container');
var globe = new DAT.Globe(container);
globe.animate();
}
大陸のジオメトリを追加する
カメラを地球の表面に近づけたかったのですが、地球をズームしてテストしたところ、テクスチャの解像度不足が明らかになりました。ズームインすると、WebGL グローブのテクスチャがブロック状になり、ぼやけます。より大きな画像を使用することもできましたが、その場合、地球のダウンロードと実行に時間がかかるため、陸地と国境をベクトル表現することにしました。
陸地のジオメトリについては、オープンソースの GlobeTweeter デモを使用して、その 3D モデルを Three.js に読み込みました。モデルを読み込んでレンダリングしたので、地球の外観を磨き始めました。最初の問題は、地球の陸地モデルが WebGL 地球とフラッシュになるほど球形ではなかったことです。最終的には、陸地モデルをより球形にするための簡単なメッシュ分割アルゴリズムを作成しました。
球状の陸地モデルでは、地球の表面から少しずらして配置し、浮遊する大陸を作成しました。大陸の下には、影のような効果を出すために 2 ピクセルの黒い線を描画しました。また、ネオンカラーの輪郭線を試して、Tron のような外観にしてみました。
地球儀と陸地のレンダリングが完了したので、地球儀のさまざまな外観を試し始めました。控えめなモノクロの外観にしたかったので、グレースケールの地球儀と陸地にしました。前述のネオンの輪郭に加えて、明るい背景に暗い陸地がある暗い地球儀も試してみました。これは実際にかなりクールに見えます。ただし、コントラストが低すぎて読みづらく、プロジェクトの雰囲気にも合わなかったので、破棄しました。
地球儀のデザインについて、最初に考えたのは、釉薬をかけた磁器のような外観にすることです。磁器の外観を実現するシェーダーを記述できなかったため、この機能は試すことができませんでした(ビジュアル マテリアル エディタがあると便利です)。私が試した中で最も近いものは、黒い陸地が描かれた白く輝く地球儀です。ちょっとかっこいいけど、コントラストが強すぎる。見た目もよくありません。廃棄物に回すためのもう 1 台です。
白黒の地球儀のシェーダは、一種の疑似拡散バックライト照明を使用しています。地球の明るさは、画面の平面に対して垂直なサーフェスの距離によって異なります。そのため、画面を指している地球の中央のピクセルは暗く、地球の端のピクセルは明るくなります。明るい背景と組み合わせると、グローブが拡散した明るい背景を反射し、上品なショールームのような雰囲気を演出できます。黒い地球儀では、WebGL 地球儀のテクスチャをグロスマップとして使用しているため、大陸棚(浅い水域)が地球儀の他の部分と比べて光沢のあるように見えます。
黒い地球儀の海シェーダーは次のようになります。非常に基本的な頂点シェーダーと、ハッキングされた「ちょっといい感じ調整調整」フラグメント シェーダー。
'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 でマーカーを作成する
マーカーについて言えば、地球と陸地が機能するようにした後、私はプレースマークの作成に取りかかりました。マーカーの作成とスタイル設定を簡単にし、チームが作成中の 2D 地図でマーカーを再利用できるようにするため、マーカーには CSS スタイルの HTML 要素を使用することにしました。また、当時は WebGL マーカーをクリック可能にする簡単な方法を知らなかったし、マーカーモデルの読み込みや作成のための余分なコードを書きたくありませんでした。後から振り返ると、CSS マーカーはうまく機能していましたが、ブラウザのコンポーザとレンダラが変化している時期には、パフォーマンスの問題が発生する傾向がありました。パフォーマンスの観点から、マーカーを WebGL で作成したほうが良かったでしょう。一方で、CSS マーカーは開発時間を大幅に短縮しました。
CSS マーカーは、CSS 変換プロパティで絶対位置に配置された 2 つの div で構成されています。マーカーの背景は CSS グラデーションであり、マーカーの三角形の部分は回転した div です。マーカーには小さなドロップ シャドウが適用されており、背景から浮かび上がるようになっています。マーカーの最大の問題は、十分なパフォーマンスを発揮させることでした。残念ながら、フレームごとに移動して z インデックスを変更する数十個の div を描画すると、さまざまなブラウザ レンダリングの落とし穴を誘発することになります。
マーカーが 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 に保持されるため、不透明度をフェードアウトする方法は使用しませんでした。また、z インデックスの代わりに 3D 変換を使用することも試みましたが、なんらかの理由でアプリで正しく機能しませんでした(ただし、簡素化したテストケースでは機能しました)。リリースまで数日しかなく、その部分はリリース後のメンテナンスに残す必要がありました。
マーカーをクリックすると、クリック可能な地名のリストが展開されます。これらはすべて通常の HTML DOM の機能であるため、記述は非常に簡単でした。リンクとテキストのレンダリングはすべて、追加の作業なしで機能します。
ファイルサイズを圧縮する
デモが機能し、世界七不思議のサイトの他の部分に接続されたものの、解決すべき大きな問題が 1 つ残っていました。地球上の陸地の JSON 形式のメッシュのサイズは約 3 MB でした。ショーケース サイトのトップページには適していません。幸い、gzip でメッシュを圧縮すると 350 kB まで小さくなりました。350 KB は少し大きいので、数通のメールをやり取りした後、巨大な Google Body メッシュの圧縮に携わった Won Chun 氏にメッシュの圧縮を依頼しました。メッシュを、JSON 座標として指定された大きなフラットな三角形のリストから、インデックス付き三角形の圧縮された 11 ビット座標に圧縮し、ファイルサイズを 95 kB に圧縮しました。
圧縮メッシュを使用すると、帯域幅を節約できるだけでなく、メッシュの解析も高速化されます。3 MB の文字列化された数値をネイティブ数値に変換するには、100 KB のバイナリデータを解析するよりもはるかに多くの作業が必要です。ページのサイズが 250 KB も削減されたのは非常に便利です。また、2 Mbps の接続で最初の読み込み時間が 1 秒未満になりました。高速化と小型化を実現しました。
同時に、GlobeTweeter のメッシュが派生した元の Natural Earth Shapefile を読み込んでいました。シェイプファイルを読み込むことができましたが、平坦な陸地としてレンダリングするには、三角形分割(湖の穴付き)が必要です。THREE.js ユーティリティを使用してシェイプが三角形化されましたが、穴は三角形化されませんでした。生成されたメッシュのエッジは非常に長く、メッシュを小さな三角形に分割する必要がありました。長い話は省きますが、時間内に動作させることはできませんでした。しかし、Shapefile 形式をさらに圧縮すると、8 kB の陸地モデルが得られるという利点があります。残念ですが、また次回に。
今後の作業
少し手間をかけると、マーカーのアニメーションをより見やすくすることができます。地平線を越えると、効果が少し安っぽく見えます。また、マーカーを開く際のクールなアニメーションも良いでしょう。
パフォーマンス面では、メッシュ分割アルゴリズムの最適化とマーカーの高速化の 2 点が欠けています。それ以外は問題ありません。やった!
概要
この記事では、Google 世界遺産プロジェクトの 3D 地球儀を構築する方法について説明しました。これらの例を参考に、独自のカスタム グローブ ウィジェットを作成してみてください。