第二章组成Three.js基本组件

在上一章中,您了解了Three.js的基础知识。我们查看了几个示例,然后您创建了第一个完整的Three.js应用程序。在本章中,我们将更深入地介绍Three.js,并解释组成Three.js应用程序的基本组件。

在本章的结尾,您将学习如何使用每一个Three.js应用程序中使用的基本组件,并且应该能够使用这些标准组件创建简单的场景。使用使用更高级对象的Three.js应用程序也应该感到舒适,因为Three.js为简单和高级组件使用的方法是相同的。

在本章中,我们将介绍以下主题:

  • 创建场景
  • 几何图形和网格如何关联
  • 对不同的场景使用不同的摄像机

我们将首先研究如何创建一个场景和添加对象。

创建场景

在第1章,使用Three.js创建第一个3D场景中,您创建了三个。场景,所以你已经知道了Three.js的一些基本知识。我们看到,一个场景要显示任何东西,我们需要四种不同类型的物体:

  • Camera照相机:这就决定了其中的哪一部分 THREE.Scene 将在屏幕上进行渲染。
  • Lights灯光:这些灯光会影响在创建阴影效果时如何显示和使用材料(在第3章,在Three.js中使用光源)中详细讨论)。
  • Meshes网格:这些是从相机角度渲染的主要对象。这些对象包含组成几何体的顶点和面(例如,球体或立方体),并包含一个定义几何体外观的材质。
  • Renderer渲染器:这将使用摄像机和场景中的信息来绘制(渲染)屏幕上的输出。

组成一个Three.js应用程序的基本组件

THREE.Scene 作为您要渲染的灯光和网格的主容器。

THREE.Scene 它本身并没有那么多的选项和功能。

THREE.Scene 是一种结构,有时也被称为场景图。场景图可以保存图形场景的所有必要信息。在Three.js中,这意味着THREE.Scene 包含渲染所需的所有对象。值得注意的是,一个场景图,顾名思义,并不仅仅是一个对象的数组;一个场景图由树状结构中的一组节点组成。正如我们将在第8章“创建和加载高级网格和几何图形中看到的,Three.js提供了可用于创建不同网格或灯光组的对象。可以使用的主要对象是三个来创建场景图。组顾名思义,此对象允许您将对象组合在一起。 THREE.Group 从Three.js中的另一个基类扩展到 THREE.Object3D,它提供了一组标准函数来添加和修改子函数。三种。网格和三。场景也从三延伸。所以您也可以使用它们来创建一个嵌套的结构。但这是惯例,在语义上更正确。进行组,以建立场景图。

一个场景的基本功能

探索场景功能的最佳方法是查看一个例子。在本章的源代码中,您可以找到chapter-2/basic-scene.html的示例。我们将使用这个示例来解释一个场景所具有的各种功能和选项。当我们在浏览器中打开这个示例时,输出看起来类似于下一个截图中显示的内容(记住,您可以使用鼠标在渲染场景中移动、缩放和平移)

image-20230506224026972

图2.1-基本的场景设置

前面的图看起来就像我们在第一章中看到的例子,创建你的第一个3D场景。尽管这个场景看起来很空,但它已经包含了一些对象:

  • 我们有 THREE.Mesh ,它代表了你可以看到的建筑面积,
  • 我们使用的是 THREE.PerspectiveCamera。来确定我们所看到的是什么,
  • 我们已经增加了 THREE.AmbientLight and THREE.DirectionalLight,以提供照明

这个示例的源代码可以在basic-scene.js中找到,我们可以使用来自bootstrap/bootstrap.js、bootstrap/floor.js和bootstrap/lighting.js的代码,因为这是我们在本书中使用的一个通用的场景设置。在所有这些文件中发生的事情可以简化为以下代码:

// 创建摄像机
const camera = new THREE.PerspectiveCamera(
 75,
 window.innerWidth / window.innerHeight,
 0.1,
 1000
);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true
 });
// 创建场景
const scene = new THREE.Scene();
// 场景灯光
scene.add(new THREE.AmbientLight(0x666666));
scene.add(THREE.DirectionalLight(0xaaaaaa));
// 创建地面
const geo = new THREE.BoxBufferGeometry(10, 0.25, 10, 10,
 10, 10);
const mat = new THREE.MeshStandardMaterial({ color:
 0xffffff,});
const mesh = new THREE.Mesh(geo, mat);
scene.add(mesh);

正如您在前面的代码中所看到的,我们创建了 THREE.WebGLRenderer 和 THREE.PerspectiveCamera,,因为我们总是需要这些。接下来,我们创建一个 THREE.Scene,只需添加我们想要使用的所有对象。在这种情况下,我们添加了两个灯和一个网格。现在,我们有所有的组件来启动的渲染循环,正如我们在第一章中看到的,创建第一个3D场景。

在我们看这三个问题之前。更深入的场景对象,我们将首先解释您在演示中可以做什么,然后,看看代码。在浏览器中打开chapter-2/basic-scene.html示例,看看右上角的控件菜单,你可以在下面的屏幕截图中看到:

image-20230506224603736

图2.2-立体背景的基本场景设置

添加和删除对象

使用这些控件,您可以将多维数据集添加到场景中,并删除上次添加的多维数据集。它还允许您更改场景的背景,并设置场景中所有对象的材质和环境贴图。我们将探讨这些不同的选项,以及您如何使用它们来配置一个THREE.Scene.。我们将首先看看如何添加和删除 THREE.Mesh 场景之间的网格对象。下面的代码显示了我们在你点击addCube按钮时调用的函数:

const addCube = (scene) => {
 const color = randomColor();
 const pos = randomVector({
 xRange: { fromX: -4, toX: 4 },
 yRange: { fromY: -3, toY: 3 },
 zRange: { fromZ: -4, toZ: 4 },
 });
 const rotation = randomVector({
 xRange: { fromX: 0, toX: Math.PI * 2 },
 yRange: { fromY: 0, toY: Math.PI * 2 },
 zRange: { fromZ: 0, toZ: Math.PI * 2 },
 });
 const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
 const cubeMaterial = new THREE.MeshStandardMaterial({
 color: color,
 roughness: 0.1,
 metalness: 0.9,
 });
 const cube = new THREE.Mesh(geometry, cubeMaterial);
 cube.position.copy(pos);
 cube.rotation.setFromVector3(rotation);
 cube.castShadow = true;
 scene.add(cube);
};

让我们详细了解前面的代码:

  • 首先,我们为将添加的立方体确定了一些随机设置:一个随机颜色(通过调用randomColor() 辅助函数),一个随机位置,和一个随机旋转。最后两个是通过调用randomVector() 随机生成的。
  • 接下来,我们将创建要添加到场景中的几何体:一个立方体。我们只是创建了一个 THREE.BoxGeometry 为此,定义一个材料(THREE.MeshStandardMaterial),并将这两个合并为THREE.Mesh。我们使用随机变量来设置立方体的位置和旋转。
  • 最后,THREE.Mesh 可以通过调用场景来将网格添加到场景中 scene.add(cube)

我们在前面的代码中引入的一个新元素是,我们还使用name属性为多维数据集提供一个名称。名称设置为多维数据集,并附加当前场景中的对象数量(scene.children.length)。名称对于调试目的非常有用,但也可以用于直接从场景中访问对象。如果你使用THREE.Scene.getObjectByName(name) 函数,您可以直接检索一个特定的对象,例如,更改它的位置,而不必使JavaScript对象成为一个全局变量。

也可能在某些情况下,您希望从THREE.Scene. Since 删除一个现有的对象。所显示的场景。因为THREE.Scene 场景通过子属性公开它的所有子元素,我们可以使用以下简单代码删除最后添加的子元素:

const removeCube = (scene) => {
 scene.children.pop();
};

Three.js也为Three.Scene提供了其他有用的功能,与处理场景中的 children 有关:

  • add:我们已经看到了这个函数,它将提供的对象添加到场景中。如果它之前被添加到一个不同的 THREE.Object3D,它将被从该对象中删除。
  • Attach:这类似于添加,但如果您使用它,那么应用于此对象的任何旋转或平移都将被保留。
  • getObjectById:当你向一个场景添加一个对象时,它会得到一个ID。第一个得到1,第二个得到2,以此类推。使用此函数,您可以基于此ID获得一个子项。
  • getObjectByName:它基于其名称属性返回一个对象。您可以在对象上设置名称——这与由Three.js分配的id属性形成对比。
  • Remove:这将从场景中删除此对象。
  • Clear:这将从场景中删除所有的子元素

请注意,前面的函数实际上来自于这 THREE.Scene 的基本对象。场景延伸到:THREE.Object3D.

在整本书中,如果我们想操纵一个场景,我们将使用这些子元素(或在 THREE.Group,,我们稍后将进行探讨。)

除了添加和删除对象的功能外,还有一个 THREE.Scene 还提供了一些其他的设置。我们将看到的第一个问题是添加雾

添加雾 fog

雾属性允许您为整个场景添加雾效果;对象离相机越远,它将隐藏得越多。这如下图中的屏幕截图所示:

image-20230506225920299

图2.3-使用fog来隐藏对象

为了更好地看到添加的雾的效果,使用鼠标放大和缩小,您将看到立方体受到雾的影响。在Three.js中启用雾真的很容易。只需在定义了场景后再添加以下几行代码:

scene.fog = new THREE.Fog( 0xffffff, 1, 20 );

在这里,我们定义了白雾(0x ffffff)。另外两个属性可以用于调整薄雾的显示方式。1值设置近属性,20值设置远属性。有了这些特性,你就可以确定雾从哪里开始,以及雾凝结的速度。与这 THREE.Fog 对象时,雾呈线性增加。在chapter-02/basic-scene.html示例中,您可以通过使用屏幕右侧的菜单来修改这些属性,以查看这些设置如何影响您在屏幕上看到的内容。

还有一个由Three.js提供的替代fog实现。FogExp2

scene.fog = new THREE.FogExp2( 0xffffff, 0.01 );

这一次,我们没有指定近和远,而只指定颜色(0x ffffff)和雾的密度(0.01)。通常,最好对这些属性进行一些实验,以获得你想要的效果。

一个场景的另一个有趣的特性是,您可以配置一个背景。

更改背景

我们已经看到,我们可以通过像这样设置WebGL渲染器的清晰颜色来改变背景颜色:renderer.setClearColor(背景颜色)。你也可以使用这三个方法。要更改背景的场景对象。对于这个问题,你有三个选择:

  • 选项1:您可以使用纯色。
  • 选项2:你可以使用一个纹理,它基本上是一个图像,被拉伸来填充整个屏幕。(更多关于纹理的信息,请参见第10章,加载和使用纹理。)
  • 选项3:您可以使用环境映射。这也是一种纹理,但它完全包含了相机,当你改变相机方向时会移动。

请注意,这设置了我们正在渲染的HTML画布的背景色,而不是HTML页面的背景色。如果您想要一个透明的画布,您需要将渲染器的alpha属性设置为true:

new THREE.WebGLRenderer({ alpha: true }}

在右边的chapter-02/basic-scene.html菜单中,有一个下拉列表显示了所有这些不同的设置。如果从背景下拉列表中选择“纹理”选项,您将看到以下内容:

image-20230506230327888

图2.4-使用纹理的背景图

我们将在第10章,加载和使用纹理中更详细地介绍纹理和立体图。但是我们现在将快速看看如何配置这些和场景的简单背景颜色(它的来源可以在controls/scene-controls.js中找到)

// 通过将背景设置为空来删除任何背景
scene.background = null;
// 如果你想要一个简单的颜色,只需设置背景为一个颜色
 color
scene.background = new THREE.Color(0x44ff44);
// 纹理可以加载。TextureLoader
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
 "/assets/textures/wood/abstract-antique-
 backdrop-164005.jpg",
 (loaded) => {
 scene.background = loaded;
 }
// 一个 cubemap 也可以装载一个THREE.TextureLoader
 textureLoader.load("/assets/equi.jpeg", (loaded) => {
 loaded.mapping = THREE.EquirectangularReflectionMapping;
 scene.background = loaded;
});

从前面的代码可以看出,可以分配 null, THREE.Color, or THREE. Texture 到场景的背景属性的纹理。加载一个纹理或一个立方体映射是异步完成的,所以,我们必须等待 THREE.TextureLoader 文本加载器加载图像数据之前,我们可以分配它到背景。在立体地图中,我们需要多花一步,告诉 Three.js 我们加载了什么样的纹理。我们将在第10章,加载和使用纹理,当我们深入研究纹理如何工作的细节。

const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const cubeMaterial = new THREE.MeshStandardMaterial({
 color: color,
 roughness: 0.1,
 metalness: 0.9,
 });
const cube = new THREE.Mesh(geometry, cubeMaterial);

在前面的代码中,我们已经创建了一个几何图形并指定了一个材质。这 The THREE.Scene 对象还提供了一种强制场景中的网格使用相同材质的方法。在下一节中,我们将探讨它是如何工作的。

更新场景中的所有材质

一个 THREE.Scene 有两个会影响场景中网格的材质的特性。第一个是被覆盖的物质属性。首先,让我们演示这是如何工作的。在 chapter-02/basic-scene.html页面上,您可以单击“Toggle Override Material”按钮。这将把场景中所有网格的材质更改为THREE.MeshNormalNormal 材料:

image-20230507092253212

图2.5-使用网格规范材料覆盖网格材料

正如您在上图中所看到的,所有对象(包括底层)现在使用相同的材料——在本例中,使用三个。MeshNormalMaterial.此材质根据网格对照相机的方向(其法向量)为网格的每个面着色。这可以很容易地在代码中通过调用来完成 scene.overrideMaterial = new THREE.MeshNormalMaterial();

除了对场景应用完整的材质外,Three.js还提供了一种方法来将每个网格的材质的环境映射属性设置为相同的值。环境地图模拟网格所在的环境(例如,房间、室外或洞穴)。环境映射可以用于在网格上创建反射,以使它们感觉更真实。

我们已经在前一节中加载环境地图。如果我们希望所有材料使用环境图进行更动态的反射和阴影,我们可以将加载的环境图分配给场景的环境属性:

textureLoader.load("/assets/equi.jpeg", (loaded) => {
 loaded.mapping = THREE.EquirectangularReflectionMapping;
 scene.environment = loaded;
});

演示前面代码的最佳方法是从chapter-02/basic-scene.html示例中**Toggle Environment **环境按钮。如果你现在放大靠近立方体,你可以看到它们的脸反映了部分环境,而不再是纯色:

image-20230507092622169

图2.6-将环境映射设置为场景中的所有网格

现在我们已经讨论了所有我们想要渲染的对象的基本容器,在下一节中,我们将更仔细地查看这些对象(THREE.Mesh。网格结合了THREE.Geometry和材质),您可以添加到场景中。

几何图形和网格是如何关联的

在到目前为止的每个示例中,您都已经看到了正在使用的几何图形和网格。例如,要创建一个球体并将其添加到场景中,我们使用了以下方法:

const sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
const sphereMaterial = new THREE.MeshBasicMaterial({color:
 0x7777ff);
const sphere = new THREE.Mesh(sphereGeometry,
 sphereMaterial);
scene.add(sphere);

我们定义了几何图形(THREE.SphereGeometry),。这是一个物体的形状,及其材料(THREE.MeshBasicMaterial),我们将这两个组合在一个网格中 (THREE.Mesh)。可以添加到场景中。在本节中,我们将仔细研究几何图形和网格。我们将从几何开始。

一个几何图形的性质和功能

Three.js提供了一组现成的几何图形,您可以在3D场景中使用。只要添加一个材质,创建一个网格,你就差不多完成了。下面是来自chapter-2/geometries 示例的屏幕截图,显示了Three.js中的一些标准几何图形:

image-20230507094517448

图2.7-场景中可用的一些基本几何图形

在第5章学习使用几何和第6章探索高级几何中,我们将探索Three.js提供的所有基本和高级几何。现在,我们将更详细地研究一个几何图形到底是什么。

Three.js中的几何图形,以及在其他大多数3D库中,基本上是三维空间中的点的集合,也称为顶点(其中单个点称为顶点),以及将这些点连接在一起的许多面。以一个立方体为例:

  • 一个立方体有八个角。每个角都可以定义为x、y和z坐标。所以,每个立方体在一个三维空间中都有8个点。
  • 一个立方体有六个边,每个角落都有一个顶点。在Three.js中,一个面总是由三个顶点组成,这些顶点构成一个三角形(它有三条边)。所以,在一个立方体的情况下,每一边都由两个三角形构成完整的一面。在图2.7中,通过查看红色的立方体,可以看到一个关于该外观的例子。

当使用Three.js提供的几何图形时,不必自己定义所有的顶点和面。对于立方体,您只需要定义宽度、高度和深度。Three.js使用该信息,并创建一个具有8个顶点位于正确位置和正确数量的几何图形(立方体为12个——每边2个三角形)。即使您通常使用几何图形和网格由Three.js提供的几何图形或自动生成它们,您仍然可以使用顶点和面完全手工创建几何图形,尽管这可能很快变得复杂,正如您可以在下面的代码行中看到的:

const v = [
 [1, 3, 1],
 [1, 3, -1],
 [1, -1, 1],
 [1, -1, -1],
 [-1, 3, -1],
 [-1, 3, 1],
 [-1, -1, -1],
 [-1, -1, 1]]
const faces = new Float32Array([
 ...v[0], ...v[2], ...v[1],
 ...v[2], ...v[3], ...v[1],
 ...v[4], ...v[6], ...v[5],
 ...v[6], ...v[7], ...v[5],
 ...v[4], ...v[5], ...v[1],
 ...v[5], ...v[0], ...v[1],
 ...v[7], ...v[6], ...v[2],
 ...v[6], ...v[3], ...v[2],
 ...v[5], ...v[7], ...v[0],
 ...v[7], ...v[2], ...v[0],
 ...v[1], ...v[3], ...v[4],
 ...v[3], ...v[6], ...v[4]
]);
const bufferGeometry = new THREE.BufferGeometry();
bufferGeometry.setAttribute("position", new THREE.
BufferAttribute(faces, 3));
bufferGeometry.computeVertexNormals();

前面的代码展示了如何创建一个简单的多维数据集。我们在v数组中定义构成这个多维数据集的点(顶点)。从这些顶点中,我们可以在下一步创建面。在Three.js中,我们需要在一个大型浮点32阵列中提供所有的面信息。如我们所提到的,一个面由三个顶点组成。所以,对于每个面,我们需要定义9个值:每个顶点的x,y,和z。因为每个面有三个顶点,所以我们有9个值。为了让它更容易阅读,我们使用了…(扩展)运算符从JavaScript将每个顶点的单独值添加到数组。所以,……v[0],……v[2],……v[1]将在数组中产生以下值:1、3、1、1、-1、1、1、3、1。

请注意,您必须处理用于定义面的顶点序列。定义它们的顺序决定了Three.js认为它是正面((面对相机的正面)还是背面。如果创建面,则对正面应使用顺时针顺序,如果要创建背面面,则应使用逆时针顺序。

在我们的示例中,我们使用了一些顶点来定义立方体的六个边,每个面都有两个三角形。在Three.js的以前版本中,您也可以使用四边形而不是三角形。四元组使用四个顶点而不是三个顶点来定义面。使用四边形还是三角形更好是三维建模世界中一个激烈的争论。但基本上,在建模过程中使用四边形通常是首选,因为它们比三角形更容易增强和平滑。对于渲染和游戏引擎,使用三角形通常更容易,因为每个形状都可以使用三角形非常有效地渲染。

使用这些顶点和面,我们现在可以创建一个新的新实例。缓冲区几何体,并将顶点分配给位置属性。最后一步是在我们已经创建的几何图形上调用 computeVertexNormals()。当我们调用这个函数时,Three.js确定每个顶点和面的法向量。这是Three.js用来确定如何根据场景中的各种灯光来着色面孔的信息 (如果你可以很容易地想象你使用它。 THREE.MeshNormalMaterial).

有了这个几何图形,我们现在可以创建一个网格,就像我们之前看到的那样。我们创建了一个示例,可以使用它来处理顶点的位置,它也显示单个面。在我们的chapter-2/custom-geometry 示例中,您可以更改立方体的所有顶点的位置,并查看面的反应方式。这如下图中的屏幕截图所示:

image-20230507100209604

图2.8-周围移动顶点以改变形状

这个示例使用与其他示例相同的设置,有一个渲染循环。每当您更改下拉控制框中的一个属性时,都将根据其中一个顶点的位置更改来呈现多维数据集。这并不是开箱即用的东西。由于性能原因,Three.js假设网格的几何形状在其生命周期内不会改变。对于大多数几何图形和用例,这是一个非常有效的假设。但是,如果您更改备份数组(在本例中, const faces = new Float32Array([...]) array),,我们需要告诉Three.js发生了变化。您可以通过将相关属性的neessUpdate属性设置为true来实现这一点。此内容如下:

mesh.geometry.attributes.position.needsUpdate = true;
mesh.geometry.computeVertexNormals();

请注意,在更新顶点的情况下,重新计算法向量也是一个好方法,以确保材料也被正确地渲染。更多关于什么是法向量的信息以及为什么它很重要的信息将在第10章,加载和使用纹理中解释。

在 chapter-2/custom-geometry 菜单中有一个按钮,我们还没有解决这个问题。在右边的菜单中,有一个克隆按钮。我们提到几何定义了对象的形式和形状,并与材质结合,创建一个可以添加到场景中由Three.js渲染的对象。使用克隆()函数,顾名所义,我们可以创建几何图形的副本,例如,使用它使用不同的材质创建不同的网格。在同一示例的chapter-2/custom-geometry 中,您可以在控件GUI的顶部看到一个克隆按钮,如下的屏幕截图所示:

image-20230507101011637

图2.9-复制该几何图形

如果单击此按钮,将复制(复制);然后,使用不同材质创建新对象,最后将对象添加到场景中。这其中的代码如下:

const cloneGeometry = (scene) => {
 const clonedGeometry = bufferGeometry.clone();
 const backingArray = clonedGeometry.getAttribute
 ("position").array;
 // change the position of the x vertices so it is placed
 // next to the original object
 for (const i in backingArray) {
     if ((i + 1) % 3 === 0) {
     backingArray[i] = backingArray[i] + 3;
     }
 }
 clonedGeometry.getAttribute("position").needsUpdate =
 true;
 const cloned = meshFromGeometry(clonedGeometry);
 cloned.name = "clonedGeometry";
 const p = scene.getObjectByName("clonedGeometry");
 if (p) scene.remove(p);
 scene.add(cloned);
}

正如您在前面的代码中所看到的,我们使用克隆()函数来克隆缓冲区几何图形。一旦克隆,我们将确保更新每个顶点的x值,以便克隆被放置在与原始顶点不同的位置(我们也可以使用translateX,我们将在本章的下一节中解释)。接下来,我们创建一个THREE.Mesh,,如果存在克隆的网格,则删除它,并添加新的克隆。为了创建新的网格,我们使用了一个名为 meshFromGeometry 的自定义函数。作为一个快速的回避,让我们来看看它是如何实现的:

const meshFromGeometry = (geometry) => {
 var materials = [
 new THREE.MeshBasicMaterial({ color: 0xff0000,
 wireframe: true }),
 new THREE.MeshLambertMaterial({
 opacity: 0.1,
 color: 0xff0044,
 transparent: true,
 }),
 ];
 var mesh = createMultiMaterialObject(geometry, materials);
 mesh.name = "customGeometry";
 mesh.children.forEach(function (e) {
 e.castShadow = true;
 });
    return mesh;
}

如果你回头看这个例子,你可以看到一个透明的立方体和提升我们的几何图形。为此,我们创建了一个多材质网格。这意味着我们告诉Three.js在一个网格中使用两种不同的材质。为此,Three.js提供了一个名为createMultiMaterialObject,顾名思义。基于几何图形和一个材质列表,它会创建一个可以添加到场景中的对象。有一件事你需要但在处理createMultiMaterialObject调用的结果时要知道。什么你回来的不是一个网格;它是一个THREE.Group,一个容器对象,在本例中包含一个单独的THREE.Mesh 我们提供的每种材料都有网格。因此,在渲染网格时,它看起来就像一个单独的对象,但它实际上包括多个THREE.Mesh 在一个对象的顶部渲染的网格对象另一个这也意味着,如果我们想要有阴影,我们需要为每个网格启用阴影在组内部(这就是我们在前面的代码片段中所做的)。

在前面的代码中,我们使用了 THREE 中的 createMultiMaterialObject。场景实用工具对象,将线框添加到我们创建的几何体中。三.js也提供了另一种方式使用 THREE 添加线框。线框几何图形。假设您有一个几何图形称为 Geom,您可以从中创建线框几何图形: const wireframe = new THREE.WireframeGeometry(geom);.接下来,您可以绘制此几何图形的线条,使用 Three.LineSegments 对象,首先创建一个 const line = new THREE.LineSegments(wireframes) 对象,然后将其添加到场景中:scene.add(line)。因为这个助手在内部只是一个 THREE.Line 对象,您可以设置线框的显示方式。 例如,要设置线框线的宽度,请使用 line.material.linewidth = 2;

我们已经看了这 THREE.Mesh对象。在下一节中,我们将更深入了解您可以用它做什么。

网格的函数和属性

我们已经知道,要创建一个网格,我们需要一个几何图形和一个或多个材料。一旦我们有了一个网格,我们就将其添加到场景中并进行渲染。有几个属性可以用来更改这个网格在场景中出现的位置和方式。在第一个示例中,我们将查看以下一组属性和函数:

  • position :这决定了对象相对于其父对象的位置的位置。通常情况下,一个对象的父对象是 THREE.Scene 对象或THREE.Group 对象。
  • rotation :使用此属性,您可以设置对象围绕其自己的任何轴的旋转。Three.js还提供了围绕单轴旋转的特定功能: rotateX(), rotateY(), 和 rotateZ().
  • scale :此属性允许您围绕对象的x轴、y轴和z轴进行缩放。
  • translateX()/translateY()和 translateZ():此属性沿着相应的轴以指定的数量移动对象。
  • lookAt() :此属性将对象指向空间中的特定向量。这是一种可以替代手动设置旋转的方式。
  • visible :此属性决定是否应该渲染此网格。
  • castShadow :此属性确定该网格在被光击中时是否会投射阴影。默认情况下,网格不会投射阴影

当我们旋转一个物体时,我们就会绕着一个轴旋转。在三维场景中,有多个可以旋转一个轴的空间。rotateN()函数在局部空间中围绕轴旋转对象。这意味着对象将围绕其父对象的轴进行旋转。因此,当您向场景添加一个对象时,roratteN()函数将围绕场景的主轴旋转该对象。当它是嵌套组的一部分时,这些函数将围绕其父轴旋转对象,这通常是您要寻找的行为。Three.js还有一个特定的 rotateOnWorldAxis,它允许你围绕主 THREE.Scene 的轴旋转一个对象而不考虑对象的实际父对象。最后,您还可以通过调用 rotateOnAxis 函数来强制对象围绕其自己的轴(这称为对象空间)旋转。

和往常一样,我们为您准备了一个示例,它将允许您尝试使用这些属性。如果您在浏览器中打开 chapter-2/mesh-properties,您会得到一个下拉菜单,在那里您可以更改所有这些属性并直接看到结果,如下截图所示:

image-20230507150855692图2.10-网格的属性

让我告诉你介绍属性;我将从位置属性开始

使用位置属性设置网格的位置

我们已经看到这个属性好几次了,所以让我们快速解决它。使用此属性,您可以设置对象相对于其父对象的x、y和z坐标。我们将在第5章,学习使用几何,当我们看对象分组。我们可以用三种不同的方式来设置一个对象的位置属性。我们可以直接设置每个坐标

cube.position.x = 10;
cube.position.y = 3;
cube.position.z = 1;

但是,我们也可以一次性设置所有它们,如下:

cube.position.set(10,3,1);

还有第三种选择。位置属性为 THREE.Vector3 的对象。这意味着我们还可以执行以下操作来设置这个对象:

cube.position = new THREE.Vector3(10,3,1)

该列表上的下一个属性是 rotation 属性。你已经在这里和第一章中使用过几次这个属性,使用三个.js创建第一个3d场景。

使用旋转特性定义网格的旋转

使用此属性,可以设置对象围绕其一个轴旋转。您可以使用与我们执行该位置相同的方式设置这个值。你可能在数学课上记得,一个完整的旋转是2π。您可以在Three.js中配置:

cube.rotation.x = 0.5*Math.PI;
cube.rotation.set(0.5*Math.PI, 0, 0);
cube.rotation = new THREE.Vector3(0.5*Math.PI,0,0);

如果你想用度(从0到360),我们必须把它们转换为弧度。这可以很容易地完成如下:

const degrees = 45;
const inRadians = degrees * (Math.PI / 180)

在前面的代码块中,我们已经自己完成了转换。Three.js还提供了MathUtils类,该类提供了许多有用的转换,包括一个与我们在前面的代码块中所做的事情相同的转换。您可以使用 chapter-2/mesh-properties 示例来处理此属性。

我们列表上的下一个属性是一个我们还没有讨论过的属性:规模。这个名称基本上概括了你可以用这个属性做些什么。可以沿特定轴缩放对象。如果将比例设置为小于1的值,对象将缩小,如下截图所示:

image-20230507151631203

图2.11-使用比例尺缩小网格

当使用大于1的值时,对象将变大,如下屏幕截图所示:

image-20230507151659344

图2.12-使用比例尺来生长一个网格

我们将会看的网格的下一部分是 translate 属性。

使用 translate 属性更改位置

使用平移,您还可以更改对象的位置,但不是定义您希望对象所在的绝对位置,而是定义对象应该相对于其当前位置移动的距离。例如,我们有一个球体被添加到一个场景中,它的位置被设置为(1,2,3)。接下来,我们沿着它的x轴平移对象: translateX (4)。它现在的位置将是(5、2、3)。如果我们想将对象恢复到其原始位置,我们使用traslateX(-4)。在 chapter-2/mesh-properties 示例中,有一个被称为“translate”的菜单选项卡。从那里,您可以试验这个功能。只需设置x、y和z的平移值,然后点击平移按钮。您将看到对象基于这三个值移动到新位置。

我们将查看的最后两个属性用于完全删除对象,通过将可见属性设置为false,并通过将cast阴影属性设置为false来禁用该对象是否投射阴影。当您单击这些按钮时,您将看到立方体变得不可见,并且您可以禁用它来投射阴影。

有关网格、几何图形以及如何处理这些对象的更多信息,请查看第5章,学习使用几何图形,和第7章,点和支柱。

到目前为止,我们已经看了 THREE.Scene,包含了我们想要渲染的所有对象的主要对象,我们已经详细研究了THREE.Mesh 是,以及你如何可以创建一个三。设置网格,并将其放置在一个场景中。在前面的章节中,我们已经使用了一个相机来确定三个部分中的哪个部分。你想要渲染的场景,但尚未详细解释如何配置摄像机。在下一节中,我们将深入探讨这些细节。

对不同的场景使用不同的摄像机

在Three.js中有两种不同的相机类型:正交相机和透视相机。请注意,Three.js还提供了一些非常具体的摄像头,用于创建可以使用3D眼镜或VR设备来观看的场景。我们不会在这本书中详细介绍这些相机,因为它们的工作原理与本章中解释的相机完全相同。如果你对这些相机感兴趣,Three.js提供了一些标准的例子:

  • Anaglyph effect 反作用力: https://threejs.org/examples/#webgl_effects_anaglyph
  • Parallax barrier 视差屏障: https://threejs.org/examples/#webgl_effects_parallaxbarrier
  • Stereo effect 立体声效果: https://threejs.org/examples/#webgl_effects_stereo

如果你正在寻找简单的虚拟现实相机,你可以使用三个。立体相机创建左右渲染的3D场景(标准立体效果),使用平行屏障(如3DS提供的),或提供一个浮雕效果,其中不同的视图以不同的颜色渲染。另外,Three.js对WebVR标准有一些实验支持,许多浏览器支持(更多信息,请参见https://webvr.info/developers/)。要使用它,并不需要改变多少。你只需把renderer.vr.enabled =设置为true,three.js将处理剩下的部分。Three.js网站有几个例子,展示了这个属性和Three.js对WebVR支持的其他特性: https://threejs.org/examples/。

目前,我们将重点关注标准的透视相机和正交法相机。解释这些相机之间的差异的最好方法是看几个例子。

正交法相机和透视相机

在本章的示例中,您可以找到一个称为chapter2/cameras. 的演示程序。当您打开这个例子时,您将看到下面类似的内容

image-20230507153510378

图2.13-透视照相机视图

前面的屏幕截图称为透视视图,是最自然的视图。从图中可以看到,立方体离相机越远,它们渲染的就越小。如果我们将相机更改为Three.js支持的其他类型,您将看到同一场景的以下视图:

image-20230507153537805

图2.14-正字相机视图

使用正交法相机,所有的立方体都呈现相同的大小;对象和相机之间的距离并不重要。这经常被用于2D游戏中,比如旧版本的《文明》和《模拟城市4》:

image-20230507153619790

图2.15-SimCity4中的正字法用法

透视相机属性

让我们先仔细看看 THREE.PerspectiveCamera. 在本例中,您可以设置许多属性来定义通过相机镜头显示的内容:

  • fov:视场(FOV)是场景中从摄像机位置可见的部分。例如,人类有一个几乎180度的FOV,而一些鸟类甚至有一个完整的360度的FOV。但由于一个普通的电脑屏幕并不能完全满足我们的视野,所以通常会选择一个较小的值。一般来说,游戏的FOV值在60到90度之间。良好的默认值: 50
  • aspect:这是我们渲染输出的区域的水平和垂直大小之间的高宽比。在我们的例子中,因为我们使用了整个窗口,所以我们只使用这个比率。高宽比决定了水平FOV和垂直FOV之间的差值。良好的默认值: window.innerWidth / window.innerHeight
  • neart:近属性定义了如何接近相机Three.js应该渲染场景。通常,我们将其设置为一个非常小的值,以直接从相机的位置渲染所有内容。良好的默认值:0.1
  • far:远属性定义了相机从相机的位置可以看到的距离。如果我们设置得过低,部分场景可能无法被渲染,如果我们设置得过高,在某些情况下,它可能会影响渲染性能。良好的默认值:100
  • zoom:缩放属性允许您放大和缩小场景。当您使用小于1的数字时,您将缩小场景,如果您使用大于1的数字,则您将放大。注意,如果指定负值,场景将倒置渲染。良好的默认值:1

下图很好地概述了这些属性如何一起工作以确定您所看到的内容:

image-20230507154433204

图2.16-透视照相机的特性

照相机的fov属性决定了水平的fov。根据方面特性,确定了垂直FOV。近属性用来确定近平面的位置,远属性决定远平面的位置。近平面和远平面之间的区域将渲染如下:

image-20230507154520662

图2.17-左右剪辑渲染的网格

正交照相机属性

要配置正交照相机,我们需要使用其他属性。正交投影对使用的高宽比或我们对场景的FOV不感兴趣,因为所有的对象都以相同的大小渲染。定义正交照相机时,需要定义需要渲染的长方体区域。正交法照相机的特性反映了这一点,如下:

  • left:这在Three.js文档中描述为相机的左平面。您应该把它看作将渲染的左侧边界。如果将此值设置为-100,则将不会看到任何位置比左侧更远的对象。
  • right:右属性的工作方式类似于左属性,但这次一次是在屏幕的另一边。更往右边的任何东西都不会被渲染。
  • top:这是要渲染的顶部位置。
  • bottom:这是要渲染的底部位置。
  • near:从这一点开始,基于相机的位置,场景将被渲染。
  • far:此时,基于摄像机的位置,将渲染场景。
  • zoom:这允许您放大和缩小场景。当使用小于1的数字时,将缩小场景;如果使用大于1的数字,则将放大。注意,如果指定负值,场景将倒置渲染。默认值为1。

所有这些属性都可以总结在下图中:

image-20230507154818617

图2.18-正字法照相机的特性

就像透视相机一样,你可以精确地定义你想要渲染的场景的区域:

image-20230507154853679

图2.19-正交相机裁剪区域

在上一节中,我们解释了Three.js支持的不同相机。您已经学习了如何配置它们,以及如何使用它们的属性来渲染场景的不同部分。我们还没有展示的是如何控制摄像机正在看的场景的哪个部分。我们将在下一节中解释一下。

查看特定的点

到目前为止,你已经看到了如何创建一个相机,以及各种论点的含义。在第1章,使用Three.js创建第一个3D场景中,您还发现需要将相机定位在场景中的某个位置,并渲染来自该相机的视图。通常,摄像机指向场景的中心:位置(0、0、0)。然而,我们可以很容易地改变相机的外观,如下:

camera.lookAt(new THREE.Vector3(x, y, z));

在 chapter2/cameras 示例中,您还可以指定您希望相机查看的坐标。请注意,当您在正字照相机设置中更改查找时,立方体仍然保持相同的大小。

image-20230507155905936

图2.20-正交相机的外观属性

使用lookAt功能时,您会将相机指向特定位置。您还可以使用此功能使照相机跟踪场景周围的对象。因为每 THREE.Mesh 对象的位置为 THREE.Vector3 对象时,您可以使用lookAt函数指向场景中的特定网格。你所需要使用的都是:camera.lookAt(网格位置)。如果在渲染循环中调用它,则将使相机在场景中移动时跟踪对象。

调试相机的什么

当考虑配置相机时,有一个菜单,你可以使用不同的设置可以有很大帮助。不过,有时,你可能想要准确地看到相机将渲染的区域。Three.js允许您通过可视化相机的挫折(相机显示的区域)来做到这一点。为此,我们只需向场景中添加一个额外的相机,并添加一个相机辅助器。要查看它的实际情况,请打开chapter-2/debug-camera.html示例:

image-20230507160210786

图2.21-显示相机的平截头体

在上图中,您可以看到透视相机的透视轮廓。如果更改菜单中的属性,可以看到结果也发生变化。这个平截头体可以通过添加以下内容来可视化:

const helper = new THREE.CameraHelper(camera);
scene.add(helper);
// 在渲染循环中
helper.update();

我们还增加了一个开关摄像机按钮,它允许你在观察场景中的外部摄像机和场景中的主摄像机之间切换。这提供了一个很好的方法来获得正确的设置为你的相机:

image-20230507160739036

图2.22-摄像机之间的切换

在Three.js中切换摄像头真的很容易。你唯一需要做的就是告诉 Three.js 你想通过一个不同的相机渲染场景。

总结

我们在第二章介绍性中讨论了很多。我们展示了THREE.Scene 函数和性质。场景,并解释了如何使用这些属性来配置主场景。我们还向您展示了如何创建几何图形。你可以使用3从头开始创建它们。或使用3.js提供的任何内置几何图形。最后,我们向您展示了如何配置Three.js提供的两个主要摄像头。三种。摄像机使用真实世界的视角渲染一个场景。正字字相机提供了在游戏中经常看到的假3D效果。我们还介绍了Three.js中几何图形的工作原理,现在您可以从Three.js提供的标准几何图形或手工手工创建自己的几何图形。

在下一章中,我们将介绍threee.js中提供的各种光源。您将了解各种光源的行为方式,如何创建和配置它们,以及它们如何影响不同的材料。