欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

入门《Three.js实战指南:基于WebGL与HTML5,在网页上绘制3D图形与动画(第三版)》系列一 - 用Three.js打造首个3D互动场景

最编程 2024-07-22 14:31:31
...
元素中。我们使用JavaScript来选择需要正确输出的元素并使用appendChild方法将结果添加到div元素中。最后告诉渲染器使用指定的摄像机来渲染场景。
接下来,我们还会使用光照、阴影、材质和动画来美化这个场景。

1.5 添加材质、光源和阴影效果

在Three.js中添加材质和光源是非常简单的,做法和前一节讲的基本一样。首先我们在场景中添加一个光源(完整代码请参见示例js/03-03.js)。代码如下所示:
image.png

通过THREE.SpotLight定义光源并从其位置(spotLight.position.set(-40,60,-10))照射场景。通过将castShadow属性设置为true,THREE.js的阴影功能被启用。此外,上面的代码还通过设置shadow.mapSize、shadow.camera.far和shadow.camera.near三个参数来控制阴影的精细程度。有关光源属性的更多细节将在第3章详细介绍。如果这时候渲染场景,那么你看到的结果和没有添加光源时是没有区别的。这是因为不同的材质对光源的反应是不一样的。我们使用的基本材质(THREE.MeshBasicMaterial)不会对光源有任何反应,基本材质只会使用指定的颜色来渲染物体。所以,我们需要改变平面、球体和立方体的材质:
image.png

如代码所示,我们将场景中物体的材质改为MeshLambertMaterial。Three.js中的材质MeshPhysicalMaterial和MeshStandardMaterial(以及被弃用的MeshPhongMaterical)在渲染时会对光源产生反应。
渲染的结果如图1.12所示,但是这还不是我们想要的结果。

image.png

虽然立方体和球体已经好看了很多,但是还缺少阴影的效果。
由于渲染阴影需要耗费大量的计算资源,所以默认情况下Three.js中是不会渲染阴影的。为了渲染阴影效果,我们需要对代码做如下修改:
image.png

首先通过设置shadowMapEnabled属性为“true”来告诉渲染器需要阴影效果。这时候如果查看修改的效果,那么你将会发现还是没有任何区别,因为你还需要明确地指定哪个物体投射阴影、哪个物体接受阴影。在示例中,我们通过将相应的属性设置为“true”来指定球体和立方体在地面上投射阴影。代码如下所示:
image.png

接下来我们还需要定义能够产生阴影的光源。因为并不是所有的光源都能够产生阴影,但是通过THREE.SpotLight定义的光源是能够产生阴影的。我们只要将属性castShadow设置为true就可以将阴影渲染出来了,代码如下所示:
image.png

这样,场景中就有了光源产生的阴影,效果如图1.13所示。
仔细观察01-03.js的源代码会发现,这段程序还创建了一个拥有不同物体的场景。若将那些名为createXXX的函数的注释去掉,并且删除前面已经创建的立方体和球,场景中便会出现一些更复杂的物体。这些物体将展示出如图1.13所示的更复杂的阴影。

image.png

最后一个将要添加到场景中的效果是简单动画。在第9章中,你将会学习使用更高级的动画。

1.6 让你的场景动起来

如果希望我们的场景动起来,那么首先需要解决的问题是如何在特定的时间间隔重新渲染场景。在HTML5和相关的JavaScript API出现之前,是通过使用setInterval(function,interval)方法来实现的。比如,通过setInterval()方法指定某个函数每100毫秒调用一次。但是这个方法的缺点在于它不管浏览器当前正在发生什么(比如正浏览其他网页),它都会每隔几毫秒执行一次。除此之外,setInterval()方法并没与屏幕的刷新同步。这将会导致较高的CPU使用率和性能不良。

1.6.1 引入requestAnimationFrame()方法

幸运的是,现代浏览器通过requestAnimationFrame函数为稳定而连续的渲染场景提供了良好的解决方案。通过这个函数,你可以向浏览器提供一个回调函数。你无须定义回调间隔,浏览器将自行决定最佳回调时机。你需要做的是在这个回调函数里完成一帧绘制操作,然后将剩下的工作交给浏览器,它负责使场景绘制尽量高效和平顺地进行。这个功能使用起来也非常简单(完整源码在04-04.js文件中),你只需要创建负责绘制场景的回调函数:
image.png

在renderScene()方法中,requestAnimationFrame()方法又一次被调用了,这样做的目的是保证动画能够持续运行。接下来我们还需要对代码做的修改是:在场景创建完毕后,不再调用renderer.render()方法,而是调用renderScene()来启动动画,代码如下:
image.png

这时候如果运行代码,效果和之前的示例相比没有任何区别,这是因为我们还没有为物体添加任何动画效果。在添加动画之前,我们先来介绍一个辅助库,这个库也是Three.js作者开发的,主要用于检测动画运行时的帧数。在动画运行时,该库可以在一个图片中显示画面每秒传输帧数。
为了能够显示帧数,首先我们需要在HTML的

标签中引入这个辅助库:
image.png

然后初始化帧数统计模块并将它添加到页面上。
image.png

上面的函数初始化帧数统计模块,并用输入的type参数来设置将要显示的统计内容。它可以是:每秒渲染的帧数、每渲染一帧所花费的时间或者内存占用量。最后,在前面介绍过的init函数末尾调用上述函数来初始化统计模块。
image.png

由于initStats函数并不是本章示例所特有的操作,因此它的实现代码并不在本章的示例代码中,而是像其他有用的辅助函数一样保存在辅助函数库util.js文件中。
辅助函数库在HTML页面代码的开始部分被引用。
image.png

示例代码为统计帧数显示所做的最后一件事,是在renderScene函数中每渲染完一帧后,调用stats.update函数更新统计。
image.png

添加完上述代码之后再次运行示例,统计图形将会显示在浏览器左上方,如图1.15所示。

image.png

1.6.2 旋转立方体

引入requestAnimationFrame()方法并配置完统计对象之后,接下来就该添加动画代码了。在本节中,我们将扩展renderScene()方法来实现红色立方体围绕轴进行旋转。代码如下所示:
image.png

看起来是不是很简单?我们所做的只是在每次调用renderScene()时使得每个坐标轴的rotation属性增加0.02,其效果就是立方体将围绕它的每个轴进行缓慢的旋转。在下一节中我们将让蓝色球体弹跳起来,这个实现起来也不是特别难。

1.6.3 弹跳球

为了让小球弹跳起来,只需要在renderScene()方法中添加如下代码即可:
image.png

旋转立方体时我们修改的是rotation属性;而让小球弹跳起来,我们所要修改的是球体在场景中的位置(position)属性。我们的目的是让小球依照一条好看的、光滑的曲线从一个地方跳到另一个地方,如图1.16所示。

image.png

为了实现这个效果,我们需要同时改变球体在x轴和y轴的位置。Math.cos()和Math.sin()方法使用step变量就可以创建出球体的运行轨迹。在这里不具体展开解释它们是怎么工作的,现在你只需要知道step+=0.04定义了球体弹跳的速度就可以,在第8章中还会详细介绍这些方法是如何用于实现动画的。图1.17展示的就是球体在弹跳中的效果。

image.png

在结束本章之前,我还想在场景中引入几个辅助库。可能你已经发现,在创建类似三维场景和动画时,我们需要尝试很多次才能够确定最合适的速度和颜色。如果有一个GUI(可视化图形界面)允许我们在运行期间修改这些属性,那么事情就会变得很简单。幸运的是,这样的GUI是存在的。

1.7 使用dat.GUI简化试验流程

Google员工创建了名为dat.GUI的库(相关文档见http://code.google.com/p/dat-gui/),使用这个库可以很容易地创建出能够改变代码变量的界面组件。在本章最后,将会使用data.GUI库为我们的示例添加用户操作界面,使得我们可以:

  • 控制小球弹跳的速度;
  • 控制立方体的旋转。

就像引入统计对象一样,首先我们需要在HTML的

标签中添加这个库,代码如下所示:
image.png

接下来我们需要定义一个JavaScript对象,该对象将保存希望通过dat.GUI改变的属性。在JavaScript代码中添加如下的JavaScript对象:
image.png

在这个JavaScript对象中,我们定义了两个属性—this.rotationSpeed 和this.bouncing Speed,以及它们的默认值。接下来需要将这个JavaScript对象传递给data.GUI对象,并设置这两个属性的取值范围,如下所示:
image.png

立方体旋转速度(rotationSpeed)和球体弹跳速度(bouncingSpeed)的取值范围为0~0.5。现在需要做的就是在renderScene()中直接引用这两个属性,这样当我们在dat.GUI中修改这两个属性的值时,就可以影响相应物体的旋转速度和弹跳速度。代码如下所示:
image.png

现在运行这个示例(05-control-gui.html)时,你就会看到一个可以控制弹跳速度和旋转速度的用户界面。如图1.18所示。

image.png

读者应该还记得在本章开头时创建的框架页面代码中,我们引用了TrackballControl.js文件。该文件用于实现利用鼠标移动摄像机,以便以不同角度观察场景。这一点将在第9章详细介绍。该文件同样需要初始化。由于它需要响应文档对象模型(DOM)元素的事件,它的初始化代码必须出现在下面代码中的appendChild函数调用之后。
image.png

initTrackballControls函数也定义于util.js文件中,它的具体实现将在本书后面章节中详细介绍。最后,与帧数统计模块相似,TrackballControl.js库也需要在render函数渲染完一帧后更新。
image.png

至此本章的代码便已完成。再次打开05-control-gui.html文件时,可以通过按下鼠标左键并移动鼠标来转动摄像机,以便从不同角度观察场景。此外,按S键可以拉近或拉远摄像机,按D键可以平移摄像机。如图1.19所示。
后续章节中的所有实践都将使用这个库来移动摄像机。
在运行这个示例并改变浏览器大小的时候,你可能已经发现场景是不会随着浏览器大小的改变而自动做出调整的。那么在接下来的小节中,我们会添加本章的最后一个特性来解决这一问题。

image.png

1.8 场景对浏览器的自适应

当浏览器大小改变时改变摄像机是很容易实现的。首先我们需要做的就是为浏览器注册一个事件监听器,如下所示:
image.png

这样,每当浏览器尺寸改变时onResize()方法就会被执行。在onRaise()方法中需要更新摄像机和渲染器,代码如下:
image.png

对于摄像机,需要更新它的aspect属性,这个属性表示屏幕的长宽比;对于渲染器,需要改变它的尺寸。最后我们需要将变量camera、renderer和scene的定义移到init()方法的外面,这样其他的方法(如onResize()方法)也可以访问它们。代码如下所示:
image.png
image.png

运行示例06-screen-size-change.html并改变浏览器的大小,就可以看到实际的效果。

1.9 总结

本章到此结束。在这一章里你学到了如下内容:如何搭建开发环境,如何获取本书相关的源码和示例;使用Three.js渲染场景时,首先需要做的是创建THREE.Scene对象,添加摄像机、光源和需要渲染的物体;如何给场景添加阴影和动画效果;添加辅助库dat.GUI和stats.js创建用户控制界面和快速获取场景渲染时的帧数。
在下一章中我们将会进一步扩展这个示例,你将会学到更多Three.js中非常重要的场景构建模块。