前言 之前在part2中說的添加自定義主題配色已經開發完成了,除此之外我還添加了一些的3d特效。 前期文章 這是part1的文章:https://www.cnblogs.com/xi12/p/16690119.html 這是part2的文章:https://www.cnblogs.com/xi12/ ...
前言
- 之前在part2中說的添加自定義主題配色已經開發完成了,除此之外我還添加了一些的3d特效。
前期文章
- 這是part1的文章:https://www.cnblogs.com/xi12/p/16690119.html
- 這是part2的文章:https://www.cnblogs.com/xi12/p/16864419.html
成果鏈接
- 瀏覽鏈接:https://xi1213.gitee.io/covid19-visualization
- 項目鏈接:https://gitee.com/xi1213/covid19-visualization
具體效果
最後出來的效果還是蠻炫的。
添加與修改
- 設置中添加主題定製,預設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();//創建球體光暈
};
接下來我們詳細分析一下。
赤道實線環
即為赤道上最靠近球體內層的一層環。
- 該環使用RingGeometry幾何體實現,參數分別為:內半徑、外半徑、分段數。
- 剛創建出來的環形是平行於屏幕的,需要改變環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);
};
赤道飛線環
即為赤道第二靠近球體的環。
- 仔細觀察會發現,它其實並不是一個環,而是一條弧線再在環繞球體運動。
- 實現該弧線的時候發現,webGL的線條由於渲染器的限制,並不能設置寬度,但可以通過three.meshline(這是一個three插件,three不自帶,需要npm i three.meshline安裝)中的MeshLineMaterial來實現寬度的設置,修改MeshLineMaterial中的lineWidth屬性值即可。
- dashArray為弧線段數量的倒數(0.5即為2條,0.3即為3條),dashRatio為線段的不可見部分與可見部分的比例。
- 幾何體使用了BufferGeometry,設置setFromPoint,提取THREE.Path繪製出的arc的數據,改變幾何體頂點屬性即可。
- arc參數依次為:弧線中心x與y值、弧線半徑、起始角、終止角、是否順時針方向創建弧線(預設false)。
- 最後創建完成後同樣需要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);
};
赤道虛線環
即為赤道第三靠近球體的白色虛線環。
- 該環是由50個小白點組成,幾何體使用了BufferGeometry,材質使用了PointsMaterial,組使用了Points。
- 幾何體中需要使用Math.cos與sin改變點單位向量的xyz值,然後將位置列表positions使用Float32BufferAttribute(值得註意的是Float32Attribute已被刪除棄用)設置position屬性至ringPointGeometry中。
- 材質中記得設置transparent: false,與size尺寸。
- 最後將幾何體與材質添加到點的組中,將點組添加到球體組中即可。
//創建赤道虛線環
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);
};
赤道尖刺環
即為赤道環中類似鐘錶刻度的環形。
- 該環使用LineBasicMaterial材質、BufferGeometry幾何體、LineSegments組。
- 材質中由於webGL限制不能使用linewidth,始終寬度為1。
- 幾何體頂點的處理類似創建虛線環,迴圈改變每個尖刺的xyz向量即可。
- 若要改變指定尖刺的長度只需使用multiplyScalar向量與標量相乘。
- 最後將材質與幾何體添加到組中即可。
//創建赤道尖刺
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); //將網格放進組
};
爆炸環
即為赤道最外面一層環,它會不斷的放大漸變,形成類似爆炸衝擊波一樣的效果。具體原理是這樣的。
- 先直接用MeshBasicMaterial材質中的map載入一張透明環形貼圖,記得設置transparent、side、depthWrite、blending屬性。
- 再使用PlaneGeometry幾何體添加一個平面矩形,其具體參數為:矩形寬,矩形高、寬分段數、高分段數。
- 然後將幾何體與材質添加到組中,完成後的平面也是平行於屏幕的,記得設置rotation.x的值,使之垂直於屏幕。
- 最後要讓環產生動畫需要結合gsap(這是最健全的web動畫庫之一,生成動畫十分方便)的fromTo方法。fromTo中第一個參數為產生動畫的對象,第二個參數為動畫開始狀態,第三個參數為動畫結束狀態(其中包含動畫時長duration)。
- 這時你會發現自己的動畫只會動一次,其實只需將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);
};
球體光暈
即為球體外部的一層光暈。
- 因為光暈是需要一直平行於屏幕的,所以我們這裡直接採用SpriteMaterial材質,將其添加到Sprite中,Sprite精靈的特性就是可以一直正對相機。
- 生成材質時需要添加光暈的透明貼圖,同時設置屬性: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啊大哥們!)。