第1章我们讲述了一些Three.js的基础知识。我们发现,要在场景中显示任何东西,我们需要以下四种对象:
❑ 相机 :决定THREE.Scene中的哪部分会被渲染到屏幕上。
❑ 光源 :会影响材质的显示,以及在创建阴影效果时使用(会在第3章详细讨论)。
❑ 网格 :从相机的视角渲染的主要对象。这些对象包含构成几何体(例如球体或立方体)的顶点和面,并包含定义几何体外观的材质。
❑ 渲染器 :它使用相机和场景中的信息在屏幕上绘制(渲染)输出。
THREE.Scene是Three.js场景渲染的主容器,负责存放需要渲染的光源和网格对象。因此THREE.Scene本身提供的功能和选项并不是很多。
THREE.Scene还是一个用于组织和管理场景中对象的树形数据结构(有时也被称为场景图)。场景图包含了一个图形场景所需的所有信息。也就是说,THREE.Scene包含了所有用于渲染的对象。值得一提的是,场景图不是一组对象的数组,而是由一组节点构成的树形结构。相比数组结构,树形结构更适合表示场景中的层级关系,可以更高效地组织和管理场景对象。第8章我们会详细介绍Three.js提供的可以用来创建不同网格或光源组的对象。Three.js中用于创建场景图的主要对象是THREE.Group。顾名思义,该对象允许你将对象组合在一起。THREE.Group扩展自另一个名为THREE.Object3D的基类,该基类具有一组添加和修改子对象的标准函数。THREE.Mesh和THREE.Scene也都扩展自THREE.Object3D,因此你也可以使用它们来创建嵌套结构。不过使用THREE.Group来构建场景图更为规范,且在语义上更准确。
探索场景功能的最好方法是查看示例。例如你可以查看本章源代码中的chapter-2/basic-scene.html示例。我们将使用这个示例来解释场景具有的各种功能和选项。当我们在浏览器中打开该示例时,输出将类似于图2.1所示(记住你可以使用鼠标来移动、缩放和平移渲染的场景)。
图2.1 基本的场景设置
图2.1与我们在第1章看到的示例类似。尽管场景看起来相当空旷,但它已经包含了一些对象:
❑ 用来表示场景地面区域的THREE.Mesh。
❑ 用来决定渲染场景中哪些部分的THREE.PerspectiveCamera。
❑ 用来提供光线的光源THREE.AmbientLight和THREE.DirectionalLight。
该示例的源代码可以在basic-scene.js文件中找到,并且使用bootstrap/bootstrap.js、bootstrap/floor.js和bootstrap/lighting.js中的代码,这些是我们在本书中使用的通用场景设置。所有这些文件中发生的事情都可以简化为以下代码:
以上代码中我们创建了THREE.WebGLRenderer和THREE.PerspectiveCamera,因为我们总是需要它们。然后我们创建了THREE.Scene,并添加我们要使用的所有对象。本例我们添加了两个光源和一个网格。现在,我们拥有了启动渲染循环所需的所有组件,就像我们在第1章看到的那样。
在更深入了解THREE.Scene对象之前,我们将首先演示一下操作,然后再讲解相关代码。我们在浏览器中打开chapter-2/basic-scene.html示例,并点开右上角的 Controls 菜单,如图2.2所示。
图2.2 带有立方体贴图背景的基本场景设置
通过Controls菜单,你可以向场景中添加立方体,并删除最后添加的立方体。你还能够通过它更改场景的背景,并为场景中的所有对象设置材质和环境贴图。我们将讲述这些选项以及如何使用它们来配置一个THREE.Scene。我们首先看看如何向场景中添加和删除THREE.Mesh对象。以下代码是单击addCube按钮时调用的函数:
现在我们详细解释一下以上代码:
❑ 首先我们为即将要添加的立方体确定随机设置:一个随机颜色(通过调用randomColor()辅助函数获得)、一个随机位置,以及一个随机旋转。后面两个值是通过调用randomVector()生成的。
❑ 接下来,我们创建要添加到场景中的几何体:一个立方体。为此我们只需创建一个新的THREE.BoxGeometry,定义一个材质(在本例中为THREE.MeshStandardMaterial),并将这两者组合成THREE.Mesh。我们使用随机变量来设置立方体的位置和旋转。
❑ 最后,通过调用scene.add(cube)将这个THREE.Mesh添加到场景中。
我们在这段代码中引入的一个新元素是:使用name属性给立方体命名。名称被设置为cube-,后接场景中当前对象的数量(scene.children.length)。名称除了对于调试非常有用,还可以用来直接访问场景中的对象。你可以使用THREE.Scene.getObjectByName(name)函数直接获取指定的对象,并且可以直接修改其属性,无须将该对象设置为全局变量,从而减少了全局变量的使用。
也可能存在需要从THREE.Scene删除现有对象的情况。由于THREE.Scene通过children属性公开了所有子对象,因此我们可以使用以下简单的代码来删除最后添加的子对象:
Three.js还为THREE.Scene提供了其他与处理场景的子对象有关的有用函数:
❑ add:之前我们已经见过这个函数,它将对象添加到场景中。如果该对象之前已经被添加到另一个THREE.Object3D中,那么add()函数会自动将其从原对象中移除。
❑ attach:与add类似,不同的是,应用于此对象的任何旋转或平移信息都会保留。
❑ getObjectById:当你将对象添加到场景中时,它会获得一个ID。第一个是1,第二个是2,以此类推。使用此函数,你可以根据这个ID获取特定子对象。
❑ getObjectByName:根据对象的name属性返回一个对象。与id属性不同,name属性是可以通过对象自身来设置的,而非由Three.js自动分配。
❑ remove:将该对象从场景中删除。
❑ clear:从场景中删除所有子对象。
注意:前面的这些函数实际上来自THREE.Scene扩展的基类对象:THREE.Object3D。
在本书中,如果我们想要操作场景的子对象,包括THREE.Scene和THREE.Group中的对象,那么我们将使用以上这些函数。
除了添加和删除对象的函数之外,THREE.Scene还提供了其他一些设置。我们首先介绍添加雾。
可以通过fog属性向整个场景添加雾效果,离相机越远的对象,雾的效果越明显,越难以被看到。具体效果如图2.3所示:
图2.3 使用雾隐藏对象
为了最好地观察所添加的雾的效果,你可以使用鼠标进行缩放,这样可以看到立方体受到雾的影响。在Three.js中启用雾非常简单。只需要在定义场景之后添加以下代码:
以上代码我们定义了白色雾(0xffffff)。然后使用另外两个属性调整雾出现的样子。near属性设置了1,far属性设置了20。通过这些属性,你可以确定雾从哪里开始以及它变浓密的速度。在使用THREE.Fog对象时,雾的浓度会线性增加。你可以在chapter-02/basic-scene.html示例中使用屏幕右侧的菜单修改这些属性,以查看这些属性具体是如何影响屏幕上显示效果的。
Three.js还有另一种雾实现方法THREE.FogExp2:
这次我们不指定near和far,而是指定颜色(0xffffff)和雾的密度(0.01)。通常,最好对这些属性进行一些实验以找到满意的效果。
场景的另一个有趣特性是可以配置背景。
我们已经知道,可以通过设置WebGLRenderer的clearColor来改变背景颜色,就像这样:renderer.setClearColor(backgroundColor)。你还可以使用THREE.Scene对象来更改背景。为此,你有三个选择:
❑ 选项1:你可以使用纯色。
❑ 选项2:你可以使用纹理,它基本上是一个图片,拉伸以填充整个屏幕(有关纹理的更多信息,请参见第10章)。
❑ 选项3:你可以使用环境贴图。这也是一种纹理,但当你改变相机方向时,环境贴图也会相应地移动,因此始终能够作为相机的背景。
请注意,这将设置我们正在渲染的HTML画布的背景颜色,而不是HTML页面的背景颜色。如果你想要一个透明的画布,则需要将渲染器的alpha属性设置为true:
在chapter-02/basic-scene.html页面右侧的菜单中,有一个下拉菜单显示了所有这些设置。在 backGround 下拉菜单中选择 Texture 选项,你将会看到如图2.4所示的内容。
我们将在第10章更详细地讨论纹理和立方体贴图。现在我们先来看一下如何配置这些内容以及场景的简单背景颜色(相关代码可以在controls/scene-controls.js中找到):
图2.4 使用纹理作为背景
从以上代码可以看出,可以将null、THREE.Color或THREE.Texture分配给场景的background属性。加载纹理或立方体贴图是异步进行的,因此,我们需要等待THREE.TextureLoader加载完图像数据后,才能将其赋值给背景。在加载立方体贴图的情况下,我们需要多进行一步,告诉Three.js我们加载了哪种类型的纹理。有关纹理的更多信息,我们将在第10章深入讲述。
如果你回顾一下以下代码的开头,那么你将看到我们是如何创建立方体并将其添加到场景中的:
在上述代码中,我们创建了一个几何体并指定了一个材质。THREE.Scene对象还提供了一种方式,可以强制场景中的网格都使用相同的材质。接下来我们将讲述这部分内容。
THREE.Scene拥有两个属性,可以影响场景中网格的材质。第一个属性是overrideMaterial。我们先来演示一下它。你可以在chapter-02/basic-scene.html页面上点击 Toggle Override Material 按钮。这会把场景中所有网格的材质更改为THREE.MeshNormalMaterial,如图2.5所示。
图2.5 使用MeshNormalMaterial覆盖网格的材质
如图2.5所示,所有对象(包括地面)现在都使用相同的材质——在本例中为THREE.MeshNormalMaterial。该材质根据网格面的朝向(其normal向量)相对于相机的方向对其进行着色。实现这一点很容易,只需要简单调用scene.overrideMaterial=new THREE.MeshNormalMaterial();即可。除了将完整的材质应用于场景之外,Three.js还提供了一种方法,可以将每个网格材质的环境贴图属性设置为相同的值。环境贴图模拟了网格所在的环境(例如房间、室外或洞穴)。环境贴图可以用来在网格上创建反射效果,使它们看起来更真实。
我们已经在之前的关于背景的章节看到了如何加载环境贴图。如果你希望场景中的所有材质都使用环境贴图以实现更动态的反射和阴影效果,那么可以将加载的环境贴图赋值给场景的environment属性:
以上代码效果的最佳演示方法是切换chapter-02/basic-scene.html示例中的 Toggle Environment 按钮。现在如果你放大观察立方体,则会看到它们的表面开始反射环境的一部分,不再是单一的纯色,如图2.6所示。
图2.6 为场景中的所有网格设置同一环境贴图
现在,我们已经讲述了要渲染的所有对象的基本容器,在2.2节中我们将更详细地了解可以添加到场景中的对象(组合THREE.Mesh、THREE.Geometry和材质)。