VRML 简介
VRML 旨在成为虚拟现实领域的 HTML——一种结构化、标准、跨平台的静态或交互式超链接内容格式。就像 HTML 一样,它可以手动编写,也可以由程序生成(后者通常更可取)。
大约一年前,VRML 迅速流行起来,但这种热情似乎已经消退。它尚未像 HTML、JPEG 和 CGI 一样,成为基本、广泛部署的网络技术之一。
其中一个原因可能是,没有适用于 Linux 或其他类 UNIX 操作系统的可用的 VRML 浏览器。现有的浏览器是不完整的,并且通常无法充分展示 VRML 中的 Web 内容以供认真使用。例如,跨平台产品无法处理脚本节点,而脚本节点是新 VRML97 中最重要的补充之一。
我推测这可能影响 VRML 普遍接受的原因是,对酷炫技术感兴趣的大部分人都在使用 Linux,并且不想支持不适用于 Linux 和其他正常运行的操作系统的应用程序。
我的科学工作需要 VRML 提供的交互式可视化。虽然有许多不同的 3D 软件包可用,但 VRML 的优势在于它支持与场景的多种交互。此外,我还可以通过电子邮件将我用它制作的图表发送给同事。
在发现可用的浏览器不能令人满意之后,我决定编写自己的 VRML 浏览器。这个名为 FreeWRL 的浏览器正在迅速接近 VRML97 规范兼容性,并支持其他 Linux 浏览器中缺少的大部分功能。我用 Perl 编写了它,因为它是唯一能让我快速完成工作的语言。
随着 FreeWRL 的可用性,我认为许多自由操作系统用户可能希望评估或重新评估 VRML。在本文中,我将尝试阐述 VRML 的基本概念,并解释它可能如何有用。
要运行此处列出的代码,您需要一个 VRML 浏览器。如果您没有浏览器,并且正在运行 Linux 或其他类 UNIX 操作系统,请访问 http://www.iki.edu/lukka/freewrl/ 并按照安装说明进行操作。
请特别注意“如果您有任何问题,请给我发邮件”这句话。对于自由软件,你能做的最糟糕的事情就是下载它,发现由于某些原因它无法工作,就放弃它,并告诉你的朋友它无法工作。作者永远不会知道他的软件包出了什么问题,也无法改进它,而关于该软件包不值得尝试的谣言将到处传播。好的错误报告是你为自由软件开发者所能做的最起码的回报,以回报他们的努力。“感谢”邮件很好,但远不如错误报告那么重要。
与其进行冗长的描述,不如从绘制简单世界的代码开始(参见列表 1)。我们将从内部部分开始:一个球体用半径 0.5 来描述。(在 VRML 中,单位通常称为米,但这只是一种约定。)这个球体是一个 Shape 节点的几何形状,其“外观”具有 0.8 0 0 的 diffuseColor,即非常鲜艳的红色。因此,Shape 实际上描述了一个红色球体。此形状是 Transform 的子项之一,其平移为 0 1 0,即在 Y 轴上向上移动一米,从正 Z 轴上的默认视点来看,这是向上。第一行是注释,告诉浏览器此文件是用 VRML 2.0 版本编写的。

图 1
简而言之,我们刚刚描述了一个半径为半米,位于原点上方一米的红色球体。(参见图 1。)现在,列表 1 中的代码描述如此简单的场景似乎有些繁琐。例如,以下代码可能更简单
# THIS IS NOT LEGAL VRML—SEE TEXT Sphere { center 0 1 0 radius 0.5 color 0.8 0 0 }
实际上,你可以这样做:可以使用原型创建适合你自己的应用程序的自定义节点,这在精神上类似于 C 语言中的预处理器宏,如列表 2所示。

图 2

图 3
列表 2 中的代码描述的场景从图 2 和图 3 中的两个不同角度显示。现在你可以使用刚刚定义的 PROTO 定义任意数量的此类场景。当然,你确实需要处理在开头包含原始 PROTO 的问题。也可以使用所谓的 EXTERNPROTO 在另一个文件中引用它,EXTERNPROTO 包含接口部分(在括号中),但省略定义(在大括号中)。即使这样有时也可能感觉打字太多。在这种情况下,你可以轻松编写 Perl 脚本,将正确的 PROTO 包含在你的 VRML 文件中,或者执行你需要完成的任何其他重复性任务。
在场景图中,你可以使用 DEF 语句为节点命名,并在另一个地方使用 USE 语句再次使用它——参见列表 3。
请注意,与 PROTO 不同,PROTO 会制作原型内容的完整副本,而只有一个外观节点存在,它在两个地方被引用。例如,如果我们稍后为外观设置动画以将颜色从红色更改为蓝色,则盒子和球体的颜色都会更改;而如果我们为 PROTO 示例中的 Thing 的颜色设置动画,则只有该 Thing 的颜色会更改。一旦节点使用 DEF 命名,它就可以在 USE 语句中多次使用。
现在你已经了解了正在发生的事情,让我们对我们刚刚做的事情进行更正式的描述。VRML 世界被描述为称为场景图的节点层次结构。每个节点都是特定的节点类型,对于每种节点类型,VRML97 规范都定义了各种字段。每个字段都有一个类型和一个默认值。节点的语法是
NodeType { field value field value ... }
其中值的语法取决于字段的类型。例如,SFVec3f 的值是三个浮点数,而 SFNode 的值是另一个节点,就像上面一样。
单值 (SF) 字段类型有
SFBool:布尔值,写为 TRUE 或 FALSE
SFFloat:浮点数
SFImage:图像,由几个整数值描述,首先是宽度、高度和组件数量,然后是宽度*高度的像素值
SFInt32:32 位整数
SFNode:节点
SFRotation:旋转:3 个浮点值表示轴,一个浮点值表示弧度角
SFString:双引号括起来的字符串。在内部,双引号和反斜杠用反斜杠引用
SFTime:浮点值,自特定原点以来的秒数
SFVec2f:包含两个浮点值的二维向量
SFVec3f:包含三个浮点值的三维向量
对于大多数 SF 字段类型,都存在相应的 MF 字段类型,这仅表示相应 SF 类型的零个或多个值。例如,以下是 MFVec3f 字段的合法值
[] 0.1 2 3 [0.1 4 2] [0.5 2 6 1 6 4 7 4 6] [0.5 2 6, 1 6 4, 7 4 6]也就是说,值可以用逗号分隔,也可以不用逗号分隔,并且如果值多于或少于一个,则必须用方括号括起来。
这些字段类型的名称也用于 PROTO 声明内部:PROTO 的语法基本上是
PROTO Name [ field Type fieldName defaultvalue exposedField Type fieldName defaultvalue eventOut Type fieldName eventIn Type fieldName ... ] { Node { ... } ... }
在 PROTO 主体内部,也可以使用 IS 语句指定字段值,这会将该字段与 PROTO 的已发布字段之一(例如,上面 Thing PROTO 的 center 字段)等同起来。
有关所有节点类型及其字段的描述,以及 VRML 的确切语法和语义,请参阅 VRML97 规范(可在 http://www.vrml.org/ 找到)或书籍。图 3 显示 Netscape 从网站显示 IndexedFaceSet 节点(它可以描述任意多边形几何图形)的定义。
这基本上就是你创建静态 VRML 场景所需了解的全部内容,除了你可以在上述来源中找到的细枝末节。一旦你使用 FreeWRL 创建了世界,请给我发个消息,我会将你世界的链接放在 FreeWRL 网页上。
当然,创建静态场景并不是什么大不了的事。VRML 真正有趣的部分是它与用户交互的能力。假设你想向朋友演示两个向量的叉积概念。你可以绘制各种二维图表,但还有什么比三维图形更好的描述呢?在三维图形中,你的朋友可以调整两个向量,并实时看到叉积的变化(尤其因为你可以使用 HTML embed 标签将其嵌入到网页中)?FreeWRL 的演示之一(图 4)正是这样做的,加上向量和与差。
在上一节中,你看到了节点如何描述要渲染的场景。我没有提到的是,节点可以沿着指定的路径相互发送事件。事件路径独立于场景层次结构。
路径由 ROUTE 语句指定。例如,列表 4 创建了一个白色盒子(图 5),当单击时,它会在红色和蓝色之间平滑循环,并在单击后 2.5 秒返回白色。当你单击盒子时会发生什么?首先,BUTTON 节点会感应到它(VRML 中的大多数传感器都会感应到来自其兄弟节点的事件,在本例中是定义盒子的 Shape 节点),并发送 touchTime 事件。ROUTE 语句导致该事件路由到 TimeSensor TS 的 startTime,这导致它开始为一个周期生成时间事件(周期长度为 2.5 秒)。在每个时钟滴答声中,TimeSensor 都会发送一个名为 fraction_changed 的事件,该事件的值为介于 0 和 1 之间的 SFFloat 值(给出周期中已过去的时间比例)。然后,此事件被路由到 CI,CI 是一个 ColorInterpolator,即它执行颜色值的分段线性插值。然后,CI 发送一个 value_changed 事件,MAT 将其接收为 diffuseColor,然后盒子的颜色发生变化。例如,如果 CI 接收到一个值为 0.3 的 set_fraction 事件,它会检查其 key 字段,并注意到 0.3 介于第一个值和第二个值之间。它是到达第二个值的十分之六的路程;因此,输出颜色为 (0.6 0.6 0.6)。
让事件通过插值器到达目的地是 VRML 中相当常见的用法,因为这使你可以相对容易且廉价地指定任意映射。VRML97 规范中有几种不同类型的插值器,用于不同的数据类型。
但是,插值器只能带你走这么远:一方面,它们无法在序列中的特定点切换灯光。VRML97 规范作者很容易添加节点类型来完成此操作,但他们选择不这样做。相反,他们选择为外部脚本语言 Java 和 JavaScript 创建一个非常通用的接口。你可以使用这些编程语言为你的世界定义任意行为。
如果我们想让上一个示例中的盒子消失,并在每个周期的最初 1.5 秒内被一个小蓝球取代,我们需要使用 Script 节点。此特定示例在列表 5中显示。TS 和 CI 节点以及它们与 MAT 之间的路径与上一个示例相同。
从示例中显而易见,在 Script 节点内部指定字段的语法类似于 PROTO 接口部分:我们不是只说 fieldName value,而是说 kind Type fieldName,后跟字段的值。这是因为规范仅为 Script 节点定义了三个字段:url、directOutput 和 mustEvaluate,并由程序员来定义其脚本的 eventIns、fields 和 eventOuts。
Script 节点内的实际脚本在 url 字段中指定。在本例中,它是用 JavaScript 编写的,并嵌入到 URL 中。也可以将脚本写入文件(带有 .js 后缀),并在 URL 中引用该文件。或者,脚本可以用另一种脚本语言(例如 Java 或 Perl)编写。
到目前为止,我们已经讨论了如何编写 VRML。然而,最有趣的结果来自自动创建 VRML。静态世界一旦被编程,主要对游戏、艺术或广告有趣。在信息世界的其余部分与 3D 浏览器之间的接口中,仍然有巨大的可能性有待开发。

图 5
此外,我们可以使用浏览器提供的 API 来创建一个应用程序,该应用程序使用 VRML 来提供 GUI 的一部分。一个相当简单的接口可以编写为一个 Perl 程序,该程序将 FreeWRL 浏览器和 GTK 结合在一起,以提供用户界面,这在 VRML 或 GTK 中单独完成是很困难的。主窗口由一个输入框组成,你可以在其中输入你想要程序绘制的函数,一个绘制它的按钮,以及三个标签,显示 X、Y 坐标以及鼠标最后触摸函数表面处的函数值。程序还在 3D 窗口中的此点放置一个蓝色盒子。一旦你在浏览器中显示了场景,就可以很容易地添加代码来拍摄此场景的 2D 或 3D 快照,无论是常用的图像文件 (GIF/JPEG) 还是 VRML。
基本上,除了创建 GTK GUI 之外,代码只是创建一个浏览器窗口,从字符串加载 VRML 世界,然后调用浏览器方法来获取对场景中 ElevationGrid 节点的引用。然后,它向此 ElevationGrid 发送事件,以通过 height 字段设置表面形状。程序还为场景中的 TouchSensor 节点注册一个监听器,因此它能够获得表面上的鼠标位置。真正有趣的事情是,此应用程序的所有代码都少于 200 行,包括注释。(该应用程序包含在 FreeWRL 发行版中,因此我不会在此处包含完整的源代码。)
也可以通过名为 EAI(外部创作接口)的 Java API 访问 VRML 浏览器。这使人们能够编写访问 VRML 场景的 Web 小程序。在撰写本文时,FreeWRL 部分实现了 Java EAI API,但尚无法在 Netscape 内部运行时提供此 API。当你阅读这篇文章时,这种情况可能已经改变。
与其编写完整的教程,不如说我试图概述 VRML 是什么以及它可能有什么用。我希望我已经提供了一些新的想法,你可以在合适的时候投入使用。如果你需要更完整的参考资料,可以在 VRML 联盟的网站 http://www.vrml.org/ 上找到许多来源的链接。FreeWRL 主页位于 http://www.iki.fi/lukka/freewrl/ (iki.fi 是一个重定向器,指向主页碰巧所在的任何位置)。
本文中引用的所有列表都可以通过匿名下载文件 ftp.linuxjournal.com/pub/lj/listings/issue57/3085.tgz 获取。
Tuomas J. Lukka (lukka@fas.harvard.edu) 于 1995 年在赫尔辛基大学获得博士学位。他目前在哈佛大学进行为期三年的初级研究,花费他的时间从事人工智能和分子量子力学研究,以及演奏音乐和编写自由软件。