Three.js 是一个开源的应用级 3D JavaScript 库,可以让开发者在网页上创建 3D 体验。Three.js 屏蔽了 WebGL的底层调用细节,让开发者能更快速的进行3D场景效果的开发。
npm init -y
初始化 package.json
npm install --save-dev parcel
安装 Web
应用打包工具 parcel
webpack
等src/index.html
和 src/script.js
两个文件DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three.js入门title>
head>
<body>
<script src="./script.js" type="module">script>
body>
html>
script.js文件中会使用到import模块化语法,所以引入文件需要加上type=“module”
package.json
中加入 "start": "parcel src/index.html"
脚本npm install three
引入 Three.js
src/script.js
文件中验证 Three.js
是否引入成功import * as THREE from "three";
console.log(THREE);
场景像一个容器(container),可以将物体(模型,粒子,光源,相机等)加入其中。
物体可以有很多种,比如原始的几何体,导入的模型 ,粒子,光源等。
理论上的视角,虽然相机也被加入了场景中,但是相机是看不见的
从相机的角度渲染场景,结果将被绘制到 canvas 中
Three.js最常使用的是透视相机,它是模拟人的观察视角:物体近大远小。透视相机有四个构造参数
constructor(fov?: number, aspect?: number, near?: number, far?: number);
我们了解了基本的概念后,可以开始写代码了。
// 1. 创建渲染器,指定渲染的分辨率和尺寸,然后添加到body中
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.pixelRatio = window.devicePixelRatio;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.append(renderer.domElement);
// 2. 创建场景
const scene = new THREE.Scene();
// 3. 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
// 4. 创建物体
const axis = new THREE.AxesHelper(5);
scene.add(axis);
// 5. 渲染
renderer.render(scene, camera);
结果令人遗憾,什么也看不到,屏幕上是一片黑暗。
如果需要了解原因,需要知道一个重要的概念:坐标系统。
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);
这时候就可以看到坐标辅助线了。
我们想创建一个立方体,我们需要创建一种名为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);
开发者都知道动画本质上是不同的重绘,由于物体的位置、大小、材质和缩放等的不同而形成了视觉上的动画。
通过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
来解决这个问题。
const clock = new THREE.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中有三种重要的光源:环境光源,方向光源和点光源。
// 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);
在现实生活中,有光照的情况下会产生阴影,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); // 辅助线
除了颜色外,对几何体材质添加纹理是非常重要和常见的操作。我们接下来给平面和立方体添加纹理实现。
地板纹理:
// 1. 引入图片
import floor from "./images/floor_wood.jpeg";
// 2. 初始化纹理加载器
const textloader = new THREE.TextureLoader();
// 3. 给地板加载纹理
const planeMaterial = new THREE.MeshStandardMaterial({
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);
const 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负方向轴的面。