Threejs實(shí)現(xiàn)滴滴官網(wǎng)首頁(yè)地球動(dòng)畫功能
偶然翻滴滴官網(wǎng)看到首頁(yè)下翻第六欄(大概)有個(gè)絢麗的地球的三維動(dòng)畫,試著用there.js實(shí)現(xiàn)了下,基本實(shí)現(xiàn)了動(dòng)畫效果,不過(guò)還是有些問(wèn)題;這個(gè)動(dòng)畫看似簡(jiǎn)單,但也用到好的繪制方法和計(jì)算,這里先寫一下主要功能的實(shí)現(xiàn);
先看示例:http://39.106.166.212:8080/webgl/t4(由于是寫dome的一個(gè)項(xiàng)目,內(nèi)容較多沒(méi)做優(yōu)化,第一次加載會(huì)會(huì)比較慢,需多等待幾秒)

(lice這個(gè)截圖工具也是很不好用,每次都截到一半 ╮(╯﹏╰)╭)
一、 3d繪制場(chǎng)景的構(gòu)建
繪制一個(gè)3d程序首先需要添加 渲染器,場(chǎng)景,照相機(jī) 這些元素,這里補(bǔ)充一個(gè)燈光;
1、渲染器
首先創(chuàng)建一個(gè)渲染器,參數(shù)為頁(yè)面中的canvas元素,
渲染器的作用就是把3d場(chǎng)景的內(nèi)容結(jié)合照相機(jī)渲染到頁(yè)面中,
最后將畫布背景設(shè)為白色。
const renderer = new Three.WebGLRenderer({canvas: this.$refs.thr});
renderer.setClearColor(0x000000);
2、場(chǎng)景
場(chǎng)景顧名思義,就是添加一個(gè)你發(fā)揮(繪制)的場(chǎng)地,后面所有繪制的元素都要添加到場(chǎng)景中;
cosnt scene = new Three.Scene();
3、照相機(jī)
照相機(jī)就像人的視覺或說(shuō)從什么角度去看場(chǎng)景,看場(chǎng)景的位置和視線的方向決定了渲染到頁(yè)面的內(nèi)容。故一般需要設(shè)置兩個(gè)參數(shù)相機(jī)位置position、視線方向lookAt,,在webgl其實(shí)是需要三組參數(shù)視點(diǎn),觀察點(diǎn),和上方向。thresjs中好像是默認(rèn)Y軸為上方向了,這里使用透視相機(jī)。
const camera = new THREE.PerspectiveCamera(45, 500 / 500, 1, 1500); camera.position.set(100, 100, 1000); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(this.camera);
4、燈光
這里使用THREE.HemisphereLight光,可以更加貼近自然的戶外光照效;
let light = new THREE.HemisphereLight(0xffffff); light.position.set(0, 0, 200); scene.add(light)
以上我們基本的繪制要素已添加完成,下面開始繪制各個(gè)幾何體內(nèi)容;
幾何體的繪制有三部:創(chuàng)建幾何體,創(chuàng)建材料,添加網(wǎng)格模型;
二、地球的繪制
threejs中提供了球體的繪制,我們只需創(chuàng)建一個(gè)球體,材料使用紋理貼圖方式添加地圖;
貼圖圖片資源是我從官網(wǎng)上找的
const geometry = new THREE.SphereGeometry(this.radius, 100, 100); // 球體
const textureLoader = new THREE.TextureLoader(); // 創(chuàng)建紋理貼圖
const texture = textureLoader.load(require("@/assets/map.jpg"),texture => {
let material = new THREE.MeshLambertMaterial({map: texture,transparent: true,});
let mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
由于圖片加載是異步的 ,這里需等圖片加載完成后才能創(chuàng)建材質(zhì);
這里我們就創(chuàng)建好了一個(gè)地球模型,接著還要讓其轉(zhuǎn)動(dòng)起來(lái);(中間為xyz坐標(biāo)軸)
threejs提供了幾何體的基本3d變換,直接使用rotateY(angleChange)根據(jù)時(shí)間設(shè)置其繞y軸(綠色軸)旋轉(zhuǎn)角度即可;

三、球面坐標(biāo)點(diǎn)的繪制
1、在繪制球面位置點(diǎn)時(shí),需先前先看下球坐標(biāo)系,確定點(diǎn)的位置,webgl中坐標(biāo)方向與下圖不一致,但是對(duì)點(diǎn)的表示方法是一致的;

球面上任意點(diǎn)可以用r,θ,φ表示,也可通過(guò)以下公式轉(zhuǎn)化到直角坐標(biāo)系中
x=rsinθcosφ.
y=rsinθsinφ.
z=rcosθ
但實(shí)際中地球位置我們一般也會(huì)使用經(jīng)緯度表示。。。 下面寫個(gè)經(jīng)緯度轉(zhuǎn)坐標(biāo)的方法。
threejs提供了THREE.Math.degToRad方法可以將經(jīng)緯度轉(zhuǎn)化為θ,φ,轉(zhuǎn)化方法如下,這里為了方便后面使用,我直接返回一個(gè)3維向量;
getPosition(longitude, latitude, radius = this.radius) { // 經(jīng)度,緯度轉(zhuǎn)換為坐標(biāo)
let lg = THREE.Math.degToRad(longitude);
let lt = THREE.Math.degToRad(latitude); // 獲取x,y,z坐標(biāo)
let temp = radius * Math.cos(lt);
let x = temp * Math.sin(lg);
let y = radius * Math.sin(lt);
let z = temp * Math.cos(lg);
return new THREE.Vector3(x, y, z);
}
2、知道了位置的表示方法后開始繪制表示位置的點(diǎn)
根據(jù)示例位置點(diǎn)的由點(diǎn)和一個(gè)圓環(huán)組成,我們先繪制一個(gè)二維平面內(nèi)的點(diǎn)和圓弧,在通過(guò)設(shè)置其位置和旋轉(zhuǎn)方式移動(dòng)到目標(biāo)坐標(biāo)位置點(diǎn);
(這里也可以繪制幾何小球體來(lái)模擬)
(1)點(diǎn)的繪制
THREE.Shape是用來(lái)繪制二維平面內(nèi)的形狀的,設(shè)置其形狀為圓弧,即可實(shí)現(xiàn)一個(gè)原點(diǎn);
let shapePoint = new THREE.Shape();
shapePoint.absarc(0, 0, r - 4, 0, 2 * Math.PI, false);
let arcGeometry = new THREE.ShapeGeometry(shapePoint);
let arcMaterial = new THREE.MeshBasicMaterial({ color: 0x008080 });
let point = new THREE.Mesh(arcGeometry, arcMaterial);
(2)圓弧的繪制
let geometryLine = new THREE.Geometry();
let arc = new THREE.ArcCurve(0, 0, r, 0, 2 * Math.PI);
let points = arc.getPoints(40);
geometryLine.setFromPoints(points);
let LineMateri = new THREE.LineBasicMaterial({ color: 0x20b2aa });
let line = new THREE.Line(geometryLine, LineMateri);
(3)位置的設(shè)置
position.set(pos.x, pos.y, pos.z);
(4)二維圖形旋轉(zhuǎn)至球面
THREE.Spherical()方法 ,可將坐標(biāo)點(diǎn)轉(zhuǎn)化為坐標(biāo)點(diǎn)轉(zhuǎn)化回球坐標(biāo)系的偏移角度;
let spherical = new THREE.Spherical(); spherical.setFromCartesianCoords(pos.x, pos.y, pos.z);
設(shè)置位置點(diǎn)旋轉(zhuǎn)
Point.rotateX(spherical.phi - Math.PI / 2); Point.rotateY(spherical.theta);
這里為什么要 - Math.PI / 2 是因?yàn)殚_始我們繪制時(shí),平面是垂直于y軸的平面,參考下面這幅圖;

四、接著繪制鏈接球面兩點(diǎn)間的連線
連接兩點(diǎn)的曲線需在球面上方,
兩點(diǎn)間可以坐出無(wú)數(shù)條曲線,那么如何確定曲線,我們需自己再選擇合適的參數(shù)來(lái)確定;
首先想的是二階貝塞爾曲線,取兩點(diǎn)的中點(diǎn)為控制點(diǎn),但如果鏈接兩點(diǎn)剛好位于球面的兩個(gè)對(duì)稱端點(diǎn)(兩點(diǎn)間連線為直徑)時(shí),控制點(diǎn)需在無(wú)窮遠(yuǎn)處;
故考慮使用三階貝塞爾曲線,連接球面兩點(diǎn)和球心,三點(diǎn)確定的一個(gè)平面如下圖,
鏈接v1 v1,取中點(diǎn)c,鏈接oc,做一條射線,在射線取一點(diǎn)p,鏈接v1p,v2p,在v1,v2上取兩點(diǎn)作為控制點(diǎn);

求兩點(diǎn)的中點(diǎn)
getVCenter(v1, v2) {
let v = v1.add(v2);
return v.divideScalar(2);
}
獲取兩點(diǎn)間指定比例位置坐標(biāo)
getLenVcetor(v1, v2, len) {
let v1v2Len = v1.distanceTo(v2);
return v1.lerp(v2, len / v1v2Len);
}
獲取貝塞爾控制點(diǎn)
getBezierPoint(v0, v3) {
let angle = (v0.angleTo(v3) * 180) / Math.PI; // 0 ~ Math.PI // 計(jì)算向量夾角
let aLen = angle * 2.5,
hLen = angle * angle * 50;
let p0 = new THREE.Vector3(0, 0, 0); // 法線向量
let rayLine = new THREE.Ray(p0, this.getVCenter(v0.clone(), v3.clone())); // 頂點(diǎn)坐標(biāo)
let vtop = rayLine.at(hLen / rayLine.at(1).distanceTo(p0), vtop); // 位置
// 控制點(diǎn)坐標(biāo)
let v1 = this.getLenVcetor(v0.clone(), vtop, aLen);
let v2 = this.getLenVcetor(v3.clone(), vtop, aLen);
return {
v1: v1,
v2: v2
};
},
繪制三次貝塞爾曲線
drawLine(longitude, latitude, longitude2, latitude2) {
let geometry = new THREE.Geometry(); //聲明一個(gè)幾何體對(duì)象Geometry
let v0 = this.getPosition(longitude, latitude, this.radius);
let v3 = this.getPosition(longitude2, latitude2, this.radius);
let { v1, v2 } = this.getBezierPoint(v0, v3); // 三維二次貝賽爾曲線
let curve = new THREE.CubicBezierCurve3(v0, v1, v2, v3);
let curvePoints = curve.getPoints(100);
geometry.setFromPoints(curvePoints);
let material = new THREE.LineBasicMaterial({
color: 0xff7e41
});
let line = new THREE.Line(geometry, material);
this.group.add(line);
this.sport(curvePoints);
},
五、小球的運(yùn)動(dòng)軌跡
小球的動(dòng)畫我們使用three的幀動(dòng)畫,路徑可以直接使用上一步中的曲線;
1、繪制小球
drawSportPoint(position, name) {
let box = new THREE.SphereGeometry(6, 6, 6);
let material = new THREE.MeshLambertMaterial({
color: 0x00bfff
}); //材質(zhì)對(duì)象
let mesh = new THREE.Mesh(box, material);
mesh.name = name;
mesh.position.set(position.x, position.y, position.z);
this.groupBall.add(mesh);
this.group.add(this.groupBall);
return mesh;
}
2、讓小球動(dòng)起來(lái)
sport(curvePoints, index) {
const Ball = this.drawSportPoint(curvePoints[0]);
let arr = Array.from(Array(101), (v, k) => k); // 生成一個(gè)時(shí)間序列
let times = new Float32Array(arr);
let posArr = [];
curvePoints.forEach(elem => {
posArr.push(elem.x, elem.y, elem.z);
}); // 創(chuàng)建一個(gè)和時(shí)間序列相對(duì)應(yīng)的位置坐標(biāo)系列
let values = new Float32Array(posArr); // 創(chuàng)建一個(gè)幀動(dòng)畫的關(guān)鍵幀數(shù)據(jù),曲線上的位置序列對(duì)應(yīng)一個(gè)時(shí)間序列
let posTrack = new THREE.KeyframeTrack("Ball.position", times, values);
let duration = 101;
let clip = new THREE.AnimationClip("default", duration, [posTrack]);
this.mixer = new THREE.AnimationMixer(Ball);
let AnimationAction = this.mixer.clipAction(clip);
AnimationAction.timeScale = 20;
AnimationAction.play();
}
3、在requestAnimationFrame中添加觸發(fā)動(dòng)畫
this.mixer.update(this.clock.getDelta());
到此這篇關(guān)于Threejs實(shí)現(xiàn)滴滴官網(wǎng)首頁(yè)地球動(dòng)畫的文章就介紹到這了,更多相關(guān)Threejs滴滴官網(wǎng)首頁(yè)地球動(dòng)畫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
drag-and-drop實(shí)現(xiàn)圖片瀏覽器預(yù)覽
chrome的drag and drop API,它能將本地的圖片放到瀏覽器中進(jìn)行預(yù)覽,猜想一下當(dāng)我們把圖片拖拽到瀏覽器里會(huì)發(fā)生什么事情,你的瀏覽器試圖打開一個(gè)新的頁(yè)面并加載這個(gè)圖片。這篇文章給我們介紹drag-and-drop實(shí)現(xiàn)圖片瀏覽器預(yù)覽,需要的朋友可以參考下2015-08-08
JS實(shí)現(xiàn)鼠標(biāo)點(diǎn)擊展開或隱藏表格行的方法
這篇文章主要介紹了JS實(shí)現(xiàn)鼠標(biāo)點(diǎn)擊展開或隱藏表格行的方法,實(shí)例分析了javascript操作table表格與css樣式的技巧,需要的朋友可以參考下2015-03-03
javascript實(shí)現(xiàn)數(shù)字+字母驗(yàn)證碼的簡(jiǎn)單實(shí)例
本篇文章只要是對(duì)javascript實(shí)現(xiàn)數(shù)字+字母驗(yàn)證碼的簡(jiǎn)單實(shí)例進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-02-02
echarts中幾種漸變方式的具體實(shí)現(xiàn)方式
在使用echarts繪制圖表時(shí),有的時(shí)候需要使用漸變色,下面這篇文章主要給大家介紹了關(guān)于echarts中幾種漸變方式的具體實(shí)現(xiàn)方式,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
純JavaScript實(shí)現(xiàn)的兼容各瀏覽器的添加和移除事件封裝
這篇文章主要介紹了純JavaScript實(shí)現(xiàn)的兼容各瀏覽器的添加和移除事件封裝,本文直接給出實(shí)現(xiàn)代碼,代碼中帶詳細(xì)注釋,需要的朋友可以參考下2015-03-03
使用postMesssage()實(shí)現(xiàn)跨域iframe頁(yè)面間的信息傳遞方法
下面小編就為大家?guī)?lái)一篇使用postMesssage()實(shí)現(xiàn)跨域iframe頁(yè)面間的信息傳遞方法。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家一個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-03-03
js神秘的電報(bào)密碼 哈弗曼編碼實(shí)現(xiàn)
這篇文章主要介紹了js神秘的電報(bào)密碼 哈弗曼編碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
javascript實(shí)現(xiàn)繼承的簡(jiǎn)單實(shí)例
這篇文章主要介紹了javascript實(shí)現(xiàn)繼承的簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2015-07-07

