Three.js一起学-如何通过官方例子高效学习 Three.js?手把手带你“抄”出一个3D动画

前言

在之前的文章中有说过,我认为学习three.js最好的方法就是通过官网的例子去学习,在实践中练习。 因为Three.js中api很多,没有系统的学习文档,因此我觉得通过例子去学习,使用到某个概念或者api在深入学习它,这是一个不错的学习方式。 那么具体如何学习呢?

好了言归正传,下面我就教大家如何打出一套如来神掌,啊不对,是通过官方例子学习Three.js。

准备

阅读本文需要一点点的 WebGL 的知识点,至少这个文档的基础知识部分看完即可。

此外各位同学需要下载一下 Three.js 源码。源码里有我们需要用到的模型和官网例子的源码。

搭建一个 web 工程,本文演示创建基于 vue3 的工程。

bash

kotlin 复制代码
npm init vue@latest

按照自己的喜好选择要安装哪些插件即可。生成的项目结构如下,我们为此项目添加 three.js,修改 package.json 如下。

json

json 复制代码
{
  "dependencies": {
    ...
    "three": "^0.143.0"
  }
}

如需使用 typescript,添加如下依赖:

json

perl 复制代码
{
  "devDependencies": {
    ...  
    "@types/three": "^0.143.0"
  }
}

然后执行 npm install 安装依赖即可。

至此我们前期创建项目的准备工作就做完了。

体验例子

接下来我们在 three.js 官网上找到 感兴趣的例子,打开它的源码,先大致阅读一遍。通过这个例子中我们将学到如下几个知识点:相机、场景、网格、灯光、材质、形状、动画等。下面我们跟着例子写一遍代码,带大家学会如何通过例子去学习。

写(抄)代码

源码在上面的链接已经给出来,我就不在这里凑字数了。我在这里一步一步地写一下上述例子的代码,并演示如何通过此例来学习 Three.js。那么就让我们愉快地开始吧。

首先,我们在 Vue 项目中创建一个组件(比如 ThreeExample.vue),并在 mounted 生命周期中编写 Three.js 代码。当然,你也可以用原生 HTML+JS,但这里我们以 Vue3 为例。

1. 初始化场景、相机和渲染器

打开官方例子的源码,我们会看到一开始就创建了 scene、camera、renderer 这三个基本对象。这是每一个 Three.js 应用的起点。

js

dart 复制代码
// 1. 创建场景
const scene = new THREE.Scene();

// 2. 创建透视相机(参数:视野角度、宽高比、近裁面、远裁面)
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(2, 2, 5); // 设置相机位置
camera.lookAt(0, 0, 0);        // 让相机看向原点

// 3. 创建 WebGL 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement); // 将 canvas 添加到页面

学习点 :这里我们用到了 PerspectiveCamera,如果不清楚它的参数含义,就可以去 官网文档 查看。文档会详细解释每个参数的作用,比如 fov(视野角度)决定了你能看到多大的范围,aspect(宽高比)通常设为画布的宽度/高度,否则图像会被拉伸。

2. 添加光源

例子中使用了多种光源:环境光、点光源和聚光灯。光源是让物体可见并产生阴影和立体感的关键。

js

csharp 复制代码
// 环境光:提供基础照明,均匀照亮所有面
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambientLight);

// 点光源:从某个点向所有方向发射光线
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(1, 2, 3);
scene.add(pointLight);

// 还可以添加其他光源,比如聚光灯、平行光等,根据需要选择

学习点 :看到 AmbientLight 和 PointLight,我们可以去文档了解每种光源的特点和适用场景。例如环境光没有方向,通常用来提亮阴影部分;点光源类似灯泡,会产生阴影(需配合阴影设置)。

3. 加载模型

例子中加载了一个带有骨骼动画和变形动画的模型(models/gltf/Soldier.glb)。我们需要使用 GLTFLoader 来加载 glTF 格式的模型。

首先,引入加载器(需要额外导入):

js

javascript 复制代码
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

然后,在代码中创建加载器并加载模型:

js

ini 复制代码
const loader = new GLTFLoader();
loader.load('models/gltf/Soldier.glb', (gltf) => {
  const model = gltf.scene;
  scene.add(model);

  // 获取模型的动画剪辑
  const animations = gltf.animations;
  if (animations && animations.length) {
    // 创建动画混合器并播放动画(后面会讲到)
    mixer = new THREE.AnimationMixer(model);
    const action = mixer.clipAction(animations[0]);
    action.play();
  }
}, undefined, (error) => {
  console.error('模型加载失败:', error);
});

注意:模型路径需要根据你存放的位置调整。官方源码中的模型路径是相对 examples/ 目录的,你可以把 models 文件夹复制到你的 public 目录下。

学习点 :遇到 GLTFLoader,我们可以去文档或源码中查看它的用法。GLTF 是 Three.js 推荐的 3D 模型格式,支持动画、材质、骨骼等。通过这个例子,我们学会了如何加载外部模型并添加到场景。

4. 动画循环

例子中使用 requestAnimationFrame 实现动画循环,并在每一帧更新动画混合器。

js

scss 复制代码
let mixer = null; // 在加载模型时赋值

function animate() {
  requestAnimationFrame(animate);

  const delta = clock.getDelta(); // 获取时间差,用于平滑动画
  if (mixer) {
    mixer.update(delta); // 更新动画混合器
  }

  // 渲染场景
  renderer.render(scene, camera);
}

// 创建 Clock 对象用于计算时间差
const clock = new THREE.Clock();

// 启动动画循环
animate();

学习点 :AnimationMixer 和 Clock 是 Three.js 中处理动画的重要 API。通过查看文档,我们可以了解 mixer.update(delta) 如何基于时间差驱动模型动画。

5. 处理窗口大小变化

为了让画布自适应窗口,我们需要监听窗口的 resize 事件,更新相机宽高比和渲染器尺寸。

js

javascript 复制代码
window.addEventListener('resize', onWindowResize, false);

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix(); // 必须调用,使相机参数生效
  renderer.setSize(window.innerWidth, window.innerHeight);
}

学习点 :updateProjectionMatrix() 方法的作用是更新相机的投影矩阵,当相机参数改变时(如宽高比),需要调用此方法让 Three.js 重新计算投影。

6. 遇到不懂的就去官网查找 API

在"抄"代码的过程中,你一定会遇到很多陌生的 API,比如 PointLight、GLTFLoader、AnimationMixer、Clock、updateProjectionMatrix 等等。这时候,最好的学习方式就是打开 Three.js 官方文档,搜索这些 API,仔细阅读其用法和参数含义。

例如,你看到 camera.lookAt(0, 0, 0),可以查阅 Object3D 的 lookAt 方法,理解它是如何让物体朝向某个点的。

文档通常包含详细说明和示例代码,非常有助于深入理解。通过这样的方式,你不仅学会了这个例子,还能举一反三,应用到其他场景。

完整的代码整合

将上述代码片段整合到一个 Vue 组件中,大概如下(省略了样式和模板部分):

vue

ini 复制代码
<template>
  <div ref="container" style="width:100%; height:100vh;"></div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const container = ref(null);

onMounted(() => {
  // 1. 创建场景
  const scene = new THREE.Scene();

  // 2. 创建相机
  const camera = new THREE.PerspectiveCamera(45, container.value.clientWidth / container.value.clientHeight, 1, 2000);
  camera.position.set(2, 2, 5);
  camera.lookAt(0, 0, 0);

  // 3. 创建渲染器
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(container.value.clientWidth, container.value.clientHeight);
  renderer.setPixelRatio(window.devicePixelRatio);
  container.value.appendChild(renderer.domElement);

  // 4. 添加光源
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
  scene.add(ambientLight);
  const pointLight = new THREE.PointLight(0xffffff, 1);
  pointLight.position.set(1, 2, 3);
  scene.add(pointLight);

  // 5. 加载模型
  const loader = new GLTFLoader();
  let mixer = null;
  loader.load('/models/gltf/Soldier.glb', (gltf) => {
    const model = gltf.scene;
    scene.add(model);
    if (gltf.animations.length) {
      mixer = new THREE.AnimationMixer(model);
      const action = mixer.clipAction(gltf.animations[0]);
      action.play();
    }
  });

  // 6. 动画循环
  const clock = new THREE.Clock();
  function animate() {
    requestAnimationFrame(animate);

    const delta = clock.getDelta();
    if (mixer) {
      mixer.update(delta);
    }

    renderer.render(scene, camera);
  }
  animate();

  // 7. 窗口大小自适应
  const onWindowResize = () => {
    camera.aspect = container.value.clientWidth / container.value.clientHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(container.value.clientWidth, container.value.clientHeight);
  };
  window.addEventListener('resize', onWindowResize);
});
</script>

如何进一步深入学习

通过"抄"这个例子,我们已经接触到了 Three.js 的核心概念。但这仅仅是开始。下面是一些进阶学习建议:

  1. 修改参数,观察效果:尝试调整相机位置、光源颜色和强度、模型缩放比例等,实时查看变化,加深理解。
  2. 尝试添加交互 :比如使用 OrbitControls 让用户可以用鼠标旋转视角。引入对应的控制器并启用,能极大地提升体验。
  3. 研究动画:例子中只播放了第一个动画剪辑。你可以尝试播放其他动画,或者混合多个动画。
  4. 阅读更多例子:Three.js 官网有上百个例子,涵盖了粒子系统、后期处理、物理效果等。按照同样的方法,一个个"抄"过去,你的 Three.js 水平会飞速提升。
  5. 参与社区:遇到问题时,可以到 Stack Overflow、GitHub Issues 或中文社区(如掘金)提问,也可以阅读他人的源码和文章。

最后

以上就是本文的全部内容了。我们通过一个官方的骨骼动画例子,逐步学习了 Three.js 的基础:场景、相机、渲染器、光源、模型加载、动画和自适应窗口。更重要的是,我们实践了"通过例子学习"的方法------遇到不懂的 API 就去查文档,然后亲手写一遍。

这种学习方法不仅适用于 Three.js,也适用于其他任何技术栈。希望各位同学能举一反三,不再畏惧陌生的框架和库,勇敢地打开源码,开始你的"抄"级学习之旅!

如果感觉阅读本文后有所收获,欢迎点赞、收藏和评论,也欢迎分享你通过例子学习 Three.js 的心得。我们下期再见!


注意:本文代码基于 Three.js r143 版本,不同版本 API 可能略有差异,请以你实际使用的版本为准。模型文件需自行从 Three.js 源码仓库中获取,并放置于项目的公共目录下。

相关推荐
Eiceblue22 分钟前
使用 Python 实现 Excel 行列转置(保留单元格样式)
前端·python·excel
西西学代码37 分钟前
像素和颜色之间的转换(Flutter)
前端·javascript·flutter
茶底世界之下1 小时前
羽毛颜色已经很鲜艳,还要继续加色吗?用 Awaking 试一次减饱和
前端·ios·运营
风早爽太1 小时前
Mac 搭建 Vue 开发环境
前端·vue.js
Lank_M1 小时前
Word图片裁剪转HTML失效,转换器各漏读了哪一层
前端
青烟小生1 小时前
前端架构设计实战:从组件思维到系统思维
前端
Amos_Web1 小时前
Rspack 源码解析(二十):Typed Hook 系统如何靠宏生成
前端·rust·前端框架
程序员大阳2 小时前
副队长CSS教程(29)–CSS Grid 实战:实现通用图文混排布局
前端·css·grid·图文混排
对空六课2 小时前
无头浏览器访问对跳出率的过滤规则
服务器·前端·算法·数据分析
小番茄夫斯基2 小时前
Nginx 介绍
前端