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