你知道 three.js 嗎

語言: CN / TW / HK

這段時間,開發的專案從資料表格,跨越到 echarts 圖表,兩種不同的展現形式,都可以直觀的分析資料之間的相互關係,但是 echarts 圖表看起來會更形象一點,並且頁面展現也很好看。當時想如果在頁面中新增一些3D的圖形,會不會變得不同,比如 github 首頁的地球展示,懷揣一些好奇心,嘗試著先看看這些圖形及動效是怎麼做的,是通過什麼實現的?

image.png

這時 threejs 出現在眼前,three.js 是 JavaScript 編寫的 WebGL 第三方庫,可以用它建立各種三維場景,其中包括了攝影機、光影、材質等各種物件。直接對 WebGL 中的原生 Api 進行研究,還是有一定難度的,會涉及圖形處理等。對於傳統 js 的開發人員,threejs 中涉及的一些概念也是很新穎的,比如什麼是幾何體、材質、場景、相機、渲染器、控制器等。

實際上建立 3D 圖形的過程可以想象成拍照,在一個大場景中,首先準備一個相機,設定相機引數,調整視窗的大小或遠近,讓物體儘可能呈現在視野中,再新增光源,使物體產生不同的陰影效果。

27dda476f912f3d3e41f85cac02051ab.png

場景

這時必須建立的,接下來的物體、座標系等需要呼叫 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); } image.png

控制器

為了更方便的檢視物體,及判斷物體座標位置,我們還需要新增輔助座標系及軌道控制器,實現滑鼠拖動360度旋轉檢視,如上圖,座標系中紅色代表 X 軸. 綠色代表 Y 軸. 藍色代表 Z 軸。

這裡我們先了解一下三維座標系,分為左手座標系和右手座標系。

image.png

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)); } ```

2022-12-18 16.52.27.gif

平行光和點光源

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); } 我們看下:

2022-12-18 17.27.45.gif

總結

去學習和使用 threejs 的過程還是挺有趣的,可以瞭解一下 WebGL 的知識及座標系等,接下來可以繼續把地球再創建出來,給它新增貼圖、紋理等,使其更加細膩。