你知道 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 的知识及坐标系等,接下来可以继续把地球再创建出来,给它添加贴图、纹理等,使其更加细腻。