天天看点

《WebGL入门指南》——第2章,第2.4节一个真实的3D示例

本节书摘来自异步社区《webgl入门指南》一书中的第2章,第2.4节一个真实的3d示例,作者 【美】tony parisi,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.4 一个真实的3d示例

webgl入门指南

到目前为止,你也许在想“真是个还不错的正方形”,然后开始怀疑我们什么时候开始画一些真正的3d图形。好吧,那就现在吧!在示例2-2中我们将会用更有趣的物体来代替正方形,我们将会完成一个看起来还不错、并且展示了大部分webgl主要特性、同时还保持代码简洁的页面。

图2-2就是页面的最终效果。其中我们设置了标题文字,添加了一个表面贴有图片的立方体,然后在页面底部也添加了文字。另外值得一提的是,这个页面是可以交互的:点击画布元素,立方体就会开始或停止旋转。

《WebGL入门指南》——第2章,第2.4节一个真实的3D示例

让我们详细看一下这一切是如何完成的。下面是示例2-2的完整代码。这比我们的第一个three.js示例要复杂一些,但是依然足够简洁,我们可以快速地浏览一下整个代码。

示例2-2 欢迎来到webgl!

除去我稍后会详细介绍的一些设置内容和添加了一个css样式表来控制字体和颜色之外,这段程序的开头部分和我们之前的那个示例非常相似。我们创建了three.js渲染器对象,并将其dom元素作为canvas容器的子元素加入。这一次我们给构造函数传递了一个名为antialias的参数,并将其设置为true,告诉three.js要启用抗锯齿(antialiased)渲染。抗锯齿可以避免绘制物体边缘时产生的锯齿。(在three.js中有很多给方法传递参数的方式。通常来说,在对象中传递构造函数参数都是使用被命名的字段,正如在本示例中这样。)然后,我们创建了一个带透视效果的相机,和之前的示例一样。但这次,相机稍微移动了一些,以便我们可以更近地观察立方体。

2.4.1 为场景着色

我们马上就可以在场景中添加立方体了。如果往前看几行,你会发现我们使用了three.js内置的cubegeometry对象来创建一个单位体积的立方体。但在添加立方体之前,我们还需要做其他的一些事情。首先,我们需要对场景进行着色(shading)。如果没有着色,你将无法分辨出立方体表面的边界。我们还需要创建一个纹理映射来渲染到立方体表面,稍后会详细介绍。

为了将着色效果添加到场景中,我们需要做两件事:添加一个光源和使用另一种不同的材质。在three.js中有好几种不同的光源。在我们的示例中,将会使用平行光(directional light),这是一种从无限远的距离(光源没有特定的位置)照向指定方向的光。three.js的语法有些奇怪,我们要做的不是设置光源的照射方向,而是要使用position属性设置光源距离原点的位置;由此可以推论出,光源的照射方向就成了从该点指向场景原点(即照射到我们的立方体上)。

我们要的第二件事就是改变使用的材质。在three.js中,meshbasicmaterial用于定义属性简单的材质,比如固定颜色或是透明。这种材质不会对光照做出任何响应。所以我们要用另一种类型的材质来代替它,这就是meshphongmaterial。这种类型的材质应用了一种相对简单、仿真度高而又性能优越的着色模型,也就是著名的“phong着色法”(phong shading)。(three.js同时还支持其他更加复杂的着色模型,我们之后会讲到。)使用了phong着色法,我们现在就可以分辨出立方体的边缘。立方体朝向光源方向的面将会更加明亮;而背对光源的面则会相对阴暗。由此,我们即可分辨出面与面之间的边界。

图片 3 也许你已经发现了,在这个章节中我所提到的都是着色,而不是我们在第1章中提到的着色器。这是因为,我们之前说过着色器其实是一小段用类c语言写成的代码,而three.js已经为我们内置了这样的着色器。我们只要简单的设置光源和材质,three.js就可以用内置的着色器来替我们处理剩下的脏活累活。感谢three.js的作者,感谢mr.doob!

2.4.2 添加纹理映射

纹理映射(texture map),也被称为纹理(texture),是一种将位图覆盖到3d网格表面的技术。使用纹理映射可以通过简单的方式定义网格表面的颜色,同时也可以通过纹理图片的组合应用创造出更加复杂的效果,如凹凸效果和高光效果。webgl提供了一些api调用来处理纹理,同时因为安全原因,限制了诸如跨域纹理的使用(详细信息参见第7章)。幸运的是,在three.js中我们只要使用简单的api就可以载入纹理并与材质绑定在一起,而不需要处理太多的琐事。我们调用three.imageutils.loadtexture()方法来从一个图片文件载入纹理,然后通过向材质的构造函数传递map参数,把处理后的纹理与材质相关联。

在这里,three.js替我们完成了很多的底层工作。它会把jpeg图像正确的映射到立方体的各个面上,并保证图像不会拉伸覆盖所有面,同时在各个面上的图像不会出现翻转或者倒立的错误。也许看起来这并不是什么大事情,但是如果你亲自用webgl原生api来完成这件事的话,你会发现需要处理的细节太多了。而three.js再一次替我们完成了这些困难的工作,它用内置的phong着色器结合光源的设置、材质颜色和纹理映射的像素值,使得最终每个像素上都显示正确的颜色,并形成最后的图像效果。在three.js中,我们可以使用纹理映射做很多事情。我们将会在随后的章节中详细讨论更多细节。

现在可以开始创建我们的立方体网格了。我们构造了一个几何体、材质和纹理,然后把它们都放到一个three.js的网格中,并存储在变量cube中。示例2-3列出了用于创建带光照效果、纹理和phong着色的立方体的代码。

示例2-3 创建带光照效果、纹理和phong着色的立方体

2.4.3 旋转物体

图片 3在我们能看到立方体之前,还需要做一件事,那就是稍微旋转一下它。否则我们永远也不会发现这是一个立方体,因为它会端正地用一个面朝向我们,看起来就和我们在之前的示例中画的那个正方形一模一样,只是多了纹理贴图。所以让我们把它朝相机方向绕着x轴(水平方向)翻转一些。我们是通过设置网格的rotation属性来完成的。在three.js中,每个物体都有位置(position)、旋转(rotation)和缩放(scale)属性。(还记得吗?我们在之前的示例中,把相机往后移动了一些。)通过给rotation.x赋一个非零值,我们告诉three.js去为物体绕着x轴做当量的旋转。同理我们也以此处理y轴,让立方体稍微向左旋转一下。这样一来,我们就可以看到立方体6个面中的3个了。

在为物体的旋转变量赋值时,我们需要注意,大部分的3d图形系统都使用了弧度制(radians)来度量角度。弧度是指单位圆上相应角度的圆弧长度(例如,弧度制的2π就是角度制的360°)。math.pi相当于180°,因此当我们赋值mesh.rotation.x = math.pi / 12的时候,实际上是绕着x轴旋转了15°。

2.4.4 循环重绘和requestanimationframe()

你也许已经发现这个示例与之前的那个在结构上有些不同。首先,我们增加了一些辅助性的函数。其次,我们定义了一些全局变量来存储那些将用于辅助性函数的值。(好吧,我知道,这样滥用全局变量又是一个不好的习惯。但就像我承诺过的一样,在下一章我们将会应用框架结构。)同时我们还增加了一个循环函数,这就是所谓的循环重绘。通过循环重绘,我们不再只渲染一次场景,而是持续不断地渲染。这对于静态场景并不重要,但是对于含有运动物体或响应用户输入而发生变化的场景来说,我们需要持续不断地渲染场景。从现在开始,我们以后所有的示例都将使用循环重绘来渲染场景。

有很多方法来应用循环重绘。一种方法就是使用settimeout()回调,每当场景渲染完毕后就重置超时时差。这也是一种经典的web开发技巧,用于制作动态效果;然而它的确已经过时了,因为新的浏览器都支持一种更好的方法,那就是requestanimationframe()。这个函数被专门设计用于制作页面动画,当然也包括webgl中的动画。

使用requestanimationframe(),浏览器可以极大地优化动画的性能表现。因为它会综合考虑所有的绘制请求,把它们都放到同一个重绘步骤中。尤其在多标签浏览器中,当动画页面处于后台时,浏览器将停止重绘以节省资源提高性能。不过并不是所有版本的所有浏览器都支持这个函数;更加添乱的是,每个浏览器中这个函数的函数名都不一样。因此,我引入了一个漂亮的工具:由paul irish编写的requestanimation frame.js。这个文件会掩盖不同浏览器的差异性,开发者只要简单地使用requestanimationframe()即可。

我们已经准备好要开始渲染场景了。我们定义了一个函数run(),负责进行循环重绘。和之前一样,我们调用了渲染器的render()方法,将场景和相机传递给它。然后,我们写了一小段逻辑代码让立方体可以动起来。我们将会在下一节详细介绍。最后我们让浏览器继续渲染下一帧。参见示例2-4。

示例2-4 循环重绘

你可以在图2-2中看到最后的运行效果。现在你可以看到立方体的前侧和顶部。我们终于在页面上显示了一个真正的3d物体!

2.4.5 让页面贴近生活

作为第一个完整的示例,我们原本可以到此就结束了。我们已经在页面上绘制了一个漂亮的图形,而且是真正的3d图形。但是在今天,3d图形不只是和渲染有关了,它还需要动画和交互性。如果没有动画和交互,开发者只需要让做3d美术建模的朋友在max和maya中渲染好之后,存为图片然后用 标签嵌入网页就好,何必还折腾什么webgl呢?这不是我们想要的。因此在这种思想的指导下,即使是一个简单的示例,我们也要为它加入动画和交互。

在之前的小节中,我们讨论了循环重绘。这就是我们在渲染下一帧之前改变场景的机会。为了旋转这个立方体,我们需要在每一帧都改变它的旋转属性值。我们不想让它随机乱转,而是一个绕着y轴平滑的旋转,所以我们需要做的就是每过一段时间就给它的旋转的y值做适度的增量。在webgl中,这是一种相对简单的制作动画效果的思路。当然,还有其他的方法,但都更加复杂。我们会在以后的章节中介绍。

最后,如果我们可以控制立方体的旋转那就再好不过了。我们已经添加了一个处理鼠标点击的函数,直接使用了dom的事件方法。其中有一个需要指出的小技巧就是,在这个示例中我们使用的dom元素是和three.js中渲染器对象关联的。具体的代码参考示例2-5。

示例2-5 添加鼠标交互

点击立方体吧!看着它旋转!是不是很沉浸其中呢?

继续阅读