Three.js介绍
Three.js是一个开源的应用级3D JavaScript库,可以让开发者在网页上创建3D体验。
Three.js屏蔽了WebGL的底层调用细节,让开发者能更快速的进行3D场景效果的开发。
Three.js的开发环境搭建
- 创建目录并使用 npm init -y 初始化 package.json
- 使用 npm install --save-dev parcel安装Web应用打包工具parcel
这一步不是必须的,可以使用其他打包工具例如webpack等
- 在目录中新建src/index.html 和 src/script.js两个文件
script.js文件中会使用到import模块化语法,所以引入文件需要加上 type="module"
- 在package.json中加入"start":"parcel src/index.html"脚本
- 使用 npm install three引入Three.js
- 在src/script.js文件中验证Three.js是否引入成功
Three.js的一些重要概念
开发一个Three.js的场景,需要如下一些元素:
- scene场景
场景像一个容器(container),可以将物体(模型,粒子,光源,相机等)加入其中。
- objects物体
物体可以有很多种,比如原始的几何体,导入的模型,粒子,光源等。
- camera相机
理论上的视角,虽然相机也被加入了场景中,但是相机是看不见的。
- renderer渲染器
从相机的角度渲染场景,结果将被绘制到canvas中
透视相机
Three.js最常使用的是透视相机,它是模拟人的观察视角:物体近大远小。
透视相机有四个构造参数:
- 视野(The field of biew)
- 视野(fov)以度为单位表示,表示视觉角度的大小。
- 角度越大看到的范围越大,但是太大就会造成场景中物体变形。
- 一般45~75会比较合适
- 宽高比(Aspect)纵横比需要设置为画布的宽度除以其高度,否则会造成场景中物体变形。
- 近平面距离(Near)比近平面距离近的物体将不会被渲染,也就是说不能被看见。
- 远平面距离(Far)比远平面距离远的物体将不会被渲染,也不能被看见。
Three.js的第一个场景
我们了解了基本的概念后,可以开始写代码了。
- 创建渲染器,并将渲染器设置为body的尺寸大小,然后将渲染器的Canvas添加body中;
- 创建场景;
- 创建相机,设置75度视野,相机的纵横比设置为画布的宽度除以其高度,近平面距离为0.01,远平面距离为1000;
- 创建了一个坐标辅助线物体,用来识别坐标位置;
- 用渲染器进行渲染,传递的参数是场景和相机两个参数。
结果令人遗憾,什么也看不到,屏幕上是一片黑暗。
如果需要了解原因,需要知道一个重要的概念:坐标系统。
坐标系统
Three.js使用的是右手坐标系,这源于OpenGL默认情况下,也是右手坐标系。x轴正方向向右,y轴正方向向上,z轴由屏幕从里向外。
所有的物体默认是摆放在坐标原点位置,也就是(0,0,0)这个位置。
由于透视相机和物体都是放在同一个位置,也就是距离是0.并且相机也默认看向的(0,0,0)这个点,所以透视相机不会渲染内容。
移动相机
为了解决这问题,可以移动相机也可以移动物体。默认是移动相机位置。
同时设置:
camera.position.set(5,5,10);
或者单独设置
camera.position.z = 10;
camera.position.x = 5;
camera.position.y = 5;
移动了相机,还需要设置相机看向原点的方向
camera.lookAt(0,0,0);
这时候就可以看到坐标辅助线了。
Geometry 和 Material
我们想创建一个立方体,我们需要创建一种名为Mesh的对象。Mesh是几何(形状)和材质(外观如何)的组合。
// 添加立方体
const geometry = new THREE.BoxGeometry(4,4,4);
const material = new THREE.MeshBasicMaterial({color:0xff0000});
const cube = new THREE.Mesh(geometry,material);
scene.add(cube);
- 创建长宽高都为4的立方几何体;
- 立方体的表明颜色为红色的材质;
- 将集合体和材质组合为Mesh对象;
- 将Mesh对象添加到场景中。
动画
开发者都知道动画本质上是不同的重绘,由于物体的位置、大小、材质和缩放等的不同而形成了视觉上的动画。
- 通过rotate设置旋转度角
cube.rotation.y = Math.PI/4;
- 不断旋转角度
function animate(){
requestAnimationFrame(animate);
cube.rotation.y += 0.01;
renderer.render(scene,camera);
}
animate();
- 通过 requestAnimationFrame 不断的回调animate函数;
- animate函数中先将旋转角度增加0.01度,然后调用renderer.render(scene,camera)进行重新绘制。
- 优化
requestAnimationFrame函数的调用频率取决于浏览器的刷新率,实际的刷新率可能因浏览器、硬件性能以及当前页面的负载而有所不同。
可以使用three.js中内置的Clock来解决这个问题。
function animate(){
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime(); // 返回已经过去的时间,以秒
cube.rotation.y = elapsedTime * Math.PI; // 两秒自转一圈
renderer.render(scene, camera);
}
通过使用Clock就能保证两秒自转一圈。
添加交互
到目前位置,用户是无法和场景进行交互的,为了能够和场景进行交互,可以添加一个控制器OrbitControls。
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls"
const controls = new OrbitControls(camera,renderer.domElement);
controls.update();
通过上面的代码,用户就可以用对场景内容进行旋转、放大缩小等操作。
添加光源
three.js中有三种重要的光源:环境光源,方向光源和点光源。
- 环境光(Ambient Light):环境光是一种均匀的光照,它会均匀地照亮场景中的所有物体,不考虑光照源的位置和方向。
- 方向光(Directional Light):方向光是一种平行光源,它具有确定的方向和强度,类似于太阳光。
- 点光源(Point Light):点光源是一种位于特定位置的光源,它向所有方向发射光线,类似于灯泡。
// 1.
const material = new THREE.MeshStandardMaterial({color:0xff0000});
// 2.
const ambientLight = new THREE.AmbientLight(0xffffff,0.4);
scene.add(ambientLight);
// 3.
const directionalLight = new THREE.DirectionalLight(0xffffff,1);
directionalLight.position.set(10,0,10);
scene.add(directionalLight);
- 因为MeshBasicMaterial 不受光源的影响,所以需要将Material改成MeshStandardMaterial;
- 添加一个彩色透明度为0.4的环境光,这个环境光会均匀地照亮场景中的所有物体表面,并且使用PBR(Physically-Based Rendering)渲染模型和材质自身的颜色进行混合得到新的颜色;
- 添加一个白色的方向光,方向光从(10,0,10)照向原点(10,0,10),所以有两个面会收到这个方向光,表面的颜色会更偏亮。
阴影
在现实生活中,有光照的情况下会产生阴影,three.js也能很容易实现这种效果。
// 1.渲染器能够渲染阴影效果
renderer.shadowMap.enabled = true;
// 2. 该方向会投射阴影效果
directionalLight.castShadow = true;
// 3.
cube.castShadow = true;
// 4.
const planeGeometry = new THREE.PlaneGeometry(20,20);
const planeMaterial = new THREE.MeshStandardMaterial({color:0xffffff});
const planeMesh = new THREE.Mesh(planeGeometry,planeMaterial);
planeMesh.rotation.x = -0.5 * Math.PI;
planeMesh.position.set(0,-3,0);
planeMesh.receiveShadow = true;
scene.add(planeMesh);
// 5.方向光的辅助线
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight);
scene.add(directionalLightHelper); // 辅助线
- 将渲染器的shadowMap.enabled设置为true,表示渲染器能够渲染阴影效果;
- directionalLight.castShadow = true,表示该方向会投射阴影效果;
- cube.castShadow = true,表示该立方体会产生影像效果;
- 新建了一个平面,该平面能够接受投射过来的阴影效果;
- 为了清晰展示方向光的方向和位置,添加了一个辅助线。
纹理
除了颜色外,对几何材质添加纹理是非常重要和常见的操作。我们接下来给平面和立方体添加纹理实现。
- 地板纹理
// 1.引入图片
import floor from "./images/floor_wood.jpeg";
// 2. 初始化纹理加载器
const textloader = new THREE.TextureLoader();
// 3. 给地板加载纹理
const planeMaterial = new THREE.MeshStandarMaterial({
map:textloader.load(floor),
});
- 立方体纹理
前面方式添加的纹理会给几何体的每个面都设置为相同的纹理,接下来我们为不同的面设置不同的纹理。
// 立方体的顶部纹理
import grass_top from "./images/grass_top.png";
// 立方体的侧边纹理
import grass_side from "./images/grass_side.png";
// 立方体的底部纹理
import grass_bottom from "./images/grass_bottom.png";
const geometry = new THREE.BoxGeometry(4,4,4);
cosnt material = [
new THREE.MeshBasicMaterial({
map:textloader.load(grass_side),
}),
new THREE.MeshBasicMaterial({
map:textloader.load(grass_side),
}),
new THREE.MeshBasicMaterial({
map:textloader.load(grass_top),
}),
new THREE.MeshBasicMaterial({
map:textloader.load(grass_bottom),
}),
new THREE.MeshBasicMaterial({
map:textloader.load(grass_side),
}),
new THREE.MeshBasicMaterial({
map:textloader.load(grass_side),
}),
];
const cube = new THREE.Mesh(geometry,material);
在构造Mesh对象的时候,传入6个MeshBasicMaterial对象的数组,数组的纹理顺序是:x正方向轴的面,x负方向轴的面,y正方向轴的面,y负方向轴的面,z正方向轴的面,z负方向轴的面。