疫情視覺化part3

曦12發表於2022-12-02

前言

  • 之前在part2中說的新增自定義主題配色已經開發完成了,除此之外我還新增了一些的3d特效。

前期文章

成果連結

具體效果

最後出來的效果還是蠻炫的。
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

新增與修改

  1. 設定中新增主題定製,預設3種主題。
  2. 主題定製支援取色器取色。
  3. 新增3d球體的環形動態特效。

主題預設

由於專案一開始就使用的dataV,並且有計劃的提取出了顏色值,所以定製主題便不是很麻煩,直接提取改變dataV的color屬性即可,其他部分直接使用vue的動態樣式繫結。

設定配置

先在設定元件SetDrawer中預先設定好需要使用的配置資訊,包括預設的主題,然後使用vue3的onBeforeMount在掛載元件之前將包含顏色的配置資訊用sessionStorage.setItem("config", JSON.stringify(setData.value))先儲存到瀏覽器本地,這樣可以防止重新整理頁面時配置資訊丟失重置。

//系統配置
function sysConfig() {
    setData.value.sysVer = PK.version//獲取系統版本號
    process.env.NODE_ENV == "development" ?
        setData.value.dataType = dataTypeList[0] ://開發環境使用離線資料
        setData.value.dataType = dataTypeList[1];//生產環境使用線上資料
    let ss = sessionStorage.getItem("config");//獲取快取配置
    //快取中有配置取出配置,無則使用初始配置
    if (ss) {
        let cuVer = setData.value.sysVer,//當前版本號
            ssVer = JSON.parse(ss).sysVer,//快取版本號
            isUpDate = null;//是否更新快取
        //當前版本號與快取版本號若不等清除快取使用當前配置,否則使用快取配置
        (cuVer !== ssVer) ?
            (isUpDate = true) :
            isUpDate = false;
        isUpDate ?
            (
                (sessionStorage.removeItem("config")),//清除快取
                (sessionStorage.setItem("config", JSON.stringify(setData.value)))//設定快取配置
            ) :
            (setData.value = JSON.parse(ss));
    } else {
        sessionStorage.setItem("config", JSON.stringify(setData.value));//設定快取配置
    }
};

值得注意的是,需要在存入配置資訊前判斷快取中是否已經存在配置資訊,若有,則直接使用快取配置,若沒有則存入預先在程式碼中寫好配置。

載入配置

在頁面載入瞬間就需要獲取顏色值的元件中使用onMounted與JSON.parse(sessionStorage.getItem("config") )獲取到上一步在快取中存下的配置資訊,從配置資訊中獲取到顏色值,最後在利用該值將dom渲染出來,dataV的部分支援使用:color="['#fff', '#aaa']"來動態改變顏色,其中陣列中第一個值為主色,第二個為副色; 非dataV的部分直接使用vue動態繫結樣式的語法:style=“{color:#fff}”來修改配色。
在這裡插入圖片描述

切換配置

切換主題時直接在設定元件中改變當前使用的配色值然後重新整理頁面即可,因為我在切換3d球體顏色時偷了一個懶,正常流程是獲取球體mesh,改變材質的color值,然後更新的。我直接重新整理了瀏覽器(又不是不能用(*  ̄︿ ̄)),重建了場景、相機、球體等。

預設主題

我預設了三對主色、副色的顏色值。為了看起來更和諧,建議使用同一顏色的深色與淺色來搭配。
在這裡插入圖片描述
v-for即可渲染出切換按鈕。
在這裡插入圖片描述

取色器

原生input實現

我也是在無意中發現input的type居然是支援color的。可以直接原生實現取色器,這樣就可以用顏色吸管獲取螢幕中的任何顏色了:

 <input id="colorInp" style="height: 0px;opacity: 0;width: 0px;margin: 0px;padding: 0px;" type="color" />

繫結事件

要使用自己的按鈕點選開啟取色器,可以直接將input的高寬賦為0,不透明度賦為0,將其隱藏後,再使用按鈕繫結事件開啟取色器即可:

<el-button class="main-color" :color="setData.sysColor[0]" @click="changeColor(0)">主色</el-button>
function changeColor(type: Number) {
    (document.getElementById("colorInp") as any).click();//手動點選取色器
    colorType = type;//改變顏色型別
};

在這裡插入圖片描述

環形特效

在這裡插入圖片描述
專案中一共有七種環形效果:

//建立環
function createRings() {
  createEquatorSolidRing(earthSize + 20);//建立赤道實線環
  createEquatorFlyline(earthSize + 30);//建立赤道飛線環
  createEquatorDottedLineRing(earthSize + 35);//建立赤道虛線環
  createSpikes(earthSize + 40);//建立赤道尖刺
  createUpDownRing(earthSize - 50, earthSize - 40);//建立南北極環
  createExpandRing();//建立爆炸環
  createSphereGlow();//建立球體光暈
};

接下來我們詳細分析一下。

赤道實線環

在這裡插入圖片描述
即為赤道上最靠近球體內層的一層環。

  1. 該環使用RingGeometry幾何體實現,引數分別為:內半徑、外半徑、分段數。
  2. 剛建立出來的環形是平行於螢幕的,需要改變環mesh的rotation屬性,繞x軸旋轉90度即可。
//建立赤道實線環
function createEquatorSolidRing(r: any) {
  //建立裡層的環
  let ringGeometry = new THREE.RingGeometry(r - 2, r + 2, 100);
  let ringMaterial = new THREE.MeshBasicMaterial({
    color: dvColor.value[0],
    opacity: .3,
    side: THREE.DoubleSide,
    fog: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  });
  let ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
  ringMesh.rotation.x = 90 * Math.PI / 180;
  earthGroup.add(ringMesh);
};

赤道飛線環

在這裡插入圖片描述
即為赤道第二靠近球體的環。

  1. 仔細觀察會發現,它其實並不是一個環,而是一條弧線再在環繞球體運動。
  2. 實現該弧線的時候發現,webGL的線條由於渲染器的限制,並不能設定寬度,但可以透過three.meshline(這是一個three外掛,three不自帶,需要npm i three.meshline安裝)中的MeshLineMaterial來實現寬度的設定,修改MeshLineMaterial中的lineWidth屬性值即可。
  3. dashArray為弧線段數量的倒數(0.5即為2條,0.3即為3條),dashRatio為線段的不可見部分與可見部分的比例。
  4. 幾何體使用了BufferGeometry,設定setFromPoint,提取THREE.Path繪製出的arc的資料,改變幾何體頂點屬性即可。
  5. arc引數依次為:弧線中心x與y值、弧線半徑、起始角、終止角、是否順時針方向建立弧線(預設false)。
  6. 最後建立完成後同樣需要rotation.x改變角度。
//建立赤道飛線
function createEquatorFlyline(r: any) {
  const geometry = new THREE.BufferGeometry();
  const path = new THREE.Path();
  path.arc(0, 0, r, 0, Math.PI * 2);
  const points = path.getPoints(100);//切割段數
  geometry.setFromPoints(points);
  const line = new MeshLine();
  // 設定幾何體
  line.setGeometry(geometry)
  const material = new MeshLineMaterial({
    color: dvColor.value[0],
    lineWidth: 1, // 線條的寬度
    dashArray: .5, // 該數值倒數為線段數量
    dashRatio: .5, // 不可見與可見比例
    transparent: true, // 設定透明度
  })
  flylineMesh = new THREE.Mesh(line.geometry, material);
  flylineMesh.rotation.x = 90 * Math.PI / 180;
  earthGroup.add(flylineMesh);
};

赤道虛線環

在這裡插入圖片描述
即為赤道第三靠近球體的白色虛線環。

  1. 該環是由50個小白點組成,幾何體使用了BufferGeometry,材質使用了PointsMaterial,組使用了Points。
  2. 幾何體中需要使用Math.cos與sin改變點單位向量的xyz值,然後將位置列表positions使用Float32BufferAttribute(值得注意的是Float32Attribute已被刪除棄用)設定position屬性至ringPointGeometry中。
  3. 材質中記得設定transparent: false,與size尺寸。
  4. 最後將幾何體與材質新增到點的組中,將點組新增到球體組中即可。
//建立赤道虛線環
function createEquatorDottedLineRing(r: any) {
  const positions = [];
  let ringPointGeometry = new THREE.BufferGeometry(); //環形點幾何體
  let pointNum = 50;//點的數量
  let ringPointAngle = (2 * Math.PI) / pointNum; //環形點角度
  for (let o = 0; o < 500; o++) {
    let n = new THREE.Vector3(); //點的向量
    n.x = r * Math.cos(ringPointAngle * o); //計算點的角度
    n.y = 0;
    n.z = r * Math.sin(ringPointAngle * o);
    positions.push(n.x, n.y, n.z);
  }
  ringPointGeometry.setAttribute(
    "position",
    new THREE.Float32BufferAttribute(positions, 3)
  );//設定位置屬性
  let ringPointMaterial = new THREE.PointsMaterial({
    //環形點材質
    size: 3,
    // color: dvColor.value[0],
    transparent: false,
    blending: THREE.AdditiveBlending,
    side: THREE.DoubleSide,
    depthWrite: false,
  });
  dotLineRingMesh = new THREE.Points(
    ringPointGeometry,
    ringPointMaterial
  );
  dotLineRingMesh.name = "赤道虛線";
  earthGroup.add(dotLineRingMesh);
};

赤道尖刺環

在這裡插入圖片描述
即為赤道環中類似鐘錶刻度的環形。

  1. 該環使用LineBasicMaterial材質、BufferGeometry幾何體、LineSegments組。
  2. 材質中由於webGL限制不能使用linewidth,始終寬度為1。
  3. 幾何體頂點的處理類似建立虛線環,迴圈改變每個尖刺的xyz向量即可。
  4. 若要改變指定尖刺的長度只需使用multiplyScalar向量與標量相乘。
  5. 最後將材質與幾何體新增到組中即可。
//建立赤道尖刺
function createSpikes(spikeRadius: any) {
  let spikesVerticesArray = [];
  let spikesObject = new THREE.Group(); //建立尖刺的組
  spikesObject.name = "赤道尖刺";
  earthGroup.add(spikesObject); //將尖刺組新增到旋轉組中
  //建立尖刺
  let spikeNum = 400;//尖刺數量
  let o = (2 * Math.PI) / spikeNum;
  for (let s = 0; s < spikeNum; s++) {
    let r = new THREE.Vector3();
    r.x = spikeRadius * Math.cos(o * s);
    r.y = 0;
    r.z = spikeRadius * Math.sin(o * s);
    r.normalize();//歸一化,將該向量轉化為向量單位
    r.multiplyScalar(spikeRadius);
    let i = r.clone(); //克隆r至i
    (s % 10 == 1) ? i.multiplyScalar(1.1) : i.multiplyScalar(1.05);//每10個計算一次向量與標量相乘
    spikesVerticesArray.push(r); //將向量存入尖刺頂點列表
    spikesVerticesArray.push(i);
  }
  let n = new Float32Array(3 * spikesVerticesArray.length); //建立頂點陣列
  for (let s = 0; s < spikesVerticesArray.length; s++) {
    n[3 * s] = spikesVerticesArray[s].x;//給頂點陣列設定座標
    n[3 * s + 1] = spikesVerticesArray[s].y;
    n[3 * s + 2] = spikesVerticesArray[s].z;
  }
  //尖刺材質
  let spikesMaterial = new THREE.LineBasicMaterial({
    // linewidth: 1,//webgl渲染器限制,不能設定寬度,始終為1(three.meshline外掛可解決)
    // color: "#fff",
    color: dvColor.value[0],
    transparent: true,
    opacity: .5
  });
  let spikesBufferGeometry = new THREE.BufferGeometry(); //建立尖刺幾何體
  spikesBufferGeometry.setAttribute(
    "position",
    new THREE.BufferAttribute(n, 3)
  ); //新增位置屬性
  let spikesMesh = new THREE.LineSegments(
    spikesBufferGeometry,
    spikesMaterial
  );
  spikesObject.add(spikesMesh); //將網格放進組
};

爆炸環

在這裡插入圖片描述
即為赤道最外面一層環,它會不斷的放大漸變,形成類似爆炸衝擊波一樣的效果。具體原理是這樣的。

  1. 先直接用MeshBasicMaterial材質中的map載入一張透明環形貼圖,記得設定transparent、side、depthWrite、blending屬性。
  2. 再使用PlaneGeometry幾何體新增一個平面矩形,其具體引數為:矩形寬,矩形高、寬分段數、高分段數。
  3. 然後將幾何體與材質新增到組中,完成後的平面也是平行於螢幕的,記得設定rotation.x的值,使之垂直於螢幕。
  4. 最後要讓環產生動畫需要結合gsap(這是最健全的web動畫庫之一,生成動畫十分方便)的fromTo方法。fromTo中第一個引數為產生動畫的物件,第二個引數為動畫開始狀態,第三個引數為動畫結束狀態(其中包含動畫時長duration)。
  5. 這時你會發現自己的動畫只會動一次,其實只需將createExpandRingAnimation新增到requestAnimationFrame動畫請求幀使用的方法中使其一直render渲染即可。
//建立漸變環
function createExpandRing() {
  let ringMaterial = new THREE.MeshBasicMaterial({
    map: new THREE.TextureLoader().load(ringImg),
    color: new THREE.Color(dvColor.value[0]),//顏色
    transparent: true,
    opacity: 1,
    side: THREE.DoubleSide,
    fog: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  });
  let ringGeometry = new THREE.PlaneGeometry(earthSize * 2, earthSize * 2, 10, 10);
  expandRingMesh = new THREE.Mesh(ringGeometry, ringMaterial);
  expandRingMesh.name = "放大環";
  expandRingMesh.rotation.x = 90 * Math.PI / 180;
  earthGroup.add(expandRingMesh);
};

//建立漸變環動畫
function createExpandRingAnimation() {
  gsap.isTweening(expandRingMesh.scale) ||//環動畫
    (gsap.fromTo(
      expandRingMesh.scale,//縮放漸變
      { x: 1, y: 1, },
      { x: 2.7, y: 2.7, duration: 1.5 }
    ),
      gsap.fromTo(
        expandRingMesh.material,//材質的透明度漸變
        { opacity: 1, },
        { opacity: 0, duration: 1.5 }
      ))
};

南北極環

在這裡插入圖片描述
在這裡插入圖片描述
如圖,即為球體上下的雙層環形。其生成方法與赤道實線環一樣,不同之處是需要改變position.y值,使之移動到球體南北極。

//建立上下環
function createUpDownRing(r1: any, r2: any) {
  let ringsObject = new THREE.Group(); //建立環的組
  ringsObject.name = "南北極環";
  earthGroup.add(ringsObject); //將環新增到場景中
  //建立內環
  let a = new THREE.RingGeometry(r1, r1 - 2, 100); //圓環幾何體(內半徑,外半徑,分段數)
  let ringsOuterMaterial = new THREE.MeshBasicMaterial({
    color: dvColor.value[0],
    transparent: true,
    opacity: .3,
    side: THREE.DoubleSide,
    fog: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  });
  let o = new THREE.Mesh(a, ringsOuterMaterial);
  o.rotation.x = 90 * Math.PI / 180; //設定旋轉
  let r = o.clone(); //克隆外環網格o至r
  o.position.y = 95; //設定位置
  r.position.y = -95;
  ringsObject.add(o);
  ringsObject.add(r);
  //建立外環
  let t = new THREE.RingGeometry(r2, r2 - 2, 100);
  let ringsInnerMaterial = new THREE.MeshBasicMaterial({
    color: dvColor.value[0],
    transparent: true,
    opacity: .3,
    side: THREE.DoubleSide,
    fog: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  });
  let i = new THREE.Mesh(t, ringsInnerMaterial);
  i.rotation.x = 90 * Math.PI / 180;
  let n = i.clone();
  i.position.y = 100;
  n.position.y = -100;
  ringsObject.add(i);
  ringsObject.add(n);
};

球體光暈

在這裡插入圖片描述
即為球體外部的一層光暈。

  1. 因為光暈是需要一直平行於螢幕的,所以我們這裡直接採用SpriteMaterial材質,將其新增到Sprite中,Sprite精靈的特性就是可以一直正對相機。
  2. 生成材質時需要新增光暈的透明貼圖,同時設定屬性:blending、depthWrite、transparent、side。
//建立球體發光環
function createSphereGlow() {
  //SpriteMaterial材質始終朝向平面
  let glowMaterial = new THREE.SpriteMaterial({
    map: new THREE.TextureLoader().load(earthGlowImg),
    color: new THREE.Color(dvColor.value[0]),//顏色
    transparent: true,
    opacity: 1,
    side: THREE.DoubleSide,
    fog: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  })
  let glowSprite = new THREE.Sprite(glowMaterial);
  glowSprite.scale.set(earthSize * 3.2, earthSize * 3.2, 1); //點大小
  earthGroup.add(glowSprite);
};

結語

成都的12月份好冷啊ヽ(≧□≦)ノ,手指頭開始造反不聽使喚了,專案到這裡差不多該是告一段落了,本專案僅作為我學習webgl與視覺化結合使用的一個demo,專案是完全開源了的,有想使用的可以直接在我的gitee上clone,連結在本文開頭(不要忘記star啊大哥們!)。

相關文章