Threejs模仿實現滴滴官網首頁地球動畫

偶然翻滴滴官網看到首頁下翻第六欄(大概)有個絢麗的地球的三維動畫,試着用there.js實現了下,基本實現了動畫效果,不過仍是有些問題;這個動畫看似簡單,但也用到好的繪製方法和計算,也是學習webgl的一個很好的示例,先寫一下主要功能的實現;git

先看示例:http://39.106.166.212:8080/webgl/t4(因爲是寫demo的一個項目,內容較多沒作優化,第一次加載會會比較慢,需多等待幾秒)web

(lice這個截圖工具也是很很差用,每次都截到一半 ╮(╯﹏╰)╭)canvas

1、 3d繪製場景的構建

繪製一個3d程序首先須要添加 渲染器場景照相機 這些元素,這裏補充一個燈光;markdown

一、渲染器

首先建立一個渲染器,參數爲頁面中的canvas元素,異步

渲染器的做用就是把3d場景的內容結合照相機渲染到頁面中,ide

最後將畫布背景設爲白色。工具

const renderer = new Three.WebGLRenderer({canvas: this.$refs.thr});
renderer.setClearColor(0x000000);
複製代碼

二、場景

場景顧名思義,就是添加一個你發揮(繪製)的場地,後面全部繪製的元素都要添加到場景中;oop

cosnt scene = new Three.Scene();學習

三、照相機

照相機就像人的視覺或說從什麼角度去看場景,看場景的位置和視線的方向決定了渲染到頁面的內容。故通常須要設置兩個參數相機位置position、視線方向lookAt,,在webgl實際上是須要三組參數視點,觀察點,和上方向。thresjs中好像是默認Y軸爲上方向了,這裏使用透視相機。優化

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);
複製代碼

四、燈光

這裏使用THREE.HemisphereLight光,能夠更加貼近天然的戶外光照效;

let light = new THREE.HemisphereLight(0xffffff); 
light.position.set(0, 0, 200); 
scene.add(light)
複製代碼

以上咱們基本的繪製要素已添加完成,下面開始繪製各個幾何體內容;

幾何體的繪製有三部:建立幾何體,建立材料,添加網格模型;

2、地球的繪製

threejs中提供了球體的繪製,咱們只需建立一個球體,材料使用紋理貼圖方式添加地圖;

貼圖圖片是我從官網上找的

const geometry = new THREE.SphereGeometry(this.radius, 100, 100); // 球體      
const textureLoader = new THREE.TextureLoader(); // 建立紋理貼圖      
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);
});
複製代碼

因爲圖片加載是異步的 ,這裏需等圖片加載完成後才能建立材質;

這裏咱們就建立好了一個地球模型,接着還要讓其轉動起來;(中間爲xyz座標軸)

threejs提供了幾何體的基本3d變換,直接使用rotateY(angleChange)根據時間設置其繞y軸(綠色軸)旋轉角度便可;

3、球面座標點的繪製

一、在繪製球面位置點時,需先前先看下球座標系,肯定點的位置,webgl中座標方向與下圖不一致,可是對點的表示方法是一致的;

球面上任意點能夠用r,θ,φ表示,也可經過如下公式轉化到直角座標系中

x=rsinθcosφ.

y=rsinθsinφ.

z=rcosθ

但實際中地球位置咱們通常也會使用經緯度表示。。。 下面寫個經緯度轉座標的方法。

threejs提供了THREE.Math.degToRad方法能夠將經緯度轉化爲θ,φ,轉化方法以下,這裏爲了方便後面使用,我直接返回一個3維向量;

getPosition(longitude, latitude, radius = this.radius) {      // 經度,緯度轉換爲座標      
  let lg = THREE.Math.degToRad(longitude);      
  let lt = THREE.Math.degToRad(latitude);      // 獲取x,y,z座標      
  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);  
},
複製代碼

二、知道了位置的表示方法後開始繪製表示位置的點

根據示例位置點的由點和一個圓環組成,咱們先繪製一個二維平面內的點和圓弧,在經過設置其位置和旋轉方式移動到目標座標位置點;

(這裏也能夠繪製幾何小球體來模擬)

(1)點的繪製

THREE.Shape是用來繪製二維平面內的形狀的,設置其形狀爲圓弧,便可實現一個原點;

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)位置的設置

position.set(pos.x, pos.y, pos.z);
複製代碼

(4)二維圖形旋轉至球面

THREE.Spherical()方法 ,可將座標點轉化爲座標點轉化回球座標系的偏移角度;

let spherical = new THREE.Spherical();
spherical.setFromCartesianCoords(pos.x, pos.y, pos.z);
複製代碼

設置位置點旋轉

Point.rotateX(spherical.phi - Math.PI / 2);
Point.rotateY(spherical.theta);
複製代碼

這裏爲何要 - Math.PI / 2 是由於開始咱們繪製時,平面是垂直於y軸的平面,看下面這幅圖吧;

4、接着繪製連接球面兩點間的連線

鏈接兩點的曲線需在球面上方,

兩點間能夠坐出無數條曲線,那麼如何肯定曲線,咱們需本身再選擇合適的參數來肯定;

首先想的是二階貝塞爾曲線,取兩點的中點爲控制點,但若是連接兩點恰好位於球面的兩個對稱端點(兩點間連線爲直徑)時,控制點需在無窮遠處;

故考慮使用三階貝塞爾曲線,鏈接球面兩點和球心,三點肯定的一個平面以下圖,

連接v1 v1,取中點c,連接oc,作一條射線,在射線取一點p,連接v1p,v2p,在v1,v2上取兩點做爲控制點;

求兩點的中點

getVCenter(v1, v2) {  let v = v1.add(v2);  return v.divideScalar(2); }
複製代碼

獲取兩點間指定比例位置座標

getLenVcetor(v1, v2, len) {   
    let v1v2Len = v1.distanceTo(v2);   
    return v1.lerp(v2, len / v1v2Len);
}
複製代碼

獲取貝塞爾控制點

// 獲取貝塞爾控制點    
getBezierPoint(v0, v3) {          
 let angle = (v0.angleTo(v3) * 180) / Math.PI; // 0 ~ Math.PI       // 計算向量夾角 
 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()));      // 頂點座標 
 let vtop = rayLine.at(hLen / rayLine.at(1).distanceTo(p0), vtop); // 位置      
 // 控制點座標      
 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(); //聲明一個幾何體對象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);    
    },
複製代碼

5、小球的運動軌跡

小球的動畫咱們使用three的幀動畫,路徑能夠直接使用上一步中的曲線;

一、繪製小球

drawSportPoint(position, name) {    
    let box = new THREE.SphereGeometry(6, 6, 6);    
    let material = new THREE.MeshLambertMaterial({      color: 0x00bfff    });      //材質對象    
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;
},
複製代碼

二、讓小球動起來

sport(curvePoints, index) {      
    const Ball = this.drawSportPoint(curvePoints[0]);      
    let arr = Array.from(Array(101), (v, k) => k);      // 生成一個時間序列      
    let times = new Float32Array(arr);
    let posArr = [];      
    curvePoints.forEach(elem => {
         posArr.push(elem.x, elem.y, elem.z);      
    });      // 建立一個和時間序列相對應的位置座標系列      
    let values = new Float32Array(posArr);      // 建立一個幀動畫的關鍵幀數據,曲線上的位置序列對應一個時間序列      
    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();
},
複製代碼

三、在requestAnimationFrame中添加觸發動畫

this.mixer.update(this.clock.getDelta());
複製代碼
相關文章
相關標籤/搜索