關於從入門three.js到作出3d地球這件事(第五篇: 以點成面矢量地球)有圖解

關於從入門three.js到作出3d地球這件事(第五篇: 以點成面矢量地球)

本篇效果圖:
image.png前端

注: 本人畫工較差哈哈哈哈哈哈...vue

一. geojson基本概念

     本篇咱們要繪製一個矢量地球, 那咱們先要知道矢量地球是由什麼組成的, 好比說要繪製'中國', 那麼咱們只要知道中國邊界上全部的點的座標, 再逐一把這些點連接起來就是一箇中國的輪廓了, 因爲每一個點相距很近因此雖然咱們是用直線連接但依然能夠造成圓滑的球面效果, 簡單理解geojson就是這樣一組數據, 它裏面有繪製各個國家輪廓所需的全部的的信息, 深刻理解你會發現geojson裏面還有各類分組信息, 但咱們本篇主要講繪製最基本的國家輪廓就不展開討論了, 讓咱們先繪製一款平面地圖。
     這是我以前寫過的一篇詳細介紹geojson的文章,有興趣的同窗能夠去了解下, 會有助於你更好的理解地圖: 記一次前端"揭開繪製地圖的神祕面紗"分享會
     本章設計的數學知識都是初級的, 再日後會涉及到矩陣之類的知識, 到時候我也會用最通俗的方式解釋給你聽, 毫不止於概念而是最通俗的方式方便你理解, 本篇後面會有詳細的經緯度轉xyz的講解與圖解。git

二. 經緯度

     這裏的概念很基礎也很重要, 若是不熟悉的話要仔細看哦。github

經度

     經度是地球上一個地點離一根被稱爲本初子午線的南北方向走線以東或以西的度數。本初子午線的經度是0°,地球上其它地點的經度是向東到180°或向西到180°, 作爲本初子午線的那條線是人選出來的, 每15°一個時區(時區引發的bug我在以前分享過: 時區相關bug)。
     如圖所示, 在計算機裏面是用正負數來區東經與西經, 東經爲正數西經爲負數, 度數範圍是[-180, 180]
image.pngweb

緯度

     過橢球面上某點做法線,該點法線與赤道平面的線面角,其數值在0至90度之間。位於赤道以北的點的緯度叫北緯,記爲N;位於赤道以南的點的緯度稱南緯,記爲S。
     如圖所示, 在計算機裏面是用正負數來區北緯與南緯, 北緯爲正數南緯爲負數, 度數範圍是[-90, 90]json

image.png

擴展知識: 測量經緯度

     在地球上任何地點,只要有只表,有根竹竿,一根捲尺,就可知道當地經緯度。但表必須與該國標準時校對, 具體方法在百度百科有興趣的能夠作下實驗。segmentfault

三. 還記得三角函數麼

     大郎不要怕咱們畢業這麼久也不用背誦了, 只要知道怎麼用就行, 咱們一塊兒來複習一下:
image.png數組

名稱 公式
sin(∠A) a/c
cos(∠A) b/c
tan(∠A) a/b
  • 由於geojson裏面存儲的數據是經緯度, 因此等下咱們要用他把經緯度轉換成座標, 固然geojson也能夠直接儲存座標。

四. 弧度

     即兩條射線從圓心向圓周射出,造成一個夾角和夾角正對的一段弧。當這段弧長正好等於圓的半徑時,兩條射線的夾角的弧度爲1。
image.pngsvg

五. 前端代碼裏的實現

     若是你想固然認爲直接在前端代碼裏寫入Math.sin(30)就會輸出0.5那你就錯了。
image.png
     由於在咱們的Math運算裏面, 須要輸入的是弧度, 這就是爲啥我上面要複述弧度的概念, 因此想求sin(30)咱們要這樣寫Math.sin(30 * Math.PI / 180)
image.png函數

爲了方便你們都能理解我仍是簡單寫下推導過程
  1. 1弧度是對應弧長爲半徑的角度, 已知一個圓的周長是 2πr
  2. 能夠得出結論, 一個圓總共有 2πr ÷ r 個弧度, 也就是弧度爲一個圓。
  3. 一個圓有360°角, 1°的角度對應的弧度就是 ÷360, 也就是 π/180
  4. 因此上面所說的sin(30°)就是Math.sin(30 * Math.PI / 180)

六. 繪製一個平面世界

     學到這裏我默認你已經瞭解了geojson的相關概念, 裏面一個國家可能有多個輪廓而且互相不接壤, 咱們要把它們處理成數組, 也就是這句country.geometry.type === "Polygon"
/cc_map_3d_pro/src/components/cc_map.vue

import worldGeo from "../assets/geojson/world.geo";
.../
    initEarth() {
      const R = envConifg.r;
      worldGeo.features.forEach((country) => {
        if (country.geometry.type === "Polygon") {
          country.geometry.coordinates = [country.geometry.coordinates];
        }
        var line = countryLine(R, country.geometry.coordinates);
        this.scene.add(line);
      });
    },

世界的geojson能夠在個人項目裏找到github查看

  • 上面咱們把半徑, 點位傳給了countryLine方法來處理, 而且把他們都加入到了環境裏面, 後面會講把圖形對象都分別放入不一樣的組裏面, 這裏先這樣不擴散知識點。

七. 用

/cc_map_3d_pro/src/utils/countryLine.js這個方法裏咱們專門繪製國家的輪廓。

import * as THREE from 'three';

function countryLine(R, polygonArr) {
  let group = new THREE.Group();
  polygonArr.forEach(polygon => {
    let pointArr = [];
    polygon[0].forEach(elem => {
      pointArr.push(elem[0], elem[1], 0)
    });
    group.add(line(pointArr));
  });
  return group;
}
new THREE.Group()

     在three.js中文網接摘下來的原話, 它幾乎和Object3D是相同的,其目的是使得組中對象在語法上的結構更加清晰。

     假設我如今生成兩個正方體geometry, 分別命名爲a 與 b, 那麼我不用下面的寫法

this.scene.add(a);
this.scene.add(b);

而是能夠建立一個組:

const group = new THREE.Group();
group.add(a)
group.add(b)
this.scene.add(group);

再詳細的咱們後續章節會詳細聊

  • 上面咱們依賴了一個名爲line的方法, 這個方法是此次的一個重要的知識點。

八 . 繪製線段不簡單(line方法)

function line(pointArr) {
  let geometry = new THREE.BufferGeometry();
  let vertices = new Float32Array(pointArr);
  let attribue = new THREE.BufferAttribute(vertices, 3);
  geometry.attributes.position = attribue;
  let material = new THREE.LineBasicMaterial({
    color: 0x00aaaa //線條顏色
  });
  let line = new THREE.LineLoop(geometry, material);
  return line;
}
1. 傳入的參數pointArr;

     由countryLine方法可知, 這裏是[x1, y1, z1, x2, y2, z2, x3, y3, z3]這樣的一系列座標, 你可能感受這種形式不太符合js的思想, 可是它符合webgl或是svg的思想, 關於webgl的知識後續會在講解着色器的時候會讓你明白的, 如今不用太深研究由於這裏學問很深。

2. new THREE.BufferGeometry();

     使用BufferGeometry能夠有效減小向GPU傳輸上述數據所需的開銷, 一個國家平均有幾百組, 因此再用普通的Geometry會變的很卡, 你們放心後續在 優化 相關的篇幅裏面我會統一講一遍, 這裏你能夠暫時理解爲一種three.js轉換的數據流

3. new Float32Array();

     js原生知識: Float32Array類型數組表明的是平臺字節順序爲32位的浮點數型數組(對應於 C 浮點數據類型), Float32Array在數據量較大時性更更好一些, 而且更符合webgl的參數標準, 關於這類TypedArray是個大課題, 詳細的我會在着色器章節好好聊聊 。

4. new THREE.BufferAttribute();

     這個類用於存儲與BufferGeometry相關聯的 attribute(例如頂點位置向量,面片索引,法向量,顏色值,UV座標以及任何自定義 attribute), 利用 BufferAttribute能夠更高效的向GPU傳遞數據。

  1. 第一個參數: 是數據源, 也就是上面處理好的座標數組。
  2. 第二個參數: 數據被存儲爲任意長度的矢量, 這裏傳的是3, 能夠理解爲每三個數據爲一組, 也就是x1, y1, z1一組, x2, y2, z2一組, 以此類推。
5. new THREE.LineBasicMaterial();

     基礎線條材質, 也就是專業繪製線條的, 能夠調節顏色與粗細, 以及線頭的樣子。

6. geometry.attributes.position = attribue

     這個寫法看起來很粗魯, 它的意思就是把圖形的位置信息, 替換成咱們處理好的數組, 也就是爲圖形設置每一個點的位置。

7. new THREE.LineLoop();

     環線也就是首尾相連的線, 就向咱們每次建立一個矩形同樣, 這個方法建立了一條環線。

因爲咱們把z軸的數值都傳的0, 因此纔會出現下圖這種平面地圖
image.png
image.png

九. 經緯度轉換到笛卡爾座標系(理論)

     也有很多是直接作這種平面地球的, 但咱們的系列文章是要學習圓形地球的, 因此咱們要把經緯度座標轉換成球面座標
     咱們就從x y z逐一開始研究。

最簡單的y軸

     y軸其實只與緯度有關, 最簡單就能夠求出來以下圖所示:
image.png

  • 球體上的一個點, 這個點距離圓心的距離是圓的半徑r, 如今咱們要求這個點距離zy平面的距離。

image.png

  • 這個點的x與z不必定爲0, 因此做垂線不必定落在x軸上, 可是無論如何做垂線這條線段與xz平面的夾角是不會變的, 而這個夾角就是緯度, 因此由此可知咱們已知斜邊的長度爲r, 三角函數sin(緯度) = 對邊 / 斜邊, 對邊就是y軸的數值, 咱們把使用左右都乘以r, 最終得出:

    sin(緯度) 乘 r = y
須要計算的x軸

     x軸須要點計算咱們一步一步來, 每步都有圖解:

image.png
上面演示的是, 咱們能夠把這個當作是一個立方體, 一個已知對角線長度爲r的立方體, 接下來咱們就能夠把這個立方體單獨拿出來研究, 能夠脫離這個座標系了。

image.png

現已知立方體對角線長度爲r, 高爲y軸座標, 接下來使用緯度求出底面對角線。

image.png
咱們採用與求y軸差很少的方式求出x1的長度:

cos(緯度) 乘 r = x1

image.png

由圖可知經度是下方沿yz平面展開的對角線的角度, 咱們能夠用sin的對邊比斜邊求出長度。

sin(經度) * x1 = x

把x1的公式帶入進來:

x = sin(經度) 乘 cos(緯度)
與x對應的z軸

image.png
與x相同的原理,只是這裏咱們用cos的臨邊比斜邊。

z = cos(經度) 乘 cos(緯度)

十. 經緯度轉換到笛卡爾座標系(代碼)

實戰的時候別忘了, 先把經緯度轉成弧度

// 經緯度轉座標
function lon2xyz(R, longitude, latitude) {
    const lon = longitude * Math.PI / 180;
    const lat = latitude * Math.PI / 180;
    const x = R * Math.cos(lat) * Math.sin(lon);
    const y = R * Math.sin(lat);
    const z = R * Math.cos(lon) * Math.cos(lat);
    return { x, y, z };
}
export default lon2xyz;
一些其餘教程

     一些其餘教程會要求 經度取反, 同時把x與z進行顛倒,不推薦那種寫法, 咱們就按正常的思路來便可。

十一. 圓圓的地球

     通過不懈的努力咱們終於拯救了圓球, 讓咱們看看他還缺什麼吧:
image.png
從上圖咱們能夠看出, 其實問題仍是挺多的, 好比線條之間互相遮蓋, 咱們應該讓這個地球不可透視, 以及暫時這個地球不可點擊, 而且真實度上作的不夠, 真是技術路漫漫那。

end

     下一篇就要講解如何在地圖上打點以及爲地球添加光暈等等效果, 到此時這個系列文章還不到一半哦, 射線拾取國家以及三角拋分方面的知識也會陸續付出水面, 精彩有趣的知識還在後面, 但願與你一塊兒進步。

相關文章
相關標籤/搜索