three.js 入门指南
引言
three.js 是什么?
three.js 是一款基于 WebGL 的 JavaScript 3D 渲染库,用于在 Web 浏览器中创建和展示交互式的 3D 场景,three.js 使得在网页上嵌入复杂的 3D 图形变得相对容易。
起源
three.js 是 2010 年由西班牙程序员 Ricardo Cabello(又名mrdoob)创建。当时,WebGL 技术开始兴起,为在浏览器中实现硬件加速的 3D 图形渲染提供了可能性。然而,直接使用原始的 WebGL API 来构建复杂的 3D 场景和交互性相对繁琐,这促使了 three.js 的诞生,three.js 通过提供一个简单而功能强大的 3D 图形库,极大地推动了 Web 上的 3D 内容的发展。
基础
-
安装和配置
下载与引入three.js
库
// cdn
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@<version>/build/three.module.js",
"three/addons/": "https://unpkg.com/three@<version>/examples/jsm/"
}
}
</script>
// 使用 npm
npm install --save three
// 导入
import * as THREE from "three.js";
// three.js 包含 3D 引擎的基本要素,
// 而其他 three.js 组件,如控件(controls)、加载器(loaders)等则属于附加组件,
// 附加组件无需单独安装,但需要单独导入
// 例如:OBJLoader 用于加载 obj 类型模型
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
-
创建场景、相机和渲染器
场景 (scene)、相机(camera)和渲染器(renderer)构成了 three.js 应用程序的基本结构。
1. 场景 : 场景是我们能看到的一切的载体和容器,场景的中心是点(0,0,0),也称为坐标系的原点。每当我们创建一个新对象并将其添加到我们的场景中时,默认它将被放置在原点。
// 创建一个场景 const scene = new THREE.Scene();
2. 相机: 要查看场景,需要打开一个进入这个领域的窗口,并将其转换为我们人眼感觉合理的东西,这就是相机的作用。有几种方法可以将场景图形转换为人类视觉友好的格式,使用称为投影的技术。对我们来说,最重要的投影类型是透视投影,使用的是
PerspectiveCamera
,它旨在匹配我们的眼睛看待世界的方式,是3D场景的渲染中使用得最普遍的投影模式。透视相机需要四个参数来创建一个有边界的空间区域,叫做视锥体,参数如下:
- 视野角度: 定义了视锥体扩展的角度。小视角会产生窄截锥体,而宽视角会产生宽截锥体。
- 纵横比: 将视锥体与场景容器元素相匹配,设置为容器的宽度除以其高度时,可以确保将视锥体完整的展示在容器中。
- 近端面: 定义了视锥体的小端。
- 远端面: 定义了视锥体的大端。
视锥体示意图如下:
// 参数设置 const fov = 35; // 视锥体垂直视野角度 const aspect = window.innerWidth/ window.innerHeight; // 摄像机视锥体长宽比 const near = 0.1; // 视锥体近端面 const far = 100; // 视锥体远端面 // 创建透视相机 const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
透视相机投影示例如下:
另一种重要的投影类型是正交投影,使用
OrthographicCamera
,在这种投影模式下,无论物体距离相机距离远或者近,在最终渲染的图片中物体的大小都保持不变,常用于渲染2D场景。3. 渲染器: 渲染器将场景和相机里面的内容绘制在
<canvas>
上,呈现在屏幕上面。three.js 中最常用到渲染器是WebGLRenderer
,除了WebGLRenderer
外 three.js 同时提供了其他几种渲染器,当用户所使用的浏览器过于老旧,或者由于其他原因不支持 WebGL 时,可以使用其他渲染器进行降级。// 创建渲染器 const renderer = new THREE.WebGLRenderer();
除了创建渲染器,我们还需要设置一个渲染器的尺寸,我们可以使用所需要的渲染区域的宽高,来让渲染器渲染出的场景填充满我们的应用程序。因此,我们可以将渲染器宽高设置为浏览器窗口宽高,如下:
renderer.setSize( window.innerWidth, window.innerHeight );
然后需要将
renderer
(渲染器)的 dom 元素(renderer.domElement
)添加到 HTML 文档中。这就是渲染器用来显示场景给我们看的<canvas>
元素。document.body.appendChild( renderer.domElement );
最后需要将调用 render
方法来绘制场景。
function render(){
// 使用 renderer.render方法画出一帧
renderer.render(scene, camera)
}
核心概念
场景、相机和渲染器构成了 three.js 的基本架构,并且它们却是不可见的。下面创建我们可以看到的对象。
-
几何体和材质
Mesh
是 three.js 中表示 3D 对象的一种基本结构,由几何体(Geometry ) 和材质(Material) 组成。Mesh
将几何体和材质结合在一起,可以是简单的几何体,也可以是导入的 3D 模型,是在场景中渲染的基本单元。几何体定义了 Mesh
的形状,材质定义了Mesh
的表面属性。几何体告诉我们网格是一个盒子、一辆汽车或一只猫,而材质告诉我们它是一个金属盒子、一辆石质汽车或一只涂成红色的猫。
几何体定义了Mesh
的形状,three.js 核心提供了很多个基本形状,比如 BoxGeometry
、CapsuleGeometry
、CircleGeometry
等。
材质定义了 Mesh
的表面的外观。three.js 自带了几种材质,如:MeshBasicMaterial
、MeshPhongMaterial
、MeshPhysicalMaterial
等。
const length = 2;
const width = 2;
const depth = 2;
// 创建几何体
const geometry = new THREE.BoxGeometry( length, width, depth );
// 创建材质
const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
// 创建 Mesh
const cube = new THREE.Mesh( geometry, material);
// 添加到场景中
scene.add( cube );
将 Mesh
添加到场景中以后,除了 MeshBasicMaterial
材质外,是无法看到任何东西的,因为场景处于黑暗当中,需要添加灯光。MeshBasicMaterial
是一种不受光照影响的材质。
-
光源和阴影
three.js 中的灯光分为两种类型,直接光照和环境光,环境光对性能更加友好,阴影效果以增强场景的真实感,但默认情况下,是禁用阴影的,因为阴影很消耗性能。
-
直接光照(光线从光源出来并沿直线继续):
DirectionalLight
:平行光,类似阳光,可以投射阴影。PointLight
:点光源,类似灯泡,可以投射阴影。RectAreaLight
:平面光,类似条形照明或明亮的窗户,不支持阴影。SpotLight
:聚光灯,可以投射阴影。- 以平行光为例,模仿的是遥远的光源,光线是平行的且不会随着距离而消失。场景 中的所有对象都将被同样明亮地照亮,无论它们放在哪里——即使是在灯光后面。
-
// 创建平行光,设置光的颜色和强度 const light = new THREE.DirectionalLight('white', 8); scene.add(light);
处于背向光线方向的面不会被照亮:
-
环境光(不能投射阴影) :
AmbientLight
:环境光,从各个方向向每个对象添加恒定数量的光,因此放置此灯光的位置无关紧要 。HemisphereLight
:半球光,光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。
-
交互和动画
OrbitControls
(轨道控制器)可以使得相机围绕目标进行轨道运动,进而实现用户交互。OrbitControls
是一个附加组件,必须显式导入。
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 添加控制器,参数为将要被控制的相机(必须)和用于事件监听的HTML元素
const controls = new OrbitControls(camera,renderer.domElement);
// 控制器事件
// change 当摄像机被组件改变时触发
// start 初始化交互时触发
// end 当交互结束时触发
// 可通过监听 change 事件实现用户交互,当控制器change,执行 render 重新渲染
controls.addEventListener("change", render)
动画是为了实现场景中物体的运动、变换或其他交互效果而进行的一系列连续的状态变化。three.js 提供了多种动画的实现方式,包括基本的手动更新、Tween.js
库的使用、以及更高级的使用骨骼动画(Skeletal Animation)等。
-
动画循环,three.js 中使用
requestAnimationFrame
来创建动画循环。动画循环是一个连续的循环,每一帧都更新场景中的物体,并进行渲染。通过动画循环,可以实现平滑的动画效果。-
function animate() { requestAnimationFrame(animate); // 更新场景中的物体状态 // ... // 渲染场景 renderer.render(scene, camera); }
-
-
Tween.js 是一个简单的 JavaScript 动画库,可以轻松实现对象属性的缓动动画。可直接在 three.js 中用于模型的动画,创建平滑的过渡效果,比如移动、旋转、颜色渐变等。
-
自定义动画通过动画循环手动更新对象的属性,可以实现更复杂的自定义动画效果。例如,逐帧更新物体的旋转、缩放等属性。
-
function animate() { requestAnimationFrame(animate); // 更新物体的属性 mesh.rotation.x += 0.01; mesh.rotation.y += 0.01; // 渲染场景 renderer.render(scene, camera); } // 启动动画循环 animate();
-
-
骨骼动画,是一种基于骨架和关键帧的动画系统。通过使用骨骼动画,可以在模型中实现更为复杂的动作,比如人物的行走、跑步等。
-
// 使用动画混合器播放动画 const mixer = new THREE.AnimationMixer(model); const action = mixer.clipAction(gltf.animations[0]); action.play();
-
示例与实战
导入外部3D模型
在 3D 图形领域,有多种广泛应用的模型格式,比如 OBJ、FBX、GLTF 等,推荐使用 GLTF, .GLB和.GLTF是这种格式的这两种不同版本,都可以被很好地支持。由于 GLTF 这种格式是专注于在程序运行时呈现三维物体的,所以它的传输效率非常高,且加载速度非常快。 功能方面则包括了网格、材质、纹理、皮肤、骨骼、变形目标、动画、灯光和摄像机。
three.js 中内置了一部分加载器,其他的需要单独引入,加载不同格式模型,需要使用对应的加载器,下面以 GLTF 为例:
// 附加组件另外导入
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
// 用 GLTFLoader 加载 GLTF 模型
loader.load( 'path/to/model.glb', function ( gltf ) {
scene.add( gltf.scene );
},
onProgress,
onError
);
辅助对象
three.js 中提供了多个辅助对象,帮助在 3D 场景中进行定位和设置。比如就坐标系辅助对象,相机辅助对象、灯光辅助对象等。辅助对象的使用特别简单,创建之后添加到场景中即可。以坐标系辅助对象为例:
// 创建坐标系辅助对象
const axesHelper = new THREE.AxesHelper( 5 );
// 添加到场景中
scene.add(axesHelper)
实战
已经介绍了构成简单 three.js 应用程序的所有组件,下面把它们组合在一起,创建一个简单的 three.js 应用程序,分为 6 个步骤:
- 初始设置
- 创建场景
- 创建相机
- 创建可见对象/导入模型
- 创建渲染器
- 渲染场景
// 初始设置,导入资源
import * as THREE from "./lib/three.js";
import { OrbitControls } from "./lib/controls/OrbitControls.js";
let object,scene,renderer,camera,controls;
// 创建场景
function setScene (){
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
}
// 创建相机
function setCamera(){
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0,0,10)
}
// 创建可见对象
function setObject(){
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建材质
//const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
const material = new THREE.MeshStandardMaterial({color: 0x00ff00});
// 创建 Mesh
object = new THREE.Mesh(geometry, material);
object.rotation.y = 0.3
scene.add(object);
}
// 创建渲染器
function setRenderer(){
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.physicallyCorrectLights = true;
document.body.appendChild(renderer.domElement);
}
// 设置灯光
function setLights(){
const light = new THREE.DirectionalLight(0x404040,10)
light.position.set(10,10,10)
scene.add(light)
}
// 添加辅助对象
function helper(){
const axesHelper = new THREE.AxesHelper( 5 );
scene.add(axesHelper)
}
// 渲染场景
function animate(){
requestAnimationFrame(animate);
object.rotation.y += 0.01;
renderer.render(scene,camera)
}
function init() {
setScene()
setCamera()
setObject()
setRenderer()
setLights()
helper()
animate()
}
init()
效果如下:
参考资料
转载自:https://juejin.cn/post/7340286503505575946