跳到主要内容

使用Three.js创建的第一个3D场景

近年来,现代浏览器已经具备了直接从JavaScript访问的强大功能。 您可以使用HTML5标签轻松添加视频和音频, 并通过使用HTML5 Canvas创建交互式组件。 与HTML5一起,现代浏览器还支持WebGL。 使用WebGL,您可以直接利用图形卡的处理资源,创建高性能的2D和3D计算机图形。 直接从JavaScript中使用WebGL来创建和动画化3D场景是一个非常复杂、冗长且容易出错的过程。 Three.js是一个使这变得更加容易的库。 以下列表显示了使用Three.js可以轻松完成的一些事情:

  • 创建简单和复杂的3D几何体并在任何浏览器中渲染它们
  • 通过3D场景中的动画移动对象
  • 为对象应用纹理和材质
  • 使用不同的光源照亮场景
  • 使用来自3D建模软件的模型,并将生成的模型导出到这些程序中
  • 向您的3D场景添加高级后处理效果
  • 创建并使用自定义着色器
  • 创建、可视化和动画化点云
  • 创建虚拟现实(VR)和增强现实(AR)场景

使用几行JavaScript(或后面在本书中将看到的TypeScript), 您可以在浏览器中实时渲染从简单的3D模型到逼真场景的任何内容。 例如,图1.1显示了使用Three.js可以完成的一个示例(您可以通过在浏览器中 打开并查看动画):

图1.1 Three.js 渲染和动画场景

在本章中,我们将直接深入研究Three.js,并创建一些示例,这些示例将向您展示Three.js的工作原理, 并且您可以使用它们进行尝试,以更全面地了解Three.js。 我们暂时不会深入探讨所有技术细节;这些内容将在接下来的章节中介绍。 通过本章结束时,您将能够创建场景,并运行并探索本书中的所有示例。

我们将以对Three.js的简短介绍开始本书,然后迅速转向前几个示例和代码样本。 在开始之前,让我们简要了解一下一些重要的浏览器以及它们对WebGL(和WebGPU)的支持。

备注

所有现代桌面和移动浏览器目前都支持WebGL。 IE的旧版本(版本11之前的版本)将无法运行基于WebGL的应用程序。 在移动设备上,大多数浏览器都支持WebGL。 因此,使用WebGL,您可以在桌面和移动设备上都能够良好运行的创建交互式3D可视化。

在本书中,我们将重点关注Three.js提供的基于WebGL的渲染器。 然而,还有一种基于CSS 3D的渲染器,它提供了一个简单的API来创建基于CSS 3D的3D场景。 使用CSS 3D的一个重要优势是,该标准在所有移动和桌面浏览器上都受支持,并允许您在3D空间中呈现HTML元素。 我们不会详细讨论这个渲染器的细节,但将在第7章“点和精灵”中展示一个示例。

除了WebGL之外,还正在开发一种新的标准,用于使用GPU在浏览器中进行渲染,称为WebGPU。 它将提供比WebGL更好的性能,并且在未来将成为新的标准。 当您使用Three.js时,您无需担心此更改。 Three.js已经部分支持WebGPU,随着该标准的成熟,Three.js对该标准的支持也将增加。 因此,您使用Three.js创建的所有内容也将在WebGPU中立即运行。

在本书的第一章中,您将直接创建一个3D场景,并能够在桌面或移动设备上运行它。 我们将解释Three.js的核心概念,如果有更高级的主题,我们将在哪一章中详细解释。 在这一章中,我们将创建两个不同的场景。第一个将显示在Three.js中呈现的基本几何体,如下图所示:

之后,我们还会快速向您展示如何加载外部模型,以及创建逼真场景有多么容易。第二个示例的结果将如下所示:

在您开始处理这些示例之前,在接下来的几个部分中, 我们将了解轻松使用Three.js的工具以及如何下载本书中显示的示例。 在本章中,我们将涵盖以下主题:

  • 使用Three.js的要求
  • 下载本书中使用的源代码和示例
  • 测试和尝试示例
  • 渲染和查看3D对象
  • 介绍一些用于统计和控制场景的辅助库

技术要求

Three.js是一个JavaScript库, 因此创建Three.js WebGL应用程序所需的只是一个文本编辑器和一个支持渲染结果的支持浏览器。 我想推荐以下几个我在过去几年中广泛使用于各种项目的文本编辑器:

  • Visual Studio Code:这是微软提供的免费编辑器,适用于所有主要平台, 提供基于类型、函数定义和导入的智能代码高亮和自动完成。 它提供了一个非常清晰的界面,非常适合在JavaScript项目上工作。 可以从这里下载。 如果您不想下载此编辑器, 也可以直接导航到https://vscode.dev/, 这将在浏览器中直接启动一个编辑器, 您可以从中连接到GitHub存储库或访问本地文件系统中的目录。

  • WebStorm:这是JetBrains提供的编辑器,对JavaScript编辑提供了很好的支持。 它支持代码完成、自动部署和直接从编辑器进行JavaScript调试。 此外,WebStorm还具有出色的GitHub(和其他版本控制系统)支持。 您可以从下载试用版

  • Notepad++:Notepad++是一个通用编辑器,支持广泛的编程语言的代码高亮。 它可以轻松地布局和格式化JavaScript。 请注意,Notepad++仅适用于Windows。 可以从下载Notepad++。

  • Sublime Text Editor:Sublime是一个很棒的编辑器,对JavaScript编辑提供了非常好的支持。 此外,它提供了许多非常有帮助的选择(如多行选择)和编辑选项, 一旦你习惯了它们,就会提供一个非常好的JavaScript编辑环境。 Sublime也可以免费测试,并且可以从下载

即使您不使用这些编辑器中的任何一个,也有很多可用的编辑器,包括开源和商业版, 您可以使用它们来编辑JavaScript并创建Three.js项目,因为您只需要能够编辑文本。 您可能想查看的一个有趣的项目是AWS Cloud9。 这是一个基于云的JavaScript编辑器,可以连接到GitHub帐户。 通过这种方式,您可以直接访问本书中的所有源代码和示例,并进行实验。

备注

另外,除了这些文本编辑器,您可以用于编辑和尝试本书源代码的编辑器之外, Three.js目前还提供了一个在线编辑器。 您可以在http://threejs.org/editor/找到这个编辑器, 通过它,您可以使用图形方式创建Three.js场景。

我建议选择使用Visual Studio Code。 这是一个非常轻量级的编辑器,对JavaScript有很好的支持, 并且有几个其他的扩展使得编写JavaScript应用程序更加容易。

之前我提到过,大多数现代的Web浏览器都支持WebGL,并且可以用来运行Three.js的例子。 我通常在Firefox中运行我的代码。 原因是通常情况下,Firefox对WebGL有最好的支持和性能,并且它有一个出色的JavaScript调试器。 使用这个调试器,如下截图所示,您可以通过使用断点和控制台输出等方式迅速定位问题:

备注

本书中的所有示例在Chrome和Firefox中同样适用。 因此,如果这是您选择的浏览器,当然可以使用它。

在整本书中,我将向您提供关于调试器的使用以及其他调试技巧的指导。 目前就介绍这么多,让我们获取源代码并开始第一个场景。

获取源代码

本书的所有代码都可以在GitHub上找到( https://github.com/yuxuetr/Learn-Three.js)。 GitHub是一个托管Git存储库的站点。 您可以使用这些存储库来存储、访问和版本控制源代码。 有几种方式可以获取这些源代码。您可以选择以下任一方式:

  • 克隆Git存储库。这意味着您使用git命令行工具获取本书源代码的最新版本。
  • 从GitHub下载并解压缩包含所有内容的存档文件。

在接下来的两个小节中,我们将稍微详细地探讨这些选项。

使用git克隆存储库

获取所有示例的一种方法是使用git命令行工具克隆此存储库。 为此,您需要为您的操作系统下载一个Git客户端。 如果您的操作系统是最新的,可能已经安装了Git。 您可以通过在终端中运行以下命令来快速检查:

$ git --version

如果尚未安装该命令,则可以从这里获取客户端并按照说明进行安装:http://git-scm.com。 安装Git后,您可以使用git命令行工具克隆本书的存储库。 打开命令提示符并转到要下载源代码的目录。在该目录中,运行以下命令:

$ git clone https://github.com/yuxuetr/Learn-Three.js.git

$ git clone git@github.com:yuxuetr/Learn-Three.js.git

执行此操作后,所有源代码将被下载到learning-threejs目录中。 从该目录中,您可以运行本书中介绍的所有示例。

下载并解压缩存档

如果您不想使用git直接从GitHub下载源代码,也可以下载存档。 在浏览器中打开https://github.com/yuxuetr/Learn-Three.js, 并单击右侧的“Code”按钮。 这将使您可以通过点击“Download ZIP”选项将所有源代码下载到单个ZIP文件中:

测试和尝试示例

代码和示例按章节组织,对于示例,我们将提供一个简单的集成服务器,您可以使用该服务器访问所有示例。 要启动此服务器,我们需要安装Node.js和npm。 这两个工具用于管理JavaScript包、构建JavaScript应用程序, 并使我们更容易将Three.js代码模块化和集成现有的JavaScript库。 要安装这两个工具,请访问https://nodejs.org/en/download/, 并选择适合您操作系统的安装程序。 安装完成后,打开终端并检查一切是否正常。在我的机器上,使用了以下版本:

$ npm -v
10.1.0
$ node -v
v20.9.0

安装了这两个工具之后,我们需要执行一些步骤,以获取所有外部需要的依赖项,然后我们才能构建和访问示例:

  1. 首先,我们需要下载示例中使用的外部库。例如,Three.js是我们需要下载的依赖项之一。

要下载所有依赖项,请在下载或解压缩所有示例的目录中运行以下命令:

$ npm install

上述命令将开始下载所有所需的JavaScript库,并将其存储在node_modules文件夹中。

  1. 接下来,我们需要构建示例。这将把我们的源代码和外部库合并成一个单一的文件,我们可以在浏览器中显示。 要使用npm构建示例,请使用以下命令:
$ npm run build

注意,您只需运行前两个命令一次。

  1. 到此为止,所有示例都已构建完毕,可以供您探索。 要打开这些示例,您需要一个Web服务器。 要启动服务器,只需运行以下命令:
$ npm run serve

此时,您可能会注意到npm已经打开了您的默认浏览器, 并显示了http://localhost:8080的内容(如果没有,请打开您选择的浏览器并导航到http://localhost:8080)。 您将看到所有章节的概览。在每个子文件夹中,您将找到在该章节中解释的示例:

这个服务器的一个非常有趣的特性是,我们现在可以立即在浏览器中看到对源代码的更改。 如果您通过运行npm run serve启动了服务器, 请在您下载的源代码中的编辑器中打开chapter-01/geometries.js示例并进行一些更改; 您将看到在保存更改后,这也会同时在浏览器中更改。这使得测试更改和微调颜色和光线变得更加容易。 如果您在代码编辑器中打开chapter-01/geometries.js文件, 并在浏览器中打开http://localhost:8080/chapter-01/geometries.html示例,您就可以看到这个过程。 在编辑器中,更改立方体的颜色。为此,找到以下代码:

initScene(props)(({ scene, camera, renderer, orbitControls }) => {
const geometry = new THREE.BoxGeometry();
const cubeMaterial = new THREE.MeshPhongMaterial({
color: 0xFF0000,
});
})

将其更改为:

initScene(props)(({ scene, camera, renderer, orbitControls }) => {
const geometry = new THREE.BoxGeometry();
const cubeMaterial = new THREE.MeshPhongMaterial({
color: 0x0000FF,
});
})

现在,当您保存文件时,您将立即看到浏览器中立方体的颜色发生变化,而无需刷新浏览器或进行其他任何操作。

备注

本书中我们使用的设置是开发Web应用程序的许多不同方法之一。 或者,您可以直接在HTML文件中包含Three.js(和其他库),或者使用导入映射的方法, 就像在Three.js网站上的示例中所做的那样。所有这些方法都有优缺点。 对于本书,我们选择了一种方法,使得在浏览器中轻松实验源代码并获得直接反馈, 并且与通常构建这类应用程序的方式非常相似。

理解一切是如何协同工作的一个良好起点是查看我们在浏览器中打开的HTML文件。

探索Three.js应用程序的HTML结构

在本节中,我们将查看geometries.html文件的源代码。 您可以通过在浏览器中查看源代码或在下载本书源代码的位置的dist/chapter-1文件夹中打开该文件来执行此操作:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
}
</style>
<script defer src="../js/vendors-node_modules_three_
build_three_module_js.js"></script>
<script defer src="../js/vendors-node_modules_lil-gui_
dist_lil-gui_esm_js.js"></script>
<script defer src="../js/vendors-node_modules_three_
examples_jsm_controls_OrbitControls_js.js"></script>
<script defer src="../js/geometries.js"></script>
</head>
<body>
</body>
</html>

此代码是在运行npm run build步骤时生成的。 这将把您使用的所有源代码和外部库合并到单独的源文件(称为包)中,并将它们添加到此页面中。 因此,您无需自己执行此操作。 前三个<script>标签是对我们使用的任何外部库的引用。 在本书的后面,我们将介绍其他库,如React.jsTween.js。 这些将以相同的方式自动包含在其中。 这里唯一的其他元素是<style><body><style>用于禁用页面中的任何边距,以便我们可以使用完整的浏览器视口来显示我们的3D场景。 此外,我们将以编程方式将3D场景添加到空的<body>元素中,我们将在下一节中解释。

如果您确实想要在此处添加自定义HTML元素,那当然可以这样做。 在下载的代码的根目录中,您会找到一个template.html文件,该文件由构建过程用于创建示例的各个HTML文件。 您添加的任何内容都将添加到所有示例中。 我们不会深入研究这是如何工作的,因为这超出了本书的范围。 但是,如果您想要了解更多关于这是如何工作的信息, 关于webpack(我们在此使用的工具)的一些良好资源如下:

在这里,您可以找到有关我们使用的webpack插件的信息, 该插件将源代码组合成您在运行npm run build然后运行npm run serve后在浏览器中看到的单独的HTML页面。

请注意,我们无需显式初始化场景或调用JavaScript。 每当打开此页面并加载geometries.js文件时, 该文件中的JavaScript将运行并创建我们的3D场景。

现在,我们已经设置了基本结构,我们可以创建和渲染我们的第一个场景。

渲染和查看3D对象

在本节中,您将创建您的第一个场景,这是一个简单的3D场景,如下所示: 在上面的截图中,您可以看到两个旋转的对象。 这些对象称为网格(meshes)。 网格描述对象的几何形状,即其形状,并包含有关对象材质的信息网格通过特性(例如颜色、对象是否闪亮或透明等)确定形状在屏幕上的显示方式。 在前面的截图中,我们可以识别出三个这样的网格:

对象描述
Plane这是一个用作地面区域的二维矩形。在图1.7中,您可以看到这一点,因为它显示了由两个网格投射的阴影。我们将其创建为一个非常大的矩形,以便您看不到任何边缘。
Cube这是一个三维立方体,显示在图1.7的左侧。它呈红色。
Torus knot这是图1.7右侧所见的TorusKnot。这个是绿色的。

要在屏幕上显示所有这些内容,我们需要执行一些步骤,我们将在接下来的几节中解释。

设置场景

每个Three.js应用程序至少需要一个相机、一个场景和一个渲染器场景是包含所有对象(网格、相机和灯光)的容器, 相机确定渲染时显示场景的哪个部分, 渲染器负责创建屏幕上的输出,考虑场景中的所有网格、相机和灯光的信息。

我们将讨论的所有代码都可以在chapter-1/getting-started.js文件中找到。 此文件的基本结构如下:

import * as THREE from "three";
import Stats from 'three/examples/jsm/libs/stats.module'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

// 创建场景
...

// 设置相机
...

// 设置渲染器并附加到画布
...

// 添加光源
...

// 创建立方体和环结构,并将它们添加到场景中
...

// 创建一个非常大的地面平面
...

// 添加OrbitControls以使用鼠标在场景周围平移
...

// 添加用于监视帧率的统计信息
...

// 渲染场景

如果您查看上述步骤,您可能已经注意到这些步骤中的许多都是为您创建的每个场景都是相同的。 由于本书中有很多示例显示了Three.js的不同特性,我们将这些代码提取到一些辅助文件中。 我们将在本章末尾展示如何做到这一点。 现在,我们将浏览不同的步骤,并向您介绍Three.js场景的基本组件。

首先,我们必须创建一个THREE.Scene这是一个基本的容器,将容纳所有的网格、灯光和相机, 并具有一些简单的属性,我们将在下一章中更深入地探讨:

// 基本场景设置
const scene = new THREE.Scene();
scene.backgroundColor = 0xffffff;
scene.fog = new THREE.Fog(0xffffff, 0.0025, 50);

在这里,我们将创建一个容器对象,将包含所有我们的对象, 将此场景的背景颜色设置为白色(0xffffff),并在此场景中启用雾效果。 启用雾效果后,远离摄像机的对象将慢慢被雾隐藏。

接下来是创建相机和渲染器

// 设置相机和基本渲染器
const camera = new THREE.PerspectiveCamera(
75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = -3;
camera.position.z = 8;
camera.position.y = 2;

// 设置渲染器并附加到画布
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.VSMShadowMap;
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff);
document.body.appendChild(renderer.domElement);

在上述代码中,我们创建了一个PerspectiveCamera,它确定场景的哪个部分将被渲染。 在这一点上,不必太担心参数,因为我们将在第3章《在Three.js中使用光源》中详细讨论这些参数。 我们还将相机定位在指定的x、y和z坐标上。 相机将默认看向场景的中心(0, 0, 0),因此我们不需要更改任何内容。

在此代码片段中,我们还创建了一个WebGLRenderer它将用于在场景上渲染来自相机的视图。 现在暂时忽略其他属性; 当我们深入研究WebGLRenderer的详细信息以及如何微调颜色并处理阴影时, 我们将在接下来的几章中解释这些属性。 值得注意的一个有趣部分是document.body.appendChild(renderer.domElement)

此步骤将在页面上添加一个HTML画布元素,显示渲染器的输出。

您可以在浏览器中检查页面时看到这一点。

此时,我们已经有了一个空的THREE.Scene、 一个THREE.PerspectiveCamera和 一个THREE.WebGLRenderer。 如果我们将一些对象添加到场景中,我们就可以在屏幕上显示一些输出了。 然而,在这样做之前,我们将添加一些额外的组件:

  • OrbitControls:这将允许您使用鼠标在场景周围旋转和平移
  • 光源:这允许我们使用一些更高级的材质、投射阴影,并总体使我们的场景看起来更好

在下一节中,我们将首先添加灯光。

添加光源

如果场景中没有光源,大多数材质将呈黑色。 因此,为了看到我们的网格(并获得阴影),我们将在场景中添加一些光源。 在这种情况下,我们将添加两种光源

  • THREE.AmbientLight:这只是一个简单的光源,以相同的强度和颜色影响所有物体。
  • THREE.DirectionalLight:这是一个光源,其光线是相互平行的。这基本上就是我们体验到太阳光的方式。

以下代码片段显示了如何做到这一点:

// 添加光源
scene.add(new THREE.AmbientLight(0x666666))
const dirLight = new THREE.DirectionalLight(0xaaaaaa)
dirLight.position.set(5, 12, 8)
dirLight.castShadow = true
// and some more shadow related properties

同样,这些光源可以以各种方式进行配置,我们将在第3章中详细解释这些细节。 此时,我们已经准备好渲染场景的所有组件,所以让我们添加网格。

添加网格

在下面的代码片段中,我们在场景中创建了三个网格:

// 创建一个立方体和环结,并将它们添加到场景中
const cubeGeometry = new THREE.BoxGeometry();
const cubeMaterial = new THREE.MeshPhongMaterial({ color: 0x0000FF });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -1;
cube.castShadow = true;
scene.add(cube);

const torusKnotGeometry = new THREE.TorusKnotBufferGeometry(0.5, 0.2, 100, 100);
const torusKnotMat = new THREE.MeshStandardMaterial({
color: 0x00ff88,
roughness: 0.1,
});
const torusKnotMesh = new THREE.Mesh(torusKnotGeometry, torusKnotMat);
torusKnotMesh.castShadow = true;
torusKnotMesh.position.x = 2;
scene.add(torusKnotMesh);

// 创建一个非常大的地面平面
const groundGeometry = new THREE.PlaneBufferGeometry(10000, 10000)
const groundMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff
})
const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial)
groundMesh.position.set(0, -2, 0)
groundMesh.rotation.set(Math.PI / -2, 0, 0)
groundMesh.receiveShadow = true
scene.add(groundMesh)

在这里,我们创建了一个立方体、一个环结和地面。所有这些网格都遵循相同的思想:

  1. 我们创建形状 - 也就是对象的几何形状:THREE.BoxGeometryTHREE.TorusKnotBufferGeometryTHREE.PlaneBufferGeometry
  2. 我们创建材质。 在这种情况下,我们为立方体使用 THREE.MeshPhongMaterial,为环结使用 THREE.MeshStandardMaterial,为地面使用 THREE.MeshLambertMaterial。立方体的颜色是蓝色,环结的颜色是绿色,地面的颜色是白色。在第4章《使用Three.js材质》中,我们将探讨所有这些材质,它们最适用于哪些情况以及如何配置它们。
  3. 我们告诉Three.js立方体和环结投射阴影,而地面则接收阴影
  4. 最后,从形状和材质创建THREE.Mesh定位网格,并将其添加到场景中

此时,我们只需调用renderer.render(scene, camera),您就会在屏幕上看到结果: 如果您有源文件(chapter-01/getting-started.js),请在编辑器中打开它; 现在也是尝试一些设置的好时机。 通过更改torusKnot.postion.xtorusKnot.position.ytorusKnot.position.z 设置, 您可以在场景中移动环结(在编辑器中保存文件后会应用更改)。 您还可以通过更改材质的color属性轻松更改网格的颜色

添加动画循环

此时,场景非常静态。您无法移动相机,也没有任何运动。 如果我们想要对场景进行动画首先要做的是找到一种在特定时间间隔重新渲染场景的方法。 在HTML5和相关的JavaScript API出现之前,使用setInterval(function, interval)函数来实现这一点。 通过setInterval,我们可以指定一个函数,例如每100毫秒调用一次。 该函数的问题在于它不考虑浏览器中正在发生的事情。 如果您浏览其他标签页,此函数仍然会每隔几毫秒触发一次。 除此之外,setInterval在屏幕重新绘制时不同步。 这可能导致更高的CPU使用率、闪烁和通常性能不佳。 幸运的是,现代浏览器通过requestAnimationFrame函数解决了这个问题。

引入requestAnimationFrame

使用requestAnimationFrame,您可以指定以一定间隔调用的函数。 但是,您不定义此间隔,它由浏览器定义。 您在提供的函数中进行任何需要的绘制,浏览器将确保以尽可能平滑和高效的方式绘制它。 使用它很简单。我们只需添加以下代码:

function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();

在上述animate函数中,我们再次调用requestAnimationFrame,以保持动画进行。 代码中唯一需要更改的是,我们不再在创建完整场景后调用renderer.render而是调用animate()函数一次以启动动画。 如果运行此代码,与之前的示例相比,您不会看到任何变化, 因为我们还没有在animate()函数中更改任何内容。 然而,在向该函数添加其他功能之前, 我们将介绍一个称为stats.js的小型辅助库,该库提供有关动画运行时帧速率的信息。 这个库来自与Three.js相同作者,它呈现一个小图表,显示有关渲染场景的速率的信息。 要添加这些统计信息,我们只需导入正确的模块并将其添加到我们的页面中:

import Stats from 'three/examples/jsm/libs/stats.module'
const stats = Stats()
document.body.appendChild(stats.dom)

如果只是这样,您会在屏幕左上角看到一个漂亮的统计计数器,但不会发生任何事情。 原因是我们需要告诉此元素何时处于requestAnimationFrame循环中。 为此,我们只需在我们的animate函数中添加以下内容:

function animate() {
requestAnimationFrame(animate);
stats.update();
renderer.render(scene, camera);
}
animate();

如果打开chapter-1/getting-started.html示例, 您会看到在屏幕左上角显示了每秒帧数(FPS)计数器: 在chapter-1/getting-started.html示例中, 您已经可以看到环结和立方体沿它们的轴移动。 在下一节中,我们将解释如何通过扩展animate()函数来实现这一点。

动画网格

使用requestAnimationFrame和配置好的统计信息,我们有了一个放置动画代码的地方。 我们只需要将其添加到animate()函数中:

cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
cube.rotation.z += 0.01;
torusKnotMesh.rotation.x -= 0.01;
torusKnotMesh.rotation.y += 0.01;
torusKnotMesh.rotation.z -= 0.01;

看起来很简单,对吧?我们所做的是在每次调用animate()函数时通过0.01增加每个轴的旋转属性, 这导致网格平滑地围绕它们的所有轴旋转。 如果我们改为在轴的旋转而不是位置上进行更改,我们可以移动网格:

let step = 0;
function animate() {
//...
step += 0.04;
cube.position.x = 4 * Math.cos(step);
cube.position.y = 4 * Math.abs(Math.sin(step));
//...
}

对于立方体,我们已经更改了旋转属性; 现在,我们还将更改它在场景中的位置属性。 我们希望立方体从场景中的一个点反弹到另一个点,并以漂亮而平滑的曲线移动。 为此,我们需要更改其在X轴和Y轴上的位置。 通过使用Math.cosMath.sin函数以及step变量,我们可以创建一个平滑的轨迹。 我在这里不会详细介绍这是如何工作的。 现在,您只需要知道step += 0.04定义了弹跳球的速度。 如果要自己启用此功能,请打开chapter-1/geometries.js文件并取消注释animate()函数中的部分。 完成后,您将在屏幕上看到类似于以下内容的内容,其中蓝色立方体在场景中跳舞:

启用轨道控制

如果尝试使用鼠标移动场景,不会发生太多事情。 这是因为我们将相机添加到固定位置,并且我们没有在动画循环中更新其位置。

当然,我们可以像对立方体的位置一样以相同的方式对相机的位置进行更新, 但是Three.js提供了几种控件,可以让您轻松地在场景中移动并移动相机。 在本示例中,我们将介绍THREE.OrbitControls。 使用这些控件,您可以使用鼠标在场景中移动相机并查看不同的对象。 为了使其工作,我们只需要创建这些控件的新实例,将其附加到相机上, 并从我们的动画循环中调用update函数:

const orbitControls = new OrbitControls(camera, renderer.domElement)
// 还有许多其他属性我们可以设置

function animate() {
//...
orbitControls.update();
}

现在,您可以使用鼠标在场景中导航。这在chapter-1/getting-started.html示例中已经启用:

在结束本节之前,我们将向我们的基本场景添加一个元素。 在处理3D场景、动画、颜色和属性时,通常需要进行一些试验,以获取正确的颜色、动画速度或材质属性。 如果有一个简单的 GUI,可以让您实时更改这些属性,那将非常方便。 幸运的是,您有!

使用lil-gui控制属性并使实验更加容易

在先前的示例中,我们为环结和立方体添加了一点点动画。 现在,我们将创建一个简单的 UI 元素,允许我们控制旋转和移动的速度。 为此,我们将使用来自 https://lil-gui.georgealways.com/lil-gui 库。 此库允许我们快速创建一个简单的控制 UI,以使实验场景更加轻松。 可以这样添加它:

import GUI from "lil-gui";
...
const gui = new GUI();
const props = {
cubeSpeed: 0.01,
torusSpeed: 0.01,
};
gui.add(props, 'cubeSpeed', -0.2, 0.2, 0.01)
gui.add(props, 'torusSpeed', -0.2, 0.2, 0.01)
function animate() {
//...
cube.rotation.x += props.cubeSpeed;
cube.rotation.y += props.cubeSpeed;
cube.rotation.z += props.cubeSpeed;
torusKnotMesh.rotation.x -= props.torusSpeed;
torusKnotMesh.rotation.y += props.torusSpeed;
torusKnotMesh.rotation.z -= props.torusSpeed;
//...
}

在上述代码片段中,我们创建了一个新的控制元素(new GUI)并配置了两个控制:cubeSpeedtorusSpeed。 在每个动画步骤中,我们只需查找当前值并使用它们旋转网格。 现在,我们可以在不必在浏览器和编辑器之间切换的情况下实验这些属性。 在本书中的大多数示例中,您都会看到此 UI, 以便您可以轻松地尝试使用材质、灯光和其他 Three.js 对象提供的不同选项。 在以下截图中,您可以看到屏幕右上角的控制,用于控制场景: 在我们继续本章的最后一节之前,这里有一个关于我们到目前为止展示的内容的快速说明。 您可以想象,大多数场景都需要几乎相同的设置。 它们都需要一些灯光、相机、场景,也许还需要一个地板。 为了避免在每个示例中都添加所有这些内容,我们已将大多数这些共同元素外部化到一组辅助库中。 这样,我们可以保持示例的整洁,以便它们只向您显示与该示例相关的代码。 如果您对这是如何设置的感兴趣,可以查看将这种方法整合在一起的 bootstrap 文件夹中的文件。

在先前的示例中,我们在场景中渲染了一些简单的网格,并将它们直接定位。 然而,有时很难确定在哪里定位对象,或者应该将它们旋转多远。 Three.js 提供了几种不同的辅助工具,可为您提供有关场景的附加信息。 在下一节中,我们将介绍一些这些辅助函数。

辅助对象和实用函数

在我们进入下一章之前,我们将快速介绍一些辅助函数和对象。 这些助手使得更容易定位对象并了解场景中发生的情况。 查看此功能的最简单方法是在浏览器中打开 chapter-01/porsche.html 示例:

在屏幕右侧的菜单底部,您将看到控制中的三个按钮: Toggle AxesHelperToggle GridHelperToggle PolarGridHelper。 当您单击其中任何一个时,Three.js 将在屏幕上添加一个覆盖层,可帮助您确定和定位网格, 确定所需的旋转并检查对象的大小。 例如,当我们切换 AxesHelper 时,我们将在场景中看到 x、y 和 z 轴:

请注意,在此示例中,您可以看到一个更广泛的控制 UI,您还可以在其中控制 WebGLRenderer 的各个方面。

摘要

这就是本章的全部内容。 在这一章中,您学会了如何设置开发环境,如何获取代码以及如何开始使用本书提供的示例。 接着,您了解到要使用 Three.js 渲染场景,必须创建一个 THREE.Scene 对象, 并添加相机、光源以及要渲染的对象。 我们还向您展示了如何通过添加动画扩展此基本场景。 最后,我们添加了一些辅助库。 我们使用了 lil-GUI,它允许您快速创建控制 UI,并添加了一个 FPS 计数器, 以提供有关渲染场景的帧率和其他指标的反馈。

所有这些内容都将帮助您理解即将在后续章节中呈现的示例, 并使您更容易尝试更高级的示例并开始根据您的喜好进行修改。 如果在接下来的几章中进行实验时出现问题或结果不符合您的期望, 请记住本章中向您展示的内容:使用 JavaScript 控制台获取额外的信息, 添加调试语句,使用 Three.js 提供的辅助工具,或添加自定义控制元素。

在接下来的章节中,我们将扩展此处显示的基本设置, 您将了解有关在 Three.js 中使用的最重要的构建块的更多信息。