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

typescript 复制代码
// 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),也称为坐标系的原点。每当我们创建一个新对象并将其添加到我们的场景中时,默认它将被放置在原点。
arduino 复制代码
// 创建一个场景
const scene = new THREE.Scene();
  1. 相机: 要查看场景,需要打开一个进入这个领域的窗口,并将其转换为我们人眼感觉合理的东西,这就是相机的作用。有几种方法可以将场景图形转换为人类视觉友好的格式,使用称为投影 的技术。对我们来说,最重要的投影类型是透视投影 ,使用的是PerspectiveCamera,它旨在匹配我们的眼睛看待世界的方式,是3D场景的渲染中使用得最普遍的投影模式。

透视相机需要四个参数来创建一个有边界的空间区域,叫做视锥体,参数如下:

  • 视野角度: 定义了视锥体扩展的角度。小视角会产生窄截锥体,而宽视角会产生宽截锥体。
  • 纵横比: 将视锥体与场景容器元素相匹配,设置为容器的宽度除以其高度时,可以确保将视锥体完整的展示在容器中。
  • 近端面: 定义了视锥体的小端。
  • 远端面: 定义了视锥体的大端。

视锥体示意图如下:

ini 复制代码
// 参数设置
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场景。

  1. 渲染器: 渲染器将场景和相机里面的内容绘制在 <canvas> 上,呈现在屏幕上面。three.js 中最常用到渲染器是 WebGLRenderer,除了WebGLRenderer 外 three.js 同时提供了其他几种渲染器,当用户所使用的浏览器过于老旧,或者由于其他原因不支持 WebGL 时,可以使用其他渲染器进行降级。
arduino 复制代码
// 创建渲染器
const renderer = new THREE.WebGLRenderer();

除了创建渲染器,我们还需要设置一个渲染器的尺寸,我们可以使用所需要的渲染区域的宽高,来让渲染器渲染出的场景填充满我们的应用程序。因此,我们可以将渲染器宽高设置为浏览器窗口宽高,如下:

javascript 复制代码
renderer.setSize( window.innerWidth, window.innerHeight );

然后需要将 renderer(渲染器)的 dom 元素(renderer.domElement)添加到 HTML 文档中。这就是渲染器用来显示场景给我们看的 <canvas> 元素。

ini 复制代码
document.body.appendChild( renderer.domElement );

最后需要将调用 render方法来绘制场景。

scss 复制代码
function render(){
    // 使用 renderer.render方法画出一帧
    renderer.render(scene, camera)
}

核心概念

场景、相机和渲染器构成了 three.js 的基本架构,并且它们却是不可见的。下面创建我们可以看到的对象。

几何体和材质

Mesh 是 three.js 中表示 3D 对象的一种基本结构,由几何体(Geometry )材质(Material) 组成。Mesh将几何体和材质结合在一起,可以是简单的几何体,也可以是导入的 3D 模型,是在场景中渲染的基本单元。几何体定义了 Mesh的形状,材质定义了Mesh的表面属性。几何体告诉我们网格是一个盒子、一辆汽车或一只猫,而材质告诉我们它是一个金属盒子、一辆石质汽车或一只涂成红色的猫

几何体定义了Mesh 的形状,three.js 核心提供了很多个基本形状,比如 BoxGeometryCapsuleGeometryCircleGeometry等。

材质定义了 Mesh的表面的外观。three.js 自带了几种材质,如:MeshBasicMaterialMeshPhongMaterialMeshPhysicalMaterial等。

ini 复制代码
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 中的灯光分为两种类型,直接光照和环境光,环境光对性能更加友好,阴影效果以增强场景的真实感,但默认情况下,是禁用阴影的,因为阴影很消耗性能。

  1. 直接光照(光线从光源出来并沿直线继续):

    1. DirectionalLight:平行光,类似阳光,可以投射阴影。
    2. PointLight:点光源,类似灯泡,可以投射阴影。
    3. RectAreaLight:平面光,类似条形照明或明亮的窗户,不支持阴影。
    4. SpotLight:聚光灯,可以投射阴影。
    5. 以平行光为例,模仿的是遥远的光源,光线是平行的且不会随着距离而消失。场景 中的所有对象都将被同样明亮地照亮,无论它们放在哪里------即使是在灯光后面。
    csharp 复制代码
    // 创建平行光,设置光的颜色和强度
    const light = new THREE.DirectionalLight('white', 8);
    scene.add(light);

处于背向光线方向的面不会被照亮:

  1. 环境光 (不能投射阴影)

    1. AmbientLight:环境光,从各个方向 向每个对象添加恒定 数量的光,因此放置此灯光的位置无关紧要
    2. HemisphereLight:半球光,光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。

交互和动画

OrbitControls(轨道控制器)可以使得相机围绕目标进行轨道运动,进而实现用户交互。OrbitControls是一个附加组件,必须显式导入。

javascript 复制代码
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)等。

  1. 动画循环 ,three.js 中使用 requestAnimationFrame 来创建动画循环。动画循环是一个连续的循环,每一帧都更新场景中的物体,并进行渲染。通过动画循环,可以实现平滑的动画效果。

    scss 复制代码
    function animate() {
        requestAnimationFrame(animate);
        // 更新场景中的物体状态
        // ...
        // 渲染场景
        renderer.render(scene, camera);
    }
  2. Tween.js 是一个简单的 JavaScript 动画库,可以轻松实现对象属性的缓动动画。可直接在 three.js 中用于模型的动画,创建平滑的过渡效果,比如移动、旋转、颜色渐变等。

  3. 自定义动画通过动画循环手动更新对象的属性,可以实现更复杂的自定义动画效果。例如,逐帧更新物体的旋转、缩放等属性。

    scss 复制代码
    function animate() {
        requestAnimationFrame(animate);
        // 更新物体的属性
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.01;
        // 渲染场景
        renderer.render(scene, camera);
    }
    // 启动动画循环
    animate();
  4. 骨骼动画,是一种基于骨架和关键帧的动画系统。通过使用骨骼动画,可以在模型中实现更为复杂的动作,比如人物的行走、跑步等。

    ini 复制代码
    // 使用动画混合器播放动画
    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 为例:

javascript 复制代码
// 附加组件另外导入
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 场景中进行定位和设置。比如就坐标系辅助对象,相机辅助对象、灯光辅助对象等。辅助对象的使用特别简单,创建之后添加到场景中即可。以坐标系辅助对象为例:

csharp 复制代码
// 创建坐标系辅助对象
const axesHelper = new THREE.AxesHelper( 5 );
// 添加到场景中
scene.add(axesHelper)

具体可参考文章:juejin.cn/post/723174...

实战

已经介绍了构成简单 three.js 应用程序的所有组件,下面把它们组合在一起,创建一个简单的 three.js 应用程序,分为 6 个步骤:

  1. 初始设置
  2. 创建场景
  3. 创建相机
  4. 创建可见对象/导入模型
  5. 创建渲染器
  6. 渲染场景
scss 复制代码
      // 初始设置,导入资源
      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()

效果如下:

参考资料

《探索 three.js》

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax