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》

相关推荐
qiyi.sky6 分钟前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~9 分钟前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒11 分钟前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常19 分钟前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n01 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
Q_w77421 小时前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录
昨天;明天。今天。1 小时前
案例-任务清单
前端·javascript·css
一丝晨光1 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
Front思2 小时前
vue使用高德地图
javascript·vue.js·ecmascript
zqx_72 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架