你知道 three.js 嗎
這段時間,開發的專案從資料表格,跨越到 echarts 圖表,兩種不同的展現形式,都可以直觀的分析資料之間的相互關係,但是 echarts 圖表看起來會更形象一點,並且頁面展現也很好看。當時想如果在頁面中新增一些3D的圖形,會不會變得不同,比如 github 首頁的地球展示,懷揣一些好奇心,嘗試著先看看這些圖形及動效是怎麼做的,是通過什麼實現的?
這時 threejs 出現在眼前,three.js 是 JavaScript 編寫的 WebGL 第三方庫,可以用它建立各種三維場景,其中包括了攝影機、光影、材質等各種物件。直接對 WebGL 中的原生 Api 進行研究,還是有一定難度的,會涉及圖形處理等。對於傳統 js 的開發人員,threejs 中涉及的一些概念也是很新穎的,比如什麼是幾何體、材質、場景、相機、渲染器、控制器等。
實際上建立 3D 圖形的過程可以想象成拍照,在一個大場景中,首先準備一個相機,設定相機引數,調整視窗的大小或遠近,讓物體儘可能呈現在視野中,再新增光源,使物體產生不同的陰影效果。
場景
這時必須建立的,接下來的物體、座標系等需要呼叫 add 方法新增到場景中去。 ``` import * as THREE from "three";
this.scene = new THREE.Scene(); ```
相機
相機就像人的眼睛,在視角內的物體,都可以被捕捉到,這裡有近端面和遠端面的配置,就好比能人能看到最近的位置以及最遠的位置。
// width、height為畫布寬高
setCamera() {
this.camera = new THREE.PerspectiveCamera(
75, // 相機視角
this.width / this.height, // 長寬比例
0.1, // 近端面位置
1000 // 遠端面位置
);
// 設定相機位置
this.camera.position.set(0, 0, 10);
}
物體
新增一個球體,可以內建物件通過 SphereGeometry
例項直接生成。
setSphere() {
const geometry = new THREE.SphereGeometry(1, 20, 20);
const material = new THREE.MeshBasicMaterial();
const sphere = new THREE.Mesh(geometry, material);
this.scene.add(sphere);
}
新增完這三個要素,要在頁面上呈現 3D 物體,則需要關鍵一步,通過 WebGLRenderer
函式進行渲染處理,將生成的canvas,及 domElement 屬性,新增到建立的 dom 容器中,this.container。
setRender() {
this.renderer = new THREE.WebGLRenderer({
antialias: true, // 抗鋸齒
});
// 設定渲染的尺寸大小
this.renderer.setSize(this.width, this.height);
// 投射陰影
this.renderer.shadowMap.enabled = true;
this.container?.appendChild(this.renderer.domElement);
}
控制器
為了更方便的檢視物體,及判斷物體座標位置,我們還需要新增輔助座標系及軌道控制器,實現滑鼠拖動360度旋轉檢視,如上圖,座標系中紅色代表 X 軸. 綠色代表 Y 軸. 藍色代表 Z 軸。
這裡我們先了解一下三維座標系,分為左手座標系和右手座標系。
WebGL 和 Three.js 使用的座標系是右手座標系,即右手伸開,拇指為 X 軸,食指為 Y 軸,手心為 Z 軸。
``` import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
setControls() { // 建立軌道控制器 this.controls = new OrbitControls(this.camera, this.renderer.domElement); // 設定控制器阻尼,讓控制器更有真實效果,必須在動畫迴圈裡呼叫.update()。 this.controls.enableDamping = true;
// 新增座標軸輔助器
const axesHelper = new THREE.AxesHelper(5);
this.scene.add(axesHelper);
}
每次旋轉或放大操作都需要重新渲染,因此需要重複函式呼叫不斷的更新物體,用到 `requestAnimationFrame` 函式。
animate() {
this.controls.update();
this.renderer.render(this.scene, this.camera);
// 渲染下一幀的時候就會呼叫render函式
requestAnimationFrame(this.animate.bind(this));
}
```
平行光和點光源
threejs 還可以模擬物體在光照下的陰影效果,但是需要滿足幾點條件:
- 材質滿足能夠對光照有反應,例如使用 MeshStandardMaterial 建立物體材料,而不是MeshBasicMaterial。
const material = new THREE.MeshBasicMaterial();
- 渲染器開啟陰影計算
this.renderer.shadowMap.enabled = true;
- 光照投射陰影
- 球投射陰影
- 平面接受陰影
首先新增光源,兩者選擇一個即可
- 平行光
setLight() {
const light = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(light);
// 平行光投射陰影
const directLight = new THREE.DirectionalLight(0xffffff, 0.5);
directLight.position.set(10, 10, 10);
directLight.castShadow = true; // 投射陰影
directLight.shadow.radius = 5;
this.scene.add(directLight);
}
- 點光源
setLight() {
// 點光源投射陰影
const pointLight = new THREE.PointLight(0xffffff, 1);
const smallPoint = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 20, 20),
new THREE.MeshStandardMaterial()
);
smallPoint.position.set(2, 2, 2);
smallPoint.add(pointLight);
this.scene.add(smallPoint);
}
有平行光和物體,還需新增一個面板接受陰影,便於觀察陰影效果
setPlane() {
const geometry = new THREE.PlaneGeometry(10, 10);
const material = new THREE.MeshStandardMaterial();
const plane = new THREE.Mesh(geometry, material);
// 修改面板位置
plane.position.set(0, -1, 0);
plane.rotation.x = -Math.PI / 2;
// 接收陰影
plane.receiveShadow = true;
this.scene.add(plane);
}
我們看下:
總結
去學習和使用 threejs 的過程還是挺有趣的,可以瞭解一下 WebGL 的知識及座標系等,接下來可以繼續把地球再創建出來,給它新增貼圖、紋理等,使其更加細膩。