KASPERSKY Earth 2050 的那些事兒

做一個酷炫的三維地球場景是每一個 webgl 或 threejs 初識者的小目標,以 KASPERSKY 2050 Earth 這一場景為例,一起來探秘它背後的一些奇技淫巧吧。

打開 Earth 2050 的首頁,映入眼帘的是一個勾勒著輪廓的綠色地球,每一個大陸上都打滿了閃爍的點,一些城市、國家等被六邊形標註並發出光錐,太空中環繞著一些衛星軌道和在軌道上穩定運行的衛星,雲層忽隱忽現。在3Der的眼中,每一個酷炫的元素背後都是滿滿的技術支撐,今天我們就來「揭秘」一下。

做一個三維場景的工具有很多,這裡以threejs為例,首先分析一下Earth 2050 場景中都有哪些需要做的事情,以及可能的解決方法。

Earth 2050 中那些你一看就知道怎麼做的事情

  • 繪製地球的輪廓:如果你會在三維空間中繪製線條,並且懂得經緯度如何轉換為三維空間中的一點,可能一份GeoJSON數據就能滿足你的需求。
  • 球面六邊形地標:如果你會在三維空間中繪製線條或圓,並且懂得如何讓其看上去與球面是相切的,那球面六邊形對你來說簡直就是小菜一碟。
  • 衛星軌道:需要的技能同球面六邊形,So easy!
  • 衛星:如果你能搞到一個衛星的三維模型(會自己做模型更好),並且懂得如何正確導入到三維場景中,可能火箭你也能搞定。
  • 散落的星星:如果你會在三維空間中畫點(會使用粒子系統更好),這件事簡直就是手到擒來。

Earth 2050 中那些看上去有些難度的事情

  • 球面徑向光錐:咦,這個光錐好立體,還是徑向的耶,而且,它究竟是怎麼發光的呢?
  • 大陸打點:呀,這個打點是怎麼區分陸地和海洋的呢?
  • 雲層:哇,這個雲層好神奇,懸浮在地球表層,還不影響你對地球的觀察,究竟怎麼做才能按照我限定的範圍去顯示呢?

接下來我們就講講怎樣解決上面三個看似棘手的問題。

如何做一個像下圖一樣的球面徑向光錐?

Earth 2050 的光錐是一個貼了看上去有發光效果的紋理的平面,之所以看上去比較立體,是在此基礎上又添加了一個完全相同的正交平面。

雙面光錐 單面光錐

要做到讓光錐徑向放置,需要對光錐進行合理的旋轉和平移等操作,也就是需要合理計算模型矩陣,代碼如下:

let texture = new THREE.TextureLoader().load(/uploads/user_upload/39231/lightray_yellow.jpg), material = new THREE.MeshBasicMaterial({ map: texture, transparent: true, depthTest: false, side: THREE.DoubleSide, blending: THREE.AdditiveBlending }), height = Math.random() * 50 + 50, geometry = new THREE.PlaneGeometry(HEXAGON_RADIUS * 2, height), matrix1 = new THREE.Matrix4, plane1 = new THREE.Mesh(geometry, material) matrix1.makeRotationX(Math.PI / 2) matrix1.setPosition(new THREE.Vector3(0, 0, height / -2)) geometry.applyMatrix(matrix1) let plane2 = plane1.clone() plane2.rotation.z = Math.PI / 2 plane1.add(plane2) plane1.position.copy(position) plane1.lookAt(0, 0, 0)scene.add(plane1)

如何像下圖一樣只在陸地上打點?

像下圖一樣在球面上打點是一件容易的事情,你可以用你能想到的任何球面劃分方法。

打點之後如何區分陸地和海洋呢?Earth 2050 用到的是圖象映射法。假設你有一張下圖這樣的世界地圖:

這張世界地圖只有兩種顏色,黑色的大陸和白色的海洋(這樣的底圖不是那麼難找)。那麼你只需要將你需要判斷的點的三維坐標映射到這張底圖上,判斷一下這個位置的像素值是否滿足你的要求。

球面劃分及球面坐標計算代碼如下:

var spherical = new THREE.Spherical spherical.radius = radius const step = 250 for (let i = 0; i < step; i++) { let vec = new THREE.Vector3 let radians = step * (1 - Math.sin(i / step * Math.PI)) / step + .5 // 每個緯線圈內的角度均分 for (let j = 0; j < step; j += radians) { let c = j / step, // 底圖上的橫向百分比 f = i / step, // 底圖上的縱向百分比 index = Math.floor(2 * Math.random()) pos = positions[index] size = sizes[index] if (isLandByUV(c, f)) { // 根據橫縱百分比判斷在底圖中的像素值 spherical.theta = c * Math.PI * 2 - Math.PI / 2 // 橫縱百分比轉換為theta和phi夾角 spherical.phi = f * Math.PI // 橫縱百分比轉換為theta和phi夾角 vec.setFromSpherical(spherical) // 夾角轉換為世界坐標 pos.positions.push(vec.x) pos.positions.push(vec.y) pos.positions.push(vec.z) if (j % 3 === 0) { size.sizes.push(8.0) } } } }

底圖圖象坐標計算及像素值判斷:

function isLandByUV(c, f) { if (!earthImgData) { // 底圖數據 console.error(data error!) } let n = parseInt(earthImg.width * c) // 根據橫縱百分比計算圖象坐標系中的坐標 o = parseInt(earthImg.height * f) // 根據橫縱百分比計算圖象坐標系中的坐標 return 0 === earthImgData.data[4 * (o * earthImgData.width + n)] // 查找底圖中對應像素點的rgba值並判斷}

如何做一個像下圖一樣不遮擋主視野只在地球外側顯示的球形雲層?

做一個懸浮在地球上空的球形雲層是很簡單的,找一張雲層圖片,貼到球面上就可以,但是如何讓它只在某一個視野內顯示呢?球面一點徑向(向量Ai)和球面一點到相機方向(向量Bi)的夾角(anglei)隨著徑向角度的變化而不同,如下圖所示。

如果你想只顯示某個範圍內的雲層,只需要設定一個angle的範圍,向量Ai和向量Bi的夾角大小在這個範圍內時,該位置透明度設置為0即可。設透明度為_alpha,dot為向量求叉乘,alphaProportion為設定夾角的餘弦值,normal為球面某點的徑向向量(上圖中的Ai向量),也就是球面一點的法向量,cameraToVertex為上圖中的Bi向量,代碼如下:

_alpha = 1.0 - max( 0.0, dot( normal, cameraToVertex ) );_alpha = max( 0.0, (_alpha - alphaProportion) / (1.0 - alphaProportion) );

看完了我的解讀,或許你也躍躍欲試,或許你有更棒更炫的思路,歡迎轉發評論點贊投硬幣投香蕉奧~

完整代碼請戳:Earth2050 - 踏得網

好啦,今天的分享就到這裡啦~

也歡迎留言與我們討論互動喔!

請大家持續關注我們的公眾號

我們會不斷地分享更多有趣的乾貨~

weixin.qq.com/r/nSqrs1P (二維碼自動識別)


推薦閱讀:

為什麼我們要數據可視化
KB03:DataMapA教程-數據解析操作
Matplotlib中關於坐標軸的控制
關於大數據你應該了解的五件事兒
利用tsne對Word2vec模型可視化展示

TAG:WebGL | 3D | 數據可視化 |